feat: 实现教师资料更新、操作日志和系统设置功能
新增教师资料更新功能,包括个人信息修改和密码更新 添加操作日志记录系统,记录用户关键操作 实现系统设置模块,支持动态配置系统参数 重构数据库模型,新增教师表和系统设置表 优化成绩录入逻辑,支持平时分、期中和期末成绩计算 添加数据导出功能,支持学生、教师和成绩数据导出 完善管理员后台,增加统计图表和操作日志查看
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user