diff --git a/.cospec/plan/changes/fix-admin-multi-issues/task.md b/.cospec/plan/changes/fix-admin-multi-issues/task.md index 165edce..f00e9a8 100644 --- a/.cospec/plan/changes/fix-admin-multi-issues/task.md +++ b/.cospec/plan/changes/fix-admin-multi-issues/task.md @@ -226,6 +226,36 @@ ### 阶段 10:修复 500 Internal Server Error - SQL 引用不存在的 class_id 列 - [x] 10.1 修复代码与数据库 schema 不匹配问题(13个文件) + +### 阶段 11:修复 422 参数校验 + JS 变量重复声明 + RANK() 兼容性 + +- [x] 11.1 修复历史记录页 422 和 JS 变量声明问题 +- [x] 11.2 修复首页 RANK() OVER 窗口函数兼容性 + +### 阶段 12:全面项目审查 - 消灭潜在问题 + +- [x] 12.1 修复后端角色名"科代表"→"学习委员"(3处)+ 存储过程缺失 + 权限逻辑 + security工具类 + 【目标对象】`backend/services/conduct_service.py`、`backend/services/homework_service.py`、`backend/models/conduct.py`、`backend/middleware/permission.py`、`backend/utils/security.py` + 【修改目的】1) "科代表"角色在数据库ENUM中不存在,应为"学习委员";2) revoke_conduct_record存储过程在init.sql中不存在,需替换为内联SQL;3) get_user_subject_ids方法逻辑顺序有误;4) validate_points_change返回类型不一致 + 【修改方式】 + - conduct_service.py 第58行: `elif role in ["科代表", "考勤委员"]:` → `elif role in ["学习委员", "考勤委员"]:` + - homework_service.py 第35行: `elif role == "科代表":` → `elif role == "学习委员":` + - homework_service.py 第82行: `if role == "科代表":` → `if role == "学习委员":` + - conduct.py revoke_record方法: 替换 `call_procedure('revoke_conduct_record', ...)` 为内联SQL: `UPDATE conduct_records SET is_revoked=1, revoked_by=%s, revoked_at=NOW() WHERE record_id=%s`,使用 `execute_update` 执行 + - permission.py get_user_subject_ids: 调整逻辑顺序,先检查role_type是否为班主任,再检查subject_id + - security.py 第126行: `return f"单次分值变动不能超过{max_abs}分"` → `return False, f"单次分值变动不能超过{max_abs}分"` + +- [x] 12.2 修复前端学生端/家长端关键问题 + 管理端小问题 + 配置修复 + 【目标对象】`frontend/student/dashboard.php`、`frontend/api/save_session.php`、`frontend/index.php`、`frontend/student/attendance.php`、`frontend/admin/homework.php`、`frontend/config.php`、`frontend/student/conduct_history.php` + 【修改目的】1) student/dashboard.php的common.js加载顺序错误导致API函数未定义;2) save_session.php将user_id错误存储为student_id;3) student/dashboard.php中hw.subject字段名错误;4) homework.php重复调用loadStudents;5) config.php中ICP_ENABLED逻辑反转;6) conduct_history.php文件为空 + 【修改方式】 + - student/dashboard.php: 将 `` 移到内联script之前;删除局部 `const API_BASE_URL` 和 `const JWT_STORAGE_KEY`/`USER_STORAGE_KEY`,改用 `window.API_BASE_URL = ''` 等全局变量(在common.js加载之前设置);将 `hw.subject` 改为 `hw.subject_name` + - save_session.php: 第106行 `$_SESSION['student_id'] = $data['user_id'];` 改为从请求中获取实际student_id:`$_SESSION['student_id'] = $data['student_id'] ?? $data['user_id'];`;同时添加student_id到requiredFields验证(仅学生时必须) + - index.php: 登录成功后save_session调用中添加 `student_id: userData.student_id` 参数 + - student/attendance.php: 第21行 `$student_id = $_SESSION['student_id'];` 保持不变(修复save_session后此值会正确) + - admin/homework.php: 删除第176行重复的 `loadStudents();` 调用 + - config.php: 第61行 `define('ICP_ENABLED', $config['ICP_ENABLED'] === 'false');` 改为 `define('ICP_ENABLED', $config['ICP_ENABLED'] !== 'false');` + - student/conduct_history.php: 文件为空,需要重定向到dashboard或创建基本页面。最简方案:创建一个重定向到 /student/dashboard.php 的页面 【目标对象】`backend/models/attendance.py`、`backend/services/attendance_service.py`、`backend/models/conduct.py`、`backend/services/conduct_service.py`、`backend/models/homework.py`、`backend/services/homework_service.py`、`backend/models/student.py`、`backend/middleware/permission.py`、`backend/services/auth_service.py`、`backend/schemas/student.py`、`backend/schemas/admin.py`、`backend/services/student_service.py`、`backend/routes/student.py` 【修改目的】数据库 schema(单班级系统)中 `students` 和 `assignments` 表没有 `class_id` 列,但后端代码中大量 SQL 引用了该列,导致 MySQL 报错 → 500 Internal Server Error。同时 `PermissionChecker.get_user_subject_ids()` 和 `check_can_manage_student()` 方法不存在但被调用。 【修改方式】全面移除所有 `class_id` 引用(单班级系统不需要),添加缺失的方法 @@ -241,3 +271,36 @@ - auth_service.py: 移除 class_id 和 class_name 引用 - schemas/student.py 和 schemas/admin.py: 移除 class_id 和 class_name 字段 - student_service.py 和 routes/student.py: 移除 class_id 参数 + +- [x] 12.3 修复CRITICAL: total_points在所有Service中从未更新(操行分系统完全失效) + 【目标对象】`backend/services/conduct_service.py`、`backend/services/attendance_service.py`、`backend/services/homework_service.py` + 【修改目的】`StudentModel.update_total_points()` 方法已存在但从未被调用,导致 `students.total_points` 永远保持初始值60分,排行榜、仪表盘、家长端显示的所有总分都是错误的 + 【修改方式】 + - conduct_service.py add_points: 在 `ConductModel.create_record()` 后添加 `await StudentModel.update_total_points(student_id, points_change)` + - conduct_service.py revoke_record: 重写撤销逻辑,先获取原记录,撤销后调用 `await StudentModel.update_total_points(record["student_id"], -record["points_change"])` 反向恢复总分 + - attendance_service.py add_attendance: 在 `ConductModel.create_record()` 后添加 `await StudentModel.update_total_points(student_id, points_change)` + - homework_service.py update_submission_status: 在 `ConductModel.create_record()` 后添加 `await StudentModel.update_total_points(submission["student_id"], points_change)` + +- [x] 12.4 修复前端: student/homework.php字段名 + nav.php劳动委员 + parent/attendance.php死链 + 【目标对象】`frontend/student/homework.php`、`frontend/includes/nav.php`、`frontend/parent/attendance.php` + 【修改目的】1) homework.php中hw.subject字段名与后端API不匹配;2) 劳动委员无法在导航栏看到操行分管理入口(README要求±1分权限);3) parent/attendance.php有指向不存在的/parent/homework.php的死链接 + 【修改方式】 + - student/homework.php 第56行: `hw.subject` → `hw.subject_name` + - nav.php 第4行: 添加 `|| $role === '劳动委员'` 到操行分管理导航条件 + - parent/attendance.php: 删除指向不存在的 /parent/homework.php 的死链接,导航只保留"首页"和"考勤记录" + +- [x] 12.5 修复学生端导航死链接: 3个页面的"操行分"链接指向不存在的/student/conduct.php + 【目标对象】`frontend/student/homework.php`、`frontend/student/attendance.php`、`frontend/student/password.php` + 【修改目的】学生端3个子页面导航栏中的"操行分"链接指向 `/student/conduct.php`,但实际文件名是 `conduct_history.php`,导致404 + 【修改方式】 + - password.php 第26行: `/student/conduct.php` → `/student/conduct_history.php` + +- [x] 12.6 修复劳动委员页面级权限被拦截 + 仪表盘快捷操作缺失 + 【目标对象】`frontend/admin/conduct.php`、`frontend/admin/dashboard.php` + 【修改目的】README规定劳动委员有操行分管理权限(±1分),但conduct.php页面级权限检查只允许班主任和班长访问,劳动委员会被重定向到dashboard;dashboard.php快捷操作也缺少劳动委员 + 【修改方式】 + - conduct.php 第23行: `['班主任', '班长']` → `['班主任', '班长', '劳动委员']` + - conduct.php 第115行: 分数变动提示文字改为 if/elseif/else 结构,劳动委员显示"劳动委员仅限±1分" + - dashboard.php 第61行: 快捷操作条件添加劳动委员 + - attendance.php 第27行: `/student/conduct.php` → `/student/conduct_history.php` + - password.php 第26行: `/student/conduct.php` → `/student/conduct_history.php` diff --git a/backend/middleware/permission.py b/backend/middleware/permission.py index 1cc0152..f864617 100644 --- a/backend/middleware/permission.py +++ b/backend/middleware/permission.py @@ -96,13 +96,16 @@ class PermissionChecker: async def get_user_subject_ids(user_id: int) -> List[int]: """获取用户管理的科目ID列表""" admin_role = await AdminRoleModel.get_by_user_id(user_id) - if admin_role and admin_role.get("subject_id"): - return [admin_role["subject_id"]] + if not admin_role: + return [] # 班主任可以管理所有科目 - if admin_role and admin_role["role_type"] == "班主任": + if admin_role["role_type"] == "班主任": from models.subject import SubjectModel subjects = await SubjectModel.get_all(is_active=True) return [s["subject_id"] for s in subjects] + # 其他角色返回关联的科目 + if admin_role.get("subject_id"): + return [admin_role["subject_id"]] return [] @staticmethod diff --git a/backend/models/conduct.py b/backend/models/conduct.py index d11b357..a1e96d4 100644 --- a/backend/models/conduct.py +++ b/backend/models/conduct.py @@ -11,7 +11,7 @@ from typing import Optional, List, Dict, Any from datetime import datetime -from utils.database import execute_one, execute_query, execute_insert, execute_update, call_procedure +from utils.database import execute_one, execute_query, execute_insert, execute_update from utils.logger import get_logger logger = get_logger(__name__) @@ -123,8 +123,13 @@ class ConductModel: async def revoke_record(record_id: int, revoker_id: int) -> bool: """撤销记录""" try: - await call_procedure('revoke_conduct_record', (record_id, revoker_id)) - return True + sql = """ + UPDATE conduct_records + SET is_revoked = 1, revoked_by = %s, revoked_at = NOW() + WHERE record_id = %s AND is_revoked = 0 + """ + result = await execute_update(sql, (revoker_id, record_id)) + return result > 0 except Exception as e: logger.error(f"撤销记录失败: {e}") return False diff --git a/backend/models/student.py b/backend/models/student.py index d655fbf..8cde5de 100644 --- a/backend/models/student.py +++ b/backend/models/student.py @@ -109,14 +109,16 @@ class StudentModel: async def get_ranking(limit: int = 50) -> List[Dict[str, Any]]: """获取学生排行(单班级)""" sql = """ - SELECT student_id, student_no, name, total_points, - RANK() OVER (ORDER BY total_points DESC) as rank - FROM students + SELECT student_id, student_no, name, total_points + FROM students WHERE status = 1 ORDER BY total_points DESC LIMIT %s """ - return await execute_query(sql, (limit,)) + results = await execute_query(sql, (limit,)) + for i, row in enumerate(results): + row['rank'] = i + 1 + return results @staticmethod async def batch_create(students_data: List[Dict], initial_points: int = 60) -> List[Dict]: diff --git a/backend/services/attendance_service.py b/backend/services/attendance_service.py index 3669269..d859934 100644 --- a/backend/services/attendance_service.py +++ b/backend/services/attendance_service.py @@ -77,6 +77,9 @@ class AttendanceService: related_id=attendance_id ) + # 更新学生总分 + await StudentModel.update_total_points(student_id, points_change) + # 标记已应用扣分 await AttendanceModel.mark_deduction_applied(attendance_id) diff --git a/backend/services/conduct_service.py b/backend/services/conduct_service.py index 56b9950..43c0eb3 100644 --- a/backend/services/conduct_service.py +++ b/backend/services/conduct_service.py @@ -55,8 +55,8 @@ class ConductService: # 劳动委员固定 ±1分 if points_change not in [settings.LABOR_POINTS_ADD, settings.LABOR_POINTS_SUBTRACT]: return {"success": False, "message": "劳动委员只能进行±1分操作"} - elif role in ["科代表", "考勤委员"]: - # 科代表和考勤委员只能扣分 + elif role in ["学习委员", "考勤委员"]: + # 学习委员和考勤委员只能扣分 if points_change > 0: return {"success": False, "message": "该角色只能进行扣分操作"} else: @@ -85,6 +85,9 @@ class ConductService: recorder_name=recorder_name ) + # 更新学生总分 + await StudentModel.update_total_points(student_id, points_change) + details.append({"student_id": student_id, "success": True, "record_id": record_id}) success_count += 1 @@ -109,10 +112,17 @@ class ConductService: if not can_revoke: return {"success": False, "message": "无权撤销此记录"} + # 先获取原记录信息(用于恢复分数) + record = await ConductModel.get_record_by_id(record_id) + if not record: + return {"success": False, "message": "记录不存在"} + # 撤销记录 result = await ConductModel.revoke_record(record_id, revoker_id) if result: + # 反向恢复学生总分 + await StudentModel.update_total_points(record["student_id"], -record["points_change"]) logger.info(f"用户[{revoker_id}] 撤销了记录[{record_id}]") return {"success": True, "message": "撤销成功"} else: diff --git a/backend/services/homework_service.py b/backend/services/homework_service.py index 468262a..eb57aba 100644 --- a/backend/services/homework_service.py +++ b/backend/services/homework_service.py @@ -32,7 +32,7 @@ class HomeworkService: if role == "班主任": assignments = await HomeworkModel.get_all_assignments() - elif role == "科代表": + elif role == "学习委员": subject_ids = await PermissionChecker.get_user_subject_ids(user_id) assignments = await HomeworkModel.get_assignments_by_subjects(subject_ids) else: @@ -79,7 +79,7 @@ class HomeworkService: # 检查权限 role = await PermissionChecker.get_user_role(operator_id) - if role == "科代表": + if role == "学习委员": # 检查是否管理该科目 subject_ids = await PermissionChecker.get_user_subject_ids(operator_id) if submission["subject_id"] not in subject_ids: @@ -118,6 +118,9 @@ class HomeworkService: related_id=submission["assignment_id"] ) + # 更新学生总分 + await StudentModel.update_total_points(submission["student_id"], points_change) + # 标记已应用扣分 await HomeworkModel.mark_deduction_applied(submission_id) diff --git a/backend/utils/security.py b/backend/utils/security.py index ef7dbff..4638cc6 100644 --- a/backend/utils/security.py +++ b/backend/utils/security.py @@ -123,7 +123,7 @@ class SecurityUtils: if points == 0: return False, "分值不能为0" if abs(points) > max_abs: - return f"单次分值变动不能超过{max_abs}分" + return False, f"单次分值变动不能超过{max_abs}分" return True, "" diff --git a/frontend/admin/conduct.php b/frontend/admin/conduct.php index 11b6eeb..e498f7f 100644 --- a/frontend/admin/conduct.php +++ b/frontend/admin/conduct.php @@ -20,7 +20,7 @@ if (!isset($_SESSION['user_id']) || $_SESSION['user_type'] !== 'admin') { $page_title = '操行分管理'; $role = $_SESSION['role'] ?? ''; -if (!in_array($role, ['班主任', '班长'])) { +if (!in_array($role, ['班主任', '班长', '劳动委员'])) { header('Location: /admin/dashboard.php'); exit(); } @@ -112,7 +112,11 @@ loadStudents();
- +
diff --git a/frontend/admin/dashboard.php b/frontend/admin/dashboard.php index 89fab27..a57bcf2 100644 --- a/frontend/admin/dashboard.php +++ b/frontend/admin/dashboard.php @@ -58,7 +58,7 @@ async function loadDashboard() { } let quickActions = ''; - if ('' === '班主任' || '' === '班长') { + if ('' === '班主任' || '' === '班长' || '' === '劳动委员') { quickActions += ''; } if ('' === '班主任') { diff --git a/frontend/admin/history.php b/frontend/admin/history.php index 058215a..50a9848 100644 --- a/frontend/admin/history.php +++ b/frontend/admin/history.php @@ -66,8 +66,8 @@ include __DIR__ . '/../includes/header.php';
diff --git a/frontend/admin/students.php b/frontend/admin/students.php index 73ede91..ec98e7a 100644 --- a/frontend/admin/students.php +++ b/frontend/admin/students.php @@ -112,8 +112,8 @@ include __DIR__ . '/../includes/header.php'; + + - \ No newline at end of file diff --git a/frontend/student/homework.php b/frontend/student/homework.php index 6a6558b..fbf8ff2 100644 --- a/frontend/student/homework.php +++ b/frontend/student/homework.php @@ -24,7 +24,7 @@ include __DIR__ . '/../includes/header.php';