feat: 实现教师资料更新、操作日志和系统设置功能

新增教师资料更新功能,包括个人信息修改和密码更新
添加操作日志记录系统,记录用户关键操作
实现系统设置模块,支持动态配置系统参数
重构数据库模型,新增教师表和系统设置表
优化成绩录入逻辑,支持平时分、期中和期末成绩计算
添加数据导出功能,支持学生、教师和成绩数据导出
完善管理员后台,增加统计图表和操作日志查看
This commit is contained in:
祀梦
2025-12-22 23:30:01 +08:00
parent 16802c85e5
commit b1da021185
43 changed files with 7860 additions and 2835 deletions

View File

@@ -1,10 +1,17 @@
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 = 1, limit = 10, search = '', role = '' } = 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';
@@ -28,9 +35,9 @@ class AdminService {
// Data
queryStr += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
queryParams.push(parseInt(limit), parseInt(offset));
const dataQueryParams = [...queryParams, limit, offset];
const users = await db.query(queryStr, queryParams);
const users = await db.query(queryStr, dataQueryParams);
return {
data: users,
@@ -43,8 +50,22 @@ class AdminService {
};
}
static async createUser(userData) {
const { id } = userData;
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);
@@ -53,7 +74,445 @@ class AdminService {
}
// 创建用户
return await User.create(userData);
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);
}
}