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:
SpecialX
2026-06-17 19:12:51 +08:00
parent baf8f679bf
commit b86255f0ea
46 changed files with 13234 additions and 80 deletions

View File

@@ -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_READteaching_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: idstatus → 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+knowledgePointIdonDuplicateKeyUpdate upsert |
| `learningDiagnosticReports` | 学情诊断报告reportType: individual/class/gradestatus: 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 必须包含 classIdclass_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 默认 30selectionMode 默认 fcfscredit 默认 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.tsx10 秒轮询刷新) |
| `/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 tabSecurity tab 含 PasswordChangeFormNotifications tab 含 NotificationPreferencesFormdataAccess: 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.getDiagnosticReportsactions: publishReportAction, deleteReportAction[DIAGNOSTIC_MANAGE]权限requirePermission(DIAGNOSTIC_READ)DataScope.class_members 仅查看自己报告) |
| `/teacher/diagnostic/student/[studentId]` | StudentDiagnosticView | client | diagnostic:read | 学生学情诊断视图(概览卡片+雷达图+强项/弱项+生成报告[DIAGNOSTIC_MANAGE]+最新报告dataAccess: getStudentMasterySummary, getKnowledgePointStats[班级平均对比], getDiagnosticReportsactions: generateStudentReportAction权限getAuthContext + DataScope 二次校验class_members 仅自己children 仅子女) |
| `/teacher/diagnostic/class/[classId]` | ClassDiagnosticView | client | diagnostic:read | 班级学情诊断视图(概览+知识点热力图+排名表+需重点关注学生+生成班级报告[DIAGNOSTIC_MANAGE]dataAccess: getClassMasterySummaryactions: generateClassReportAction权限getAuthContext + DataScope 校验class_taught 必须包含 classIdclass_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: createElectiveCourseActiondataAccess: getSubjectOptions权限requirePermission(ELECTIVE_MANAGE) |
| `/admin/elective/[id]/edit` | ElectiveCourseForm (edit) | client | elective:manage | 编辑选修课程actions: updateElectiveCourseActiondataAccess: 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, getStudentSelectionsactions: selectCourseAction, dropCourseAction权限requirePermission(ELECTIVE_SELECT) |
### API 路由(含速率限制)
| 路由 | 方法 | 限流规则 | 说明 |

View File

@@ -65,15 +65,20 @@
"MESSAGE_READ": "message:read",
"MESSAGE_DELETE": "message:delete",
"SCHEDULE_AUTO": "schedule:auto",
"SCHEDULE_ADJUST": "schedule:adjust"
"SCHEDULE_ADJUST": "schedule:adjust",
"DIAGNOSTIC_MANAGE": "diagnostic:manage",
"DIAGNOSTIC_READ": "diagnostic:read",
"ELECTIVE_MANAGE": "elective:manage",
"ELECTIVE_READ": "elective:read",
"ELECTIVE_SELECT": "elective:select"
},
"rolePermissions": {
"admin": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","TEXTBOOK_DELETE","CLASS_CREATE","CLASS_READ","CLASS_UPDATE","CLASS_DELETE","CLASS_ENROLL","CLASS_SCHEDULE","SCHOOL_MANAGE","GRADE_MANAGE","USER_MANAGE","AI_CHAT","AI_CONFIGURE","SETTINGS_ADMIN","AUDIT_LOG_READ","ANNOUNCEMENT_MANAGE","ANNOUNCEMENT_READ","GRADE_RECORD_MANAGE","GRADE_RECORD_READ","FILE_UPLOAD","FILE_READ","FILE_DELETE","COURSE_PLAN_MANAGE","COURSE_PLAN_READ","ATTENDANCE_MANAGE","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE","SCHEDULE_AUTO","SCHEDULE_ADJUST"],
"teacher": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","CLASS_READ","CLASS_ENROLL","CLASS_SCHEDULE","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_MANAGE","GRADE_RECORD_READ","FILE_UPLOAD","FILE_READ","FILE_DELETE","COURSE_PLAN_READ","ATTENDANCE_MANAGE","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE"],
"student": ["EXAM_READ","HOMEWORK_SUBMIT","QUESTION_READ","TEXTBOOK_READ","CLASS_READ","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_READ","FILE_READ","COURSE_PLAN_READ","ATTENDANCE_READ","MESSAGE_READ","MESSAGE_DELETE"],
"admin": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","TEXTBOOK_DELETE","CLASS_CREATE","CLASS_READ","CLASS_UPDATE","CLASS_DELETE","CLASS_ENROLL","CLASS_SCHEDULE","SCHOOL_MANAGE","GRADE_MANAGE","USER_MANAGE","AI_CHAT","AI_CONFIGURE","SETTINGS_ADMIN","AUDIT_LOG_READ","ANNOUNCEMENT_MANAGE","ANNOUNCEMENT_READ","GRADE_RECORD_MANAGE","GRADE_RECORD_READ","FILE_UPLOAD","FILE_READ","FILE_DELETE","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"],
"teacher": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","CLASS_READ","CLASS_ENROLL","CLASS_SCHEDULE","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_MANAGE","GRADE_RECORD_READ","FILE_UPLOAD","FILE_READ","FILE_DELETE","COURSE_PLAN_READ","ATTENDANCE_MANAGE","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE","DIAGNOSTIC_MANAGE","DIAGNOSTIC_READ","ELECTIVE_MANAGE","ELECTIVE_READ"],
"student": ["EXAM_READ","HOMEWORK_SUBMIT","QUESTION_READ","TEXTBOOK_READ","CLASS_READ","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_READ","FILE_READ","COURSE_PLAN_READ","ATTENDANCE_READ","MESSAGE_READ","MESSAGE_DELETE","DIAGNOSTIC_READ","ELECTIVE_SELECT","ELECTIVE_READ"],
"parent": ["EXAM_READ","TEXTBOOK_READ","CLASS_READ","ANNOUNCEMENT_READ","GRADE_RECORD_READ","FILE_READ","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE"],
"grade_head": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","CLASS_CREATE","CLASS_READ","CLASS_UPDATE","CLASS_ENROLL","CLASS_SCHEDULE","GRADE_MANAGE","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_READ","COURSE_PLAN_READ","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE"],
"teaching_head": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","CLASS_READ","GRADE_MANAGE","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_READ","COURSE_PLAN_READ","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE"]
"grade_head": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","CLASS_CREATE","CLASS_READ","CLASS_UPDATE","CLASS_ENROLL","CLASS_SCHEDULE","GRADE_MANAGE","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_READ","COURSE_PLAN_READ","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE","DIAGNOSTIC_MANAGE","DIAGNOSTIC_READ","ELECTIVE_READ"],
"teaching_head": ["EXAM_CREATE","EXAM_READ","EXAM_UPDATE","EXAM_DELETE","EXAM_DUPLICATE","EXAM_PUBLISH","EXAM_AI_GENERATE","HOMEWORK_CREATE","HOMEWORK_GRADE","QUESTION_CREATE","QUESTION_READ","QUESTION_UPDATE","QUESTION_DELETE","TEXTBOOK_CREATE","TEXTBOOK_READ","TEXTBOOK_UPDATE","CLASS_READ","GRADE_MANAGE","AI_CHAT","ANNOUNCEMENT_READ","GRADE_RECORD_READ","COURSE_PLAN_READ","ATTENDANCE_READ","MESSAGE_SEND","MESSAGE_READ","MESSAGE_DELETE","DIAGNOSTIC_READ","ELECTIVE_READ"]
},
"dataScopeTypes": {
"all": "管理员:无过滤",
@@ -419,7 +424,11 @@
"attendanceRules": {"fields": ["id","classId","lateThresholdMinutes","earlyLeaveThresholdMinutes","enableAutoMark","createdAt","updatedAt"], "usedBy": ["attendance"]},
"schedulingRules": {"fields": ["id","classId","maxDailyHours","maxContinuousHours","lunchBreakStart","lunchBreakEnd","morningStart","afternoonEnd","avoidBackToBack","balancedSubjects","createdAt","updatedAt"], "usedBy": ["scheduling"]},
"scheduleChanges": {"fields": ["id","originalScheduleId","classId","originalTeacherId","substituteTeacherId","originalDate","newDate","newStartTime","newEndTime","reason","status","requestedBy","approvedBy","createdAt","updatedAt"], "usedBy": ["scheduling"]},
"passwordSecurity": {"fields": ["id","userId","failedLoginAttempts","lockedUntil","passwordChangedAt","mustChangePassword","lastPasswordChange","createdAt","updatedAt"], "usedBy": ["auth","settings"]}
"passwordSecurity": {"fields": ["id","userId","failedLoginAttempts","lockedUntil","passwordChangedAt","mustChangePassword","lastPasswordChange","createdAt","updatedAt"], "usedBy": ["auth","settings"]},
"knowledgePointMastery": {"fields": ["id","studentId","knowledgePointId","masteryLevel","totalQuestions","correctQuestions","lastAssessedAt","createdAt","updatedAt"], "usedBy": ["diagnostic"], "description": "知识点掌握度记录(复合主键 studentId+knowledgePointIdonDuplicateKeyUpdate upsert"},
"learningDiagnosticReports": {"fields": ["id","studentId","generatedBy","reportType","period","summary","strengths","weaknesses","recommendations","overallScore","status","createdAt","updatedAt"], "usedBy": ["diagnostic"], "description": "学情诊断报告reportType: individual/class/gradestatus: draft/published/archived"},
"electiveCourses": {"fields": ["id","name","subjectId","teacherId","gradeId","description","capacity","enrolledCount","classroom","schedule","startDate","endDate","selectionStartAt","selectionEndAt","status","selectionMode","credit","createdAt","updatedAt"], "usedBy": ["elective"], "description": "选修课程status: draft/open/closed/cancelledselectionMode: fcfs/lottery"},
"courseSelections": {"fields": ["id","courseId","studentId","status","priority","selectedAt","enrolledAt","droppedAt","lotteryRank","createdAt","updatedAt"], "usedBy": ["elective"], "description": "选课记录(复合主键 courseId+studentIdstatus: selected/enrolled/waitlist/dropped/rejected"}
}
},
"auth": {
@@ -909,7 +918,7 @@
{"name": "NavItem", "type": "type", "definition": "{ title, href, icon?, permission? }", "usedBy": ["NAV_CONFIG", "AppSidebar"]}
],
"config": [
{"name": "NAV_CONFIG", "type": "Record<Role, NavItem[]>", "note": "每个NavItem含permission字段用于权限过滤。admin角色菜单包含Audit Logs项(icon: ScrollText, href: /admin/audit-logs, permission: AUDIT_LOG_READ)含子项Operation Logs与Login Logs。admin角色菜单的School Management子菜单包含Import Users项(href: /admin/users/import, permission: USER_MANAGE)。admin角色菜单包含Scheduling项(icon: CalendarClock, href: /admin/scheduling/rules, permission: SCHEDULE_ADJUST)含子项Rules(/admin/scheduling/rules, permission: SCHEDULE_ADJUST)、Auto Schedule(/admin/scheduling/auto, permission: SCHEDULE_AUTO)、Change Requests(/admin/scheduling/changes, permission: SCHEDULE_ADJUST)。teacher角色菜单包含Grades项(icon: GraduationCap, permission: GRADE_RECORD_READ)含子项All Grades(/teacher/grades)、Batch Entry(/teacher/grades/entry, permission: GRADE_RECORD_MANAGE)、Statistics(/teacher/grades/stats)。teacher角色菜单包含Schedule Changes项(icon: CalendarClock, href: /teacher/schedule-changes, permission: SCHEDULE_ADJUST)。student角色菜单包含My Grades项(icon: GraduationCap, href: /student/grades, permission: GRADE_RECORD_READ)。parent角色菜单包含Dashboard项(icon: LayoutDashboard, href: /parent/dashboard无permission字段仅需登录)、Grades项(icon: GraduationCap, href: /parent/grades, permission: GRADE_RECORD_READ)、Announcements项(icon: Megaphone, href: /announcements, permission: ANNOUNCEMENT_READ)"}
{"name": "NAV_CONFIG", "type": "Record<Role, NavItem[]>", "note": "每个NavItem含permission字段用于权限过滤。admin角色菜单包含Audit Logs项(icon: ScrollText, href: /admin/audit-logs, permission: AUDIT_LOG_READ)含子项Operation Logs与Login Logs。admin角色菜单的School Management子菜单包含Import Users项(href: /admin/users/import, permission: USER_MANAGE)。admin角色菜单包含Scheduling项(icon: CalendarClock, href: /admin/scheduling/rules, permission: SCHEDULE_ADJUST)含子项Rules(/admin/scheduling/rules, permission: SCHEDULE_ADJUST)、Auto Schedule(/admin/scheduling/auto, permission: SCHEDULE_AUTO)、Change Requests(/admin/scheduling/changes, permission: SCHEDULE_ADJUST)。teacher角色菜单包含Grades项(icon: GraduationCap, permission: GRADE_RECORD_READ)含子项All Grades(/teacher/grades)、Batch Entry(/teacher/grades/entry, permission: GRADE_RECORD_MANAGE)、Statistics(/teacher/grades/stats)。teacher角色菜单包含Schedule Changes项(icon: CalendarClock, href: /teacher/schedule-changes, permission: SCHEDULE_ADJUST)。teacher角色菜单包含Diagnostic项(icon: Stethoscope, href: /teacher/diagnostic, permission: DIAGNOSTIC_READ)。student角色菜单包含My Grades项(icon: GraduationCap, href: /student/grades, permission: GRADE_RECORD_READ)。student角色菜单包含Diagnostic项(icon: Stethoscope, href: /student/diagnostic, permission: DIAGNOSTIC_READ)。parent角色菜单包含Dashboard项(icon: LayoutDashboard, href: /parent/dashboard无permission字段仅需登录)、Grades项(icon: GraduationCap, href: /parent/grades, permission: GRADE_RECORD_READ)、Announcements项(icon: Megaphone, href: /announcements, permission: ANNOUNCEMENT_READ)"}
]
}
},
@@ -1430,6 +1439,155 @@
{ "name": "ScheduleConflictsView", "file": "components/schedule-conflicts-view.tsx", "purpose": "冲突检测视图(班级选择器 + 检测按钮 + 冲突结果列表)" }
]
}
},
"proctoring": {
"path": "src/modules/proctoring",
"description": "考试监考模块:监考模式考试实时监控、防作弊事件采集、教师监考面板、学生端防作弊监控、考试模式配置",
"exports": {
"actions": [
{"name": "recordProctoringEventAction", "permission": "requireAuth()", "signature": "(prevState: ActionState<{id:string}> | null, formData: FormData) => Promise<ActionState<{id:string}>>", "purpose": "学生端上报监考事件(含 submission 归属校验)", "deps": ["requireAuth", "shared/db", "data-access.recordProctoringEvent"], "usedBy": ["anti-cheat-monitor.tsx"]},
{"name": "getProctoringDashboardAction", "permission": "EXAM_PROCTOR", "signature": "(examId: string) => Promise<ActionState<ProctoringDashboardData>>", "purpose": "获取监考面板数据(摘要+学生状态+最近事件)", "deps": ["requirePermission(EXAM_PROCTOR)", "data-access.getExamForProctoring,getExamProctoringSummary,getStudentProctoringStatuses,getRecentProctoringEvents"], "usedBy": ["proctoring-dashboard.tsx"]}
],
"dataAccess": [
{"name": "recordProctoringEvent", "signature": "(input: RecordProctoringEventInput) => Promise<ProctoringEvent>", "purpose": "记录一条监考事件", "usedBy": ["actions.recordProctoringEventAction", "api/proctoring/event/route.ts"]},
{"name": "getProctoringEvents", "signature": "(examId: string, filters?: GetProctoringEventsFilters) => Promise<ProctoringEventWithDetails[]>", "purpose": "查询考试监考事件(含学生姓名、考试标题)", "usedBy": ["待扩展"]},
{"name": "getProctoringEventsBySubmission", "signature": "(submissionId: string) => Promise<ProctoringEvent[]>", "purpose": "查询提交的监考事件", "usedBy": ["待扩展"]},
{"name": "getExamProctoringSummary", "signature": "(examId: string) => Promise<ExamProctoringSummary>", "purpose": "获取考试监考摘要", "usedBy": ["actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx"]},
{"name": "getStudentProctoringStatuses", "signature": "(examId: string) => Promise<StudentProctoringStatus[]>", "purpose": "获取所有学生监考状态", "usedBy": ["actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx"]},
{"name": "getExamForProctoring", "signature": "(examId: string) => Promise<{id,title,examMode,config} | null>", "purpose": "获取考试信息(含 examMode 设置)", "usedBy": ["actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx"]},
{"name": "getRecentProctoringEvents", "signature": "(examId: string, limit?: number) => Promise<ProctoringEventWithDetails[]>", "purpose": "获取最近 N 条监考事件", "usedBy": ["actions.getProctoringDashboardAction", "teacher/exams/[id]/proctoring/page.tsx"]}
],
"types": [
{"name": "ProctoringEventType", "type": "type", "definition": "\"tab_switch\" | \"window_blur\" | \"copy_attempt\" | \"paste_attempt\" | \"right_click\" | \"devtools_open\" | \"fullscreen_exit\" | \"idle_timeout\""},
{"name": "ExamMode", "type": "type", "definition": "\"homework\" | \"timed\" | \"proctored\""},
{"name": "ProctoringEvent", "type": "interface", "definition": "{ id, submissionId, studentId, examId, eventType, eventDetail?, occurredAt, createdAt }"},
{"name": "ProctoringEventWithDetails", "type": "interface", "definition": "ProctoringEvent & { studentName, examTitle }"},
{"name": "ExamProctoringSummary", "type": "interface", "definition": "{ examId, examTitle, examMode, totalStudents, startedStudents, submittedStudents, totalEvents, abnormalStudents, eventsByType }"},
{"name": "StudentProctoringStatus", "type": "interface", "definition": "{ studentId, studentName, submissionId, submissionStatus, eventCount, lastEventAt, isAbnormal, eventsByType }"},
{"name": "ProctoringDashboardData", "type": "interface", "definition": "{ summary, students, recentEvents }"},
{"name": "ExamModeConfig", "type": "interface", "definition": "{ examMode, durationMinutes, shuffleQuestions, allowLateStart, lateStartGraceMinutes, antiCheatEnabled }"},
{"name": "PROCTORING_EVENT_LABELS", "type": "const", "description": "事件类型中文标签常量"},
{"name": "EXAM_MODE_LABELS", "type": "const", "description": "考试模式中文标签常量"},
{"name": "ABNORMAL_EVENT_THRESHOLD", "type": "const", "description": "异常学生事件数阈值3"}
],
"components": [
{"name": "ProctoringDashboard", "file": "components/proctoring-dashboard.tsx", "purpose": "教师监考面板实时学生状态、异常事件统计、异常学生高亮、10 秒轮询、usePermission 权限控制)"},
{"name": "AntiCheatMonitor", "file": "components/anti-cheat-monitor.tsx", "purpose": "学生端防作弊监控visibilitychange/blur/copy/paste/contextmenu/keydown/fullscreenchange 监听、空闲超时检测、强制全屏、警告提示、事件上报)"},
{"name": "ExamModeConfig", "file": "components/exam-mode-config.tsx", "purpose": "考试模式配置react-hook-form Controller作业/限时/监考模式选择,限时设置时长,监考设置防作弊选项)"}
]
}
},
"diagnostic": {
"path": "src/modules/diagnostic",
"description": "学情诊断报告模块基于知识点掌握度knowledgePointMastery生成个人/班级诊断报告,掌握度雷达图(学生 vs 班级平均),强项/弱项分析,知识点掌握度热力图,需重点关注学生列表,报告发布/删除管理",
"exports": {
"dataAccess": [
{ "name": "getStudentMastery", "signature": "(studentId: string) => Promise<MasteryWithKnowledgePoint[]>", "file": "data-access.ts", "purpose": "获取学生在所有知识点的掌握度(含知识点名称,按掌握度降序)", "deps": ["shared.db", "shared.db.schema.knowledgePointMastery", "shared.db.schema.knowledgePoints"], "usedBy": ["data-access.getStudentMasterySummary", "teacher/diagnostic/student/[studentId]/page.tsx"] },
{ "name": "getStudentMasterySummary", "signature": "(studentId: string) => Promise<StudentMasterySummary | null>", "file": "data-access.ts", "purpose": "获取学生掌握度摘要平均掌握度、强项≥80%、弱项<60%", "deps": ["shared.db", "shared.db.schema.users", "data-access.getStudentMastery"], "usedBy": ["data-access-reports.generateDiagnosticReport", "teacher/diagnostic/student/[studentId]/page.tsx", "student/diagnostic/page.tsx"] },
{ "name": "updateMasteryFromSubmission", "signature": "(submissionId: string) => Promise<void>", "file": "data-access.ts", "purpose": "从提交答案更新掌握度按知识点聚合正确率onDuplicateKeyUpdate upsert", "deps": ["shared.db", "shared.db.schema.examSubmissions", "shared.db.schema.submissionAnswers", "shared.db.schema.questionsToKnowledgePoints", "shared.db.schema.knowledgePointMastery"], "usedBy": ["待扩展(作业/考试提交后触发)"] },
{ "name": "getClassMasterySummary", "signature": "(classId: string) => Promise<ClassMasterySummary | null>", "file": "data-access.ts", "purpose": "获取班级掌握度摘要(学生数、平均掌握度、知识点统计、需重点关注学生)", "deps": ["shared.db", "shared.db.schema.classes", "shared.db.schema.classEnrollments", "shared.db.schema.users", "shared.db.schema.knowledgePointMastery", "shared.db.schema.knowledgePoints"], "usedBy": ["data-access-reports.generateClassDiagnosticReport", "teacher/diagnostic/class/[classId]/page.tsx"] },
{ "name": "getKnowledgePointStats", "signature": "(classId?: string, gradeId?: string) => Promise<KnowledgePointStat[]>", "file": "data-access.ts", "purpose": "获取知识点统计(按班级或年级聚合平均掌握度、掌握人数、未掌握人数)", "deps": ["shared.db", "shared.db.schema.classEnrollments", "shared.db.schema.users", "shared.db.schema.knowledgePointMastery", "shared.db.schema.knowledgePoints"], "usedBy": ["teacher/diagnostic/student/[studentId]/page.tsx (班级平均对比)"] },
{ "name": "generateDiagnosticReport", "signature": "(studentId: string, period: string, generatedBy: string) => Promise<string>", "file": "data-access-reports.ts", "purpose": "生成个人诊断报告(计算 overallScore、强项/弱项列表、复习建议status=draft", "deps": ["shared.db", "shared.db.schema.learningDiagnosticReports", "data-access.getStudentMasterySummary", "@paralleldrive/cuid2"], "usedBy": ["actions.generateStudentReportAction"] },
{ "name": "generateClassDiagnosticReport", "signature": "(classId: string, period: string, generatedBy: string) => Promise<string>", "file": "data-access-reports.ts", "purpose": "生成班级诊断报告聚合班级掌握度识别薄弱知识点status=draftstudentId 存生成者 ID", "deps": ["shared.db", "shared.db.schema.learningDiagnosticReports", "data-access.getClassMasterySummary", "@paralleldrive/cuid2"], "usedBy": ["actions.generateClassReportAction"] },
{ "name": "getDiagnosticReports", "signature": "(filters: DiagnosticReportQueryParams) => Promise<DiagnosticReportWithDetails[]>", "file": "data-access-reports.ts", "purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名)", "deps": ["shared.db", "shared.db.schema.learningDiagnosticReports", "shared.db.schema.users"], "usedBy": ["actions.getDiagnosticReportsAction", "teacher/diagnostic/page.tsx", "teacher/diagnostic/student/[studentId]/page.tsx", "student/diagnostic/page.tsx"] },
{ "name": "getDiagnosticReportById", "signature": "(id: string) => Promise<DiagnosticReportWithDetails | null>", "file": "data-access-reports.ts", "purpose": "获取报告详情(含学生名和生成者名)", "deps": ["shared.db", "shared.db.schema.learningDiagnosticReports", "shared.db.schema.users"], "usedBy": ["actions.getDiagnosticReportByIdAction"] },
{ "name": "publishDiagnosticReport", "signature": "(id: string) => Promise<void>", "file": "data-access-reports.ts", "purpose": "发布诊断报告status=published", "deps": ["shared.db", "shared.db.schema.learningDiagnosticReports"], "usedBy": ["actions.publishReportAction"] },
{ "name": "deleteDiagnosticReport", "signature": "(id: string) => Promise<void>", "file": "data-access-reports.ts", "purpose": "删除诊断报告", "deps": ["shared.db", "shared.db.schema.learningDiagnosticReports"], "usedBy": ["actions.deleteReportAction"] }
],
"actions": [
{ "name": "generateStudentReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "生成学生个人诊断报告formData: studentId, period", "deps": ["requirePermission", "data-access-reports.generateDiagnosticReport", "revalidatePath"], "usedBy": ["components/student-diagnostic-view.tsx"] },
{ "name": "generateClassReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "生成班级诊断报告formData: classId, period", "deps": ["requirePermission", "data-access-reports.generateClassDiagnosticReport", "revalidatePath"], "usedBy": ["components/class-diagnostic-view.tsx"] },
{ "name": "publishReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "发布诊断报告formData: id", "deps": ["requirePermission", "data-access-reports.publishDiagnosticReport", "revalidatePath"], "usedBy": ["components/report-list.tsx"] },
{ "name": "deleteReportAction", "permission": "DIAGNOSTIC_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "删除诊断报告formData: id", "deps": ["requirePermission", "data-access-reports.deleteDiagnosticReport", "revalidatePath"], "usedBy": ["components/report-list.tsx"] },
{ "name": "getDiagnosticReportsAction", "permission": "DIAGNOSTIC_READ", "signature": "(params: DiagnosticReportQueryParams) => Promise<ActionState<DiagnosticReportWithDetails[]>>", "file": "actions.ts", "purpose": "查询诊断报告列表(读权限)", "deps": ["requirePermission", "data-access-reports.getDiagnosticReports"], "usedBy": ["待扩展"] },
{ "name": "getDiagnosticReportByIdAction", "permission": "DIAGNOSTIC_READ", "signature": "(id: string) => Promise<ActionState<DiagnosticReportWithDetails | null>>", "file": "actions.ts", "purpose": "获取诊断报告详情(读权限)", "deps": ["requirePermission", "data-access-reports.getDiagnosticReportById"], "usedBy": ["待扩展"] }
],
"types": [
{ "name": "DiagnosticReportType", "type": "type", "file": "types.ts", "definition": "\"individual\" | \"class\" | \"grade\"", "usedBy": ["types.DiagnosticReport.reportType", "actions", "components/report-list.tsx"] },
{ "name": "DiagnosticReportStatus", "type": "type", "file": "types.ts", "definition": "\"draft\" | \"published\" | \"archived\"", "usedBy": ["types.DiagnosticReport.status", "actions", "components/report-list.tsx"] },
{ "name": "KnowledgePointMastery", "type": "interface", "file": "types.ts", "definition": "{ id, studentId, knowledgePointId, masteryLevel(0-100), totalQuestions, correctQuestions, lastAssessedAt, createdAt, updatedAt }", "usedBy": ["data-access", "types.MasteryWithKnowledgePoint"] },
{ "name": "MasteryWithKnowledgePoint", "type": "interface", "file": "types.ts", "definition": "KnowledgePointMastery & { knowledgePointName, knowledgePointDescription }", "usedBy": ["data-access.getStudentMastery", "types.StudentMasterySummary"] },
{ "name": "StudentMasterySummary", "type": "interface", "file": "types.ts", "definition": "{ studentId, studentName, averageMastery, totalKnowledgePoints, strengths(≥80), weaknesses(<60), allMastery }", "usedBy": ["data-access.getStudentMasterySummary", "data-access-reports.generateDiagnosticReport", "components/student-diagnostic-view.tsx"] },
{ "name": "DiagnosticReport", "type": "interface", "file": "types.ts", "definition": "{ id, studentId, generatedBy, reportType, period, summary, strengths[], weaknesses[], recommendations[], overallScore, status, createdAt, updatedAt }", "usedBy": ["data-access-reports", "types.DiagnosticReportWithDetails"] },
{ "name": "DiagnosticReportWithDetails", "type": "interface", "file": "types.ts", "definition": "DiagnosticReport & { studentName, generatedByName }", "usedBy": ["data-access-reports.getDiagnosticReports", "actions", "components/report-list.tsx", "components/student-diagnostic-view.tsx"] },
{ "name": "ClassMasterySummary", "type": "interface", "file": "types.ts", "definition": "{ classId, className, studentCount, averageMastery, knowledgePointStats[], studentsNeedingAttention[] }", "usedBy": ["data-access.getClassMasterySummary", "data-access-reports.generateClassDiagnosticReport", "components/class-diagnostic-view.tsx"] },
{ "name": "KnowledgePointStat", "type": "interface", "file": "types.ts", "definition": "{ knowledgePointId, knowledgePointName, averageMastery, masteredCount(≥80), notMasteredCount(<60), totalStudents }", "usedBy": ["data-access.getKnowledgePointStats", "types.ClassMasterySummary", "components/class-diagnostic-view.tsx"] },
{ "name": "DiagnosticReportQueryParams", "type": "interface", "file": "types.ts", "definition": "{ studentId?, reportType?, status?, period? }", "usedBy": ["data-access-reports.getDiagnosticReports", "actions.getDiagnosticReportsAction"] },
{ "name": "MasteryRadarPoint", "type": "interface", "file": "types.ts", "definition": "{ knowledgePoint, student(0-100), classAverage?(0-100) }", "usedBy": ["components/mastery-radar-chart.tsx", "components/student-diagnostic-view.tsx", "teacher/diagnostic/student/[studentId]/page.tsx"] }
],
"components": [
{ "name": "MasteryRadarChart", "file": "components/mastery-radar-chart.tsx", "purpose": "知识点掌握度雷达图recharts RadarChart学生 vs 班级平均对比,无数据时显示 EmptyState", "deps": ["recharts", "shared/components/ui/card", "shared/components/ui/chart", "shared/components/ui/empty-state"] },
{ "name": "StudentDiagnosticView", "file": "components/student-diagnostic-view.tsx", "purpose": "学生诊断视图(概览卡片、雷达图、强项/弱项列表、生成报告表单[DIAGNOSTIC_MANAGE]、最新报告与建议展示)", "deps": ["usePermission", "actions.generateStudentReportAction", "components/mastery-radar-chart", "shared/components/ui/*"] },
{ "name": "ClassDiagnosticView", "file": "components/class-diagnostic-view.tsx", "purpose": "班级诊断视图(概览卡片、知识点掌握度热力图[绿/黄/橙/红]、知识点排名表、需重点关注学生表[链接到学生视图]、生成班级报告表单[DIAGNOSTIC_MANAGE]", "deps": ["usePermission", "actions.generateClassReportAction", "shared/components/ui/*"] },
{ "name": "ReportList", "file": "components/report-list.tsx", "purpose": "诊断报告列表reportType/status 过滤器[URL searchParams]、报告表格、发布/删除操作[DIAGNOSTIC_MANAGE]、确认对话框)", "deps": ["usePermission", "actions.publishReportAction", "actions.deleteReportAction", "shared/components/ui/*"] }
]
}
},
"elective": {
"path": "src/modules/elective",
"description": "选课管理模块:选修课程 CRUD、选课开放/关闭、学生选课/退课、抽签模式批量录取、FCFS 即时录取、DataScope 行级过滤admin 全部、teacher 所教、grade_head 所管年级、student 可选课程)",
"exports": {
"actions": [
{"name": "createElectiveCourseAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "创建选修课程formData: name, subjectId?, teacherId, gradeId?, description?, capacity?, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, selectionMode?, credit?", "deps": ["requirePermission(ELECTIVE_MANAGE)", "data-access.createElectiveCourse", "revalidatePath"], "usedBy": ["admin/elective/create/page.tsx"]},
{"name": "updateElectiveCourseAction", "permission": "ELECTIVE_MANAGE", "signature": "(id: string, prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "更新选修课程", "deps": ["requirePermission(ELECTIVE_MANAGE)", "data-access.getElectiveCourseById", "data-access.updateElectiveCourse", "revalidatePath"], "usedBy": ["admin/elective/[id]/edit/page.tsx"]},
{"name": "deleteElectiveCourseAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "删除选修课程formData: courseId", "deps": ["requirePermission(ELECTIVE_MANAGE)", "data-access.deleteElectiveCourse", "revalidatePath"], "usedBy": ["components/elective-course-list.tsx"]},
{"name": "openSelectionAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "开放选课formData: courseId", "deps": ["requirePermission(ELECTIVE_MANAGE)", "data-access.openSelection", "revalidatePath"], "usedBy": ["components/elective-course-list.tsx"]},
{"name": "closeSelectionAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "关闭选课formData: courseId", "deps": ["requirePermission(ELECTIVE_MANAGE)", "data-access.closeSelection", "revalidatePath"], "usedBy": ["components/elective-course-list.tsx"]},
{"name": "runLotteryAction", "permission": "ELECTIVE_MANAGE", "signature": "(prevState: ActionState<{enrolled:number,waitlist:number}> | null, formData: FormData) => Promise<ActionState<{enrolled:number,waitlist:number}>>", "file": "actions.ts", "purpose": "执行抽签录取formData: courseId", "deps": ["requirePermission(ELECTIVE_MANAGE)", "data-access-operations.runLottery", "revalidatePath"], "usedBy": ["components/elective-course-list.tsx"]},
{"name": "selectCourseAction", "permission": "ELECTIVE_SELECT", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "学生选课formData: courseId, priority?", "deps": ["requirePermission(ELECTIVE_SELECT)", "data-access-operations.selectCourse", "revalidatePath"], "usedBy": ["components/student-selection-view.tsx"]},
{"name": "dropCourseAction", "permission": "ELECTIVE_SELECT", "signature": "(prevState: ActionState<string> | null, formData: FormData) => Promise<ActionState<string>>", "file": "actions.ts", "purpose": "学生退课formData: courseId", "deps": ["requirePermission(ELECTIVE_SELECT)", "data-access-operations.dropCourse", "revalidatePath"], "usedBy": ["components/student-selection-view.tsx"]},
{"name": "getElectiveCoursesAction", "permission": "ELECTIVE_READ", "signature": "(params?: GetElectiveCoursesParams) => Promise<ActionState<ElectiveCourseWithDetails[]>>", "file": "actions.ts", "purpose": "查询选修课程列表(按 DataScope 过滤)", "deps": ["requirePermission(ELECTIVE_READ)", "data-access.getElectiveCourses (scope, currentUserId)"], "usedBy": ["admin/elective/page.tsx", "teacher/elective/page.tsx"]},
{"name": "getStudentSelectionsAction", "permission": "ELECTIVE_READ", "signature": "(studentId: string) => Promise<ActionState<CourseSelectionWithDetails[]>>", "file": "actions.ts", "purpose": "查询学生选课记录(含 DataScope 二次校验class_members 仅自己children 仅子女)", "deps": ["requirePermission(ELECTIVE_READ)", "data-access-selections.getStudentSelections"], "usedBy": ["待扩展"]},
{"name": "getAvailableCoursesAction", "permission": "ELECTIVE_SELECT", "signature": "() => Promise<ActionState<ElectiveCourseWithDetails[]>>", "file": "actions.ts", "purpose": "获取学生可选课程status=open 且匹配年级)", "deps": ["requirePermission(ELECTIVE_SELECT)", "data-access-selections.getAvailableCoursesForStudent"], "usedBy": ["待扩展"]}
],
"dataAccess": [
{"name": "getElectiveCourses", "file": "data-access.ts", "signature": "(params?: GetElectiveCoursesParams & { scope?: DataScope; currentUserId?: string }) => Promise<ElectiveCourseWithDetails[]>", "purpose": "查询选修课程列表(按 scope 行级过滤owned/class_taught 按 teacherIdgrade_managed 按 gradeIds", "usedBy": ["actions.getElectiveCoursesAction", "admin/elective/page.tsx", "teacher/elective/page.tsx"]},
{"name": "getElectiveCourseById", "file": "data-access.ts", "signature": "(id: string) => Promise<ElectiveCourseWithDetails | null>", "purpose": "获取课程详情", "usedBy": ["actions.updateElectiveCourseAction", "admin/elective/[id]/edit/page.tsx"]},
{"name": "createElectiveCourse", "file": "data-access.ts", "signature": "(data: CreateElectiveCourseInput, teacherId: string) => Promise<string>", "purpose": "创建选修课程status=draft, enrolledCount=0", "usedBy": ["actions.createElectiveCourseAction"]},
{"name": "updateElectiveCourse", "file": "data-access.ts", "signature": "(id: string, data: Partial<UpdateElectiveCourseInput>) => Promise<void>", "purpose": "更新选修课程字段", "usedBy": ["actions.updateElectiveCourseAction"]},
{"name": "deleteElectiveCourse", "file": "data-access.ts", "signature": "(id: string) => Promise<void>", "purpose": "删除选修课程", "usedBy": ["actions.deleteElectiveCourseAction"]},
{"name": "openSelection", "file": "data-access.ts", "signature": "(courseId: string) => Promise<void>", "purpose": "开放选课status=open", "usedBy": ["actions.openSelectionAction"]},
{"name": "closeSelection", "file": "data-access.ts", "signature": "(courseId: string) => Promise<void>", "purpose": "关闭选课status=closed", "usedBy": ["actions.closeSelectionAction"]},
{"name": "getSubjectOptions", "file": "data-access.ts", "signature": "() => Promise<{id, name}[]>", "purpose": "获取学科选项(按 order, name 排序)", "usedBy": ["admin/elective/create/page.tsx", "admin/elective/[id]/edit/page.tsx"]},
{"name": "getCourseSelections", "file": "data-access-selections.ts", "signature": "(courseId: string) => Promise<CourseSelectionWithDetails[]>", "purpose": "查询课程所有选课记录(按 priority, selectedAt 排序)", "usedBy": ["待扩展"]},
{"name": "getStudentSelections", "file": "data-access-selections.ts", "signature": "(studentId: string) => Promise<CourseSelectionWithDetails[]>", "purpose": "查询学生选课记录(按 selectedAt 降序)", "usedBy": ["actions.getStudentSelectionsAction", "student/elective/page.tsx"]},
{"name": "getStudentGradeId", "file": "data-access-selections.ts", "signature": "(studentId: string) => Promise<string | null>", "purpose": "获取学生所在年级 ID通过 classEnrollments active 记录)", "usedBy": ["data-access-selections.getAvailableCoursesForStudent"]},
{"name": "getAvailableCoursesForStudent", "file": "data-access-selections.ts", "signature": "(studentId: string, gradeId?: string | null) => Promise<ElectiveCourseWithDetails[]>", "purpose": "获取学生可选课程status=open 且 gradeId 匹配或为空)", "usedBy": ["actions.getAvailableCoursesAction", "student/elective/page.tsx"]},
{"name": "runLottery", "file": "data-access-operations.ts", "signature": "(courseId: string) => Promise<{enrolled: number, waitlist: number}>", "purpose": "抽签录取(随机打乱 selected 记录,前 capacity 名 enrolled其余 waitlist课程 status=closed", "usedBy": ["actions.runLotteryAction"]},
{"name": "selectCourse", "file": "data-access-operations.ts", "signature": "(courseId: string, studentId: string, priority?: number) => Promise<{status: CourseSelectionStatus, message: string}>", "purpose": "学生选课(校验课程状态/时间窗口/重复选课FCFS 模式即时 enrolled/waitlistlottery 模式 selected", "usedBy": ["actions.selectCourseAction"]},
{"name": "dropCourse", "file": "data-access-operations.ts", "signature": "(courseId: string, studentId: string) => Promise<void>", "purpose": "学生退课status=droppedFCFS 模式自动递补 waitlist 首位)", "usedBy": ["actions.dropCourseAction"]}
],
"types": [
{"name": "ElectiveCourseStatus", "type": "type", "file": "types.ts", "definition": "\"draft\" | \"open\" | \"closed\" | \"cancelled\""},
{"name": "ElectiveSelectionMode", "type": "type", "file": "types.ts", "definition": "\"fcfs\" | \"lottery\""},
{"name": "CourseSelectionStatus", "type": "type", "file": "types.ts", "definition": "\"selected\" | \"enrolled\" | \"waitlist\" | \"dropped\" | \"rejected\""},
{"name": "ElectiveCourse", "type": "interface", "file": "types.ts", "definition": "{ id, name, subjectId?, teacherId, gradeId?, description?, capacity, enrolledCount, classroom?, schedule?, startDate?, endDate?, selectionStartAt?, selectionEndAt?, status, selectionMode, credit, createdAt, updatedAt }"},
{"name": "ElectiveCourseWithDetails", "type": "interface", "file": "types.ts", "definition": "ElectiveCourse & { teacherName?, subjectName?, gradeName? }"},
{"name": "CourseSelection", "type": "interface", "file": "types.ts", "definition": "{ id, courseId, studentId, status, priority?, selectedAt, enrolledAt?, droppedAt?, lotteryRank?, createdAt, updatedAt }"},
{"name": "CourseSelectionWithDetails", "type": "interface", "file": "types.ts", "definition": "CourseSelection & { courseName?, studentName?, courseCapacity?, courseEnrolledCount?, courseStatus? }"},
{"name": "GetElectiveCoursesParams", "type": "interface", "file": "types.ts", "definition": "{ status?, gradeId?, subjectId?, teacherId? }"},
{"name": "ELECTIVE_STATUS_LABELS", "type": "const", "file": "types.ts", "description": "课程状态标签常量"},
{"name": "ELECTIVE_STATUS_COLORS", "type": "const", "file": "types.ts", "description": "课程状态颜色常量Badge variant"},
{"name": "SELECTION_MODE_LABELS", "type": "const", "file": "types.ts", "description": "选课模式标签常量"},
{"name": "COURSE_SELECTION_STATUS_LABELS", "type": "const", "file": "types.ts", "description": "选课状态标签常量"},
{"name": "COURSE_SELECTION_STATUS_COLORS", "type": "const", "file": "types.ts", "description": "选课状态颜色常量Badge variant"}
],
"schemas": [
{"name": "ElectiveCourseStatusEnum", "file": "schema.ts", "definition": "z.enum([\"draft\",\"open\",\"closed\",\"cancelled\"])"},
{"name": "ElectiveSelectionModeEnum", "file": "schema.ts", "definition": "z.enum([\"fcfs\",\"lottery\"])"},
{"name": "CourseSelectionStatusEnum", "file": "schema.ts", "definition": "z.enum([\"selected\",\"enrolled\",\"waitlist\",\"dropped\",\"rejected\"])"},
{"name": "CreateElectiveCourseSchema", "file": "schema.ts", "purpose": "创建课程校验name 必填teacherId 必填capacity 1-500 默认 30selectionMode 默认 fcfscredit 默认 1.0"},
{"name": "UpdateElectiveCourseSchema", "file": "schema.ts", "purpose": "更新课程校验(所有字段可选,含 status"},
{"name": "SelectCourseSchema", "file": "schema.ts", "purpose": "选课校验courseId 必填priority 1-10 可选)"},
{"name": "DropCourseSchema", "file": "schema.ts", "purpose": "退课校验courseId 必填)"},
{"name": "RunLotterySchema", "file": "schema.ts", "purpose": "抽签校验courseId 必填)"}
],
"components": [
{"name": "ElectiveCourseList", "file": "components/elective-course-list.tsx", "purpose": "课程卡片列表(管理员/教师视图,含编辑/开放/关闭/抽签/删除操作按钮usePermission 控制权限)", "deps": ["usePermission", "actions.deleteElectiveCourseAction", "actions.openSelectionAction", "actions.closeSelectionAction", "actions.runLotteryAction", "shared/components/ui/*"]},
{"name": "ElectiveCourseForm", "file": "components/elective-course-form.tsx", "purpose": "课程创建/编辑表单name, subjectId, teacherId, gradeId, description, capacity, classroom, schedule, dates, selectionMode, credit", "deps": ["react-hook-form", "actions.createElectiveCourseAction", "actions.updateElectiveCourseAction", "shared/components/ui/*"]},
{"name": "StudentSelectionView", "file": "components/student-selection-view.tsx", "purpose": "学生选课视图(可选课程列表 + 我的选课记录,含选课/退课按钮)", "deps": ["usePermission", "actions.selectCourseAction", "actions.dropCourseAction", "shared/components/ui/*"]}
]
}
}
},
"dependencyMatrix": {
@@ -1453,7 +1611,9 @@
"parent": {"dependsOn": ["shared", "auth", "homework", "classes", "grades"], "uses": {"shared": ["db", "auth-guard.requireAuth", "db.schema.parentStudentRelations", "db.schema.users", "db.schema.grades", "db.schema.classEnrollments", "db.schema.classes", "types"], "auth": ["auth"], "homework": ["data-access.getStudentHomeworkAssignments", "data-access.getStudentDashboardGrades"], "classes": ["data-access.getStudentClasses", "data-access.getStudentSchedule"], "grades": ["data-access.getStudentGradeSummary"]}},
"messaging": {"dependsOn": ["shared", "auth"], "uses": {"shared": ["db", "auth-guard.requirePermission", "auth-guard.requireAuth", "db.schema.messages", "db.schema.messageNotifications", "db.schema.notificationPreferences", "db.schema.users", "db.schema.classEnrollments", "db.schema.classes", "db.schema.grades", "types.permissions", "types.action-state"], "auth": ["auth"]}},
"attendance": {"dependsOn": ["shared", "auth", "classes"], "uses": {"shared": ["db", "auth-guard.requirePermission", "db.schema.attendanceRecords", "db.schema.attendanceRules", "db.schema.classEnrollments", "db.schema.users", "db.schema.classes", "types.permissions", "types.action-state", "types.DataScope"], "auth": ["auth"], "classes": ["data-access.getTeacherClasses", "data-access.getAdminClasses"]}},
"scheduling": {"dependsOn": ["shared", "auth", "classes"], "uses": {"shared": ["db", "auth-guard.requirePermission", "auth-guard.getAuthContext", "db.schema.schedulingRules", "db.schema.scheduleChanges", "db.schema.classSchedule", "db.schema.classes", "db.schema.users", "db.schema.classSubjectTeachers", "db.schema.subjects", "db.schema.classrooms", "types.permissions", "types.action-state"], "auth": ["auth"], "classes": []}}
"scheduling": {"dependsOn": ["shared", "auth", "classes"], "uses": {"shared": ["db", "auth-guard.requirePermission", "auth-guard.getAuthContext", "db.schema.schedulingRules", "db.schema.scheduleChanges", "db.schema.classSchedule", "db.schema.classes", "db.schema.users", "db.schema.classSubjectTeachers", "db.schema.subjects", "db.schema.classrooms", "types.permissions", "types.action-state"], "auth": ["auth"], "classes": []}},
"diagnostic": {"dependsOn": ["shared", "auth"], "uses": {"shared": ["db", "auth-guard.requirePermission", "auth-guard.getAuthContext", "db.schema.knowledgePointMastery", "db.schema.learningDiagnosticReports", "db.schema.knowledgePoints", "db.schema.questionsToKnowledgePoints", "db.schema.examSubmissions", "db.schema.submissionAnswers", "db.schema.classEnrollments", "db.schema.classes", "db.schema.users", "types.permissions", "types.action-state", "hooks.usePermission", "components.ui.*"], "auth": ["auth"]}},
"elective": {"dependsOn": ["shared", "auth"], "uses": {"shared": ["db", "auth-guard.requirePermission", "db.schema.electiveCourses", "db.schema.courseSelections", "db.schema.users", "db.schema.subjects", "db.schema.grades", "db.schema.classes", "db.schema.classEnrollments", "types.permissions", "types.action-state", "types.DataScope", "hooks.usePermission", "components.ui.*"], "auth": ["auth"]}}
},
"parameterFlowChains": {
"userId": {
@@ -1469,7 +1629,10 @@
"homework/actions.ts → 作为 creatorId 写入 homeworkAssignments 表",
"classes/data-access.ts → getTeacherClasses(teacherId), getGradeManagedClasses(userId)",
"grades/actions.ts → 作为 recordedBy 写入 gradeRecords 表",
"attendance/actions.ts → 作为 recordedBy 写入 attendanceRecords 表"
"attendance/actions.ts → 作为 recordedBy 写入 attendanceRecords 表",
"elective/actions.ts → 作为 teacherId 默认值写入 electiveCourses 表createElectiveCourseAction",
"elective/actions.ts → 作为 studentId 查询学生选课selectCourseAction/dropCourseAction/getStudentSelectionsAction",
"elective/data-access.ts → getElectiveCourses({ scope, currentUserId }) 按 teacherId 过滤class_taught/owned"
]
},
"examId": {
@@ -1501,15 +1664,15 @@
]
},
"permission": {
"origin": "shared/types/permissions.ts Permissions 常量定义(47 个权限点,含 FILE_UPLOAD/FILE_READ/FILE_DELETE、GRADE_RECORD_MANAGE/GRADE_RECORD_READ、ATTENDANCE_MANAGE/ATTENDANCE_READ、MESSAGE_SEND/MESSAGE_READ/MESSAGE_DELETE、SCHEDULE_AUTO/SCHEDULE_ADJUST",
"origin": "shared/types/permissions.ts Permissions 常量定义(50 个权限点,含 FILE_UPLOAD/FILE_READ/FILE_DELETE、GRADE_RECORD_MANAGE/GRADE_RECORD_READ、ATTENDANCE_MANAGE/ATTENDANCE_READ、MESSAGE_SEND/MESSAGE_READ/MESSAGE_DELETE、SCHEDULE_AUTO/SCHEDULE_ADJUST、ELECTIVE_MANAGE/ELECTIVE_READ/ELECTIVE_SELECT",
"flow": [
"shared/lib/permissions.ts ROLE_PERMISSIONS → 角色到权限映射admin/teacher 拥有全部 FILE_* 及 GRADE_RECORD_MANAGE/READstudent/parent/grade_head/teaching_head 拥有 GRADE_RECORD_READadmin/teacher 拥有 ATTENDANCE_MANAGE+ATTENDANCE_READstudent/parent/grade_head/teaching_head 拥有 ATTENDANCE_READadmin/teacher/parent/grade_head/teaching_head 拥有 MESSAGE_SEND/READ/DELETEstudent 拥有 MESSAGE_READ/DELETE 但无 MESSAGE_SENDadmin 拥有 SCHEDULE_AUTO+SCHEDULE_ADJUSTteacher 无排课权限)",
"shared/lib/permissions.ts ROLE_PERMISSIONS → 角色到权限映射admin/teacher 拥有全部 FILE_* 及 GRADE_RECORD_MANAGE/READstudent/parent/grade_head/teaching_head 拥有 GRADE_RECORD_READadmin/teacher 拥有 ATTENDANCE_MANAGE+ATTENDANCE_READstudent/parent/grade_head/teaching_head 拥有 ATTENDANCE_READadmin/teacher/parent/grade_head/teaching_head 拥有 MESSAGE_SEND/READ/DELETEstudent 拥有 MESSAGE_READ/DELETE 但无 MESSAGE_SENDadmin 拥有 SCHEDULE_AUTO+SCHEDULE_ADJUSTteacher 无排课权限admin/teacher 拥有 ELECTIVE_MANAGE+ELECTIVE_READstudent 拥有 ELECTIVE_SELECT+ELECTIVE_READgrade_head/teaching_head 拥有 ELECTIVE_READ",
"auth.ts JWT callback → resolvePermissions(roleNames) → token.permissions",
"proxy.ts middleware → token.permissions → 路由权限检查",
"auth-guard.ts requirePermission(permission) → Server Action权限断言如 /api/files/[id] DELETE 使用 FILE_DELETEgrades/actions.ts 使用 GRADE_RECORD_MANAGE/READmessaging/actions.ts 使用 MESSAGE_SEND/READ/DELETEattendance/actions.ts 使用 ATTENDANCE_MANAGE/READscheduling/actions.ts 使用 SCHEDULE_AUTO/SCHEDULE_ADJUST",
"auth-guard.ts requirePermission(permission) → Server Action权限断言如 /api/files/[id] DELETE 使用 FILE_DELETEgrades/actions.ts 使用 GRADE_RECORD_MANAGE/READmessaging/actions.ts 使用 MESSAGE_SEND/READ/DELETEattendance/actions.ts 使用 ATTENDANCE_MANAGE/READscheduling/actions.ts 使用 SCHEDULE_AUTO/SCHEDULE_ADJUSTelective/actions.ts 使用 ELECTIVE_MANAGE/READ/SELECT",
"auth-guard.ts requireAuth() → 仅校验登录(如 /api/upload POST、/api/files/[id] GET、messaging 通知读取 actions",
"use-permission.ts hasPermission(permission) → 客户端条件渲染(如 file-list.tsx 删除按钮可见性message-list/detail.tsx 写消息/删除按钮可见性)",
"layout/config/navigation.ts NavItem.permission → 侧边栏菜单过滤Grades 菜单项使用 GRADE_RECORD_READMessages 菜单项使用 MESSAGE_READAttendance 菜单项 teacher 使用 ATTENDANCE_MANAGEstudent/parent 使用 ATTENDANCE_READScheduling 菜单项 admin 使用 SCHEDULE_ADJUST/SCHEDULE_AUTOteacher Schedule Changes 使用 SCHEDULE_ADJUST"
"use-permission.ts hasPermission(permission) → 客户端条件渲染(如 file-list.tsx 删除按钮可见性message-list/detail.tsx 写消息/删除按钮可见性elective-course-list.tsx 操作按钮可见性",
"layout/config/navigation.ts NavItem.permission → 侧边栏菜单过滤Grades 菜单项使用 GRADE_RECORD_READMessages 菜单项使用 MESSAGE_READAttendance 菜单项 teacher 使用 ATTENDANCE_MANAGEstudent/parent 使用 ATTENDANCE_READScheduling 菜单项 admin 使用 SCHEDULE_ADJUST/SCHEDULE_AUTOteacher Schedule Changes 使用 SCHEDULE_ADJUSTElectives 菜单项 admin/teacher 使用 ELECTIVE_MANAGEstudent 使用 ELECTIVE_SELECT"
]
},
"dataScope": {
@@ -1522,7 +1685,9 @@
"exams/actions.ts update/delete → scope.type !== 'all' 时校验资源归属",
"grades/data-access.getGradeRecords({ scope }) → 行级过滤class_taught 限制所教班级class_members 限制学生本人children 限制子女)",
"attendance/data-access.getAttendanceRecords({ scope }) → 行级过滤class_taught 按教师班级过滤children 按子女过滤class_members 仅查自己all 查全部)",
"attendance/actions.ts getStudentAttendanceAction → 对 class_members/children 进行 DataScope 二次校验"
"attendance/actions.ts getStudentAttendanceAction → 对 class_members/children 进行 DataScope 二次校验",
"elective/data-access.getElectiveCourses({ scope, currentUserId }) → 行级过滤owned/class_taught 按 teacherId 过滤grade_managed 按 gradeIds 过滤class_members/children 返回 null 由 getAvailableCoursesForStudent 处理)",
"elective/actions.ts getStudentSelectionsAction → 对 class_members/children 进行 DataScope 二次校验"
]
}
},
@@ -1555,7 +1720,10 @@
"/admin/users/import": {"component": "UserImportPage (含 UserImportDialog)", "type": "server", "module": "users", "actions": ["users/actions.downloadUserTemplateAction", "users/actions.importUsersAction"], "permission": "user:manage", "description": "用户批量导入页面(说明卡片+字段文档表+导入对话框权限requirePermission(USER_MANAGE)"},
"/admin/scheduling/rules": {"component": "SchedulingRulesForm", "type": "server", "module": "scheduling", "dataAccess": ["scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getSchedulingRules"], "actions": ["saveSchedulingRulesAction"], "permission": "schedule:adjust", "description": "排课规则配置页面权限requirePermission(SCHEDULE_ADJUST)"},
"/admin/scheduling/auto": {"component": "AutoSchedulePanel + AutoScheduleResultView", "type": "server", "module": "scheduling", "dataAccess": ["scheduling/actions.getAdminClassesForScheduling"], "actions": ["autoScheduleAction", "applyAutoScheduleAction"], "permission": "schedule:auto", "description": "自动排课页面(预览+应用权限requirePermission(SCHEDULE_AUTO)"},
"/admin/scheduling/changes": {"component": "ScheduleChangeList + ScheduleConflictsView", "type": "server", "module": "scheduling", "dataAccess": ["scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getScheduleChanges"], "actions": ["approveScheduleChangeAction", "rejectScheduleChangeAction", "getClassConflictsAction"], "permission": "schedule:adjust", "description": "调课申请审批+冲突检测页面权限requirePermission(SCHEDULE_ADJUST);审批操作需 SCHEDULE_AUTO"}
"/admin/scheduling/changes": {"component": "ScheduleChangeList + ScheduleConflictsView", "type": "server", "module": "scheduling", "dataAccess": ["scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getScheduleChanges"], "actions": ["approveScheduleChangeAction", "rejectScheduleChangeAction", "getClassConflictsAction"], "permission": "schedule:adjust", "description": "调课申请审批+冲突检测页面权限requirePermission(SCHEDULE_ADJUST);审批操作需 SCHEDULE_AUTO"},
"/admin/elective": {"component": "ElectiveCourseList", "type": "server", "module": "elective", "dataAccess": ["elective/data-access.getElectiveCourses (scope=all)"], "actions": ["deleteElectiveCourseAction", "openSelectionAction", "closeSelectionAction", "runLotteryAction"], "permission": "elective:manage", "description": "管理员选修课程列表权限requirePermission(ELECTIVE_MANAGE)"},
"/admin/elective/create": {"component": "ElectiveCourseForm", "type": "client", "module": "elective", "actions": ["createElectiveCourseAction"], "dataAccess": ["elective/data-access.getSubjectOptions"], "permission": "elective:manage", "description": "创建选修课程权限requirePermission(ELECTIVE_MANAGE)"},
"/admin/elective/[id]/edit": {"component": "ElectiveCourseForm (edit)", "type": "client", "module": "elective", "actions": ["updateElectiveCourseAction"], "dataAccess": ["elective/data-access.getElectiveCourseById", "elective/data-access.getSubjectOptions"], "permission": "elective:manage", "description": "编辑选修课程权限requirePermission(ELECTIVE_MANAGE)"}
},
"teacher": {
"/teacher/dashboard": {"component": "TeacherDashboardView", "type": "server", "dataAccess": ["dashboard/data-access (teacher)", "homework/data-access.getTeacherGradeTrends", "classes/data-access.getTeacherClasses"], "permission": "exam:read"},
@@ -1588,7 +1756,11 @@
"/teacher/attendance": {"component": "AttendanceRecordList + AttendanceFilters", "type": "server", "module": "attendance", "dataAccess": ["attendance/data-access.getAttendanceRecords", "classes/data-access.getTeacherClasses"], "permission": "attendance:manage", "description": "教师考勤记录列表权限requirePermission(ATTENDANCE_MANAGE)"},
"/teacher/attendance/sheet": {"component": "AttendanceSheet", "type": "client", "module": "attendance", "actions": ["batchRecordAttendanceAction", "getClassAttendanceForDateAction"], "dataAccess": ["attendance/data-access.getClassStudentsForAttendance"], "permission": "attendance:manage", "description": "批量点名页面权限requirePermission(ATTENDANCE_MANAGE)"},
"/teacher/attendance/stats": {"component": "AttendanceStatsCard", "type": "server", "module": "attendance", "dataAccess": ["attendance/data-access-stats.getClassAttendanceStats", "classes/data-access.getTeacherClasses"], "permission": "attendance:read", "description": "班级考勤统计权限requirePermission(ATTENDANCE_READ)"},
"/teacher/schedule-changes": {"component": "ScheduleChangeForm + ScheduleChangeList", "type": "server", "module": "scheduling", "dataAccess": ["scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getTeachersForScheduling", "scheduling/actions.getScheduleChanges (requesterId=ctx.userId)"], "actions": ["requestScheduleChangeAction"], "permission": "schedule:adjust", "description": "教师调课/代课申请页面(提交申请+查看本人申请列表权限requirePermission(SCHEDULE_ADJUST)admin 角色查看全部申请)"}
"/teacher/schedule-changes": {"component": "ScheduleChangeForm + ScheduleChangeList", "type": "server", "module": "scheduling", "dataAccess": ["scheduling/actions.getAdminClassesForScheduling", "scheduling/actions.getTeachersForScheduling", "scheduling/actions.getScheduleChanges (requesterId=ctx.userId)"], "actions": ["requestScheduleChangeAction"], "permission": "schedule:adjust", "description": "教师调课/代课申请页面(提交申请+查看本人申请列表权限requirePermission(SCHEDULE_ADJUST)admin 角色查看全部申请)"},
"/teacher/diagnostic": {"component": "ReportList", "type": "client", "module": "diagnostic", "dataAccess": ["diagnostic/data-access-reports.getDiagnosticReports"], "actions": ["publishReportAction", "deleteReportAction"], "permission": "diagnostic:read", "description": "学情诊断报告列表reportType/status 过滤器权限requirePermission(DIAGNOSTIC_READ)DataScope.class_members 仅查看自己报告;发布/删除操作需 DIAGNOSTIC_MANAGE"},
"/teacher/diagnostic/student/[studentId]": {"component": "StudentDiagnosticView", "type": "client", "module": "diagnostic", "dataAccess": ["diagnostic/data-access.getStudentMasterySummary", "diagnostic/data-access.getKnowledgePointStats (班级平均对比)", "diagnostic/data-access-reports.getDiagnosticReports"], "actions": ["generateStudentReportAction"], "permission": "diagnostic:read", "description": "学生学情诊断视图(概览卡片+雷达图+强项/弱项+生成报告[DIAGNOSTIC_MANAGE]+最新报告权限getAuthContext + DataScope 二次校验class_members 仅自己children 仅子女)"},
"/teacher/diagnostic/class/[classId]": {"component": "ClassDiagnosticView", "type": "client", "module": "diagnostic", "dataAccess": ["diagnostic/data-access.getClassMasterySummary"], "actions": ["generateClassReportAction"], "permission": "diagnostic:read", "description": "班级学情诊断视图(概览+知识点热力图+排名表+需重点关注学生+生成班级报告[DIAGNOSTIC_MANAGE]权限getAuthContext + DataScope 校验class_taught 必须包含 classIdclass_members/children notFound"},
"/teacher/elective": {"component": "ElectiveCourseList (teacher)", "type": "server", "module": "elective", "dataAccess": ["elective/data-access.getElectiveCourses (scope=class_taught/owned, currentUserId)"], "actions": ["deleteElectiveCourseAction", "openSelectionAction", "closeSelectionAction", "runLotteryAction"], "permission": "elective:manage", "description": "教师选修课程列表权限requirePermission(ELECTIVE_MANAGE)DataScope.class_taught/owned 按 teacherId 过滤)"}
},
"student": {
"/student/dashboard": {"component": "StudentDashboardView", "type": "server", "dataAccess": ["dashboard/data-access (student)", "homework/data-access.getStudentDashboardGrades", "classes/data-access.getStudentClasses"], "permission": "homework:submit"},
@@ -1599,7 +1771,9 @@
"/student/learning/textbooks/[id]": {"component": "学生教材阅读(只读)", "type": "client", "module": "textbooks", "dataAccess": ["textbooks/data-access.getTextbookById", "getChaptersByTextbookId", "getKnowledgePointsByTextbookId"], "permission": "textbook:read"},
"/student/schedule": {"component": "学生课表", "type": "server", "module": "classes", "dataAccess": ["classes/data-access.getStudentSchedule"], "permission": "homework:submit"},
"/student/grades": {"component": "我的成绩", "type": "server", "module": "grades", "dataAccess": ["grades/actions.getStudentGradeSummaryAction"], "permission": "grade_record:read"},
"/student/attendance": {"component": "StudentAttendanceView", "type": "server", "module": "attendance", "dataAccess": ["attendance/data-access-stats.getStudentAttendanceSummary"], "permission": "attendance:read", "description": "学生考勤视图(统计卡片 + 最近记录权限requirePermission(ATTENDANCE_READ)DataScope.class_members 仅查自己)"}
"/student/attendance": {"component": "StudentAttendanceView", "type": "server", "module": "attendance", "dataAccess": ["attendance/data-access-stats.getStudentAttendanceSummary"], "permission": "attendance:read", "description": "学生考勤视图(统计卡片 + 最近记录权限requirePermission(ATTENDANCE_READ)DataScope.class_members 仅查自己)"},
"/student/diagnostic": {"component": "StudentDiagnosticView", "type": "client", "module": "diagnostic", "dataAccess": ["diagnostic/data-access.getStudentMasterySummary (ctx.userId)", "diagnostic/data-access-reports.getDiagnosticReports (studentId=ctx.userId)"], "permission": "diagnostic:read", "description": "学生本人学情诊断视图(概览+雷达图+强项/弱项+最新报告权限requirePermission(DIAGNOSTIC_READ)DataScope.class_members 仅查自己)"},
"/student/elective": {"component": "StudentSelectionView", "type": "server", "module": "elective", "dataAccess": ["elective/data-access-selections.getAvailableCoursesForStudent", "elective/data-access-selections.getStudentSelections"], "actions": ["selectCourseAction", "dropCourseAction"], "permission": "elective:select", "description": "学生选课页面(可选课程列表 + 我的选课记录权限requirePermission(ELECTIVE_SELECT)"}
},
"management": {
"/management/grade/classes": {"component": "GradeClassesClient", "type": "client", "module": "classes", "permission": "grade:manage"},