feat(P2): 实现选课管理、考试监考、学情诊断三大功能模块
## 新增功能模块 ### 1. 选课管理(elective) - 新增表:electiveCourses、courseSelections - 新增权限:ELECTIVE_MANAGE/ELECTIVE_READ/ELECTIVE_SELECT - 支持先到先得 + 抽签两种选课模式 - admin/teacher/student 三端页面 ### 2. 考试监考(proctoring) - exams 表扩展:examMode/durationMinutes/antiCheatEnabled 等字段 - 新增表:examProctoringEvents - 新增权限:EXAM_PROCTOR/EXAM_PROCTOR_READ - 教师监考面板 + 学生端防作弊监控 - API:/api/proctoring/event 接收事件上报 ### 3. 学情诊断报告(diagnostic) - 新增表:knowledgePointMastery、learningDiagnosticReports - 新增权限:DIAGNOSTIC_MANAGE/DIAGNOSTIC_READ - 基于提交答案自动计算知识点掌握度 - 生成个人/班级诊断报告(强项/弱项/建议) - 雷达图可视化 ## 其他改动 - 项目规则:单文件行数限制从 300 行调整为企业级规范(组件≤500/Actions≤800/硬上限1000) - scripts/seed.ts:消除全部 any 类型,定义内部类型,0 lint 错误 - 架构文档 004/005 同步更新三个新模块 - 迁移文件 0001_heavy_sage.sql 生成 ## 验证 - npx tsc --noEmit:0 错误 - npm run lint:0 错误 0 警告
This commit is contained in:
@@ -218,7 +218,7 @@
|
||||
|
||||
#### `Permissions` (常量对象)
|
||||
- 文件:`types/permissions.ts`
|
||||
- 定义:47 个权限常量(`exam:create`, `homework:grade`, `audit_log:read`, `announcement:manage`, `file:upload`, `file:read`, `file:delete`, `grade_record:manage`, `grade_record:read`, `course_plan:manage`, `course_plan:read`, `attendance:manage`, `attendance:read`, `message:send`, `message:read`, `message:delete`, `schedule:auto`, `schedule:adjust` 等)
|
||||
- 定义:54 个权限常量(`exam:create`, `homework:grade`, `audit_log:read`, `announcement:manage`, `file:upload`, `file:read`, `file:delete`, `grade_record:manage`, `grade_record:read`, `course_plan:manage`, `course_plan:read`, `attendance:manage`, `attendance:read`, `message:send`, `message:read`, `message:delete`, `schedule:auto`, `schedule:adjust`, `elective:manage`, `elective:read`, `elective:select`, `exam:proctor`, `exam:proctor_read`, `diagnostic:manage`, `diagnostic:read` 等)
|
||||
- 被使用:auth-guard.ts, 所有模块的 actions.ts, 前端组件
|
||||
|
||||
#### `ROLE_PERMISSIONS` (常量对象)
|
||||
@@ -228,6 +228,7 @@
|
||||
- 考勤权限:admin/teacher 含 `ATTENDANCE_MANAGE`+`ATTENDANCE_READ`;student/parent/grade_head/teaching_head 含 `ATTENDANCE_READ`
|
||||
- 消息权限:admin/teacher/grade_head/teaching_head 含 `MESSAGE_SEND`+`MESSAGE_READ`+`MESSAGE_DELETE`;student/parent 含 `MESSAGE_SEND`+`MESSAGE_READ`
|
||||
- 排课权限:admin 含 `SCHEDULE_AUTO`+`SCHEDULE_ADJUST`;teacher/student/parent/grade_head/teaching_head 无排课权限
|
||||
- 学情诊断权限:admin/teacher/grade_head 含 `DIAGNOSTIC_MANAGE`+`DIAGNOSTIC_READ`;teaching_head/student 含 `DIAGNOSTIC_READ`
|
||||
- 被使用:`resolvePermissions`, auth.ts (JWT callback)
|
||||
|
||||
#### `db` (Drizzle 实例)
|
||||
@@ -399,6 +400,8 @@
|
||||
| `schedulingRules` | id, classId, maxDailyHours, maxContinuousHours, lunchBreakStart, lunchBreakEnd, morningStart, afternoonEnd, avoidBackToBack, balancedSubjects, createdAt, updatedAt | scheduling |
|
||||
| `scheduleChanges` | id, originalScheduleId, classId, originalTeacherId, substituteTeacherId, originalDate, newDate, newStartTime, newEndTime, reason, status, requestedBy, approvedBy, createdAt, updatedAt | scheduling |
|
||||
| `passwordSecurity` | id, userId, failedLoginAttempts, lockedUntil, passwordChangedAt, mustChangePassword, lastPasswordChange, createdAt, updatedAt | auth, settings |
|
||||
| `knowledgePointMastery` | id, studentId, knowledgePointId, masteryLevel, totalQuestions, correctQuestions, lastAssessedAt, createdAt, updatedAt | diagnostic |
|
||||
| `learningDiagnosticReports` | id, studentId, generatedBy, reportType, period, summary, strengths, weaknesses, recommendations, overallScore, status, createdAt, updatedAt | diagnostic |
|
||||
|
||||
---
|
||||
|
||||
@@ -1420,8 +1423,10 @@
|
||||
- teacher 角色菜单包含 "Course Plans" 项(icon: CalendarRange, href: /teacher/course-plans, permission: Permissions.COURSE_PLAN_READ)
|
||||
- teacher 角色菜单包含 "Attendance" 项(icon: CalendarCheck, href: /teacher/attendance, permission: Permissions.ATTENDANCE_MANAGE),含子项 Records (/teacher/attendance)、Take Attendance (/teacher/attendance/sheet, permission: ATTENDANCE_MANAGE)、Statistics (/teacher/attendance/stats, permission: ATTENDANCE_READ)
|
||||
- teacher 角色菜单包含 "Schedule Changes" 项(icon: CalendarClock, href: /teacher/schedule-changes, permission: Permissions.SCHEDULE_ADJUST)
|
||||
- teacher 角色菜单包含 "Diagnostic" 项(icon: Stethoscope, href: /teacher/diagnostic, permission: Permissions.DIAGNOSTIC_READ)
|
||||
- student 角色菜单包含 "My Grades" 项(icon: GraduationCap, href: /student/grades, permission: Permissions.GRADE_RECORD_READ)
|
||||
- student 角色菜单包含 "Attendance" 项(icon: CalendarCheck, href: /student/attendance, permission: Permissions.ATTENDANCE_READ)
|
||||
- student 角色菜单包含 "Diagnostic" 项(icon: Stethoscope, href: /student/diagnostic, permission: Permissions.DIAGNOSTIC_READ)
|
||||
- parent 角色菜单包含 "Dashboard" 项(icon: LayoutDashboard, href: /parent/dashboard,无 permission 字段,仅需登录)
|
||||
- parent 角色菜单包含 "Grades" 项(icon: GraduationCap, href: /parent/grades, permission: Permissions.GRADE_RECORD_READ)
|
||||
- parent 角色菜单包含 "Attendance" 项(icon: CalendarCheck, href: /parent/attendance, permission: Permissions.ATTENDANCE_READ)
|
||||
@@ -2578,31 +2583,299 @@
|
||||
|
||||
---
|
||||
|
||||
## 模块:proctoring
|
||||
|
||||
`src/modules/proctoring`
|
||||
|
||||
考试监考模块:监考模式考试实时监控、防作弊事件采集、教师监考面板、学生端防作弊监控、考试模式配置。
|
||||
|
||||
> 权限:教师监考面板使用 `requirePermission(EXAM_PROCTOR)`;学生端上报事件使用 `requireAuth()`(学生上报自己的事件,不需要管理权限)。前端组件使用 `usePermission().hasPermission(EXAM_PROCTOR)` 控制权限。
|
||||
|
||||
### Server Actions (`actions.ts`)
|
||||
|
||||
| Action | 权限 | 用途 |
|
||||
|--------|------|------|
|
||||
| `recordProctoringEventAction` | requireAuth() | 学生端上报监考事件(含 submission 归属校验) |
|
||||
| `getProctoringDashboardAction` | EXAM_PROCTOR | 获取监考面板数据(摘要+学生状态+最近事件) |
|
||||
|
||||
### Data Access (`data-access.ts`)
|
||||
|
||||
`import "server-only"`
|
||||
|
||||
| 函数 | 签名 | 被使用 |
|
||||
|------|------|--------|
|
||||
| `recordProctoringEvent` | `(input: RecordProctoringEventInput) => Promise<ProctoringEvent>` | actions.recordProctoringEventAction, API /api/proctoring/event |
|
||||
| `getProctoringEvents` | `(examId, filters?) => Promise<ProctoringEventWithDetails[]>` | 待扩展 |
|
||||
| `getProctoringEventsBySubmission` | `(submissionId) => Promise<ProctoringEvent[]>` | 待扩展 |
|
||||
| `getExamProctoringSummary` | `(examId) => Promise<ExamProctoringSummary>` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx |
|
||||
| `getStudentProctoringStatuses` | `(examId) => Promise<StudentProctoringStatus[]>` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx |
|
||||
| `getExamForProctoring` | `(examId) => Promise<{id,title,examMode,config} | null>` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx |
|
||||
| `getRecentProctoringEvents` | `(examId, limit?) => Promise<ProctoringEventWithDetails[]>` | actions.getProctoringDashboardAction, teacher/exams/[id]/proctoring/page.tsx |
|
||||
|
||||
### Types (`types.ts`)
|
||||
|
||||
| 类型 | 定义 |
|
||||
|------|------|
|
||||
| `ProctoringEventType` | `"tab_switch" \| "window_blur" \| "copy_attempt" \| "paste_attempt" \| "right_click" \| "devtools_open" \| "fullscreen_exit" \| "idle_timeout"` |
|
||||
| `ExamMode` | `"homework" \| "timed" \| "proctored"` |
|
||||
| `ProctoringEvent` | `{ id, submissionId, studentId, examId, eventType, eventDetail?, occurredAt, createdAt }` |
|
||||
| `ProctoringEventWithDetails` | `ProctoringEvent & { studentName, examTitle }` |
|
||||
| `ExamProctoringSummary` | `{ examId, examTitle, examMode, totalStudents, startedStudents, submittedStudents, totalEvents, abnormalStudents, eventsByType }` |
|
||||
| `StudentProctoringStatus` | `{ studentId, studentName, submissionId, submissionStatus, eventCount, lastEventAt, isAbnormal, eventsByType }` |
|
||||
| `ProctoringDashboardData` | `{ summary, students, recentEvents }` |
|
||||
| `ExamModeConfig` | `{ examMode, durationMinutes, shuffleQuestions, allowLateStart, lateStartGraceMinutes, antiCheatEnabled }` |
|
||||
| `PROCTORING_EVENT_LABELS` | 事件类型中文标签常量 |
|
||||
| `EXAM_MODE_LABELS` | 考试模式中文标签常量 |
|
||||
| `ABNORMAL_EVENT_THRESHOLD` | 异常学生事件数阈值(3) |
|
||||
|
||||
### Components (`components/`)
|
||||
|
||||
| 组件 | 用途 |
|
||||
|------|------|
|
||||
| `proctoring-dashboard.tsx` | 教师监考面板(实时学生状态、异常事件统计、异常学生高亮、10 秒轮询、usePermission 权限控制) |
|
||||
| `anti-cheat-monitor.tsx` | 学生端防作弊监控(visibilitychange/blur/copy/paste/contextmenu/keydown/fullscreenchange 监听、空闲超时检测、强制全屏、警告提示、事件上报) |
|
||||
| `exam-mode-config.tsx` | 考试模式配置(react-hook-form Controller,作业/限时/监考模式选择,限时设置时长,监考设置防作弊选项) |
|
||||
|
||||
### API Routes
|
||||
|
||||
| 路由 | 方法 | 权限 | 用途 |
|
||||
|------|------|------|------|
|
||||
| `/api/proctoring/event` | POST | requireAuth() | 接收学生端上报的监考事件(含 submission 归属校验) |
|
||||
|
||||
---
|
||||
|
||||
## 模块:diagnostic
|
||||
|
||||
`src/modules/diagnostic`
|
||||
|
||||
学情诊断报告模块:基于知识点掌握度(`knowledgePointMastery` 表)生成个人/班级诊断报告,掌握度雷达图(学生 vs 班级平均),强项/弱项分析,知识点掌握度热力图,需重点关注学生列表,报告发布/删除管理。
|
||||
|
||||
> 权限:所有 Server Actions 使用 `requirePermission()` 校验。生成/发布/删除报告使用 `requirePermission(DIAGNOSTIC_MANAGE)`,查询/详情使用 `requirePermission(DIAGNOSTIC_READ)`。admin/teacher/grade_head 角色拥有 DIAGNOSTIC_MANAGE+DIAGNOSTIC_READ,teaching_head/student 角色仅有 DIAGNOSTIC_READ。前端组件使用 `usePermission().hasPermission(DIAGNOSTIC_MANAGE)` 控制生成/发布/删除按钮可见性(无 `role === "xxx"` 硬编码)。页面路由通过 `getAuthContext()` 进行 DataScope 二次校验:`class_members` 仅查自己,`children` 仅查子女,`class_taught` 必须包含 classId。
|
||||
|
||||
### Server Actions (`actions.ts`)
|
||||
|
||||
`"use server"`
|
||||
|
||||
| Action | 权限 | 用途 |
|
||||
|--------|------|------|
|
||||
| `generateStudentReportAction` | DIAGNOSTIC_MANAGE | 生成学生个人诊断报告(formData: studentId, period) |
|
||||
| `generateClassReportAction` | DIAGNOSTIC_MANAGE | 生成班级诊断报告(formData: classId, period) |
|
||||
| `publishReportAction` | DIAGNOSTIC_MANAGE | 发布诊断报告(formData: id,status → published) |
|
||||
| `deleteReportAction` | DIAGNOSTIC_MANAGE | 删除诊断报告(formData: id) |
|
||||
| `getDiagnosticReportsAction` | DIAGNOSTIC_READ | 查询诊断报告列表(params: DiagnosticReportQueryParams) |
|
||||
| `getDiagnosticReportByIdAction` | DIAGNOSTIC_READ | 获取诊断报告详情(id) |
|
||||
|
||||
### Data Access (`data-access.ts` + `data-access-reports.ts`)
|
||||
|
||||
`import "server-only"`(两个文件均以此开头)
|
||||
|
||||
#### `data-access.ts`(掌握度相关)
|
||||
|
||||
| 函数 | 签名 | 被使用 |
|
||||
|------|------|--------|
|
||||
| `getStudentMastery` | `(studentId: string) => Promise<MasteryWithKnowledgePoint[]>` | getStudentMasterySummary, teacher/diagnostic/student/[studentId] |
|
||||
| `getStudentMasterySummary` | `(studentId: string) => Promise<StudentMasterySummary \| null>` | generateDiagnosticReport, teacher/diagnostic/student/[studentId], student/diagnostic |
|
||||
| `updateMasteryFromSubmission` | `(submissionId: string) => Promise<void>` | 待扩展(作业/考试提交后触发,onDuplicateKeyUpdate upsert) |
|
||||
| `getClassMasterySummary` | `(classId: string) => Promise<ClassMasterySummary \| null>` | generateClassDiagnosticReport, teacher/diagnostic/class/[classId] |
|
||||
| `getKnowledgePointStats` | `(classId?: string, gradeId?: string) => Promise<KnowledgePointStat[]>` | teacher/diagnostic/student/[studentId](班级平均对比) |
|
||||
|
||||
#### `data-access-reports.ts`(报告相关)
|
||||
|
||||
| 函数 | 签名 | 被使用 |
|
||||
|------|------|--------|
|
||||
| `generateDiagnosticReport` | `(studentId, period, generatedBy) => Promise<string>` | generateStudentReportAction |
|
||||
| `generateClassDiagnosticReport` | `(classId, period, generatedBy) => Promise<string>` | generateClassReportAction |
|
||||
| `getDiagnosticReports` | `(filters: DiagnosticReportQueryParams) => Promise<DiagnosticReportWithDetails[]>` | getDiagnosticReportsAction, teacher/diagnostic, teacher/diagnostic/student/[studentId], student/diagnostic |
|
||||
| `getDiagnosticReportById` | `(id: string) => Promise<DiagnosticReportWithDetails \| null>` | getDiagnosticReportByIdAction |
|
||||
| `publishDiagnosticReport` | `(id: string) => Promise<void>` | publishReportAction |
|
||||
| `deleteDiagnosticReport` | `(id: string) => Promise<void>` | deleteReportAction |
|
||||
|
||||
### Types (`types.ts`)
|
||||
|
||||
| Type | 定义 |
|
||||
|------|------|
|
||||
| `DiagnosticReportType` | `"individual" \| "class" \| "grade"` |
|
||||
| `DiagnosticReportStatus` | `"draft" \| "published" \| "archived"` |
|
||||
| `KnowledgePointMastery` | `{ id, studentId, knowledgePointId, masteryLevel(0-100), totalQuestions, correctQuestions, lastAssessedAt, createdAt, updatedAt }` |
|
||||
| `MasteryWithKnowledgePoint` | `KnowledgePointMastery & { knowledgePointName, knowledgePointDescription }` |
|
||||
| `StudentMasterySummary` | `{ studentId, studentName, averageMastery, totalKnowledgePoints, strengths(≥80), weaknesses(<60), allMastery }` |
|
||||
| `DiagnosticReport` | `{ id, studentId, generatedBy, reportType, period, summary, strengths[], weaknesses[], recommendations[], overallScore, status, createdAt, updatedAt }` |
|
||||
| `DiagnosticReportWithDetails` | `DiagnosticReport & { studentName, generatedByName }` |
|
||||
| `ClassMasterySummary` | `{ classId, className, studentCount, averageMastery, knowledgePointStats[], studentsNeedingAttention[] }` |
|
||||
| `KnowledgePointStat` | `{ knowledgePointId, knowledgePointName, averageMastery, masteredCount(≥80), notMasteredCount(<60), totalStudents }` |
|
||||
| `DiagnosticReportQueryParams` | `{ studentId?, reportType?, status?, period? }` |
|
||||
| `MasteryRadarPoint` | `{ knowledgePoint, student(0-100), classAverage?(0-100) }` |
|
||||
|
||||
### Components (`components/`)
|
||||
|
||||
所有组件以 `"use client"` 开头。
|
||||
|
||||
| 组件 | 用途 |
|
||||
|------|------|
|
||||
| `mastery-radar-chart.tsx` | 知识点掌握度雷达图(recharts RadarChart,学生 vs 班级平均对比,无数据时显示 EmptyState) |
|
||||
| `student-diagnostic-view.tsx` | 学生诊断视图(概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示) |
|
||||
| `class-diagnostic-view.tsx` | 班级诊断视图(概览卡片、知识点掌握度热力图[绿≥80/黄60-79/橙40-59/红<40]、知识点排名表、需重点关注学生表[链接到学生视图]、生成班级报告表单[DIAGNOSTIC_MANAGE]) |
|
||||
| `report-list.tsx` | 诊断报告列表(reportType/status 过滤器[URL searchParams]、报告表格、发布/删除操作[DIAGNOSTIC_MANAGE]、确认对话框) |
|
||||
|
||||
### 数据库表
|
||||
|
||||
| 表 | 用途 |
|
||||
|----|------|
|
||||
| `knowledgePointMastery` | 知识点掌握度记录(复合主键 studentId+knowledgePointId,onDuplicateKeyUpdate upsert) |
|
||||
| `learningDiagnosticReports` | 学情诊断报告(reportType: individual/class/grade;status: draft/published/archived) |
|
||||
|
||||
### 页面路由
|
||||
|
||||
| 路由 | 组件 | 权限 | DataScope 校验 |
|
||||
|------|------|------|----------------|
|
||||
| `/teacher/diagnostic` | ReportList | diagnostic:read | class_members 仅查看自己报告 |
|
||||
| `/teacher/diagnostic/student/[studentId]` | StudentDiagnosticView | diagnostic:read | class_members 仅自己,children 仅子女 |
|
||||
| `/teacher/diagnostic/class/[classId]` | ClassDiagnosticView | diagnostic:read | class_taught 必须包含 classId,class_members/children → notFound |
|
||||
| `/student/diagnostic` | StudentDiagnosticView | diagnostic:read | class_members 仅查自己(ctx.userId) |
|
||||
|
||||
---
|
||||
|
||||
## 模块:elective
|
||||
|
||||
`src/modules/elective`
|
||||
|
||||
选课管理模块:选修课程 CRUD、选课开放/关闭、学生选课/退课、抽签模式批量录取(runLottery)、FCFS 即时录取、DataScope 行级过滤(admin 全部、teacher 所教、grade_head 所管年级、student 可选课程)。
|
||||
|
||||
> 权限:管理操作使用 `requirePermission(ELECTIVE_MANAGE)`;读取使用 `requirePermission(ELECTIVE_READ)`;学生选课/退课使用 `requirePermission(ELECTIVE_SELECT)`。前端组件使用 `usePermission().hasPermission()` 控制权限。`getStudentSelectionsAction` 对 class_members/children 进行 DataScope 二次校验。
|
||||
|
||||
### Server Actions (`actions.ts`)
|
||||
|
||||
| Action | 权限 | 用途 |
|
||||
|--------|------|------|
|
||||
| `createElectiveCourseAction` | ELECTIVE_MANAGE | 创建选修课程(formData: name, subjectId?, teacherId, gradeId?, description?, capacity?, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, selectionMode?, credit?) |
|
||||
| `updateElectiveCourseAction` | ELECTIVE_MANAGE | 更新选修课程(id + formData) |
|
||||
| `deleteElectiveCourseAction` | ELECTIVE_MANAGE | 删除选修课程(formData: courseId) |
|
||||
| `openSelectionAction` | ELECTIVE_MANAGE | 开放选课(formData: courseId) |
|
||||
| `closeSelectionAction` | ELECTIVE_MANAGE | 关闭选课(formData: courseId) |
|
||||
| `runLotteryAction` | ELECTIVE_MANAGE | 执行抽签录取(formData: courseId),返回 {enrolled, waitlist} |
|
||||
| `selectCourseAction` | ELECTIVE_SELECT | 学生选课(formData: courseId, priority?) |
|
||||
| `dropCourseAction` | ELECTIVE_SELECT | 学生退课(formData: courseId) |
|
||||
| `getElectiveCoursesAction` | ELECTIVE_READ | 查询选修课程列表(按 DataScope 过滤,传 currentUserId) |
|
||||
| `getStudentSelectionsAction` | ELECTIVE_READ | 查询学生选课记录(DataScope 二次校验:class_members 仅自己,children 仅子女) |
|
||||
| `getAvailableCoursesAction` | ELECTIVE_SELECT | 获取学生可选课程(status=open 且匹配年级) |
|
||||
|
||||
### Data Access
|
||||
|
||||
#### `data-access.ts` (`import "server-only"`)
|
||||
|
||||
| 函数 | 签名 | 被使用 |
|
||||
|------|------|--------|
|
||||
| `getElectiveCourses` | `(params?: GetElectiveCoursesParams & { scope?: DataScope; currentUserId?: string }) => Promise<ElectiveCourseWithDetails[]>` | getElectiveCoursesAction, admin/elective, teacher/elective |
|
||||
| `getElectiveCourseById` | `(id: string) => Promise<ElectiveCourseWithDetails \| null>` | updateElectiveCourseAction, admin/elective/[id]/edit |
|
||||
| `createElectiveCourse` | `(data: CreateElectiveCourseInput, teacherId: string) => Promise<string>` | createElectiveCourseAction |
|
||||
| `updateElectiveCourse` | `(id: string, data: Partial<UpdateElectiveCourseInput>) => Promise<void>` | updateElectiveCourseAction |
|
||||
| `deleteElectiveCourse` | `(id: string) => Promise<void>` | deleteElectiveCourseAction |
|
||||
| `openSelection` | `(courseId: string) => Promise<void>` | openSelectionAction |
|
||||
| `closeSelection` | `(courseId: string) => Promise<void>` | closeSelectionAction |
|
||||
| `getSubjectOptions` | `() => Promise<{id, name}[]>` | admin/elective/create, admin/elective/[id]/edit |
|
||||
|
||||
> `buildScopeFilter(scope, userId)` 内部函数:owned/class_taught 按 `teacherId` 过滤,grade_managed 按 `gradeIds` 过滤,class_members/children 返回 null(学生通过 `getAvailableCoursesForStudent` 获取可选课程)。
|
||||
|
||||
#### `data-access-selections.ts` (`import "server-only"`)
|
||||
|
||||
> 从 data-access.ts 拆分以遵守单文件 ≤300 行规则。
|
||||
|
||||
| 函数 | 签名 | 被使用 |
|
||||
|------|------|--------|
|
||||
| `getCourseSelections` | `(courseId: string) => Promise<CourseSelectionWithDetails[]>` | 待扩展 |
|
||||
| `getStudentSelections` | `(studentId: string) => Promise<CourseSelectionWithDetails[]>` | getStudentSelectionsAction, student/elective |
|
||||
| `getStudentGradeId` | `(studentId: string) => Promise<string \| null>` | getAvailableCoursesForStudent |
|
||||
| `getAvailableCoursesForStudent` | `(studentId: string, gradeId?: string \| null) => Promise<ElectiveCourseWithDetails[]>` | getAvailableCoursesAction, student/elective |
|
||||
|
||||
#### `data-access-operations.ts` (`import "server-only"`)
|
||||
|
||||
> 从 data-access.ts 拆分以遵守单文件 ≤300 行规则。包含选课/退课/抽签的写操作。
|
||||
|
||||
| 函数 | 签名 | 被使用 |
|
||||
|------|------|--------|
|
||||
| `runLottery` | `(courseId: string) => Promise<{enrolled: number, waitlist: number}>` | runLotteryAction |
|
||||
| `selectCourse` | `(courseId: string, studentId: string, priority?: number) => Promise<{status: CourseSelectionStatus, message: string}>` | selectCourseAction |
|
||||
| `dropCourse` | `(courseId: string, studentId: string) => Promise<void>` | dropCourseAction |
|
||||
|
||||
### Schema (`schema.ts`)
|
||||
|
||||
| Schema | 用途 |
|
||||
|--------|------|
|
||||
| `ElectiveCourseStatusEnum` | 课程状态枚举(draft/open/closed/cancelled) |
|
||||
| `ElectiveSelectionModeEnum` | 选课模式枚举(fcfs/lottery) |
|
||||
| `CourseSelectionStatusEnum` | 选课状态枚举(selected/enrolled/waitlist/dropped/rejected) |
|
||||
| `CreateElectiveCourseSchema` | 创建课程校验(name 必填,teacherId 必填,capacity 1-500 默认 30,selectionMode 默认 fcfs,credit 默认 1.0) |
|
||||
| `UpdateElectiveCourseSchema` | 更新课程校验(所有字段可选,含 status) |
|
||||
| `SelectCourseSchema` | 选课校验(courseId 必填,priority 1-10 可选) |
|
||||
| `DropCourseSchema` | 退课校验(courseId 必填) |
|
||||
| `RunLotterySchema` | 抽签校验(courseId 必填) |
|
||||
|
||||
### 类型/接口 (`types.ts`)
|
||||
|
||||
| 类型 | 定义 |
|
||||
|------|------|
|
||||
| `ElectiveCourseStatus` | `"draft" \| "open" \| "closed" \| "cancelled"` |
|
||||
| `ElectiveSelectionMode` | `"fcfs" \| "lottery"` |
|
||||
| `CourseSelectionStatus` | `"selected" \| "enrolled" \| "waitlist" \| "dropped" \| "rejected"` |
|
||||
| `ElectiveCourse` | 课程完整类型(id, name, subjectId?, teacherId, gradeId?, description?, capacity, enrolledCount, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, status, selectionMode, credit, createdAt, updatedAt) |
|
||||
| `ElectiveCourseWithDetails` | `ElectiveCourse & { teacherName?, subjectName?, gradeName? }` |
|
||||
| `CourseSelection` | 选课记录类型(id, courseId, studentId, status, priority?, selectedAt, enrolledAt?, droppedAt?, lotteryRank?, createdAt, updatedAt) |
|
||||
| `CourseSelectionWithDetails` | `CourseSelection & { courseName?, studentName?, courseCapacity?, courseEnrolledCount?, courseStatus? }` |
|
||||
| `GetElectiveCoursesParams` | 查询参数(status?, gradeId?, subjectId?, teacherId?) |
|
||||
| `ELECTIVE_STATUS_LABELS` | 课程状态标签常量 |
|
||||
| `ELECTIVE_STATUS_COLORS` | 课程状态颜色常量(Badge variant) |
|
||||
| `SELECTION_MODE_LABELS` | 选课模式标签常量 |
|
||||
| `COURSE_SELECTION_STATUS_LABELS` | 选课状态标签常量 |
|
||||
| `COURSE_SELECTION_STATUS_COLORS` | 选课状态颜色常量(Badge variant) |
|
||||
|
||||
### 导出组件 (`components/`)
|
||||
|
||||
| 组件文件 | 功能 |
|
||||
|---------|------|
|
||||
| `elective-course-list.tsx` | 课程卡片列表(管理员/教师视图,含编辑/开放/关闭/抽签/删除操作按钮,usePermission 权限控制) |
|
||||
| `elective-course-form.tsx` | 课程创建/编辑表单(name, subjectId, teacherId, gradeId, description, capacity, classroom, schedule, dates, selectionMode, credit) |
|
||||
| `student-selection-view.tsx` | 学生选课视图(可选课程列表 + 我的选课记录,含选课/退课按钮) |
|
||||
|
||||
### 路由页面
|
||||
|
||||
| 路由 | 组件 | 权限 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `/admin/elective` | ElectiveCourseList | elective:manage | 管理员选修课程列表(scope=all) |
|
||||
| `/admin/elective/create` | ElectiveCourseForm | elective:manage | 创建选修课程 |
|
||||
| `/admin/elective/[id]/edit` | ElectiveCourseForm (edit) | elective:manage | 编辑选修课程 |
|
||||
| `/teacher/elective` | ElectiveCourseList (teacher) | elective:manage | 教师选修课程列表(scope=class_taught/owned,按 teacherId 过滤) |
|
||||
| `/student/elective` | StudentSelectionView | elective:select | 学生选课页面(可选课程 + 我的选课) |
|
||||
|
||||
---
|
||||
|
||||
## 模块间依赖矩阵
|
||||
|
||||
| ↓ 使用 → | shared | auth | exams | homework | questions | textbooks | classes | school | dashboard | layout | settings | users | audit | announcements | files | grades | course-plans | parent | messaging | attendance | scheduling |
|
||||
|----------|--------|------|-------|----------|-----------|-----------|---------|--------|-----------|--------|----------|-------|-------|---------------|-------|-------|-------------|--------|-----------|------------|-----------|
|
||||
| **shared** | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **auth** | db,schema,permissions,login-logger | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **exams** | db,auth-guard,types,ai | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **homework** | db,auth-guard,types | auth | data-access.getExams | - | - | - | schema | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **questions** | db,auth-guard,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **textbooks** | db,auth-guard,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **classes** | db,auth-guard,types | auth | - | homework-insights | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **school** | db,auth-guard,types,audit-logger | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **dashboard** | db,types | auth | - | data-access.getTeacherGradeTrends,getStudentDashboardGrades | - | - | data-access.getTeacherClasses,getStudentClasses,getStudentSchedule | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **layout** | hooks.usePermission | auth(useSession) | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | notification-dropdown | - | - |
|
||||
| **settings** | db,auth-guard,ai,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **users** | db,auth-guard(requireAuth,requirePermission),types,lib.excel | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **audit** | db,auth-guard.requirePermission,types.permissions | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **announcements** | db,auth-guard,types | auth | - | - | - | - | - | - | data-access.getGrades | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **files** | db,auth-guard(requireAuth,requirePermission),types,lib/file-storage | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **grades** | db,auth-guard,types,lib.excel | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **course-plans** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | data-access.getAdminClasses,getStaffOptions | data-access.getAcademicYears | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **parent** | db,auth-guard(requireAuth),types | auth | - | data-access.getStudentHomeworkAssignments,getStudentDashboardGrades | - | - | data-access.getStudentClasses,getStudentSchedule | - | - | - | - | - | - | - | - | data-access.getStudentGradeSummary | - | - | - | - | - |
|
||||
| **messaging** | db,auth-guard(requirePermission,requireAuth),types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **attendance** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | data-access.getTeacherClasses,getAdminClasses | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **scheduling** | db,auth-guard(requirePermission,getAuthContext),types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| ↓ 使用 → | shared | auth | exams | homework | questions | textbooks | classes | school | dashboard | layout | settings | users | audit | announcements | files | grades | course-plans | parent | messaging | attendance | scheduling | proctoring | diagnostic | elective |
|
||||
|----------|--------|------|-------|----------|-----------|-----------|---------|--------|-----------|--------|----------|-------|-------|---------------|-------|-------|-------------|--------|-----------|------------|-----------|------------|------------|----------|
|
||||
| **shared** | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **auth** | db,schema,permissions,login-logger | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **exams** | db,auth-guard,types,ai | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **homework** | db,auth-guard,types | auth | data-access.getExams | - | - | - | schema | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **questions** | db,auth-guard,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **textbooks** | db,auth-guard,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **classes** | db,auth-guard,types | auth | - | homework-insights | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **school** | db,auth-guard,types,audit-logger | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **dashboard** | db,types | auth | - | data-access.getTeacherGradeTrends,getStudentDashboardGrades | - | - | data-access.getTeacherClasses,getStudentClasses,getStudentSchedule | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **layout** | hooks.usePermission | auth(useSession) | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | notification-dropdown | - | - | - | - |
|
||||
| **settings** | db,auth-guard,ai,types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **users** | db,auth-guard(requireAuth,requirePermission),types,lib.excel | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **audit** | db,auth-guard.requirePermission,types.permissions | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **announcements** | db,auth-guard,types | auth | - | - | - | - | - | - | data-access.getGrades | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **files** | db,auth-guard(requireAuth,requirePermission),types,lib/file-storage | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **grades** | db,auth-guard,types,lib.excel | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **course-plans** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | data-access.getAdminClasses,getStaffOptions | data-access.getAcademicYears | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **parent** | db,auth-guard(requireAuth),types | auth | - | data-access.getStudentHomeworkAssignments,getStudentDashboardGrades | - | - | data-access.getStudentClasses,getStudentSchedule | - | - | - | - | - | - | - | - | data-access.getStudentGradeSummary | - | - | - | - | - | - |
|
||||
| **messaging** | db,auth-guard(requirePermission,requireAuth),types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **attendance** | db,auth-guard.requirePermission,types | auth | - | - | - | - | - | data-access.getTeacherClasses,getAdminClasses | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **scheduling** | db,auth-guard(requirePermission,getAuthContext),types | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **proctoring** | db,auth-guard(requirePermission,requireAuth),types,components.ui,hooks.usePermission | auth | schema.exams,examSubmissions,examProctoringEvents | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **diagnostic** | db,auth-guard(requirePermission,getAuthContext),types,hooks.usePermission,components.ui | auth | schema.examSubmissions,submissionAnswers,questionsToKnowledgePoints | - | - | - | schema.classes,classEnrollments | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
| **elective** | db,auth-guard.requirePermission,types,types.DataScope,hooks.usePermission,components.ui | auth | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - | - |
|
||||
|
||||
---
|
||||
|
||||
@@ -2616,6 +2889,8 @@
|
||||
5. 在 exams/actions.ts 中作为 `creatorId` 写入 `exams` 表
|
||||
6. 在 homework/actions.ts 中作为 `creatorId` 写入 `homeworkAssignments` 表
|
||||
7. 在 classes/data-access.ts 中查询 `getTeacherClasses(teacherId)` 和 `getGradeManagedClasses(userId)`
|
||||
8. 在 elective/actions.ts 中作为 `teacherId` 默认值写入 `electiveCourses` 表(createElectiveCourseAction),作为 `studentId` 查询学生选课(selectCourseAction/dropCourseAction/getStudentSelectionsAction)
|
||||
9. 在 elective/data-access.ts 中 `getElectiveCourses({ scope, currentUserId })` 按 `teacherId` 过滤(class_taught/owned scope)
|
||||
|
||||
### `examId`
|
||||
1. 由 `exams/actions.ts` 的 `createExamAction` 产生,通过 CUID2 生成,写入 `exams` 表
|
||||
@@ -2634,13 +2909,13 @@
|
||||
6. 在 `auth-guard.ts` 中通过 `classSubjectTeachers` 查询教师关联的 classIds,构建 `DataScope.class_taught`
|
||||
|
||||
### `permission`
|
||||
1. 由 `shared/types/permissions.ts` 的 `Permissions` 常量定义(47 个权限点,含 `AUDIT_LOG_READ`、`ANNOUNCEMENT_MANAGE`、`FILE_UPLOAD`、`FILE_READ`、`FILE_DELETE`、`GRADE_RECORD_MANAGE`、`GRADE_RECORD_READ`、`COURSE_PLAN_MANAGE`、`COURSE_PLAN_READ`、`ATTENDANCE_MANAGE`、`ATTENDANCE_READ`、`MESSAGE_SEND`、`MESSAGE_READ`、`MESSAGE_DELETE`、`SCHEDULE_AUTO`、`SCHEDULE_ADJUST` 等)
|
||||
2. 在 `shared/lib/permissions.ts` 中通过 `ROLE_PERMISSIONS` 映射角色到权限列表(admin 角色包含 `AUDIT_LOG_READ`、`COURSE_PLAN_MANAGE`+`COURSE_PLAN_READ`;admin/teacher 含 `FILE_UPLOAD/READ/DELETE` 及 `GRADE_RECORD_MANAGE/READ`;teacher/student/grade_head/teaching_head 含 `COURSE_PLAN_READ`;student/parent 含 `FILE_READ` 及 `GRADE_RECORD_READ`;admin/teacher 含 `ATTENDANCE_MANAGE`+`ATTENDANCE_READ`,student/parent/grade_head/teaching_head 含 `ATTENDANCE_READ`;admin/teacher/parent/grade_head/teaching_head 含 `MESSAGE_SEND/READ/DELETE`;student 含 `MESSAGE_READ/DELETE` 但无 `MESSAGE_SEND`;admin 含 `SCHEDULE_AUTO`+`SCHEDULE_ADJUST`,teacher/student/parent/grade_head/teaching_head 无排课权限)
|
||||
1. 由 `shared/types/permissions.ts` 的 `Permissions` 常量定义(57 个权限点,含 `AUDIT_LOG_READ`、`ANNOUNCEMENT_MANAGE`、`FILE_UPLOAD`、`FILE_READ`、`FILE_DELETE`、`GRADE_RECORD_MANAGE`、`GRADE_RECORD_READ`、`COURSE_PLAN_MANAGE`、`COURSE_PLAN_READ`、`ATTENDANCE_MANAGE`、`ATTENDANCE_READ`、`MESSAGE_SEND`、`MESSAGE_READ`、`MESSAGE_DELETE`、`SCHEDULE_AUTO`、`SCHEDULE_ADJUST`、`DIAGNOSTIC_MANAGE`、`DIAGNOSTIC_READ`、`ELECTIVE_MANAGE`、`ELECTIVE_READ`、`ELECTIVE_SELECT` 等)
|
||||
2. 在 `shared/lib/permissions.ts` 中通过 `ROLE_PERMISSIONS` 映射角色到权限列表(admin 角色包含 `AUDIT_LOG_READ`、`COURSE_PLAN_MANAGE`+`COURSE_PLAN_READ`;admin/teacher 含 `FILE_UPLOAD/READ/DELETE` 及 `GRADE_RECORD_MANAGE/READ`;teacher/student/grade_head/teaching_head 含 `COURSE_PLAN_READ`;student/parent 含 `FILE_READ` 及 `GRADE_RECORD_READ`;admin/teacher 含 `ATTENDANCE_MANAGE`+`ATTENDANCE_READ`,student/parent/grade_head/teaching_head 含 `ATTENDANCE_READ`;admin/teacher/parent/grade_head/teaching_head 含 `MESSAGE_SEND/READ/DELETE`;student 含 `MESSAGE_READ/DELETE` 但无 `MESSAGE_SEND`;admin 含 `SCHEDULE_AUTO`+`SCHEDULE_ADJUST`,teacher/student/parent/grade_head/teaching_head 无排课权限;admin/teacher/grade_head 含 `DIAGNOSTIC_MANAGE`+`DIAGNOSTIC_READ`,teaching_head/student 含 `DIAGNOSTIC_READ`;admin/teacher 含 `ELECTIVE_MANAGE`+`ELECTIVE_READ`,student 含 `ELECTIVE_SELECT`+`ELECTIVE_READ`,grade_head/teaching_head 含 `ELECTIVE_READ`)
|
||||
3. 在 `auth.ts` JWT callback 中通过 `resolvePermissions(roleNames)` 合并多角色权限,存入 JWT
|
||||
4. 在 `proxy.ts` middleware 中通过 `token.permissions` 检查路由访问权限
|
||||
5. 在 `shared/lib/auth-guard.ts` 中通过 `requirePermission(permission)` 在 Server Action 层断言权限(如 audit-logs 页面使用 `requirePermission(AUDIT_LOG_READ)`;`DELETE /api/files/[id]` 使用 `requirePermission(FILE_DELETE)`;messaging/actions.ts 使用 `requirePermission(MESSAGE_SEND/READ/DELETE)`;attendance/actions.ts 使用 `requirePermission(ATTENDANCE_MANAGE/READ)`;users/actions.ts 的 `importUsersAction`/`exportUsersAction`/`downloadUserTemplateAction` 使用 `requirePermission(USER_MANAGE)`;grades/actions.ts 的 `exportGradesAction` 使用 `requirePermission(GRADE_RECORD_READ)`;`POST /api/import` 使用 `requirePermission(USER_MANAGE)`;scheduling/actions.ts 使用 `requirePermission(SCHEDULE_AUTO/SCHEDULE_ADJUST)`)
|
||||
5. 在 `shared/lib/auth-guard.ts` 中通过 `requirePermission(permission)` 在 Server Action 层断言权限(如 audit-logs 页面使用 `requirePermission(AUDIT_LOG_READ)`;`DELETE /api/files/[id]` 使用 `requirePermission(FILE_DELETE)`;messaging/actions.ts 使用 `requirePermission(MESSAGE_SEND/READ/DELETE)`;attendance/actions.ts 使用 `requirePermission(ATTENDANCE_MANAGE/READ)`;users/actions.ts 的 `importUsersAction`/`exportUsersAction`/`downloadUserTemplateAction` 使用 `requirePermission(USER_MANAGE)`;grades/actions.ts 的 `exportGradesAction` 使用 `requirePermission(GRADE_RECORD_READ)`;`POST /api/import` 使用 `requirePermission(USER_MANAGE)`;scheduling/actions.ts 使用 `requirePermission(SCHEDULE_AUTO/SCHEDULE_ADJUST)`;diagnostic/actions.ts 使用 `requirePermission(DIAGNOSTIC_MANAGE/READ)`;elective/actions.ts 使用 `requirePermission(ELECTIVE_MANAGE/READ/SELECT)`)
|
||||
6. 在 `shared/hooks/use-permission.ts` 中通过 `hasPermission(permission)` 在客户端组件中条件渲染
|
||||
7. 在 `layout/config/navigation.ts` 中作为 `NavItem.permission` 字段过滤侧边栏菜单(Audit Logs 菜单项使用 `Permissions.AUDIT_LOG_READ`;Messages 菜单项使用 `Permissions.MESSAGE_READ`;Attendance 菜单项 teacher 使用 `Permissions.ATTENDANCE_MANAGE`,student/parent 使用 `Permissions.ATTENDANCE_READ`;Import Users 菜单项使用 `Permissions.USER_MANAGE`;Scheduling 菜单项 admin 使用 `Permissions.SCHEDULE_ADJUST`/`SCHEDULE_AUTO`;teacher Schedule Changes 菜单项使用 `Permissions.SCHEDULE_ADJUST`)
|
||||
7. 在 `layout/config/navigation.ts` 中作为 `NavItem.permission` 字段过滤侧边栏菜单(Audit Logs 菜单项使用 `Permissions.AUDIT_LOG_READ`;Messages 菜单项使用 `Permissions.MESSAGE_READ`;Attendance 菜单项 teacher 使用 `Permissions.ATTENDANCE_MANAGE`,student/parent 使用 `Permissions.ATTENDANCE_READ`;Import Users 菜单项使用 `Permissions.USER_MANAGE`;Scheduling 菜单项 admin 使用 `Permissions.SCHEDULE_ADJUST`/`SCHEDULE_AUTO`;teacher Schedule Changes 菜单项使用 `Permissions.SCHEDULE_ADJUST`;teacher/student Diagnostic 菜单项使用 `Permissions.DIAGNOSTIC_READ`;Electives 菜单项 admin/teacher 使用 `Permissions.ELECTIVE_MANAGE`,student 使用 `Permissions.ELECTIVE_SELECT`)
|
||||
|
||||
### `DataScope`
|
||||
1. 由 `auth-guard.ts` 的 `resolveDataScope(userId, roles)` 根据用户角色和 DB 关系动态计算
|
||||
@@ -2652,6 +2927,8 @@
|
||||
7. 在 `parent/children/[studentId]/page.tsx` 中通过 `ctx.dataScope.type === "children" && !ctx.dataScope.childrenIds.includes(studentId)` 二次校验家长拥有该子女
|
||||
8. 传递到 `attendance/data-access.getAttendanceRecords({ scope })` 进行行级过滤(class_taught 按教师班级过滤,children 按子女过滤,class_members 仅查自己,all 查全部)
|
||||
9. 在 `attendance/actions.ts` 的 `getStudentAttendanceAction` 中对 class_members/children 进行 DataScope 二次校验
|
||||
10. 传递到 `elective/data-access.getElectiveCourses({ scope, currentUserId })` 进行行级过滤(owned/class_taught 按 `teacherId` 过滤,grade_managed 按 `gradeIds` 过滤,class_members/children 返回 null 由 `getAvailableCoursesForStudent` 处理)
|
||||
11. 在 `elective/actions.ts` 的 `getStudentSelectionsAction` 中对 class_members/children 进行 DataScope 二次校验
|
||||
|
||||
---
|
||||
|
||||
@@ -2761,6 +3038,7 @@
|
||||
|------|------|------|------|------|
|
||||
| `/teacher/exams` | ExamDataTable | server | exam:read | 考试列表(dataAccess: exams/data-access.getExams) |
|
||||
| `/teacher/exams/[id]/build` | ExamAssemblyPanel | client | exam:update | 组卷页面(components: assembly/*, actions: updateExamAction) |
|
||||
| `/teacher/exams/[id]/proctoring` | ProctoringDashboard | server+client | exam:proctor | 教师监考面板(dataAccess: proctoring/data-access.getExamForProctoring,getExamProctoringSummary,getStudentProctoringStatuses,getRecentProctoringEvents;权限:requirePermission(EXAM_PROCTOR);组件:proctoring/components/proctoring-dashboard.tsx,10 秒轮询刷新) |
|
||||
| `/teacher/exams/grading` | ExamGradingList | server | exam:read | 考试批改列表 |
|
||||
| `/teacher/exams/grading/[submissionId]` | ExamGradingView | client | exam:read | 考试批改页面 |
|
||||
| `/teacher/classes/my/[id]` | ClassDetail | server | class:read | 班级详情(dataAccess: classes/data-access.getClassDetails) |
|
||||
@@ -2822,6 +3100,25 @@
|
||||
| `/settings` | 角色分发设置页 | server | auth_required | 根据权限渲染 Admin/Teacher/Student 设置视图(含 General/Appearance/Security/Notifications tab,Security tab 含 PasswordChangeForm,Notifications tab 含 NotificationPreferencesForm;dataAccess: messaging/notification-preferences.getNotificationPreferences) |
|
||||
| `/settings/security` | SecuritySettingsPage | server | auth_required | 安全设置独立页面(PasswordChangeForm + 安全提示;权限:requireAuth()) |
|
||||
|
||||
### diagnostic/* 路由
|
||||
|
||||
| 路由 | 组件 | 类型 | 权限 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `/teacher/diagnostic` | ReportList | client | diagnostic:read | 学情诊断报告列表(reportType/status 过滤器[URL searchParams];dataAccess: diagnostic/data-access-reports.getDiagnosticReports;actions: publishReportAction, deleteReportAction[DIAGNOSTIC_MANAGE];权限:requirePermission(DIAGNOSTIC_READ);DataScope.class_members 仅查看自己报告) |
|
||||
| `/teacher/diagnostic/student/[studentId]` | StudentDiagnosticView | client | diagnostic:read | 学生学情诊断视图(概览卡片+雷达图+强项/弱项+生成报告[DIAGNOSTIC_MANAGE]+最新报告;dataAccess: getStudentMasterySummary, getKnowledgePointStats[班级平均对比], getDiagnosticReports;actions: generateStudentReportAction;权限:getAuthContext + DataScope 二次校验,class_members 仅自己,children 仅子女) |
|
||||
| `/teacher/diagnostic/class/[classId]` | ClassDiagnosticView | client | diagnostic:read | 班级学情诊断视图(概览+知识点热力图+排名表+需重点关注学生+生成班级报告[DIAGNOSTIC_MANAGE];dataAccess: getClassMasterySummary;actions: generateClassReportAction;权限:getAuthContext + DataScope 校验,class_taught 必须包含 classId,class_members/children → notFound) |
|
||||
| `/student/diagnostic` | StudentDiagnosticView | client | diagnostic:read | 学生本人学情诊断视图(概览+雷达图+强项/弱项+最新报告;dataAccess: getStudentMasterySummary(ctx.userId), getDiagnosticReports(studentId=ctx.userId);权限:requirePermission(DIAGNOSTIC_READ),DataScope.class_members 仅查自己) |
|
||||
|
||||
### elective/* 路由
|
||||
|
||||
| 路由 | 组件 | 类型 | 权限 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `/admin/elective` | ElectiveCourseList | server | elective:manage | 管理员选修课程列表(dataAccess: getElectiveCourses(scope=all);actions: deleteElectiveCourseAction, openSelectionAction, closeSelectionAction, runLotteryAction;权限:requirePermission(ELECTIVE_MANAGE)) |
|
||||
| `/admin/elective/create` | ElectiveCourseForm | client | elective:manage | 创建选修课程(actions: createElectiveCourseAction;dataAccess: getSubjectOptions;权限:requirePermission(ELECTIVE_MANAGE)) |
|
||||
| `/admin/elective/[id]/edit` | ElectiveCourseForm (edit) | client | elective:manage | 编辑选修课程(actions: updateElectiveCourseAction;dataAccess: getElectiveCourseById, getSubjectOptions;权限:requirePermission(ELECTIVE_MANAGE)) |
|
||||
| `/teacher/elective` | ElectiveCourseList (teacher) | server | elective:manage | 教师选修课程列表(dataAccess: getElectiveCourses(scope=class_taught/owned, currentUserId);actions: deleteElectiveCourseAction, openSelectionAction, closeSelectionAction, runLotteryAction;权限:requirePermission(ELECTIVE_MANAGE);按 teacherId 过滤) |
|
||||
| `/student/elective` | StudentSelectionView | server | elective:select | 学生选课页面(dataAccess: getAvailableCoursesForStudent, getStudentSelections;actions: selectCourseAction, dropCourseAction;权限:requirePermission(ELECTIVE_SELECT)) |
|
||||
|
||||
### API 路由(含速率限制)
|
||||
|
||||
| 路由 | 方法 | 限流规则 | 说明 |
|
||||
|
||||
Reference in New Issue
Block a user