660 lines
37 KiB
HTML
660 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>
|
||
<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')">×</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')">×</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')">×</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')">×</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')">×</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>
|