/** * 管理员端功能管理 */ class AdminManager { constructor() { this.apiBase = '/api/admin'; this.init(); } async init() { this.updateCurrentTime(); setInterval(() => this.updateCurrentTime(), 1000); await this.loadUserInfo(); // 页面路由逻辑 const path = window.location.pathname; if (path.includes('/dashboard')) { this.initDashboard(); } else if (path.includes('/user_management')) { this.initUserManagement(); } else if (path.includes('/student_management')) { this.initStudentManagement(); } else if (path.includes('/teacher_management')) { this.initTeacherManagement(); } else if (path.includes('/grade_statistics')) { this.initGradeStatistics(); } else if (path.includes('/system_settings')) { this.initSystemSettings(); } else if (path.includes('/data_export')) { this.initDataExport(); } else if (path.includes('/operation_logs')) { this.initOperationLogs(); } // Logout const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', async (e) => { e.preventDefault(); if (confirm('确定要退出登录吗?')) { try { const res = await fetch('/api/auth/logout', { method: 'POST' }); const result = await res.json(); if (result.success) { alert('退出登录成功'); window.location.href = '/login'; } else { alert(result.message || '退出登录失败'); } } catch (e) { console.error('Logout failed', e); alert('退出登录出错: ' + e.message); } } }); } } updateCurrentTime() { const timeElement = document.getElementById('currentTime'); if (timeElement) { const now = new Date(); const options = { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }; timeElement.textContent = now.toLocaleString('zh-CN', options); } } async loadUserInfo() { try { const res = await fetch('/api/auth/me'); const result = await res.json(); if (result.success && result.data.user) { const user = result.data.user; this.user = user; const nameEls = document.querySelectorAll('#adminName, #userName'); nameEls.forEach(el => el.textContent = user.name || '管理员'); } } catch (error) { console.error('Load user info failed:', error); } } // ================= Dashboard ================= async initDashboard() { try { const res = await fetch(`${this.apiBase}/stats`); const result = await res.json(); if (result.success) { const stats = result.data; if (document.getElementById('totalUsers')) document.getElementById('totalUsers').textContent = stats.users; if (document.getElementById('totalStudents')) document.getElementById('totalStudents').textContent = stats.students; if (document.getElementById('totalTeachers')) document.getElementById('totalTeachers').textContent = stats.teachers; if (document.getElementById('totalCourses')) document.getElementById('totalCourses').textContent = stats.courses; } } catch (e) { console.error('Load stats failed', e); } } // ================= User Management ================= async initUserManagement() { this.currentPage = 1; this.pageSize = 10; // Bind Filter Events document.getElementById('search').addEventListener('input', () => this.loadUsers()); document.getElementById('roleFilter').addEventListener('change', () => this.loadUsers()); // Bind Modal Events const modalEl = document.getElementById('userModal'); this.userModal = new bootstrap.Modal(modalEl); document.getElementById('addUserBtn').addEventListener('click', () => { document.getElementById('userForm').reset(); document.getElementById('userModalTitle').textContent = '新增用户'; document.getElementById('isEdit').value = 'false'; document.getElementById('userId').readOnly = false; this.userModal.show(); }); document.getElementById('saveUserBtn').addEventListener('click', () => this.saveUser()); // Initial Load await this.loadUsers(); } async loadUsers() { const search = document.getElementById('search').value; const role = document.getElementById('roleFilter').value; const tbody = document.getElementById('userTableBody'); tbody.innerHTML = '加载中...'; try { const query = new URLSearchParams({ page: this.currentPage, limit: this.pageSize, search, role }); const res = await fetch(`${this.apiBase}/users?${query}`); const result = await res.json(); if (result.success) { this.renderUserTable(result.data, result.pagination); } else { tbody.innerHTML = `${result.message}`; } } catch (e) { console.error(e); tbody.innerHTML = '加载失败'; } } renderUserTable(users, pagination) { const tbody = document.getElementById('userTableBody'); if (users.length === 0) { tbody.innerHTML = '暂无用户'; return; } tbody.innerHTML = users.map(u => ` ${u.id} ${u.name} ${this.getRoleName(u.role)} ${u.class || '-'} ${new Date(u.created_at).toLocaleDateString()} `).join(''); // Bind Action Buttons document.querySelectorAll('.btn-edit-user').forEach(btn => { btn.addEventListener('click', () => { const user = JSON.parse(btn.dataset.user); this.openEditUserModal(user); }); }); document.querySelectorAll('.btn-delete-user').forEach(btn => { btn.addEventListener('click', () => { if(confirm('确定要删除该用户吗?此操作不可恢复。')) { this.deleteUser(btn.dataset.id); } }); }); this.renderPagination(pagination); } getRoleBadgeColor(role) { switch(role) { case 'admin': return 'danger'; case 'teacher': return 'info'; case 'student': return 'success'; default: return 'secondary'; } } getRoleName(role) { switch(role) { case 'admin': return '管理员'; case 'teacher': return '教师'; case 'student': return '学生'; default: role; } } renderPagination(pagination) { const el = document.getElementById('pagination'); let html = ''; if (pagination.page > 1) { html += `
  • 上一页
  • `; } else { html += `
  • 上一页
  • `; } for (let i = 1; i <= pagination.pages; i++) { html += `
  • ${i}
  • `; } if (pagination.page < pagination.pages) { html += `
  • 下一页
  • `; } else { html += `
  • 下一页
  • `; } el.innerHTML = html; } changePage(page) { this.currentPage = page; this.loadUsers(); } openEditUserModal(user) { document.getElementById('userForm').reset(); document.getElementById('userModalTitle').textContent = '编辑用户'; document.getElementById('isEdit').value = 'true'; document.getElementById('userId').value = user.id; document.getElementById('userId').readOnly = true; document.getElementById('userNameInput').value = user.name; document.getElementById('userRole').value = user.role; document.getElementById('userClass').value = user.class || ''; this.userModal.show(); } async saveUser() { const form = document.getElementById('userForm'); const formData = new FormData(form); const data = Object.fromEntries(formData.entries()); const isEdit = data.isEdit === 'true'; // Validation if (!data.id || !data.name || !data.role) { alert('请填写必填字段'); return; } if (data.role === 'student' && !data.class) { alert('学生角色必须填写班级'); return; } try { let res; if (isEdit) { res = await fetch(`${this.apiBase}/users/${data.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } else { res = await fetch(`${this.apiBase}/users`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } const result = await res.json(); if (result.success) { alert(isEdit ? '更新成功' : '创建成功'); this.userModal.hide(); this.loadUsers(); } else { alert(result.message || '操作失败'); } } catch (e) { console.error(e); alert('系统错误'); } } async deleteUser(id) { try { const res = await fetch(`${this.apiBase}/users/${id}`, { method: 'DELETE' }); const result = await res.json(); if (result.success) { alert('删除成功'); this.loadUsers(); } else { alert(result.message || '删除失败'); } } catch (e) { console.error(e); alert('系统错误'); } } // ================= Student Management ================= async initStudentManagement() { this.studentCurrentPage = 1; this.studentPageSize = 10; // Bind Filter Events document.getElementById('studentSearch').addEventListener('input', () => this.loadStudents()); // Bind Modal Events const modalEl = document.getElementById('studentModal'); this.studentModal = new bootstrap.Modal(modalEl); document.getElementById('addStudentBtn').addEventListener('click', () => { document.getElementById('studentForm').reset(); document.getElementById('studentModalTitle').textContent = '新增学生'; document.getElementById('studentIsEdit').value = 'false'; document.getElementById('studentId').readOnly = false; this.studentModal.show(); }); document.getElementById('saveStudentBtn').addEventListener('click', () => this.saveStudent()); // Initial Load await this.loadStudents(); } async loadStudents() { const search = document.getElementById('studentSearch').value; const tbody = document.getElementById('studentTableBody'); tbody.innerHTML = '加载中...'; try { const query = new URLSearchParams({ page: this.studentCurrentPage, limit: this.studentPageSize, search }); const res = await fetch(`${this.apiBase}/students?${query}`); const result = await res.json(); if (result.success) { this.renderStudentTable(result.data, result.pagination); } else { tbody.innerHTML = `${result.message}`; } } catch (e) { console.error(e); tbody.innerHTML = '加载失败'; } } renderStudentTable(students, pagination) { const tbody = document.getElementById('studentTableBody'); if (students.length === 0) { tbody.innerHTML = '暂无学生'; return; } tbody.innerHTML = students.map(s => ` ${s.id} ${s.name} ${s.class || '-'} ${s.major || '-'} ${s.grade || '-'} ${s.contact_info || '-'} `).join(''); // Bind Action Buttons document.querySelectorAll('.btn-edit-student').forEach(btn => { btn.addEventListener('click', () => { const student = JSON.parse(btn.dataset.student); this.openEditStudentModal(student); }); }); document.querySelectorAll('.btn-delete-student').forEach(btn => { btn.addEventListener('click', () => { if(confirm('确定要删除该学生吗?这将同时删除其用户账号。')) { this.deleteStudent(btn.dataset.id); } }); }); this.renderStudentPagination(pagination); } renderStudentPagination(pagination) { const el = document.getElementById('studentPagination'); let html = ''; if (pagination.page > 1) { html += `
  • 上一页
  • `; } else { html += `
  • 上一页
  • `; } for (let i = 1; i <= pagination.pages; i++) { html += `
  • ${i}
  • `; } if (pagination.page < pagination.pages) { html += `
  • 下一页
  • `; } else { html += `
  • 下一页
  • `; } el.innerHTML = html; } changeStudentPage(page) { this.studentCurrentPage = page; this.loadStudents(); } openEditStudentModal(student) { document.getElementById('studentForm').reset(); document.getElementById('studentModalTitle').textContent = '编辑学生'; document.getElementById('studentIsEdit').value = 'true'; document.getElementById('studentId').value = student.id; document.getElementById('studentId').readOnly = true; document.getElementById('studentNameInput').value = student.name; document.getElementById('studentClass').value = student.class || ''; document.getElementById('studentMajor').value = student.major || ''; document.getElementById('studentGrade').value = student.grade || ''; document.getElementById('studentContact').value = student.contact_info || ''; this.studentModal.show(); } async saveStudent() { const form = document.getElementById('studentForm'); const formData = new FormData(form); const data = Object.fromEntries(formData.entries()); const isEdit = data.isEdit === 'true'; // Validation if (!data.id || !data.name || !data.class) { alert('学号、姓名和班级为必填项'); return; } try { let res; if (isEdit) { res = await fetch(`${this.apiBase}/students/${data.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } else { res = await fetch(`${this.apiBase}/students`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } const result = await res.json(); if (result.success) { alert(isEdit ? '更新成功' : '创建成功'); this.studentModal.hide(); this.loadStudents(); } else { alert(result.message || '操作失败'); } } catch (e) { console.error(e); alert('系统错误'); } } async deleteStudent(id) { try { const res = await fetch(`${this.apiBase}/students/${id}`, { method: 'DELETE' }); const result = await res.json(); if (result.success) { alert('删除成功'); this.loadStudents(); } else { alert(result.message || '删除失败'); } } catch (e) { console.error(e); alert('系统错误'); } } // ================= Teacher Management ================= async initTeacherManagement() { this.teacherCurrentPage = 1; this.teacherPageSize = 10; // Bind Filter Events document.getElementById('teacherSearch').addEventListener('input', () => this.loadTeachers()); // Bind Modal Events const modalEl = document.getElementById('teacherModal'); this.teacherModal = new bootstrap.Modal(modalEl); document.getElementById('addTeacherBtn').addEventListener('click', () => { document.getElementById('teacherForm').reset(); document.getElementById('teacherModalTitle').textContent = '新增教师'; document.getElementById('teacherIsEdit').value = 'false'; document.getElementById('teacherId').readOnly = false; this.teacherModal.show(); }); document.getElementById('saveTeacherBtn').addEventListener('click', () => this.saveTeacher()); // Initial Load await this.loadTeachers(); } async loadTeachers() { const search = document.getElementById('teacherSearch').value; const tbody = document.getElementById('teacherTableBody'); tbody.innerHTML = '加载中...'; try { const query = new URLSearchParams({ page: this.teacherCurrentPage, limit: this.teacherPageSize, search }); const res = await fetch(`${this.apiBase}/teachers?${query}`); const result = await res.json(); if (result.success) { this.renderTeacherTable(result.data, result.pagination); } else { tbody.innerHTML = `${result.message}`; } } catch (e) { console.error(e); tbody.innerHTML = '加载失败'; } } renderTeacherTable(teachers, pagination) { const tbody = document.getElementById('teacherTableBody'); if (teachers.length === 0) { tbody.innerHTML = '暂无教师'; return; } tbody.innerHTML = teachers.map(t => ` ${t.id} ${t.name} ${t.title || '-'} ${t.department || '-'} ${t.contact_info || '-'} `).join(''); // Bind Action Buttons document.querySelectorAll('.btn-edit-teacher').forEach(btn => { btn.addEventListener('click', () => { const teacher = JSON.parse(btn.dataset.teacher); this.openEditTeacherModal(teacher); }); }); document.querySelectorAll('.btn-delete-teacher').forEach(btn => { btn.addEventListener('click', () => { if(confirm('确定要删除该教师吗?这将同时删除其用户账号。')) { this.deleteTeacher(btn.dataset.id); } }); }); this.renderTeacherPagination(pagination); } renderTeacherPagination(pagination) { const el = document.getElementById('teacherPagination'); let html = ''; if (pagination.page > 1) { html += `
  • 上一页
  • `; } else { html += `
  • 上一页
  • `; } for (let i = 1; i <= pagination.pages; i++) { html += `
  • ${i}
  • `; } if (pagination.page < pagination.pages) { html += `
  • 下一页
  • `; } else { html += `
  • 下一页
  • `; } el.innerHTML = html; } changeTeacherPage(page) { this.teacherCurrentPage = page; this.loadTeachers(); } openEditTeacherModal(teacher) { document.getElementById('teacherForm').reset(); document.getElementById('teacherModalTitle').textContent = '编辑教师'; document.getElementById('teacherIsEdit').value = 'true'; document.getElementById('teacherId').value = teacher.id; document.getElementById('teacherId').readOnly = true; document.getElementById('teacherNameInput').value = teacher.name; document.getElementById('teacherDepartment').value = teacher.department || ''; document.getElementById('teacherTitle').value = teacher.title || ''; document.getElementById('teacherContact').value = teacher.contact_info || ''; this.teacherModal.show(); } async saveTeacher() { const form = document.getElementById('teacherForm'); const formData = new FormData(form); const data = Object.fromEntries(formData.entries()); const isEdit = data.isEdit === 'true'; // Validation if (!data.id || !data.name) { alert('工号和姓名为必填项'); return; } try { let res; if (isEdit) { res = await fetch(`${this.apiBase}/teachers/${data.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } else { res = await fetch(`${this.apiBase}/teachers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); } const result = await res.json(); if (result.success) { alert(isEdit ? '更新成功' : '创建成功'); this.teacherModal.hide(); this.loadTeachers(); } else { alert(result.message || '操作失败'); } } catch (e) { console.error(e); alert('系统错误'); } } async deleteTeacher(id) { try { const res = await fetch(`${this.apiBase}/teachers/${id}`, { method: 'DELETE' }); const result = await res.json(); if (result.success) { alert('删除成功'); this.loadTeachers(); } else { alert(result.message || '删除失败'); } } catch (e) { console.error(e); alert('系统错误'); } } // ================= Grade Statistics ================= async initGradeStatistics() { try { const res = await fetch(`${this.apiBase}/grade-stats`); const result = await res.json(); if (result.success) { const stats = result.data; this.renderGradeStatsTable(stats); this.renderGradeCharts(stats); } } catch (e) { console.error(e); } } renderGradeStatsTable(stats) { const tbody = document.getElementById('gradeStatsBody'); if (!tbody) return; if (stats.length === 0) { tbody.innerHTML = '暂无数据'; return; } tbody.innerHTML = stats.map(s => ` ${s.course_code || '-'} ${s.course_name} ${s.teacher_name || '未分配'} ${s.student_count} ${s.avg_score} ${s.max_score || '-'} ${s.min_score || '-'}
    ${s.pass_rate}%
    `).join(''); } renderGradeCharts(stats) { if (!window.Chart) return; // 1. Course Average Scores Bar Chart const ctxBar = document.getElementById('courseAvgChart'); if (ctxBar) { new Chart(ctxBar, { type: 'bar', data: { labels: stats.map(s => s.course_name), datasets: [{ label: '平均分', data: stats.map(s => s.avg_score), backgroundColor: 'rgba(78, 115, 223, 0.5)', borderColor: 'rgba(78, 115, 223, 1)', borderWidth: 1 }] }, options: { responsive: true, scales: { y: { beginAtZero: true, max: 100 } } } }); } // 2. Overall Pass Rate Pie Chart const ctxPie = document.getElementById('passRateChart'); if (ctxPie) { const totalStudents = stats.reduce((sum, s) => sum + s.student_count, 0); const totalPass = stats.reduce((sum, s) => sum + parseInt(s.pass_count), 0); const totalFail = totalStudents - totalPass; new Chart(ctxPie, { type: 'doughnut', data: { labels: ['及格', '不及格'], datasets: [{ data: [totalPass, totalFail], backgroundColor: ['#1cc88a', '#e74a3b'], hoverOffset: 4 }] }, options: { responsive: true, plugins: { legend: { position: 'bottom' } } } }); } } // ================= System Settings ================= async initSystemSettings() { const form = document.getElementById('basicSettingsForm'); if (!form) return; // Load current settings try { const response = await fetch('/api/admin/settings'); const result = await response.json(); if (result.success) { const settings = result.data; const nameInput = form.querySelector('input[type="text"]'); const semesterSelect = form.querySelector('select'); const courseSwitch = document.getElementById('courseSelectionSwitch'); const gradeSwitch = document.getElementById('gradeCheckSwitch'); if (nameInput) nameInput.value = settings.system_name || ''; if (semesterSelect) semesterSelect.value = settings.current_semester || ''; if (courseSwitch) courseSwitch.checked = settings.allow_course_selection === '1'; if (gradeSwitch) gradeSwitch.checked = settings.allow_grade_check === '1'; } } catch (err) { console.error('加载设置失败:', err); } form.addEventListener('submit', async (e) => { e.preventDefault(); const settings = { system_name: form.querySelector('input[type="text"]').value, current_semester: form.querySelector('select').value, allow_course_selection: document.getElementById('courseSelectionSwitch').checked ? '1' : '0', allow_grade_check: document.getElementById('gradeCheckSwitch').checked ? '1' : '0' }; try { const response = await fetch('/api/admin/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); const result = await response.json(); if (result.success) { alert('系统设置已保存'); } else { alert('保存失败: ' + result.message); } } catch (err) { alert('保存出错'); } }); // Data Maintenance Buttons const backupBtn = document.querySelector('.btn-outline-primary i.fa-database')?.parentElement; const clearCacheBtn = document.querySelector('.btn-outline-warning i.fa-trash-alt')?.parentElement; const resetPassBtn = document.querySelector('.btn-outline-danger i.fa-history')?.parentElement; if (backupBtn) { backupBtn.addEventListener('click', async () => { if (!confirm('确定要立即备份数据库吗?')) return; try { const res = await fetch('/api/admin/maintenance/backup', { method: 'POST' }); const result = await res.json(); if (result.success) alert('备份成功!文件已保存到 backups 目录: ' + result.data.filename); else alert('备份失败: ' + result.message); } catch (err) { alert('请求失败'); } }); } if (clearCacheBtn) { clearCacheBtn.addEventListener('click', async () => { try { const res = await fetch('/api/admin/maintenance/clear-cache', { method: 'POST' }); const result = await res.json(); if (result.success) alert('系统缓存已清理'); else alert('清理失败'); } catch (err) { alert('请求失败'); } }); } if (resetPassBtn) { resetPassBtn.addEventListener('click', async () => { if (!confirm('确定要重置所有学生密码为 123456 吗?此操作不可撤销!')) return; try { const res = await fetch('/api/admin/maintenance/reset-passwords', { method: 'POST' }); const result = await res.json(); if (result.success) alert('所有学生密码已重置为 123456'); else alert('重置失败'); } catch (err) { alert('请求失败'); } }); } } // ================= Data Export ================= async initDataExport() { const studentExportBtn = document.getElementById('exportStudentsBtn'); const teacherExportBtn = document.getElementById('exportTeachersBtn'); const gradeExportBtn = document.getElementById('exportGradesBtn'); if (studentExportBtn) { studentExportBtn.addEventListener('click', () => { window.location.href = '/api/admin/export/students'; }); } if (teacherExportBtn) { teacherExportBtn.addEventListener('click', () => { window.location.href = '/api/admin/export/teachers'; }); } if (gradeExportBtn) { gradeExportBtn.addEventListener('click', () => { window.location.href = '/api/admin/export/grades'; }); } } // ================= Operation Logs ================= async initOperationLogs() { const tbody = document.getElementById('logsTableBody'); if (!tbody) return; try { const response = await fetch('/api/admin/logs'); const result = await response.json(); if (result.success) { const logs = result.data; if (logs.length === 0) { tbody.innerHTML = '暂无操作日志'; return; } tbody.innerHTML = logs.map(log => ` ${log.created_at} ${log.user_id} (${log.user_name || '未知'}) ${log.operation_type} ${log.description} ${log.ip_address || '-'} `).join(''); } } catch (err) { console.error('获取日志失败:', err); tbody.innerHTML = '加载失败'; } } } document.addEventListener('DOMContentLoaded', () => { window.adminManager = new AdminManager(); });