diff --git a/backend/main.py b/backend/main.py index fb7a7c4..df88275 100644 --- a/backend/main.py +++ b/backend/main.py @@ -85,13 +85,28 @@ app.add_middleware( async def global_exception_handler(request: Request, exc: Exception): """全局异常处理器 - 捕获所有未处理异常""" logger.error(f"未处理异常: {exc}", exc_info=True) + + # 获取origin用于CORS头 + origin = request.headers.get("origin", "") + allowed_origins = settings.CORS_ORIGINS or [] + + # 使用HTTP 200 + 业务错误码返回,避免CORS头丢失问题 + # (FastAPI exception_handler返回的500响应可能不经过CORS中间件,导致跨域读取失败) + headers = {} + if origin in allowed_origins: + headers["access-control-allow-origin"] = origin + headers["access-control-allow-credentials"] = "true" + headers["access-control-expose-headers"] = "*" + return JSONResponse( - status_code=500, + status_code=200, content={ "success": False, + "code": 500, "message": f"服务器内部错误: {str(exc)}", "detail": traceback.format_exc() if settings.DEBUG else None - } + }, + headers=headers ) diff --git a/backend/middleware/permission.py b/backend/middleware/permission.py index f864617..42539aa 100644 --- a/backend/middleware/permission.py +++ b/backend/middleware/permission.py @@ -77,6 +77,12 @@ class PermissionChecker: role = await PermissionChecker.get_user_role(user_id) return role == "劳动委员" + @staticmethod + async def check_is_volunteer_rep(user_id: int) -> bool: + """检查是否为志愿委员""" + role = await PermissionChecker.get_user_role(user_id) + return role == "志愿委员" + @staticmethod async def check_can_manage_subjects(user_id: int) -> bool: """检查是否可以管理科目(班主任或学习委员)""" @@ -127,7 +133,7 @@ class PermissionChecker: if not record: return False role = await PermissionChecker.get_user_role(user_id) - if role in ["班主任", "班长"]: + if role in ["班主任", "班长", "志愿委员"]: return True return record["recorder_id"] == user_id diff --git a/backend/routes/admin.py b/backend/routes/admin.py index b6632e0..d805cb8 100644 --- a/backend/routes/admin.py +++ b/backend/routes/admin.py @@ -194,7 +194,7 @@ async def get_conduct_history( return success_response(data=result) except Exception as e: logger.error(f"获取历史记录失败: {e}", exc_info=True) - return error_response(message=f"获取历史记录失败: {str(e)}", code=500) + return error_response(message=f"获取历史记录失败: {str(e)}") # ========== 作业管理 ========== @@ -299,7 +299,8 @@ async def add_attendance(request: Request, req: AddAttendanceRequest): status=req.status, reason=req.reason, apply_deduction=req.apply_deduction, - recorder_id=user["user_id"] + recorder_id=user["user_id"], + custom_deduction=req.custom_deduction ) if result["success"]: await LogService.write_operation_log( @@ -339,7 +340,7 @@ async def add_admin(request: Request, req: AddAdminRequest): is_teacher = await PermissionChecker.check_is_teacher(user["user_id"]) if not is_teacher: return error_response(message="仅班主任可添加管理员", code=403) - if req.role_type not in ["班长", "学习委员", "考勤委员", "劳动委员"]: + if req.role_type not in ["班长", "学习委员", "考勤委员", "劳动委员", "志愿委员"]: return error_response(message="无效的角色类型", code=400) result = await AdminService.add_admin( username=req.username, @@ -373,4 +374,4 @@ async def get_admins(request: Request): return success_response(data=result) except Exception as e: logger.error(f"获取管理员列表失败: {e}", exc_info=True) - return error_response(message=f"获取管理员列表失败: {str(e)}", code=500) \ No newline at end of file + return error_response(message=f"获取管理员列表失败: {str(e)}") \ No newline at end of file diff --git a/backend/routes/debug.py b/backend/routes/debug.py index 5f66e4e..ad5d80f 100644 --- a/backend/routes/debug.py +++ b/backend/routes/debug.py @@ -34,7 +34,7 @@ class AddAdminDebugRequest(BaseModel): async def debug_add_admin(request: Request, req: AddAdminDebugRequest): from models.user import UserModel - valid_roles = ["班主任", "班长", "学习委员", "考勤委员", "劳动委员"] + valid_roles = ["班主任", "班长", "学习委员", "考勤委员", "劳动委员", "志愿委员"] if req.role_type not in valid_roles: return error_response(message=f"无效的角色类型,可选: {', '.join(valid_roles)}") diff --git a/backend/routes/student.py b/backend/routes/student.py index 35a5611..bcaa87b 100644 --- a/backend/routes/student.py +++ b/backend/routes/student.py @@ -47,7 +47,7 @@ async def get_conduct_history( return success_response(data=result) except Exception as e: logger.error(f"获取学生操行分失败: {e}", exc_info=True) - return error_response(message=f"获取学生操行分失败: {str(e)}", code=500) + return error_response(message=f"获取学生操行分失败: {str(e)}") @router.get("/homework/{student_id}") diff --git a/backend/routes/subject.py b/backend/routes/subject.py index a4c4b87..913a5d4 100644 --- a/backend/routes/subject.py +++ b/backend/routes/subject.py @@ -28,7 +28,7 @@ async def get_subjects(request: Request, is_active: Optional[bool] = None): return success_response(data=result) except Exception as e: logger.error(f"获取科目列表失败: {e}", exc_info=True) - return error_response(message=f"获取科目列表失败: {str(e)}", code=500) + return error_response(message=f"获取科目列表失败: {str(e)}") @router.post("/create") async def create_subject(request: Request, req: CreateSubjectRequest): diff --git a/backend/schemas/admin.py b/backend/schemas/admin.py index 5b8ebee..99a2122 100644 --- a/backend/schemas/admin.py +++ b/backend/schemas/admin.py @@ -84,4 +84,5 @@ class AddAttendanceRequest(BaseModel): date: date status: str reason: Optional[str] = None - apply_deduction: bool = False \ No newline at end of file + apply_deduction: bool = True + custom_deduction: Optional[int] = Field(default=None, gt=0, description="自定义扣分值") \ No newline at end of file diff --git a/backend/services/attendance_service.py b/backend/services/attendance_service.py index d859934..6322f45 100644 --- a/backend/services/attendance_service.py +++ b/backend/services/attendance_service.py @@ -32,7 +32,8 @@ class AttendanceService: status: str, reason: Optional[str], apply_deduction: bool, - recorder_id: int + recorder_id: int, + custom_deduction: Optional[int] = None ) -> Dict[str, Any]: """添加考勤记录""" # 检查权限 @@ -57,8 +58,10 @@ class AttendanceService: # 应用扣分 if apply_deduction and status in ["absent", "late", "leave"]: - # 确定扣分数值 - if status == "absent": + # 确定扣分数值(优先使用自定义扣分) + if custom_deduction is not None: + points_change = -custom_deduction + elif status == "absent": points_change = -settings.DEDUCTION_ATTENDANCE_ABSENT elif status == "late": points_change = -settings.DEDUCTION_ATTENDANCE_LATE diff --git a/backend/services/conduct_service.py b/backend/services/conduct_service.py index cb19e30..3fddf9d 100644 --- a/backend/services/conduct_service.py +++ b/backend/services/conduct_service.py @@ -55,6 +55,10 @@ class ConductService: # 劳动委员固定 ±1分 if points_change not in [settings.LABOR_POINTS_ADD, settings.LABOR_POINTS_SUBTRACT]: return {"success": False, "message": "劳动委员只能进行±1分操作"} + elif role == "志愿委员": + # 志愿委员只能加分,不限制正分上限 + if points_change < 0: + return {"success": False, "message": "志愿委员只能加分"} elif role in ["学习委员", "考勤委员"]: # 学习委员和考勤委员只能扣分 if points_change > 0: @@ -147,8 +151,8 @@ class ConductService: role = await PermissionChecker.get_user_role(user_id) offset = (page - 1) * page_size - # 班主任/班长可查看全班 - if role in ["班主任", "班长"]: + # 班主任/班长/志愿委员可查看全班 + if role in ["班主任", "班长", "志愿委员"]: records = await ConductModel.get_all_records( limit=page_size, offset=offset, diff --git a/frontend/admin/admins.php b/frontend/admin/admins.php index d2fc80a..cc36ff0 100644 --- a/frontend/admin/admins.php +++ b/frontend/admin/admins.php @@ -70,10 +70,11 @@ include __DIR__ . '/../includes/header.php';
- - - + + + +
@@ -93,6 +94,13 @@ function selectStatus(btn) { document.querySelectorAll('.status-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); currentStatus = btn.dataset.status; + // 自动设置默认扣分值 + const defaultDeduction = btn.dataset.defaultDeduction; + if (defaultDeduction && defaultDeduction !== '0') { + document.getElementById('customDeduction').value = defaultDeduction; + } else { + document.getElementById('customDeduction').value = ''; + } } // 加载学生列表 @@ -165,6 +173,8 @@ async function submitAttendance() { const date = document.getElementById('attendanceDate').value; const reason = document.getElementById('attendanceReason').value; + const customDeduction = document.getElementById('customDeduction').value; + const customDeductionValue = customDeduction ? parseInt(customDeduction) : null; // 检查是否有已存在记录的学生 const hasRecordStudents = []; @@ -183,15 +193,18 @@ async function submitAttendance() { const promises = []; selectedCells.forEach(cell => { const studentId = parseInt(cell.dataset.id); - promises.push( - apiPost('/api/admin/attendance', { - student_id: studentId, - date: date, - status: currentStatus, - reason: reason, - apply_deduction: true - }) - ); + const payload = { + student_id: studentId, + date: date, + status: currentStatus, + reason: reason, + apply_deduction: true + }; + // 只有设置了自定义扣分时才发送 + if (customDeductionValue !== null && customDeductionValue > 0) { + payload.custom_deduction = customDeductionValue; + } + promises.push(apiPost('/api/admin/attendance', payload)); }); const results = await Promise.allSettled(promises); diff --git a/frontend/admin/conduct.php b/frontend/admin/conduct.php index 126e5d4..b257704 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(); } @@ -115,6 +115,7 @@ loadStudents(); diff --git a/frontend/admin/dashboard.php b/frontend/admin/dashboard.php index a57bcf2..ddd6ee7 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 043efae..1820718 100644 --- a/frontend/admin/history.php +++ b/frontend/admin/history.php @@ -53,7 +53,7 @@ include __DIR__ . '/../includes/header.php'; 分数变动 原因 操作人 - + 操作 @@ -105,14 +105,14 @@ async function loadHistory(page = 1) { ${record.points_change > 0 ? '+' : ''}${record.points_change} ${escapeHtml(record.reason)} ${escapeHtml(record.recorder_name)}`; - + html += ``; html += ``; }); if (res.data.records.length === 0) { - const colSpan = ; + const colSpan = ; html = `暂无记录`; } diff --git a/frontend/includes/nav.php b/frontend/includes/nav.php index ac99f94..8320836 100644 --- a/frontend/includes/nav.php +++ b/frontend/includes/nav.php @@ -1,7 +1,7 @@