Files
student_manage_system/static/index.html
T
2026-09-23 10:43:14 +08:00

660 lines
37 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>教育培训管理系统</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f7fa; color: #333; }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px 30px; display: flex; justify-content: space-between; align-items: center; }
.header h1 { font-size: 24px; font-weight: 600; }
.nav { display: flex; gap: 10px; }
.nav-btn { padding: 10px 20px; background: rgba(255,255,255,0.2); border: none; color: white; border-radius: 8px; cursor: pointer; font-size: 14px; transition: all 0.3s; }
.nav-btn:hover, .nav-btn.active { background: rgba(255,255,255,0.9); color: #667eea; }
.container { max-width: 1400px; margin: 0 auto; padding: 20px; }
.section { display: none; }
.section.active { display: block; }
.card { background: white; border-radius: 12px; padding: 24px; margin-bottom: 20px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.card h2 { font-size: 18px; margin-bottom: 16px; color: #333; display: flex; justify-content: space-between; align-items: center; }
.btn { padding: 8px 16px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; }
.btn-primary { background: #667eea; color: white; }
.btn-primary:hover { background: #5568d3; }
.btn-danger { background: #e74c3c; color: white; }
.btn-danger:hover { background: #c0392b; }
.btn-success { background: #27ae60; color: white; }
.btn-success:hover { background: #219a52; }
.btn-secondary { background: #95a5a6; color: white; }
.btn-secondary:hover { background: #7f8c8d; }
table { width: 100%; border-collapse: collapse; margin-top: 16px; }
th, td { padding: 12px; text-align: left; border-bottom: 1px solid #eee; }
th { background: #f8f9fa; font-weight: 600; color: #666; }
tr:hover { background: #f8f9fa; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; justify-content: center; align-items: center; }
.modal.show { display: flex; }
.modal-content { background: white; border-radius: 12px; padding: 24px; width: 90%; max-width: 600px; max-height: 90vh; overflow-y: auto; }
.modal-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.modal-header h3 { font-size: 18px; }
.close { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; }
.form-group { margin-bottom: 16px; }
.form-group label { display: block; margin-bottom: 6px; font-weight: 500; color: #555; }
.form-group input, .form-group select { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
.form-group input:focus, .form-group select:focus { outline: none; border-color: #667eea; }
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 20px; }
.stat-card { background: white; border-radius: 12px; padding: 24px; box-shadow: 0 2px 8px rgba(0,0,0,0.08); }
.stat-card h3 { font-size: 14px; color: #666; margin-bottom: 8px; }
.stat-card .value { font-size: 32px; font-weight: 700; color: #667eea; }
.stat-card .label { font-size: 12px; color: #999; margin-top: 4px; }
.filter-bar { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.filter-bar input, .filter-bar select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 14px; }
.badge { display: inline-block; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 500; }
.badge-success { background: #d4edda; color: #155724; }
.badge-warning { background: #fff3cd; color: #856404; }
.badge-danger { background: #f8d7da; color: #721c24; }
.badge-info { background: #d1ecf1; color: #0c5460; }
</style>
</head>
<body>
<div class="header">
<h1>📚 教育培训管理系统</h1>
<h3>贡献人员:元堡,long, yb, 陈志平,geyuan, yifan,Agent</h3>
<div class="nav">
<button class="nav-btn active" onclick="showSection('dashboard')">概览</button>
<button class="nav-btn" onclick="showSection('students')">学生</button>
<button class="nav-btn" onclick="showSection('teachers')">教师</button>
<button class="nav-btn" onclick="showSection('classes')">班级</button>
<button class="nav-btn" onclick="showSection('scores')">成绩</button>
<button class="nav-btn" onclick="showSection('employment')">就业</button>
</div>
</div>
<div class="container">
<!-- 概览 -->
<div id="dashboard" class="section active">
<div class="stats-grid">
<div class="stat-card">
<h3>学生总数</h3>
<div class="value" id="stat-students">-</div>
</div>
<div class="stat-card">
<h3>教师总数</h3>
<div class="value" id="stat-teachers">-</div>
</div>
<div class="stat-card">
<h3>班级数量</h3>
<div class="value" id="stat-classes">-</div>
</div>
<div class="stat-card">
<h3>已就业人数</h3>
<div class="value" id="stat-employed">-</div>
</div>
</div>
<div class="card">
<h2>成绩统计</h2>
<table>
<thead><tr><th>班级</th><th>考核</th><th>平均分</th><th>最高分</th><th>最低分</th><th>人数</th></tr></thead>
<tbody id="score-stats-body"></tbody>
</table>
</div>
<div class="card">
<h2>就业统计</h2>
<table>
<thead><tr><th>班级</th><th>就业人数</th><th>总人数</th><th>就业率</th><th>平均薪资</th><th>平均签约周期(天)</th></tr></thead>
<tbody id="employment-stats-body"></tbody>
</table>
</div>
</div>
<!-- 学生 -->
<div id="students" class="section">
<div class="card">
<h2>
学生管理
<button class="btn btn-primary" onclick="openModal('student-modal')">+ 添加学生</button>
</h2>
<div class="filter-bar">
<input type="text" id="student-search" placeholder="搜索姓名..." oninput="loadStudents()">
<select id="student-class-filter" onchange="loadStudents()">
<option value="">全部班级</option>
</select>
</div>
<table>
<thead><tr><th>编号</th><th>姓名</th><th>年龄</th><th>性别</th><th>班级</th><th>顾问</th><th>操作</th></tr></thead>
<tbody id="students-body"></tbody>
</table>
</div>
</div>
<!-- 教师 -->
<div id="teachers" class="section">
<div class="card">
<h2>
教师管理
<button class="btn btn-primary" onclick="openModal('teacher-modal')">+ 添加教师</button>
</h2>
<table>
<thead><tr><th>编号</th><th>姓名</th><th>年龄</th><th>性别</th><th>院校</th><th>专业</th><th>教学方向</th><th>操作</th></tr></thead>
<tbody id="teachers-body"></tbody>
</table>
</div>
</div>
<!-- 班级 -->
<div id="classes" class="section">
<div class="card">
<h2>
班级管理
<button class="btn btn-primary" onclick="openModal('class-modal')">+ 添加班级</button>
</h2>
<table>
<thead><tr><th>编号</th><th>名称</th><th>开课时间</th><th>班主任</th><th>授课老师</th><th>助教</th><th>操作</th></tr></thead>
<tbody id="classes-body"></tbody>
</table>
</div>
</div>
<!-- 成绩 -->
<div id="scores" class="section">
<div class="card">
<h2>
成绩管理
<button class="btn btn-primary" onclick="openModal('score-modal')">+ 添加成绩</button>
</h2>
<div class="filter-bar">
<select id="score-student-filter" onchange="loadScores()">
<option value="">全部学生</option>
</select>
<select id="score-num-filter" onchange="loadScores()">
<option value="">全部考核</option>
<option value="第一次考核">第一次考核</option>
<option value="第二次考核">第二次考核</option>
<option value="第三次考核">第三次考核</option>
</select>
</div>
<table>
<thead><tr><th>学生</th><th>考核序次</th><th>分数</th><th>操作</th></tr></thead>
<tbody id="scores-body"></tbody>
</table>
</div>
</div>
<!-- 就业 -->
<div id="employment" class="section">
<div class="card">
<h2>
就业管理
<button class="btn btn-primary" onclick="openModal('employment-modal')">+ 添加就业记录</button>
</h2>
<div class="filter-bar">
<select id="employment-class-filter" onchange="loadEmployments()">
<option value="">全部班级</option>
</select>
</div>
<table>
<thead><tr><th>学生</th><th>班级</th><th>就业公司</th><th>薪资</th><th>开放时间</th><th>offer时间</th><th>状态</th><th>操作</th></tr></thead>
<tbody id="employment-body"></tbody>
</table>
</div>
</div>
</div>
<!-- Student Modal -->
<div id="student-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="student-modal-title">添加学生</h3>
<button class="close" onclick="closeModal('student-modal')">&times;</button>
</div>
<form id="student-form" onsubmit="saveStudent(event)">
<input type="hidden" id="student-id">
<div class="form-group"><label>学生编号</label><input type="text" id="student-num" required></div>
<div class="form-group"><label>姓名</label><input type="text" id="student-name" required></div>
<div class="form-group"><label>年龄</label><input type="number" id="student-age" value="18"></div>
<div class="form-group"><label>性别</label><select id="student-sex"><option value="0">未知</option><option value="1">男</option><option value="2">女</option></select></div>
<div class="form-group"><label>家乡</label><input type="text" id="student-home"></div>
<div class="form-group"><label>毕业院校</label><input type="text" id="student-college"></div>
<div class="form-group"><label>专业</label><input type="text" id="student-specialty"></div>
<div class="form-group"><label>入学时间</label><input type="date" id="student-enrollment"></div>
<div class="form-group"><label>毕业时间</label><input type="date" id="student-graduate"></div>
<div class="form-group"><label>学历</label><input type="text" id="student-education"></div>
<div class="form-group"><label>班级</label><select id="student-class" required></select></div>
<div class="form-group"><label>顾问</label><select id="student-advisor"><option value="">无</option></select></div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
</div>
<!-- Teacher Modal -->
<div id="teacher-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="teacher-modal-title">添加教师</h3>
<button class="close" onclick="closeModal('teacher-modal')">&times;</button>
</div>
<form id="teacher-form" onsubmit="saveTeacher(event)">
<input type="hidden" id="teacher-id">
<div class="form-group"><label>教师编号</label><input type="text" id="teacher-num" required></div>
<div class="form-group"><label>姓名</label><input type="text" id="teacher-name" required></div>
<div class="form-group"><label>年龄</label><input type="number" id="teacher-age" value="30"></div>
<div class="form-group"><label>性别</label><select id="teacher-sex"><option value="0">未知</option><option value="1">男</option><option value="2">女</option></select></div>
<div class="form-group"><label>家乡</label><input type="text" id="teacher-home"></div>
<div class="form-group"><label>毕业院校</label><input type="text" id="teacher-college"></div>
<div class="form-group"><label>专业</label><input type="text" id="teacher-specialty"></div>
<div class="form-group"><label>教学方向</label><select id="teacher-coach-area"><option value="">无</option><option value="应用">应用</option><option value="项目">项目</option><option value="算法">算法</option></select></div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
</div>
<!-- Class Modal -->
<div id="class-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3 id="class-modal-title">添加班级</h3>
<button class="close" onclick="closeModal('class-modal')">&times;</button>
</div>
<form id="class-form" onsubmit="saveClass(event)">
<input type="hidden" id="class-id">
<div class="form-group"><label>班级编号</label><input type="text" id="class-num" required></div>
<div class="form-group"><label>班级名称</label><input type="text" id="class-name" required></div>
<div class="form-group"><label>开课时间</label><input type="date" id="class-start-time"></div>
<div class="form-group"><label>班主任</label><select id="class-head-teacher"><option value="">无</option></select></div>
<div class="form-group"><label>授课老师</label><select id="class-coach-teacher"><option value="">无</option></select></div>
<div class="form-group"><label>助教</label><select id="class-tutor-teacher"><option value="">无</option></select></div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
</div>
<!-- Score Modal -->
<div id="score-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>添加成绩</h3>
<button class="close" onclick="closeModal('score-modal')">&times;</button>
</div>
<form id="score-form" onsubmit="saveScore(event)">
<div class="form-group"><label>学生</label><select id="score-student" required></select></div>
<div class="form-group"><label>考核序次</label><select id="score-num" required>
<option value="第一次考核">第一次考核</option>
<option value="第二次考核">第二次考核</option>
<option value="第三次考核">第三次考核</option>
</select></div>
<div class="form-group"><label>分数</label><input type="number" step="0.01" id="score-value" required></div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
</div>
<!-- Employment Modal -->
<div id="employment-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>添加就业记录</h3>
<button class="close" onclick="closeModal('employment-modal')">&times;</button>
</div>
<form id="employment-form" onsubmit="saveEmployment(event)">
<div class="form-group"><label>学生</label><select id="employment-student" required></select></div>
<div class="form-group"><label>班级</label><select id="employment-class" required></select></div>
<div class="form-group"><label>就业公司</label><input type="text" id="employment-company"></div>
<div class="form-group"><label>薪资</label><input type="number" step="0.01" id="employment-salary"></div>
<div class="form-group"><label>就业开放时间</label><input type="date" id="employment-open-time"></div>
<div class="form-group"><label>offer下发时间</label><input type="date" id="employment-offer-time"></div>
<button type="submit" class="btn btn-primary">保存</button>
</form>
</div>
</div>
<script>
const API = 'http://localhost:8000/api';
// Navigation
function showSection(id) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
document.getElementById(id).classList.add('active');
event.target.classList.add('active');
if (id === 'dashboard') loadDashboard();
}
// Modal helpers
function openModal(id) { document.getElementById(id).classList.add('show'); }
function closeModal(id) { document.getElementById(id).classList.remove('show'); }
// Fetch helper
async function fetchAPI(url, options = {}) {
const res = await fetch(API + url, options);
return res.json();
}
// Load dashboard
async function loadDashboard() {
const [students, teachers, classes, employments] = await Promise.all([
fetchAPI('/students'),
fetchAPI('/teachers'),
fetchAPI('/classes'),
fetchAPI('/employments')
]);
document.getElementById('stat-students').textContent = students.length;
document.getElementById('stat-teachers').textContent = teachers.length;
document.getElementById('stat-classes').textContent = classes.length;
document.getElementById('stat-employed').textContent = employments.filter(e => e.employment_company).length;
// Score stats
const scoreStats = await fetchAPI('/stats/scores');
const tbody = document.getElementById('score-stats-body');
tbody.innerHTML = scoreStats.map(s => `<tr><td>${s.class_name}</td><td>${s.score_num}</td><td>${s.avg_score.toFixed(2)}</td><td>${s.max_score}</td><td>${s.min_score}</td><td>${s.count}</td></tr>`).join('');
// Employment stats
const empStats = await fetchAPI('/stats/employment');
const empBody = document.getElementById('employment-stats-body');
empBody.innerHTML = empStats.map(s => `<tr><td>${s.class_name}</td><td>${s.employed_count}</td><td>${s.total_count}</td><td>${(s.employed_count/s.total_count*100).toFixed(1)}%</td><td>${s.avg_salary ? '¥'+s.avg_salary.toFixed(0) : '-'}</td><td>${s.avg_duration_days || '-'}</td></tr>`).join('');
}
// Load students
async function loadStudents() {
const search = document.getElementById('student-search').value;
const classId = document.getElementById('student-class-filter').value;
const params = new URLSearchParams();
if (search) params.set('name', search);
if (classId) params.set('class_id', classId);
const data = await fetchAPI(`/students?${params}`);
const classes = await fetchAPI('/classes');
const teachers = await fetchAPI('/teachers');
const tbody = document.getElementById('students-body');
tbody.innerHTML = data.map(s => `<tr>
<td>${s.num}</td><td>${s.name}</td><td>${s.age}</td>
<td>${s.sex == 1 ? '男' : s.sex == 2 ? '女' : '未知'}</td>
<td>${classes.find(c => c.id === s.class_id)?.name || '-'}</td>
<td>${teachers.find(t => t.id === s.advisor_id)?.name || '-'}</td>
<td>
<button class="btn btn-secondary" onclick="editStudent(${s.id})">编辑</button>
<button class="btn btn-danger" onclick="deleteStudent(${s.id})">删除</button>
</td>
</tr>`).join('');
populateClassSelect('student-class', classes);
populateTeacherSelect('student-advisor', teachers);
populateClassFilter('student-class-filter', classes);
}
async function editStudent(id) {
const s = await fetchAPI(`/students/${id}`);
const classes = await fetchAPI('/classes');
const teachers = await fetchAPI('/teachers');
document.getElementById('student-modal-title').textContent = '编辑学生';
document.getElementById('student-id').value = s.id;
document.getElementById('student-num').value = s.num;
document.getElementById('student-name').value = s.name;
document.getElementById('student-age').value = s.age;
document.getElementById('student-sex').value = s.sex;
document.getElementById('student-home').value = s.home_place || '';
document.getElementById('student-college').value = s.college || '';
document.getElementById('student-specialty').value = s.specialty || '';
document.getElementById('student-enrollment').value = s.enrollment_time ? s.enrollment_time.split('T')[0] : '';
document.getElementById('student-graduate').value = s.graduate_time ? s.graduate_time.split('T')[0] : '';
document.getElementById('student-education').value = s.education || '';
populateClassSelect('student-class', classes, s.class_id);
populateTeacherSelect('student-advisor', teachers, s.advisor_id);
openModal('student-modal');
}
async function saveStudent(e) {
e.preventDefault();
const id = document.getElementById('student-id').value;
const data = {
num: document.getElementById('student-num').value,
name: document.getElementById('student-name').value,
age: parseInt(document.getElementById('student-age').value),
sex: parseInt(document.getElementById('student-sex').value),
home_place: document.getElementById('student-home').value || null,
college: document.getElementById('student-college').value || null,
specialty: document.getElementById('student-specialty').value || null,
enrollment_time: document.getElementById('student-enrollment').value ? document.getElementById('student-enrollment').value + 'T00:00:00' : null,
graduate_time: document.getElementById('student-graduate').value ? document.getElementById('student-graduate').value + 'T00:00:00' : null,
education: document.getElementById('student-education').value || null,
class_id: parseInt(document.getElementById('student-class').value),
advisor_id: document.getElementById('student-advisor').value ? parseInt(document.getElementById('student-advisor').value) : null,
};
const url = id ? `/students/${id}` : '/students';
const method = id ? 'PUT' : 'POST';
await fetchAPI(url, { method, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) });
closeModal('student-modal');
loadStudents();
}
async function deleteStudent(id) {
if (!confirm('确定删除?')) return;
await fetchAPI(`/students/${id}`, { method: 'DELETE' });
loadStudents();
}
// Load teachers
async function loadTeachers() {
const data = await fetchAPI('/teachers');
const tbody = document.getElementById('teachers-body');
tbody.innerHTML = data.map(t => `<tr>
<td>${t.num}</td><td>${t.name}</td><td>${t.age}</td>
<td>${t.sex == 1 ? '男' : t.sex == 2 ? '女' : '未知'}</td>
<td>${t.college || '-'}</td><td>${t.specialty || '-'}</td>
<td>${t.coach_area || '-'}</td>
<td>
<button class="btn btn-secondary" onclick="editTeacher(${t.id})">编辑</button>
<button class="btn btn-danger" onclick="deleteTeacher(${t.id})">删除</button>
</td>
</tr>`).join('');
}
async function editTeacher(id) {
const t = await fetchAPI(`/teachers/${id}`);
document.getElementById('teacher-modal-title').textContent = '编辑教师';
document.getElementById('teacher-id').value = t.id;
document.getElementById('teacher-num').value = t.num;
document.getElementById('teacher-name').value = t.name;
document.getElementById('teacher-age').value = t.age;
document.getElementById('teacher-sex').value = t.sex;
document.getElementById('teacher-home').value = t.home_place || '';
document.getElementById('teacher-college').value = t.college || '';
document.getElementById('teacher-specialty').value = t.specialty || '';
document.getElementById('teacher-coach-area').value = t.coach_area || '';
openModal('teacher-modal');
}
async function saveTeacher(e) {
e.preventDefault();
const id = document.getElementById('teacher-id').value;
const data = {
num: document.getElementById('teacher-num').value,
name: document.getElementById('teacher-name').value,
age: parseInt(document.getElementById('teacher-age').value),
sex: parseInt(document.getElementById('teacher-sex').value),
home_place: document.getElementById('teacher-home').value || null,
college: document.getElementById('teacher-college').value || null,
specialty: document.getElementById('teacher-specialty').value || null,
coach_area: document.getElementById('teacher-coach-area').value || null,
};
const url = id ? `/teachers/${id}` : '/teachers';
const method = id ? 'PUT' : 'POST';
await fetchAPI(url, { method, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) });
closeModal('teacher-modal');
loadTeachers();
}
async function deleteTeacher(id) {
if (!confirm('确定删除?')) return;
await fetchAPI(`/teachers/${id}`, { method: 'DELETE' });
loadTeachers();
}
// Load classes
async function loadClasses() {
const data = await fetchAPI('/classes');
const teachers = await fetchAPI('/teachers');
const tbody = document.getElementById('classes-body');
tbody.innerHTML = data.map(c => `<tr>
<td>${c.num}</td><td>${c.name}</td><td>${c.class_start_time?.split('T')[0] || '-'}</td>
<td>${teachers.find(t => t.id === c.head_teacher_id)?.name || '-'}</td>
<td>${teachers.find(t => t.id === c.coach_teacher_id)?.name || '-'}</td>
<td>${teachers.find(t => t.id === c.tutor_teacher_id)?.name || '-'}</td>
<td>
<button class="btn btn-secondary" onclick="editClass(${c.id})">编辑</button>
<button class="btn btn-danger" onclick="deleteClass(${c.id})">删除</button>
</td>
</tr>`).join('');
populateTeacherSelect('class-head-teacher', teachers);
populateTeacherSelect('class-coach-teacher', teachers);
populateTeacherSelect('class-tutor-teacher', teachers);
}
async function editClass(id) {
const c = await fetchAPI(`/classes/${id}`);
const teachers = await fetchAPI('/teachers');
document.getElementById('class-modal-title').textContent = '编辑班级';
document.getElementById('class-id').value = c.id;
document.getElementById('class-num').value = c.num;
document.getElementById('class-name').value = c.name;
document.getElementById('class-start-time').value = c.class_start_time?.split('T')[0] || '';
populateTeacherSelect('class-head-teacher', teachers, c.head_teacher_id);
populateTeacherSelect('class-coach-teacher', teachers, c.coach_teacher_id);
populateTeacherSelect('class-tutor-teacher', teachers, c.tutor_teacher_id);
openModal('class-modal');
}
async function saveClass(e) {
e.preventDefault();
const id = document.getElementById('class-id').value;
const data = {
num: document.getElementById('class-num').value,
name: document.getElementById('class-name').value,
class_start_time: document.getElementById('class-start-time').value ? document.getElementById('class-start-time').value + 'T00:00:00' : null,
head_teacher_id: document.getElementById('class-head-teacher').value ? parseInt(document.getElementById('class-head-teacher').value) : null,
coach_teacher_id: document.getElementById('class-coach-teacher').value ? parseInt(document.getElementById('class-coach-teacher').value) : null,
tutor_teacher_id: document.getElementById('class-tutor-teacher').value ? parseInt(document.getElementById('class-tutor-teacher').value) : null,
};
const url = id ? `/classes/${id}` : '/classes';
const method = id ? 'PUT' : 'POST';
await fetchAPI(url, { method, headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) });
closeModal('class-modal');
loadClasses();
}
async function deleteClass(id) {
if (!confirm('确定删除?')) return;
await fetchAPI(`/classes/${id}`, { method: 'DELETE' });
loadClasses();
}
// Load scores
async function loadScores() {
const sid = document.getElementById('score-student-filter').value;
const num = document.getElementById('score-num-filter').value;
const params = new URLSearchParams();
if (sid) params.set('sid', sid);
if (num) params.set('num', num);
const data = await fetchAPI(`/scores?${params}`);
const students = await fetchAPI('/students');
const tbody = document.getElementById('scores-body');
tbody.innerHTML = data.map(s => `<tr>
<td>${students.find(st => st.id === s.sid)?.name || '-'}</td>
<td>${s.num}</td>
<td>${s.score}</td>
<td>
<button class="btn btn-danger" onclick="deleteScore(${s.id})">删除</button>
</td>
</tr>`).join('');
populateStudentSelect('score-student', students);
}
async function saveScore(e) {
e.preventDefault();
const data = {
sid: parseInt(document.getElementById('score-student').value),
num: document.getElementById('score-num').value,
score: parseFloat(document.getElementById('score-value').value),
};
await fetchAPI('/scores', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) });
closeModal('score-modal');
loadScores();
}
async function deleteScore(id) {
if (!confirm('确定删除?')) return;
await fetchAPI(`/scores/${id}`, { method: 'DELETE' });
loadScores();
}
// Load employments
async function loadEmployments() {
const classId = document.getElementById('employment-class-filter').value;
const params = new URLSearchParams();
if (classId) params.set('class_id', classId);
const data = await fetchAPI(`/employments?${params}`);
const students = await fetchAPI('/students');
const classes = await fetchAPI('/classes');
const tbody = document.getElementById('employment-body');
tbody.innerHTML = data.map(e => `<tr>
<td>${students.find(s => s.id === e.sid)?.name || '-'}</td>
<td>${classes.find(c => c.id === e.class_id)?.name || '-'}</td>
<td>${e.employment_company || '-'}</td>
<td>${e.employment_salary ? '¥' + e.employment_salary : '-'}</td>
<td>${e.employment_open_time?.split('T')[0] || '-'}</td>
<td>${e.offer_recived_time?.split('T')[0] || '-'}</td>
<td><span class="badge ${e.employment_company ? 'badge-success' : 'badge-warning'}">${e.employment_company ? '已就业' : '未就业'}</span></td>
<td><button class="btn btn-danger" onclick="deleteEmployment(${e.id})">删除</button></td>
</tr>`).join('');
populateClassSelect('employment-class', classes);
populateStudentSelect('employment-student', students);
}
async function saveEmployment(e) {
e.preventDefault();
const data = {
sid: parseInt(document.getElementById('employment-student').value),
class_id: parseInt(document.getElementById('employment-class').value),
employment_open_time: document.getElementById('employment-open-time').value ? document.getElementById('employment-open-time').value + 'T00:00:00' : null,
offer_recived_time: document.getElementById('employment-offer-time').value ? document.getElementById('employment-offer-time').value + 'T00:00:00' : null,
employment_company: document.getElementById('employment-company').value || null,
employment_salary: document.getElementById('employment-salary').value ? parseFloat(document.getElementById('employment-salary').value) : null,
};
await fetchAPI('/employments', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) });
closeModal('employment-modal');
loadEmployments();
}
async function deleteEmployment(id) {
if (!confirm('确定删除?')) return;
await fetchAPI(`/employments/${id}`, { method: 'DELETE' });
loadEmployments();
}
// Select helpers
function populateClassSelect(id, classes, selected = null) {
const sel = document.getElementById(id);
sel.innerHTML = '<option value="">请选择</option>' + classes.map(c => `<option value="${c.id}" ${c.id == selected ? 'selected' : ''}>${c.name}</option>`).join('');
}
function populateTeacherSelect(id, teachers, selected = null) {
const sel = document.getElementById(id);
sel.innerHTML = '<option value="">无</option>' + teachers.map(t => `<option value="${t.id}" ${t.id == selected ? 'selected' : ''}>${t.name}</option>`).join('');
}
function populateStudentSelect(id, students, selected = null) {
const sel = document.getElementById(id);
sel.innerHTML = '<option value="">请选择</option>' + students.map(s => `<option value="${s.id}" ${s.id == selected ? 'selected' : ''}>${s.name} (${s.num})</option>`).join('');
}
function populateClassFilter(id, classes) {
const sel = document.getElementById(id);
sel.innerHTML = '<option value="">全部班级</option>' + classes.map(c => `<option value="${c.id}">${c.name}</option>`).join('');
}
// Init
loadDashboard();
loadStudents();
loadTeachers();
loadClasses();
loadScores();
loadEmployments();
</script>
</body>
</html>