/**
* 管理员端功能管理
*/
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;
}
}
renderGenericPagination(containerId, pagination, callbackName) {
const el = document.getElementById(containerId);
if (!el) return;
const { page, pages } = pagination;
if (pages <= 0) {
el.innerHTML = '';
return;
}
let html = '';
// Previous page
if (page > 1) {
html += `上一页`;
} else {
html += `上一页`;
}
// Page numbers with ellipsis
if (pages <= 7) {
for (let i = 1; i <= pages; i++) {
html += `${i}`;
}
} else {
// First page
html += `1`;
if (page > 4) {
html += `...`;
}
// Middle pages
let start = Math.max(2, page - 2);
let end = Math.min(pages - 1, page + 2);
if (page <= 4) {
start = 2;
end = 5;
} else if (page >= pages - 3) {
start = pages - 4;
end = pages - 1;
}
for (let i = start; i <= end; i++) {
html += `${i}`;
}
if (page < pages - 3) {
html += `...`;
}
// Last page
html += `${pages}`;
}
// Next page
if (page < pages) {
html += `下一页`;
} else {
html += `下一页`;
}
// Jump to page
html += `
跳转至
页 / 共 ${pages} 页
`;
el.innerHTML = html;
}
renderPagination(pagination) {
this.renderGenericPagination('pagination', pagination, 'changePage');
}
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) {
this.renderGenericPagination('studentPagination', pagination, 'changeStudentPage');
}
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) {
this.renderGenericPagination('teacherPagination', pagination, 'changeTeacherPage');
}
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 || '-'} |
|
`).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();
});