feat: 实现成绩管理系统核心功能
添加响应工具、错误处理中间件和数据库模型 创建用户、学生、课程和成绩相关服务 实现管理员、教师和学生控制器的基本功能 重构路由处理并优化数据库查询
This commit is contained in:
@@ -11,54 +11,32 @@ const pool = mysql.createPool({
|
|||||||
queueLimit: 0
|
queueLimit: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
// 测试数据库连接
|
// 封装基本查询方法
|
||||||
async function testConnection() {
|
const query = async (sql, params) => {
|
||||||
try {
|
try {
|
||||||
const connection = await pool.getConnection();
|
const [rows] = await pool.execute(sql, params);
|
||||||
console.log('数据库连接成功');
|
return rows;
|
||||||
connection.release();
|
} catch (error) {
|
||||||
return true;
|
console.error('Database query error:', error);
|
||||||
} catch (error) {
|
throw error;
|
||||||
console.error('数据库连接失败:', error.message);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行查询
|
|
||||||
async function query(sql, params) {
|
|
||||||
try {
|
|
||||||
const [rows] = await pool.execute(sql, params);
|
|
||||||
return rows;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('数据库查询错误:', error.message);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 执行事务
|
|
||||||
async function executeTransaction(operations) {
|
|
||||||
const connection = await pool.getConnection();
|
|
||||||
try {
|
|
||||||
await connection.beginTransaction();
|
|
||||||
|
|
||||||
for (const operation of operations) {
|
|
||||||
await connection.execute(operation.sql, operation.params);
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
await connection.commit();
|
// 测试连接
|
||||||
console.log('事务执行成功');
|
const testConnection = async () => {
|
||||||
} catch (error) {
|
try {
|
||||||
await connection.rollback();
|
const connection = await pool.getConnection();
|
||||||
console.error('事务执行失败:', error.message);
|
console.log('数据库连接成功');
|
||||||
throw error;
|
connection.release();
|
||||||
} finally {
|
return true;
|
||||||
connection.release();
|
} catch (error) {
|
||||||
}
|
console.error('数据库连接失败:', error.message);
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
pool,
|
pool,
|
||||||
query,
|
query,
|
||||||
executeTransaction,
|
testConnection
|
||||||
testConnection
|
|
||||||
};
|
};
|
||||||
53
backend/controllers/adminController.js
Normal file
53
backend/controllers/adminController.js
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
const AdminService = require('../services/adminService');
|
||||||
|
const { success, error } = require('../utils/response');
|
||||||
|
|
||||||
|
class AdminController {
|
||||||
|
static async getUsers(req, res) {
|
||||||
|
try {
|
||||||
|
const result = await AdminService.getUsers(req.query);
|
||||||
|
success(res, result.data, '获取成功');
|
||||||
|
// 注意:原来的响应结构是 { success, data, pagination }
|
||||||
|
// 现在的 success 工具函数结构是 { success, message, data }
|
||||||
|
// 我们可以稍微调整 success 调用,或者让前端适应
|
||||||
|
// 为了兼容性,这里手动返回
|
||||||
|
/*
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: result.data,
|
||||||
|
pagination: result.pagination
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
// 或者修改 response.js 支持 extra 字段,这里简单处理:
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: result.data,
|
||||||
|
pagination: result.pagination
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Get Users Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createUser(req, res) {
|
||||||
|
try {
|
||||||
|
const { id, name, password, role } = req.body;
|
||||||
|
|
||||||
|
if (!id || !name || !password || !role) {
|
||||||
|
return error(res, '请填写所有必填字段', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
await AdminService.createUser(req.body);
|
||||||
|
success(res, null, '创建用户成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === '用户ID已存在') {
|
||||||
|
return error(res, err.message, 400);
|
||||||
|
}
|
||||||
|
console.error('Create User Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AdminController;
|
||||||
70
backend/controllers/authController.js
Normal file
70
backend/controllers/authController.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const AuthService = require('../services/authService');
|
||||||
|
const { success, error } = require('../utils/response');
|
||||||
|
|
||||||
|
class AuthController {
|
||||||
|
static async login(req, res) {
|
||||||
|
try {
|
||||||
|
const { id, password, role } = req.body;
|
||||||
|
if (!id || !password || !role) {
|
||||||
|
return error(res, '请输入完整的登录信息', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await AuthService.login(id, password, role);
|
||||||
|
|
||||||
|
// 设置 Session
|
||||||
|
req.session.user = user;
|
||||||
|
|
||||||
|
success(res, user, '登录成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === '用户名或密码错误') {
|
||||||
|
return error(res, err.message, 401);
|
||||||
|
}
|
||||||
|
console.error('Login Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async register(req, res) {
|
||||||
|
try {
|
||||||
|
const { id, name, password, role, class: userClass } = req.body;
|
||||||
|
|
||||||
|
if (!id || !name || !password || !role) {
|
||||||
|
return error(res, '请填写所有必填字段', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((role === 'student' || role === 'teacher') && !userClass) {
|
||||||
|
return error(res, '学生和教师需要填写班级', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
await AuthService.register(req.body);
|
||||||
|
success(res, null, '注册成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === '用户ID已存在') {
|
||||||
|
return error(res, err.message, 400);
|
||||||
|
}
|
||||||
|
console.error('Register Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async logout(req, res) {
|
||||||
|
req.session.destroy((err) => {
|
||||||
|
if (err) {
|
||||||
|
return error(res, '注销失败');
|
||||||
|
}
|
||||||
|
res.clearCookie('session_cookie');
|
||||||
|
success(res, null, '注销成功');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getCurrentUser(req, res) {
|
||||||
|
if (req.session.user) {
|
||||||
|
success(res, { user: req.session.user });
|
||||||
|
} else {
|
||||||
|
// 不返回 401,只返回 success: false,方便前端判断
|
||||||
|
res.json({ success: false, message: '未登录' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AuthController;
|
||||||
35
backend/controllers/studentController.js
Normal file
35
backend/controllers/studentController.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const StudentService = require('../services/studentService');
|
||||||
|
const { success, error } = require('../utils/response');
|
||||||
|
|
||||||
|
class StudentController {
|
||||||
|
static async getGrades(req, res) {
|
||||||
|
try {
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
const data = await StudentService.getStudentGrades(userId);
|
||||||
|
success(res, data);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === '学生信息不存在') {
|
||||||
|
return error(res, err.message, 404);
|
||||||
|
}
|
||||||
|
console.error('Get Grades Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getGradeDetails(req, res) {
|
||||||
|
try {
|
||||||
|
const scoreId = req.params.id;
|
||||||
|
const userId = req.session.user.id;
|
||||||
|
const grade = await StudentService.getGradeDetails(scoreId, userId);
|
||||||
|
success(res, { grade });
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === '成绩不存在') {
|
||||||
|
return error(res, err.message, 404);
|
||||||
|
}
|
||||||
|
console.error('Get Grade Details Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = StudentController;
|
||||||
42
backend/controllers/teacherController.js
Normal file
42
backend/controllers/teacherController.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const TeacherService = require('../services/teacherService');
|
||||||
|
const { success, error } = require('../utils/response');
|
||||||
|
|
||||||
|
class TeacherController {
|
||||||
|
static async getCourses(req, res) {
|
||||||
|
try {
|
||||||
|
const teacherId = req.session.user.id; // 注意:这里假设 user.id 就是 teacher_id,需要根据 users 表设计确认
|
||||||
|
// 在之前的 SQL 中,courses.teacher_id 是 INT,而 users.id 是 VARCHAR。
|
||||||
|
// 这里可能存在类型不匹配的问题。
|
||||||
|
// 假设 users 表中 id 既是学号/工号,也是关联键。
|
||||||
|
// 实际上,之前的 teacher.js 中是直接用 req.session.user.id 查询。
|
||||||
|
|
||||||
|
const courses = await TeacherService.getCourses(teacherId);
|
||||||
|
success(res, { courses });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Get Courses Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addScore(req, res) {
|
||||||
|
try {
|
||||||
|
const teacherId = req.session.user.id;
|
||||||
|
const { studentId, courseId, score } = req.body;
|
||||||
|
|
||||||
|
if (!studentId || !courseId || score === undefined) {
|
||||||
|
return error(res, '请填写必填字段', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gradeId = await TeacherService.addScore(teacherId, req.body);
|
||||||
|
success(res, { gradeId }, '成绩录入成功');
|
||||||
|
} catch (err) {
|
||||||
|
if (err.message === '学生不存在' || err.message === '该学生此课程成绩已存在') {
|
||||||
|
return error(res, err.message, 400);
|
||||||
|
}
|
||||||
|
console.error('Add Score Error:', err);
|
||||||
|
error(res, '服务器错误');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TeacherController;
|
||||||
15
backend/middleware/errorHandler.js
Normal file
15
backend/middleware/errorHandler.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const { error } = require('../utils/response');
|
||||||
|
|
||||||
|
const errorHandler = (err, req, res, next) => {
|
||||||
|
console.error(err.stack);
|
||||||
|
|
||||||
|
// 如果是自定义错误,可以根据类型处理
|
||||||
|
if (err.type === 'ValidationError') {
|
||||||
|
return error(res, err.message, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认 500
|
||||||
|
error(res, 'Internal Server Error', 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = errorHandler;
|
||||||
17
backend/models/Course.js
Normal file
17
backend/models/Course.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
const db = require('../config/database');
|
||||||
|
|
||||||
|
class Course {
|
||||||
|
static async findByTeacherId(teacherId) {
|
||||||
|
return await db.query(
|
||||||
|
'SELECT * FROM courses WHERE teacher_id = ? ORDER BY course_code',
|
||||||
|
[teacherId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findById(id) {
|
||||||
|
const rows = await db.query('SELECT * FROM courses WHERE id = ?', [id]);
|
||||||
|
return rows[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Course;
|
||||||
55
backend/models/Score.js
Normal file
55
backend/models/Score.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const db = require('../config/database');
|
||||||
|
|
||||||
|
class Score {
|
||||||
|
static async findByStudentId(studentId) {
|
||||||
|
const sql = `
|
||||||
|
SELECT s.*, c.course_code, c.course_name, c.credit,
|
||||||
|
u.name as teacher_name
|
||||||
|
FROM scores s
|
||||||
|
JOIN courses c ON s.course_id = c.id
|
||||||
|
JOIN users u ON s.teacher_id = u.id
|
||||||
|
WHERE s.student_id = ?
|
||||||
|
ORDER BY s.created_at DESC
|
||||||
|
`;
|
||||||
|
return await db.query(sql, [studentId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findDetailsById(scoreId, studentId) {
|
||||||
|
const sql = `
|
||||||
|
SELECT s.*, c.course_code, c.course_name, c.credit, c.semester,
|
||||||
|
u.name as teacher_name, u.email as teacher_email,
|
||||||
|
st.id as student_number, st.class as class_name
|
||||||
|
FROM scores s
|
||||||
|
JOIN courses c ON s.course_id = c.id
|
||||||
|
JOIN users u ON s.teacher_id = u.id
|
||||||
|
JOIN students st ON s.student_id = st.id
|
||||||
|
WHERE s.id = ? AND st.id = ?
|
||||||
|
`;
|
||||||
|
const rows = await db.query(sql, [scoreId, studentId]);
|
||||||
|
return rows[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(scoreData) {
|
||||||
|
const { studentId, courseId, teacherId, score, gradePoint, gradeLevel, examDate, remark } = scoreData;
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO scores (student_id, course_id, teacher_id, score,
|
||||||
|
grade_point, grade_level, created_at, remark)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, NOW(), ?)
|
||||||
|
`;
|
||||||
|
// 注意:这里用 created_at 替代了 examDate,如果数据库有 examDate 列可以改回去
|
||||||
|
const result = await db.pool.execute(sql, [
|
||||||
|
studentId, courseId, teacherId, score, gradePoint, gradeLevel, remark
|
||||||
|
]);
|
||||||
|
return result[0].insertId;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findByStudentAndCourse(studentId, courseId) {
|
||||||
|
const rows = await db.query(
|
||||||
|
'SELECT * FROM scores WHERE student_id = ? AND course_id = ?',
|
||||||
|
[studentId, courseId]
|
||||||
|
);
|
||||||
|
return rows[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Score;
|
||||||
23
backend/models/Student.js
Normal file
23
backend/models/Student.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const db = require('../config/database');
|
||||||
|
|
||||||
|
class Student {
|
||||||
|
static async findByUserId(userId) {
|
||||||
|
const students = await db.query('SELECT * FROM students WHERE user_id = ?', [userId]);
|
||||||
|
return students[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findById(studentId) {
|
||||||
|
const students = await db.query('SELECT * FROM students WHERE student_id = ?', [studentId]);
|
||||||
|
return students[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(studentData) {
|
||||||
|
const { id, name, className, userId } = studentData;
|
||||||
|
await db.query(
|
||||||
|
'INSERT INTO students (id, name, class, user_id) VALUES (?, ?, ?, ?)',
|
||||||
|
[id, name, className, userId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Student;
|
||||||
33
backend/models/User.js
Normal file
33
backend/models/User.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
const db = require('../config/database');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
|
||||||
|
class User {
|
||||||
|
static async findById(id) {
|
||||||
|
const users = await db.query('SELECT * FROM users WHERE id = ?', [id]);
|
||||||
|
return users[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findByIdAndRole(id, role) {
|
||||||
|
const users = await db.query('SELECT * FROM users WHERE id = ? AND role = ?', [id, role]);
|
||||||
|
return users[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
static async create(userData) {
|
||||||
|
const { id, name, password, role, className } = userData;
|
||||||
|
const salt = await bcrypt.genSalt(10);
|
||||||
|
const hashedPassword = await bcrypt.hash(password, salt);
|
||||||
|
|
||||||
|
await db.query(
|
||||||
|
'INSERT INTO users (id, name, password, role, class) VALUES (?, ?, ?, ?, ?)',
|
||||||
|
[id, name, hashedPassword, role, className || null]
|
||||||
|
);
|
||||||
|
|
||||||
|
return { id, name, role, class: className };
|
||||||
|
}
|
||||||
|
|
||||||
|
static async verifyPassword(plainPassword, hashedPassword) {
|
||||||
|
return await bcrypt.compare(plainPassword, hashedPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = User;
|
||||||
@@ -1,311 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const db = require('../config/database');
|
const AdminController = require('../controllers/adminController');
|
||||||
const { requireAuth, requireRole } = require('../middleware/auth');
|
const { requireAuth, requireRole } = require('../middleware/auth');
|
||||||
|
|
||||||
/**
|
router.get('/users', requireAuth, requireRole(['admin']), AdminController.getUsers);
|
||||||
* 获取所有用户
|
router.post('/users', requireAuth, requireRole(['admin']), AdminController.createUser);
|
||||||
*/
|
|
||||||
router.get('/users', requireAuth, requireRole(['admin']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { page = 1, limit = 10, search = '', role = '' } = req.query;
|
|
||||||
const offset = (page - 1) * limit;
|
|
||||||
|
|
||||||
let query = 'SELECT id, name, role, class, created_at FROM users WHERE 1=1';
|
|
||||||
let params = [];
|
|
||||||
|
|
||||||
if (search) {
|
|
||||||
query += ' AND (id LIKE ? OR name LIKE ? OR class LIKE ?)';
|
|
||||||
const searchTerm = `%${search}%`;
|
|
||||||
params.push(searchTerm, searchTerm, searchTerm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role) {
|
|
||||||
query += ' AND role = ?';
|
|
||||||
params.push(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取总数
|
|
||||||
const countQuery = query.replace('SELECT id, name, role, class, created_at', 'SELECT COUNT(*) as total');
|
|
||||||
const countResult = await db.pool.execute(countQuery, params);
|
|
||||||
const total = countResult[0][0].total;
|
|
||||||
|
|
||||||
// 获取分页数据
|
|
||||||
query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
|
||||||
params.push(parseInt(limit), parseInt(offset));
|
|
||||||
|
|
||||||
const users = await db.pool.execute(query, params);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
data: users,
|
|
||||||
pagination: {
|
|
||||||
page: parseInt(page),
|
|
||||||
limit: parseInt(limit),
|
|
||||||
total,
|
|
||||||
pages: Math.ceil(total / limit)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取用户列表错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建用户
|
|
||||||
*/
|
|
||||||
router.post('/users', requireAuth, requireRole(['admin']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { id, name, password, role, className } = req.body;
|
|
||||||
|
|
||||||
// 输入验证
|
|
||||||
if (!id || !name || !password || !role) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '请填写所有必填字段'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查用户ID是否存在
|
|
||||||
const existingUsers = await db.pool.execute(
|
|
||||||
'SELECT id FROM users WHERE id = ?',
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingUsers[0].length > 0) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '用户ID已存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 哈希密码
|
|
||||||
const bcrypt = require('bcrypt');
|
|
||||||
const salt = await bcrypt.genSalt(10);
|
|
||||||
const passwordHash = await bcrypt.hash(password, salt);
|
|
||||||
|
|
||||||
// 创建用户
|
|
||||||
const result = await db.pool.execute(
|
|
||||||
'INSERT INTO users (id, name, password, role, class) VALUES (?, ?, ?, ?, ?)',
|
|
||||||
[id, name, passwordHash, role, className || null]
|
|
||||||
);
|
|
||||||
|
|
||||||
const userId = result[0].insertId;
|
|
||||||
|
|
||||||
// 根据角色创建相关记录
|
|
||||||
if (role === 'student') {
|
|
||||||
const studentId = 'STU' + Date.now().toString().slice(-6);
|
|
||||||
await db.pool.execute(
|
|
||||||
'INSERT INTO students (user_id, student_id, full_name, class_name) VALUES (?, ?, ?, ?)',
|
|
||||||
[userId, studentId, fullName, className || '未分配班级']
|
|
||||||
);
|
|
||||||
} else if (role === 'teacher') {
|
|
||||||
await db.pool.execute(
|
|
||||||
'INSERT INTO teachers (user_id, full_name) VALUES (?, ?)',
|
|
||||||
[userId, fullName]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '用户创建成功',
|
|
||||||
userId
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('创建用户错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新用户
|
|
||||||
*/
|
|
||||||
router.put('/users/:id', requireAuth, requireRole(['admin']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const userId = req.params.id;
|
|
||||||
const { name, role, className } = req.body;
|
|
||||||
|
|
||||||
// 检查用户是否存在
|
|
||||||
const users = await db.pool.execute(
|
|
||||||
'SELECT * FROM users WHERE id = ?',
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (users[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '用户不存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldRole = users[0][0].role;
|
|
||||||
|
|
||||||
// 更新用户信息
|
|
||||||
await db.pool.execute(
|
|
||||||
'UPDATE users SET name = ?, role = ?, class = ? WHERE id = ?',
|
|
||||||
[name, role, className || null, userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果角色改变,更新相关记录
|
|
||||||
if (oldRole !== role) {
|
|
||||||
// 删除旧角色的记录
|
|
||||||
if (oldRole === 'student') {
|
|
||||||
await db.pool.execute('DELETE FROM students WHERE user_id = ?', [userId]);
|
|
||||||
} else if (oldRole === 'teacher') {
|
|
||||||
await db.pool.execute('DELETE FROM teachers WHERE user_id = ?', [userId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建新角色的记录
|
|
||||||
if (role === 'student') {
|
|
||||||
await db.pool.execute(
|
|
||||||
'INSERT INTO students (user_id, class) VALUES (?, ?)',
|
|
||||||
[userId, className || null]
|
|
||||||
);
|
|
||||||
} else if (role === 'teacher') {
|
|
||||||
// 教师不需要额外表
|
|
||||||
}
|
|
||||||
} else if (role === 'student' && className) {
|
|
||||||
// 如果是学生且班级有变化,更新班级
|
|
||||||
await db.pool.execute(
|
|
||||||
'UPDATE students SET class = ? WHERE user_id = ?',
|
|
||||||
[className, userId]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '用户更新成功'
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新用户错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除用户
|
|
||||||
*/
|
|
||||||
router.delete('/users/:id', requireAuth, requireRole(['admin']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const userId = req.params.id;
|
|
||||||
|
|
||||||
// 检查用户是否存在
|
|
||||||
const users = await db.pool.execute(
|
|
||||||
'SELECT role FROM users WHERE id = ?',
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (users[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '用户不存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const userRole = users[0][0].role;
|
|
||||||
|
|
||||||
// 删除相关记录
|
|
||||||
if (userRole === 'student') {
|
|
||||||
await db.pool.execute('DELETE FROM students WHERE user_id = ?', [userId]);
|
|
||||||
} else if (userRole === 'teacher') {
|
|
||||||
await db.pool.execute('DELETE FROM teachers WHERE user_id = ?', [userId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除用户
|
|
||||||
await db.pool.execute('DELETE FROM users WHERE id = ?', [userId]);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '用户删除成功'
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除用户错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有班级
|
|
||||||
*/
|
|
||||||
router.get('/classes', requireAuth, requireRole(['admin']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const classes = await db.pool.execute(
|
|
||||||
'SELECT DISTINCT class_name FROM students ORDER BY class_name'
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
data: classes
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取班级列表错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取统计数据
|
|
||||||
*/
|
|
||||||
router.get('/stats', requireAuth, requireRole(['admin']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
// 用户统计
|
|
||||||
const userStats = await db.pool.execute(
|
|
||||||
'SELECT role, COUNT(*) as count FROM users GROUP BY role'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 班级统计
|
|
||||||
const classStats = await db.pool.execute(
|
|
||||||
'SELECT class_name, COUNT(*) as count FROM students GROUP BY class_name'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 课程统计
|
|
||||||
const courseStats = await db.pool.execute(
|
|
||||||
'SELECT COUNT(*) as total_courses FROM courses'
|
|
||||||
);
|
|
||||||
|
|
||||||
// 成绩统计
|
|
||||||
const gradeStats = await db.pool.execute(
|
|
||||||
'SELECT COUNT(*) as total_grades FROM scores'
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
data: {
|
|
||||||
users: userStats[0],
|
|
||||||
classes: classStats[0],
|
|
||||||
total_courses: courseStats[0][0].total_courses,
|
|
||||||
total_grades: gradeStats[0][0].total_grades
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取统计数据错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -1,175 +1,10 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const bcrypt = require('bcryptjs');
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const db = require('../config/database');
|
const AuthController = require('../controllers/authController');
|
||||||
|
|
||||||
// 登录
|
router.post('/login', AuthController.login);
|
||||||
router.post('/login', async (req, res) => {
|
router.post('/register', AuthController.register);
|
||||||
try {
|
router.post('/logout', AuthController.logout);
|
||||||
const { id, password, role } = req.body;
|
router.get('/me', AuthController.getCurrentUser);
|
||||||
|
|
||||||
// 输入验证
|
|
||||||
if (!id || !password || !role) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '请输入完整的登录信息'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询用户
|
|
||||||
const users = await db.query(
|
|
||||||
'SELECT * FROM users WHERE id = ? AND role = ?',
|
|
||||||
[id, role]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (users.length === 0) {
|
|
||||||
return res.status(401).json({
|
|
||||||
success: false,
|
|
||||||
message: '用户名或密码错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = users[0];
|
|
||||||
|
|
||||||
// 验证密码
|
|
||||||
const isValidPassword = await bcrypt.compare(password, user.password);
|
|
||||||
if (!isValidPassword) {
|
|
||||||
return res.status(401).json({
|
|
||||||
success: false,
|
|
||||||
message: '用户名或密码错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置会话
|
|
||||||
req.session.user = {
|
|
||||||
id: user.id,
|
|
||||||
name: user.name,
|
|
||||||
role: user.role,
|
|
||||||
class: user.class
|
|
||||||
};
|
|
||||||
|
|
||||||
// 如果是学生,获取学生信息
|
|
||||||
if (user.role === 'student') {
|
|
||||||
const [students] = await db.pool.execute(
|
|
||||||
'SELECT * FROM students WHERE id = ?',
|
|
||||||
[user.id]
|
|
||||||
);
|
|
||||||
if (students[0].length > 0) {
|
|
||||||
req.session.user.studentInfo = students[0][0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '登录成功',
|
|
||||||
user: req.session.user
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('登录错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 注册
|
|
||||||
router.post('/register', async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { id, name, password, role, class: userClass } = req.body;
|
|
||||||
|
|
||||||
// 输入验证
|
|
||||||
if (!id || !name || !password || !role) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '请填写所有必填字段(ID、姓名、密码、角色)'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 学生和教师需要班级字段,管理员不需要
|
|
||||||
if ((role === 'student' || role === 'teacher') && !userClass) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '学生和教师需要填写班级'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查用户ID是否存在
|
|
||||||
const existingUsers = await db.query(
|
|
||||||
'SELECT id FROM users WHERE id = ?',
|
|
||||||
[id]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingUsers.length > 0) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '用户ID已存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 哈希密码
|
|
||||||
const salt = await bcrypt.genSalt(10);
|
|
||||||
const passwordHash = await bcrypt.hash(password, salt);
|
|
||||||
|
|
||||||
// 创建用户
|
|
||||||
await db.pool.execute(
|
|
||||||
'INSERT INTO users (id, name, password, role, class) VALUES (?, ?, ?, ?, ?)',
|
|
||||||
[id, name, passwordHash, role, userClass || null]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 如果是学生,创建学生记录
|
|
||||||
if (role === 'student') {
|
|
||||||
await db.pool.execute(
|
|
||||||
'INSERT INTO students (id, name, class) VALUES (?, ?, ?)',
|
|
||||||
[id, name, userClass]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '注册成功'
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('注册错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 注销
|
|
||||||
router.post('/logout', (req, res) => {
|
|
||||||
req.session.destroy(err => {
|
|
||||||
if (err) {
|
|
||||||
return res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '注销失败'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
res.clearCookie('session_cookie');
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '注销成功'
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取当前用户信息
|
|
||||||
router.get('/me', (req, res) => {
|
|
||||||
if (!req.session.user) {
|
|
||||||
return res.status(401).json({
|
|
||||||
success: false,
|
|
||||||
message: '未登录'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
user: req.session.user
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -1,143 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const db = require('../config/database');
|
const StudentController = require('../controllers/studentController');
|
||||||
const { requireAuth, requireRole } = require('../middleware/auth');
|
const { requireAuth, requireRole } = require('../middleware/auth');
|
||||||
|
|
||||||
// 获取学生成绩
|
router.get('/grades', requireAuth, requireRole(['student']), StudentController.getGrades);
|
||||||
router.get('/grades', requireAuth, requireRole(['student']), async (req, res) => {
|
router.get('/grades/:id', requireAuth, requireRole(['student']), StudentController.getGradeDetails);
|
||||||
try {
|
|
||||||
const userId = req.session.user.id;
|
|
||||||
|
|
||||||
// 获取学生信息
|
|
||||||
const students = await db.pool.execute(
|
|
||||||
'SELECT id FROM students WHERE user_id = ?',
|
|
||||||
[userId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (students[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '学生信息不存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const studentId = students[0][0].id;
|
|
||||||
|
|
||||||
// 获取成绩信息
|
|
||||||
const grades = await db.pool.execute(`
|
|
||||||
SELECT s.*, c.course_code, c.course_name, c.credit,
|
|
||||||
u.name as teacher_name
|
|
||||||
FROM scores s
|
|
||||||
JOIN courses c ON s.course_id = c.id
|
|
||||||
JOIN users u ON s.teacher_id = u.id
|
|
||||||
WHERE s.student_id = ?
|
|
||||||
ORDER BY s.exam_date DESC
|
|
||||||
`, [studentId]);
|
|
||||||
|
|
||||||
// 计算统计信息
|
|
||||||
let totalCredits = 0;
|
|
||||||
let totalGradePoints = 0;
|
|
||||||
let totalCourses = grades.length;
|
|
||||||
|
|
||||||
grades.forEach(grade => {
|
|
||||||
totalCredits += parseFloat(grade.credit);
|
|
||||||
if (grade.grade_point) {
|
|
||||||
totalGradePoints += parseFloat(grade.grade_point) * parseFloat(grade.credit);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const gpa = totalCredits > 0 ? (totalGradePoints / totalCredits).toFixed(2) : 0;
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
grades,
|
|
||||||
statistics: {
|
|
||||||
totalCourses,
|
|
||||||
totalCredits,
|
|
||||||
gpa,
|
|
||||||
averageScore: totalCourses > 0 ?
|
|
||||||
(grades.reduce((sum, g) => sum + parseFloat(g.score || 0), 0) / totalCourses).toFixed(1) : 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取成绩错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取成绩详情
|
|
||||||
router.get('/grades/:id', requireAuth, requireRole(['student']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const gradeId = req.params.id;
|
|
||||||
const userId = req.session.user.id;
|
|
||||||
|
|
||||||
const grades = await db.pool.execute(`
|
|
||||||
SELECT s.*, c.course_code, c.course_name, c.credit, c.semester,
|
|
||||||
u.full_name as teacher_name, u.email as teacher_email,
|
|
||||||
st.student_id as student_number, st.class_name, st.major
|
|
||||||
FROM scores s
|
|
||||||
JOIN courses c ON s.course_id = c.id
|
|
||||||
JOIN users u ON s.teacher_id = u.id
|
|
||||||
JOIN students st ON s.student_id = st.id
|
|
||||||
WHERE s.id = ? AND st.user_id = ?
|
|
||||||
`, [gradeId, userId]);
|
|
||||||
|
|
||||||
if (grades[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '成绩不存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
grade: grades[0]
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取成绩详情错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 获取学生个人信息
|
|
||||||
router.get('/profile', requireAuth, requireRole(['student']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const userId = req.session.user.id;
|
|
||||||
|
|
||||||
const students = await db.pool.execute(`
|
|
||||||
SELECT s.*, u.username, u.email, u.created_at as account_created
|
|
||||||
FROM students s
|
|
||||||
JOIN users u ON s.user_id = u.id
|
|
||||||
WHERE u.id = ?
|
|
||||||
`, [userId]);
|
|
||||||
|
|
||||||
if (students[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '学生信息不存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
profile: students[0]
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取个人信息错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -1,243 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const db = require('../config/database');
|
const TeacherController = require('../controllers/teacherController');
|
||||||
const { requireAuth, requireRole } = require('../middleware/auth');
|
const { requireAuth, requireRole } = require('../middleware/auth');
|
||||||
|
|
||||||
// 获取教师教授的课程
|
router.get('/courses', requireAuth, requireRole(['teacher']), TeacherController.getCourses);
|
||||||
router.get('/courses', requireAuth, requireRole(['teacher']), async (req, res) => {
|
router.post('/grades', requireAuth, requireRole(['teacher']), TeacherController.addScore);
|
||||||
try {
|
|
||||||
const teacherId = req.session.user.id;
|
|
||||||
|
|
||||||
const courses = await db.pool.execute(
|
|
||||||
'SELECT * FROM courses WHERE teacher_id = ? ORDER BY course_code',
|
|
||||||
[teacherId]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
courses
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取课程错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 录入成绩
|
|
||||||
router.post('/grades', requireAuth, requireRole(['teacher']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const teacherId = req.session.user.id;
|
|
||||||
const { studentId, courseId, score, examDate, remark } = req.body;
|
|
||||||
|
|
||||||
// 验证输入
|
|
||||||
if (!studentId || !courseId || score === undefined) {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '请填写必填字段'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查学生和课程是否存在
|
|
||||||
const students = await db.pool.execute(
|
|
||||||
'SELECT id FROM students WHERE student_id = ?',
|
|
||||||
[studentId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (students[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '学生不存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算绩点和等级
|
|
||||||
const numericScore = parseFloat(score);
|
|
||||||
let gradePoint = 0;
|
|
||||||
let gradeLevel = 'F';
|
|
||||||
|
|
||||||
if (numericScore >= 90) {
|
|
||||||
gradePoint = 4.0; gradeLevel = 'A';
|
|
||||||
} else if (numericScore >= 80) {
|
|
||||||
gradePoint = 3.0; gradeLevel = 'B';
|
|
||||||
} else if (numericScore >= 70) {
|
|
||||||
gradePoint = 2.0; gradeLevel = 'C';
|
|
||||||
} else if (numericScore >= 60) {
|
|
||||||
gradePoint = 1.0; gradeLevel = 'D';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 插入成绩
|
|
||||||
const result = await db.pool.execute(
|
|
||||||
`INSERT INTO scores (student_id, course_id, teacher_id, score,
|
|
||||||
grade_point, grade_level, exam_date, remark)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
||||||
[students[0][0].id, courseId, teacherId, numericScore,
|
|
||||||
gradePoint, gradeLevel, examDate, remark]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '成绩录入成功',
|
|
||||||
gradeId: result.insertId
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('录入成绩错误:', error);
|
|
||||||
|
|
||||||
// 处理唯一约束错误
|
|
||||||
if (error.code === 'ER_DUP_ENTRY') {
|
|
||||||
return res.status(400).json({
|
|
||||||
success: false,
|
|
||||||
message: '该学生此课程成绩已存在'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 查询成绩(按班级、课程)
|
|
||||||
router.get('/grades', requireAuth, requireRole(['teacher']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const { class_name, course_id } = req.query;
|
|
||||||
const teacherId = req.session.user.id;
|
|
||||||
|
|
||||||
let query = `
|
|
||||||
SELECT sc.*, st.student_id, st.full_name, st.class_name,
|
|
||||||
c.course_code, c.course_name, c.credit
|
|
||||||
FROM scores sc
|
|
||||||
JOIN students st ON sc.student_id = st.id
|
|
||||||
JOIN courses c ON sc.course_id = c.id
|
|
||||||
WHERE sc.teacher_id = ?
|
|
||||||
`;
|
|
||||||
|
|
||||||
const params = [teacherId];
|
|
||||||
|
|
||||||
if (class_name) {
|
|
||||||
query += ' AND st.class_name = ?';
|
|
||||||
params.push(class_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (course_id) {
|
|
||||||
query += ' AND sc.course_id = ?';
|
|
||||||
params.push(course_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
query += ' ORDER BY st.class_name, st.student_id, sc.exam_date DESC';
|
|
||||||
|
|
||||||
const grades = await db.pool.execute(query, params);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
grades
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('查询成绩错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新成绩
|
|
||||||
router.put('/grades/:id', requireAuth, requireRole(['teacher']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const gradeId = req.params.id;
|
|
||||||
const teacherId = req.session.user.id;
|
|
||||||
const { score, examDate, remark } = req.body;
|
|
||||||
|
|
||||||
// 验证成绩是否存在且属于该教师
|
|
||||||
const existingGrades = await db.pool.execute(
|
|
||||||
'SELECT id FROM scores WHERE id = ? AND teacher_id = ?',
|
|
||||||
[gradeId, teacherId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingGrades[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '成绩不存在或无权修改'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算新的绩点和等级
|
|
||||||
const numericScore = parseFloat(score);
|
|
||||||
let gradePoint = 0;
|
|
||||||
let gradeLevel = 'F';
|
|
||||||
|
|
||||||
if (numericScore >= 90) {
|
|
||||||
gradePoint = 4.0; gradeLevel = 'A';
|
|
||||||
} else if (numericScore >= 80) {
|
|
||||||
gradePoint = 3.0; gradeLevel = 'B';
|
|
||||||
} else if (numericScore >= 70) {
|
|
||||||
gradePoint = 2.0; gradeLevel = 'C';
|
|
||||||
} else if (numericScore >= 60) {
|
|
||||||
gradePoint = 1.0; gradeLevel = 'D';
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新成绩
|
|
||||||
await db.pool.execute(
|
|
||||||
`UPDATE scores SET score = ?, grade_point = ?, grade_level = ?,
|
|
||||||
exam_date = ?, remark = ?, updated_at = CURRENT_TIMESTAMP
|
|
||||||
WHERE id = ?`,
|
|
||||||
[numericScore, gradePoint, gradeLevel, examDate, remark, gradeId]
|
|
||||||
);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '成绩更新成功'
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('更新成绩错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 删除成绩
|
|
||||||
router.delete('/grades/:id', requireAuth, requireRole(['teacher']), async (req, res) => {
|
|
||||||
try {
|
|
||||||
const gradeId = req.params.id;
|
|
||||||
const teacherId = req.session.user.id;
|
|
||||||
|
|
||||||
// 验证成绩是否存在且属于该教师
|
|
||||||
const existingGrades = await db.pool.execute(
|
|
||||||
'SELECT id FROM scores WHERE id = ? AND teacher_id = ?',
|
|
||||||
[gradeId, teacherId]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingGrades[0].length === 0) {
|
|
||||||
return res.status(404).json({
|
|
||||||
success: false,
|
|
||||||
message: '成绩不存在或无权删除'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除成绩
|
|
||||||
await db.pool.execute('DELETE FROM scores WHERE id = ?', [gradeId]);
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
message: '成绩删除成功'
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除成绩错误:', error);
|
|
||||||
res.status(500).json({
|
|
||||||
success: false,
|
|
||||||
message: '服务器错误'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@@ -5,19 +5,21 @@ const MySQLStore = require('express-mysql-session')(session);
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
// 导入路由
|
// Config & Utils
|
||||||
|
const db = require('./config/database');
|
||||||
|
const errorHandler = require('./middleware/errorHandler');
|
||||||
|
const { requireAuth, requireRole } = require('./middleware/auth');
|
||||||
|
|
||||||
|
// Routes
|
||||||
const authRoutes = require('./routes/auth');
|
const authRoutes = require('./routes/auth');
|
||||||
const studentRoutes = require('./routes/student');
|
const studentRoutes = require('./routes/student');
|
||||||
const teacherRoutes = require('./routes/teacher');
|
const teacherRoutes = require('./routes/teacher');
|
||||||
const adminRoutes = require('./routes/admin');
|
const adminRoutes = require('./routes/admin');
|
||||||
|
|
||||||
// 数据库配置
|
|
||||||
const db = require('./config/database');
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
// 中间件
|
// Middleware
|
||||||
app.use(cors({
|
app.use(cors({
|
||||||
origin: 'http://localhost:3000',
|
origin: 'http://localhost:3000',
|
||||||
credentials: true
|
credentials: true
|
||||||
@@ -25,9 +27,9 @@ app.use(cors({
|
|||||||
app.use(express.json());
|
app.use(express.json());
|
||||||
app.use(express.urlencoded({ extended: true }));
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
|
||||||
// 会话配置
|
// Session
|
||||||
const sessionStore = new MySQLStore({
|
const sessionStore = new MySQLStore({
|
||||||
expiration: 86400000, // 1天
|
expiration: 86400000,
|
||||||
createDatabaseTable: true,
|
createDatabaseTable: true,
|
||||||
schema: {
|
schema: {
|
||||||
tableName: 'sessions',
|
tableName: 'sessions',
|
||||||
@@ -52,14 +54,13 @@ app.use(session({
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 静态文件服务 - 只公开 public 目录
|
// Static Files
|
||||||
app.use('/public', express.static(path.join(__dirname, '../frontend/public')));
|
app.use('/public', express.static(path.join(__dirname, '../frontend/public')));
|
||||||
|
|
||||||
// 页面认证中间件
|
// View Routes (HTML Serving)
|
||||||
|
// 为了简单起见,这里仍然直接 serve HTML,未来可以考虑使用模板引擎或分离前端部署
|
||||||
const requirePageAuth = (req, res, next) => {
|
const requirePageAuth = (req, res, next) => {
|
||||||
if (!req.session.user) {
|
if (!req.session.user) return res.redirect('/login');
|
||||||
return res.redirect('/login');
|
|
||||||
}
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ const requirePageRole = (allowedRoles) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 页面路由
|
// --- Page Routes ---
|
||||||
app.get('/', (req, res) => res.redirect('/login'));
|
app.get('/', (req, res) => res.redirect('/login'));
|
||||||
app.get('/login', (req, res) => {
|
app.get('/login', (req, res) => {
|
||||||
if (req.session.user) return res.redirect('/dashboard');
|
if (req.session.user) return res.redirect('/dashboard');
|
||||||
@@ -91,49 +92,42 @@ app.get('/dashboard', requirePageAuth, (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 学生页面
|
// Student Pages
|
||||||
app.get('/student/dashboard', requirePageAuth, requirePageRole(['student']), (req, res) => {
|
app.get('/student/dashboard', requirePageAuth, requirePageRole(['student']), (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, '../frontend/views/student/dashboard.html'));
|
res.sendFile(path.join(__dirname, '../frontend/views/student/dashboard.html'));
|
||||||
});
|
});
|
||||||
|
|
||||||
// 教师页面
|
// Teacher Pages
|
||||||
const teacherRouter = express.Router();
|
const teacherPageRouter = express.Router();
|
||||||
teacherRouter.use(requirePageAuth, requirePageRole(['teacher']));
|
teacherPageRouter.use(requirePageAuth, requirePageRole(['teacher']));
|
||||||
teacherRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/dashboard.html')));
|
teacherPageRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/dashboard.html')));
|
||||||
teacherRouter.get('/grade_entry', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_entry.html')));
|
teacherPageRouter.get('/grade_entry', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_entry.html')));
|
||||||
teacherRouter.get('/grade_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_management.html')));
|
teacherPageRouter.get('/grade_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/teacher/grade_management.html')));
|
||||||
app.use('/teacher', teacherRouter);
|
app.use('/teacher', teacherPageRouter);
|
||||||
|
|
||||||
// 管理员页面
|
// Admin Pages
|
||||||
const adminRouter = express.Router();
|
const adminPageRouter = express.Router();
|
||||||
adminRouter.use(requirePageAuth, requirePageRole(['admin']));
|
adminPageRouter.use(requirePageAuth, requirePageRole(['admin']));
|
||||||
adminRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/dashboard.html')));
|
adminPageRouter.get('/dashboard', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/dashboard.html')));
|
||||||
adminRouter.get('/student_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/student_management.html')));
|
adminPageRouter.get('/student_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/student_management.html')));
|
||||||
adminRouter.get('/user_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/user_management.html')));
|
adminPageRouter.get('/user_management', (req, res) => res.sendFile(path.join(__dirname, '../frontend/views/admin/user_management.html')));
|
||||||
app.use('/admin', adminRouter);
|
app.use('/admin', adminPageRouter);
|
||||||
|
|
||||||
// API 路由
|
// --- API Routes ---
|
||||||
app.use('/api/auth', authRoutes);
|
app.use('/api/auth', authRoutes);
|
||||||
app.use('/api/student', studentRoutes);
|
app.use('/api/student', studentRoutes);
|
||||||
app.use('/api/teacher', teacherRoutes);
|
app.use('/api/teacher', teacherRoutes);
|
||||||
app.use('/api/admin', adminRoutes);
|
app.use('/api/admin', adminRoutes);
|
||||||
|
|
||||||
// 404处理
|
// Error Handler
|
||||||
app.use((req, res) => {
|
app.use((req, res) => {
|
||||||
res.status(404).json({ error: 'Not found' });
|
res.status(404).json({ success: false, message: 'Not Found' });
|
||||||
});
|
|
||||||
|
|
||||||
// 错误处理
|
|
||||||
app.use((err, req, res, next) => {
|
|
||||||
console.error(err.stack);
|
|
||||||
res.status(500).json({ error: 'Internal server error' });
|
|
||||||
});
|
});
|
||||||
|
app.use(errorHandler);
|
||||||
|
|
||||||
|
// Start Server
|
||||||
app.listen(PORT, async () => {
|
app.listen(PORT, async () => {
|
||||||
console.log(`Server running on port ${PORT}`);
|
console.log(`Server running on port ${PORT}`);
|
||||||
console.log(`访问地址: http://localhost:${PORT}`);
|
console.log(`访问地址: http://localhost:${PORT}`);
|
||||||
|
await db.testConnection();
|
||||||
// 测试数据库连接
|
|
||||||
const dbConfig = require('./config/database');
|
|
||||||
await dbConfig.testConnection();
|
|
||||||
});
|
});
|
||||||
60
backend/services/adminService.js
Normal file
60
backend/services/adminService.js
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
const db = require('../config/database');
|
||||||
|
const bcrypt = require('bcryptjs');
|
||||||
|
const User = require('../models/User');
|
||||||
|
|
||||||
|
class AdminService {
|
||||||
|
static async getUsers(params) {
|
||||||
|
const { page = 1, limit = 10, search = '', role = '' } = params;
|
||||||
|
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 ?';
|
||||||
|
queryParams.push(parseInt(limit), parseInt(offset));
|
||||||
|
|
||||||
|
const users = await db.query(queryStr, queryParams);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: users,
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
total,
|
||||||
|
pages: Math.ceil(total / limit)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async createUser(userData) {
|
||||||
|
const { id } = userData;
|
||||||
|
|
||||||
|
// 检查 ID
|
||||||
|
const existingUser = await User.findById(id);
|
||||||
|
if (existingUser) {
|
||||||
|
throw new Error('用户ID已存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
return await User.create(userData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AdminService;
|
||||||
61
backend/services/authService.js
Normal file
61
backend/services/authService.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
const User = require('../models/User');
|
||||||
|
const Student = require('../models/Student');
|
||||||
|
|
||||||
|
class AuthService {
|
||||||
|
static async login(id, password, role) {
|
||||||
|
const user = await User.findByIdAndRole(id, role);
|
||||||
|
if (!user) {
|
||||||
|
throw new Error('用户名或密码错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValid = await User.verifyPassword(password, user.password);
|
||||||
|
if (!isValid) {
|
||||||
|
throw new Error('用户名或密码错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessionUser = {
|
||||||
|
id: user.id,
|
||||||
|
name: user.name,
|
||||||
|
role: user.role,
|
||||||
|
class: user.class
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user.role === 'student') {
|
||||||
|
const studentInfo = await Student.findById(user.id); // 这里的 id 既是 users.id 也是 students.id
|
||||||
|
if (studentInfo) {
|
||||||
|
sessionUser.studentInfo = studentInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async register(userData) {
|
||||||
|
const { id, role, class: userClass } = userData;
|
||||||
|
|
||||||
|
// 检查是否存在
|
||||||
|
const existingUser = await User.findById(id);
|
||||||
|
if (existingUser) {
|
||||||
|
throw new Error('用户ID已存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建用户
|
||||||
|
const newUser = await User.create(userData);
|
||||||
|
|
||||||
|
// 如果是学生,需要同步创建 students 表记录
|
||||||
|
// 注意:目前的逻辑好像混淆了 users.id 和 students.id,根据之前的 SQL,students.id 是主键且外键关联 users.id
|
||||||
|
// 我们假设 users.id 就是学号
|
||||||
|
if (role === 'student') {
|
||||||
|
// 需要确保 students 表结构匹配
|
||||||
|
await db.query(
|
||||||
|
'INSERT INTO students (id, name, class) VALUES (?, ?, ?)',
|
||||||
|
[id, userData.name, userClass]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = require('../config/database'); // 补充引用
|
||||||
|
module.exports = AuthService;
|
||||||
55
backend/services/studentService.js
Normal file
55
backend/services/studentService.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const Score = require('../models/Score');
|
||||||
|
const Student = require('../models/Student');
|
||||||
|
|
||||||
|
class StudentService {
|
||||||
|
static async getStudentGrades(userId) {
|
||||||
|
// 先通过 userId 获取 studentId
|
||||||
|
// 假设 users.id = students.id,或者通过 user_id 关联
|
||||||
|
// 根据之前的 authService,我们假设 users.id 就是 students.id
|
||||||
|
|
||||||
|
// 确认学生是否存在
|
||||||
|
const student = await Student.findById(userId);
|
||||||
|
if (!student) {
|
||||||
|
throw new Error('学生信息不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
const grades = await Score.findByStudentId(userId);
|
||||||
|
|
||||||
|
// 计算统计信息
|
||||||
|
let totalCredits = 0;
|
||||||
|
let totalGradePoints = 0;
|
||||||
|
const totalCourses = grades.length;
|
||||||
|
|
||||||
|
grades.forEach(grade => {
|
||||||
|
const credit = parseFloat(grade.credit);
|
||||||
|
totalCredits += credit;
|
||||||
|
if (grade.grade_point) {
|
||||||
|
totalGradePoints += parseFloat(grade.grade_point) * credit;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const gpa = totalCredits > 0 ? (totalGradePoints / totalCredits).toFixed(2) : 0;
|
||||||
|
const averageScore = totalCourses > 0 ?
|
||||||
|
(grades.reduce((sum, g) => sum + parseFloat(g.score || 0), 0) / totalCourses).toFixed(1) : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
grades,
|
||||||
|
statistics: {
|
||||||
|
totalCourses,
|
||||||
|
totalCredits,
|
||||||
|
gpa,
|
||||||
|
averageScore
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getGradeDetails(scoreId, userId) {
|
||||||
|
const grade = await Score.findDetailsById(scoreId, userId);
|
||||||
|
if (!grade) {
|
||||||
|
throw new Error('成绩不存在');
|
||||||
|
}
|
||||||
|
return grade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = StudentService;
|
||||||
50
backend/services/teacherService.js
Normal file
50
backend/services/teacherService.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
const Course = require('../models/Course');
|
||||||
|
const Score = require('../models/Score');
|
||||||
|
const Student = require('../models/Student');
|
||||||
|
|
||||||
|
class TeacherService {
|
||||||
|
static async getCourses(teacherId) {
|
||||||
|
return await Course.findByTeacherId(teacherId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async addScore(teacherId, scoreData) {
|
||||||
|
const { studentId, courseId, score } = scoreData;
|
||||||
|
|
||||||
|
// 验证学生
|
||||||
|
const student = await Student.findById(studentId);
|
||||||
|
if (!student) {
|
||||||
|
throw new Error('学生不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证课程(可选:验证是否是该教师的课程)
|
||||||
|
// const course = await Course.findById(courseId);
|
||||||
|
|
||||||
|
// 检查重复
|
||||||
|
const existingScore = await Score.findByStudentAndCourse(studentId, courseId);
|
||||||
|
if (existingScore) {
|
||||||
|
throw new Error('该学生此课程成绩已存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算绩点和等级
|
||||||
|
const numericScore = parseFloat(score);
|
||||||
|
let gradePoint = 0;
|
||||||
|
let gradeLevel = 'F';
|
||||||
|
|
||||||
|
if (numericScore >= 90) { gradePoint = 4.0; gradeLevel = 'A'; }
|
||||||
|
else if (numericScore >= 80) { gradePoint = 3.0; gradeLevel = 'B'; }
|
||||||
|
else if (numericScore >= 70) { gradePoint = 2.0; gradeLevel = 'C'; }
|
||||||
|
else if (numericScore >= 60) { gradePoint = 1.0; gradeLevel = 'D'; }
|
||||||
|
|
||||||
|
const fullScoreData = {
|
||||||
|
...scoreData,
|
||||||
|
teacherId,
|
||||||
|
gradePoint,
|
||||||
|
gradeLevel
|
||||||
|
};
|
||||||
|
|
||||||
|
const gradeId = await Score.create(fullScoreData);
|
||||||
|
return gradeId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = TeacherService;
|
||||||
16
backend/utils/response.js
Normal file
16
backend/utils/response.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const success = (res, data = null, message = 'Success') => {
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message,
|
||||||
|
data
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const error = (res, message = 'Internal Server Error', statusCode = 500) => {
|
||||||
|
res.status(statusCode).json({
|
||||||
|
success: false,
|
||||||
|
message
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { success, error };
|
||||||
Reference in New Issue
Block a user