新增教师资料更新功能,包括个人信息修改和密码更新 添加操作日志记录系统,记录用户关键操作 实现系统设置模块,支持动态配置系统参数 重构数据库模型,新增教师表和系统设置表 优化成绩录入逻辑,支持平时分、期中和期末成绩计算 添加数据导出功能,支持学生、教师和成绩数据导出 完善管理员后台,增加统计图表和操作日志查看
519 lines
17 KiB
JavaScript
519 lines
17 KiB
JavaScript
const db = require('../config/database');
|
|
const bcrypt = require('bcryptjs');
|
|
const User = require('../models/User');
|
|
const Student = require('../models/Student');
|
|
const Teacher = require('../models/Teacher');
|
|
const SystemSetting = require('../models/SystemSetting');
|
|
const OperationLog = require('../models/OperationLog');
|
|
|
|
class AdminService {
|
|
static async getUsers(params) {
|
|
const page = parseInt(params.page) || 1;
|
|
const limit = parseInt(params.limit) || 10;
|
|
const search = params.search || '';
|
|
const role = params.role || '';
|
|
const offset = (page - 1) * limit;
|
|
|
|
let queryStr = 'SELECT id, name, role, class, created_at FROM users WHERE 1=1';
|
|
let queryParams = [];
|
|
|
|
if (search) {
|
|
queryStr += ' AND (id LIKE ? OR name LIKE ? OR class LIKE ?)';
|
|
const searchTerm = `%${search}%`;
|
|
queryParams.push(searchTerm, searchTerm, searchTerm);
|
|
}
|
|
|
|
if (role) {
|
|
queryStr += ' AND role = ?';
|
|
queryParams.push(role);
|
|
}
|
|
|
|
// Count
|
|
const countSql = queryStr.replace('SELECT id, name, role, class, created_at', 'SELECT COUNT(*) as total');
|
|
const countRows = await db.query(countSql, queryParams);
|
|
const total = countRows[0].total;
|
|
|
|
// Data
|
|
queryStr += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
|
const dataQueryParams = [...queryParams, limit, offset];
|
|
|
|
const users = await db.query(queryStr, dataQueryParams);
|
|
|
|
return {
|
|
data: users,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
total,
|
|
pages: Math.ceil(total / limit)
|
|
}
|
|
};
|
|
}
|
|
|
|
static async getStats() {
|
|
const usersCount = await db.query('SELECT COUNT(*) as count FROM users');
|
|
const studentsCount = await db.query('SELECT COUNT(*) as count FROM students');
|
|
const teachersCount = await db.query('SELECT COUNT(*) as count FROM users WHERE role = "teacher"');
|
|
const coursesCount = await db.query('SELECT COUNT(*) as count FROM courses');
|
|
|
|
return {
|
|
users: usersCount[0].count,
|
|
students: studentsCount[0].count,
|
|
teachers: teachersCount[0].count,
|
|
courses: coursesCount[0].count
|
|
};
|
|
}
|
|
|
|
static async createUser(userData, operator) {
|
|
const { id, role, name } = userData;
|
|
|
|
// 检查 ID
|
|
const existingUser = await User.findById(id);
|
|
if (existingUser) {
|
|
throw new Error('用户ID已存在');
|
|
}
|
|
|
|
// 创建用户
|
|
const userId = await User.create(userData);
|
|
|
|
// 如果是学生,同时创建学生记录
|
|
if (role === 'student') {
|
|
await Student.create(userData);
|
|
} else if (role === 'teacher') {
|
|
await Teacher.create({
|
|
id,
|
|
name,
|
|
department: userData.class || ''
|
|
});
|
|
}
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '用户管理',
|
|
target: `user:${id}`,
|
|
description: `创建了${role === 'student' ? '学生' : (role === 'teacher' ? '教师' : '管理员')}用户: ${name}(${id})`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return userId;
|
|
}
|
|
|
|
static async updateUser(id, userData, operator) {
|
|
// 如果修改密码
|
|
if (userData.password) {
|
|
const salt = await bcrypt.genSalt(10);
|
|
userData.password = await bcrypt.hash(userData.password, salt);
|
|
} else {
|
|
delete userData.password;
|
|
}
|
|
|
|
const fields = [];
|
|
const values = [];
|
|
|
|
if (userData.name) { fields.push('name = ?'); values.push(userData.name); }
|
|
if (userData.role) { fields.push('role = ?'); values.push(userData.role); }
|
|
if (userData.class !== undefined) { fields.push('class = ?'); values.push(userData.class); } // class can be empty string
|
|
if (userData.password) { fields.push('password = ?'); values.push(userData.password); }
|
|
|
|
if (fields.length === 0) return true;
|
|
|
|
values.push(id);
|
|
const sql = `UPDATE users SET ${fields.join(', ')} WHERE id = ?`;
|
|
|
|
const result = await db.query(sql, values);
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '用户管理',
|
|
target: `user:${id}`,
|
|
description: `修改了用户信息: ${id}`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return result.affectedRows > 0;
|
|
}
|
|
|
|
static async deleteUser(id, operator) {
|
|
// 删除用户
|
|
await Student.delete(id);
|
|
await Teacher.delete(id);
|
|
const result = await db.query('DELETE FROM users WHERE id = ?', [id]);
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '用户管理',
|
|
target: `user:${id}`,
|
|
description: `删除了用户: ${id}`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return result.affectedRows > 0;
|
|
}
|
|
|
|
// ================= Student Management =================
|
|
static async getStudents(params) {
|
|
const page = parseInt(params.page) || 1;
|
|
const limit = parseInt(params.limit) || 10;
|
|
const search = params.search || '';
|
|
const offset = (page - 1) * limit;
|
|
|
|
let queryStr = 'SELECT * FROM students WHERE 1=1';
|
|
let queryParams = [];
|
|
|
|
if (search) {
|
|
queryStr += ' AND (id LIKE ? OR name LIKE ? OR class LIKE ?)';
|
|
const searchTerm = `%${search}%`;
|
|
queryParams.push(searchTerm, searchTerm, searchTerm);
|
|
}
|
|
|
|
// Count
|
|
const countSql = queryStr.replace('SELECT *', 'SELECT COUNT(*) as total');
|
|
const countRows = await db.query(countSql, queryParams);
|
|
const total = countRows[0].total;
|
|
|
|
// Data
|
|
queryStr += ' ORDER BY id ASC LIMIT ? OFFSET ?';
|
|
const dataQueryParams = [...queryParams, limit, offset];
|
|
|
|
const students = await db.query(queryStr, dataQueryParams);
|
|
|
|
return {
|
|
data: students,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
total,
|
|
pages: Math.ceil(total / limit)
|
|
}
|
|
};
|
|
}
|
|
|
|
static async createStudent(studentData, operator) {
|
|
const { id, name, class: className } = studentData;
|
|
|
|
// Check ID
|
|
const existing = await User.findById(id);
|
|
if (existing) throw new Error('学号已存在');
|
|
|
|
// Create User first
|
|
await User.create({
|
|
id,
|
|
name,
|
|
password: id, // Default password
|
|
role: 'student',
|
|
class: className
|
|
});
|
|
|
|
// Create Student
|
|
await Student.create(studentData);
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '学生管理',
|
|
target: `student:${id}`,
|
|
description: `创建了学生: ${name}(${id})`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
static async updateStudent(id, data, operator) {
|
|
// Update students table
|
|
await Student.update(id, data);
|
|
|
|
// Sync User table (name, class)
|
|
const userFields = [];
|
|
const userValues = [];
|
|
if (data.name) { userFields.push('name = ?'); userValues.push(data.name); }
|
|
if (data.class) { userFields.push('class = ?'); userValues.push(data.class); }
|
|
|
|
if (userFields.length > 0) {
|
|
userValues.push(id);
|
|
await db.query(`UPDATE users SET ${userFields.join(', ')} WHERE id = ?`, userValues);
|
|
}
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '学生管理',
|
|
target: `student:${id}`,
|
|
description: `修改了学生信息: ${id}`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static async deleteStudent(id, operator) {
|
|
await Student.delete(id);
|
|
await db.query('DELETE FROM users WHERE id = ?', [id]);
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '学生管理',
|
|
target: `student:${id}`,
|
|
description: `删除了学生: ${id}`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ================= Teacher Management =================
|
|
static async getTeachers(params) {
|
|
const page = parseInt(params.page) || 1;
|
|
const limit = parseInt(params.limit) || 10;
|
|
const search = params.search || '';
|
|
const offset = (page - 1) * limit;
|
|
|
|
let queryStr = 'SELECT t.*, u.created_at FROM teachers t JOIN users u ON t.id = u.id WHERE 1=1';
|
|
let queryParams = [];
|
|
|
|
if (search) {
|
|
queryStr += ' AND (t.id LIKE ? OR t.name LIKE ? OR t.department LIKE ?)';
|
|
const searchTerm = `%${search}%`;
|
|
queryParams.push(searchTerm, searchTerm, searchTerm);
|
|
}
|
|
|
|
// Count
|
|
const countSql = `SELECT COUNT(*) as total FROM teachers t WHERE 1=1 ${search ? 'AND (t.id LIKE ? OR t.name LIKE ? OR t.department LIKE ?)' : ''}`;
|
|
const countRows = await db.query(countSql, queryParams);
|
|
const total = countRows[0].total;
|
|
|
|
// Data
|
|
queryStr += ' ORDER BY t.id ASC LIMIT ? OFFSET ?';
|
|
const dataQueryParams = [...queryParams, limit, offset];
|
|
|
|
const teachers = await db.query(queryStr, dataQueryParams);
|
|
|
|
return {
|
|
data: teachers,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
total,
|
|
pages: Math.ceil(total / limit)
|
|
}
|
|
};
|
|
}
|
|
|
|
static async createTeacher(teacherData, operator) {
|
|
const { id, name, department } = teacherData;
|
|
|
|
// Check ID
|
|
const existing = await User.findById(id);
|
|
if (existing) throw new Error('工号已存在');
|
|
|
|
// Create User
|
|
await User.create({
|
|
id,
|
|
name,
|
|
password: id, // Default password
|
|
role: 'teacher',
|
|
class: department // Use class field for department in user table
|
|
});
|
|
|
|
// Create Teacher
|
|
await Teacher.create(teacherData);
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '教师管理',
|
|
target: `teacher:${id}`,
|
|
description: `创建了教师: ${name}(${id})`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return id;
|
|
}
|
|
|
|
static async updateTeacher(id, data, operator) {
|
|
await Teacher.update(id, data);
|
|
|
|
// Sync User table (name, department)
|
|
const userFields = [];
|
|
const userValues = [];
|
|
if (data.name) { userFields.push('name = ?'); userValues.push(data.name); }
|
|
if (data.department) { userFields.push('class = ?'); userValues.push(data.department); }
|
|
|
|
if (userFields.length > 0) {
|
|
userValues.push(id);
|
|
await db.query(`UPDATE users SET ${userFields.join(', ')} WHERE id = ?`, userValues);
|
|
}
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '教师管理',
|
|
target: `teacher:${id}`,
|
|
description: `修改了教师信息: ${id}`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static async deleteTeacher(id, operator) {
|
|
await Teacher.delete(id);
|
|
await db.query('DELETE FROM users WHERE id = ?', [id]);
|
|
|
|
if (operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '教师管理',
|
|
target: `teacher:${id}`,
|
|
description: `删除了教师: ${id}`,
|
|
ip: operator.ip
|
|
});
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// ================= Grade Statistics =================
|
|
static async getGradeStats() {
|
|
const sql = `
|
|
SELECT
|
|
c.course_code,
|
|
c.course_name,
|
|
u.name as teacher_name,
|
|
COUNT(g.student_id) as student_count,
|
|
AVG(g.total_score) as avg_score,
|
|
MAX(g.total_score) as max_score,
|
|
MIN(g.total_score) as min_score,
|
|
SUM(CASE WHEN g.total_score >= 60 THEN 1 ELSE 0 END) as pass_count
|
|
FROM courses c
|
|
LEFT JOIN grades g ON c.id = g.course_id
|
|
LEFT JOIN users u ON c.teacher_id = u.id
|
|
GROUP BY c.id
|
|
ORDER BY avg_score DESC
|
|
`;
|
|
|
|
const rows = await db.query(sql);
|
|
|
|
return rows.map(row => ({
|
|
...row,
|
|
pass_rate: row.student_count > 0 ? ((row.pass_count / row.student_count) * 100).toFixed(1) : 0,
|
|
avg_score: row.avg_score ? Number(row.avg_score).toFixed(1) : 0
|
|
}));
|
|
}
|
|
|
|
// ================= System Settings =================
|
|
static async getSettings() {
|
|
return await SystemSetting.getAll();
|
|
}
|
|
|
|
static async saveSettings(settings, operator) {
|
|
await SystemSetting.setMany(settings);
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '系统设置',
|
|
target: 'system_settings',
|
|
description: '修改了系统基础设置',
|
|
ip: operator.ip
|
|
});
|
|
return true;
|
|
}
|
|
|
|
// ================= Data Maintenance =================
|
|
static async backupDatabase(operator) {
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const source = path.resolve(__dirname, '../database.sqlite');
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
const backupDir = path.resolve(__dirname, '../backups');
|
|
const target = path.resolve(backupDir, `database-${timestamp}.sqlite`);
|
|
|
|
if (!fs.existsSync(backupDir)) {
|
|
fs.mkdirSync(backupDir);
|
|
}
|
|
|
|
fs.copyFileSync(source, target);
|
|
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '数据维护',
|
|
target: 'database',
|
|
description: `手动备份数据库: ${path.basename(target)}`,
|
|
ip: operator.ip
|
|
});
|
|
|
|
return { filename: path.basename(target) };
|
|
}
|
|
|
|
static async clearCache(operator) {
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '数据维护',
|
|
target: 'cache',
|
|
description: '清理系统缓存',
|
|
ip: operator.ip
|
|
});
|
|
return true;
|
|
}
|
|
|
|
static async resetStudentPasswords(operator) {
|
|
const hashedPassword = await bcrypt.hash('123456', 10);
|
|
await db.query('UPDATE users SET password = ? WHERE role = "student"', [hashedPassword]);
|
|
|
|
await OperationLog.add({
|
|
user_id: operator.user_id,
|
|
type: '数据维护',
|
|
target: 'users',
|
|
description: '重置了所有学生的密码为 123456',
|
|
ip: operator.ip
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
// ================= Operation Logs =================
|
|
static async getLogs(params) {
|
|
return await OperationLog.findAll(params);
|
|
}
|
|
|
|
// ================= Data Export =================
|
|
static async exportStudents() {
|
|
return await db.query('SELECT * FROM students');
|
|
}
|
|
|
|
static async exportTeachers() {
|
|
return await db.query('SELECT t.*, u.created_at FROM teachers t JOIN users u ON t.id = u.id');
|
|
}
|
|
|
|
static async exportGrades() {
|
|
const sql = `
|
|
SELECT
|
|
g.student_id,
|
|
s.name as student_name,
|
|
c.course_code,
|
|
c.course_name,
|
|
g.total_score,
|
|
g.grade_point,
|
|
g.grade_level,
|
|
u.name as teacher_name
|
|
FROM grades g
|
|
JOIN students s ON g.student_id = s.id
|
|
JOIN courses c ON g.course_id = c.id
|
|
JOIN users u ON g.teacher_id = u.id
|
|
`;
|
|
return await db.query(sql);
|
|
}
|
|
}
|
|
|
|
module.exports = AdminService; |