Files
student_manage_system/static/index.html
T
2026-09-22 08:21:35 +08:00

659 lines
37 KiB
HTML

<!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>
<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></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.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>