feat: 新增备课模块并修复全模块 P0/P1/P2 缺陷
Some checks failed
Security / deep-security-scan (push) Failing after 20m5s
DR Drill / dr-drill (push) Failing after 1m31s
CI / scheduled-backup (push) Failing after 1m31s
CI / backup-verify (push) Has been skipped
CI / weekly-dr-drill (push) Failing after 0s
CI / build-deploy (push) Has been cancelled
CI / security-scan (push) Has been cancelled

主要变更:

- 新增 lesson-preparation 模块: 备课编辑器、节点编辑、AI 建议、知识点选择、版本历史、作业发布

- 新增 shared 通用组件: charts/question-bank-filters/schedule-list/ui (chip-nav/filter-bar/page-header/stat-card/stat-item)

- 新增 student/admin 端 loading.tsx 与 error.tsx, 优化加载与错误态体验

- 新增 teacher/lesson-plans 页面 (列表/新建/编辑)

- 新增 drizzle 迁移 0002_tiny_lionheart 及 snapshot

- 新增 textbooks/schema.ts 与 exams/utils/normalize-structure.ts

- 修复 Tiptap v3 SSR hydration 崩溃 (rich-text-block immediatelyRender: false)

- 重构多模块 data-access/actions/组件, 修复权限校验与类型规范

- 同步架构文档 004/005 反映新增模块、导出、依赖关系

- 归档 bugs/* 测试报告与 e2e 测试脚本 (admin/parent/student/teacher web_test)
This commit is contained in:
SpecialX
2026-06-22 01:06:16 +08:00
parent d8962aba96
commit 978d9a8309
327 changed files with 34070 additions and 5642 deletions

View File

@@ -390,6 +390,27 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- `validatePassword()` / `isAccountLocked()` / `rateLimit()` — 安全策略
- `exportToExcel()` / `parseExcel()` / `generateTemplate()` — Excel 工具
- `cn()` / `formatDate()` / `formatFileSize()` — 通用工具
- `getInitials(name)` / `formatDateForFile(d?)` — 通用工具P1-c / P1-a 重构新增:从 parent/lib/utils.ts、grades/export-button.tsx 等多处重复实现抽取)
- `downloadBase64File(base64, filename, mimeType?)` / `downloadBlob(blob, filename)` — 客户端文件下载P1-c 重构新增:从 grades/export-button、users/user-import-dialog、audit/audit-log-export-button 三处重复实现抽取,位于 `lib/download.ts`
**共享组件导出**P0-b / P1-a / P1-b / P1-c / P2-a / P2-b / P3-a / P3-b / P3-c / P3-d 重构新增,按类别组织):
| 类别 | 组件 | 文件 | 用途 | 消费方数量 |
|------|------|------|------|-----------|
| **UI 组件** | `StatCard` | `components/ui/stat-card.tsx` | 统计卡片(标题+数值+图标+描述+跳转+骨架屏) | 8 个P1-a |
| **UI 组件** | `StatItem` | `components/ui/stat-item.tsx` | 紧凑统计项label+icon+value+hint用于统计面板网格 | 8 个P1-a |
| **UI 组件** | `ChipNav` | `components/ui/chip-nav.tsx` | 芯片导航组(通过 URL search params 切换筛选维度Link 跳转) | 3 个P1-b |
| **UI 组件** | `PageHeader` | `components/ui/page-header.tsx` | 页面头部(标题+描述+icon+actions响应式布局 | 2 个P2-b: profile/page.tsx, settings/security/page.tsx |
| **UI 组件** | `FilterBar` / `FilterSearchInput` / `FilterResetButton` | `components/ui/filter-bar.tsx` | 筛选栏容器+搜索框+重置按钮统一布局壳URL 状态由各模块处理) | 5 个P3-b: exam/textbook/question/audit-log/login-log filters |
| **图表组件** | `ChartCardShell` | `components/charts/chart-card-shell.tsx` | 图表卡片外壳Card+Header+EmptyState+Content 统一结构) | 8 个P3-c |
| **图表组件** | `TrendLineChart` | `components/charts/trend-line-chart.tsx` | 趋势折线图LineChart 统一配置,支持单/多系列) | 8 个P3-c: grade-trend-chart 等) |
| **图表组件** | `SimpleBarChart` | `components/charts/simple-bar-chart.tsx` | 柱状图BarChart 统一配置,支持单/多 Bar + Cell 分桶着色) | 8 个P3-c: grade-distribution-chart 等) |
| **图表组件** | `ComparisonRadarChart` | `components/charts/comparison-radar-chart.tsx` | 对比雷达图RadarChart 统一配置,支持双 Radar 对比) | 8 个P3-c: subject-comparison-chart, mastery-radar-chart 等) |
| **课表组件** | `ScheduleList` / `ScheduleListItem` | `components/schedule/schedule-list.tsx` | 课表列表+列表项(课程+时间+地点+班级徽章separator/card 两种变体) | 3 个P3-a: student-today-schedule-card, child-schedule-card, student-schedule-view |
| **题库组件** | `QuestionBankFilters` | `components/question/question-bank-filters.tsx` | 题库筛选栏(搜索+题型+难度default/compact 两种布局) | 2 个P3-d: exam-assembly, question-bank-picker |
| **设置组件** | `SettingsView` | `modules/settings/components/settings-view.tsx` | 统一设置页布局4 标签页General/Notifications/Appearance/Security角色差异通过 props 注入) | 3 个P2-a: admin/teacher/student 设置页) |
> 注:`SettingsView` 位于 `modules/settings/components/`(非 shared 层),因仅被 settings 模块消费,未下沉到 shared。此处列出以完整反映本次重构的组件抽取范围。
**依赖关系**
- 被依赖方:**所有模块**依赖 shared
@@ -431,9 +452,22 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `lib/file-storage.ts` | - | 文件存储抽象 |
| `hooks/use-permission.ts` | - | 客户端权限 Hook |
| `components/ui/*` | 34 文件 | shadcn/ui 标准组件 |
| `components/ui/stat-card.tsx` | 95 | StatCard 统计卡片P1-a 新增) |
| `components/ui/stat-item.tsx` | 38 | StatItem 紧凑统计项P1-a 新增) |
| `components/ui/chip-nav.tsx` | 78 | ChipNav 芯片导航P1-b 新增) |
| `components/ui/page-header.tsx` | 44 | PageHeader 页面头部P2-b 新增,含 icon 属性) |
| `components/ui/filter-bar.tsx` | 124 | FilterBar + FilterSearchInput + FilterResetButtonP3-b 新增) |
| `components/charts/chart-card-shell.tsx` | 90 | ChartCardShell 图表卡片外壳P3-c 新增) |
| `components/charts/trend-line-chart.tsx` | 153 | TrendLineChart 趋势折线图P3-c 新增) |
| `components/charts/simple-bar-chart.tsx` | 162 | SimpleBarChart 柱状图P3-c 新增) |
| `components/charts/comparison-radar-chart.tsx` | 143 | ComparisonRadarChart 对比雷达图P3-c 新增) |
| `components/schedule/schedule-list.tsx` | 112 | ScheduleList + ScheduleListItem 课表列表P3-a 新增) |
| `components/question/question-bank-filters.tsx` | 137 | QuestionBankFilters 题库筛选栏P3-d 新增) |
| `lib/download.ts` | 47 | downloadBase64File + downloadBlob 客户端下载工具P1-c 新增) |
| `lib/utils.ts` | - | 通用工具P1-a/P1-c 新增 getInitials + formatDateForFile |
| `components/onboarding-gate.tsx` | 312 | 引导流程(业务泄漏) |
| `components/global-search.tsx` | 221 | 全局搜索(业务泄漏) |
| `types/permissions.ts` | 92 | 54 个权限点常量 |
| `types/permissions.ts` | 157 | 61 个权限点常量 + Role/DataScope/AuthContext 类型 |
---
@@ -445,6 +479,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- Actions`createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction`(✅ P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access
- Data-access`getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` / `getExamCreatorId` / `updateExamWithQuestions` / `deleteExamById` / `duplicateExam` / `getExamPreview` / `getExamSubjects` / `getExamGrades`(后 7 个为 P1-2 新增)
- AI Pipeline`generateAiCreateDraftFromSource` / `generateAiPreviewData` / `regenerateAiQuestionByInstruction`
- Utils`normalizeStructure`v3 新增:将持久化的 `exam.structure` unknown JSON 运行时校验并归一化为类型安全的 `ExamNode[]`,类型守卫模式无 `as` 断言,从 `teacher/exams/[id]/build/page.tsx` 提取)
**依赖关系**
- 依赖:`shared/*``@/auth``questions`(✅ P0-1 已修复:通过 data-access.createQuestionWithRelations`classes`(✅ P0-2 已修复:通过 data-access.getClassGradeIdsByClassIds`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions
@@ -466,6 +501,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `data-access.ts` | 473 | 考试 CRUD含 P1-2 新增 7 个写/查询函数P0-1/P0-2 已修复:通过 questions/classes data-access 跨模块通信) |
| `types.ts` | 31 | 类型定义 |
| `hooks/use-exam-preview.ts` | 295 | 预览 Hook |
| `utils/normalize-structure.ts` | 57 | v3 新增exam.structure 运行时校验与归一化(从 build/page.tsx 提取) |
| `components/*` | 18 文件 | 考试表单/组卷/预览组件 |
---
@@ -601,12 +637,12 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**导出函数**
- Actions`createTeacherClassAction` / `updateTeacherClassAction` / `deleteTeacherClassAction` / `createAdminClassAction` / `updateAdminClassAction` / `deleteAdminClassAction` / `createGradeClassAction` / `updateGradeClassAction` / `deleteGradeClassAction`
- Data-access`getAdminClasses` / `getTeacherClasses` / `getGradeManagedClasses` / `getStudentClasses` / `getClassDetails` / `getClassStudents` / `getClassSchedule` / `getClassHomeworkInsights` / `getGradeHomeworkInsights` / `getStudentsSubjectScores` / `verifyTeacherOwnsClass`(✅ P0-5 已修复classSchedule 写函数 createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem 已迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留 classSchedule 读函数;✅ P2 已修复:`getAccessibleClassIdsForTeacher` 使用 `Promise.all` 并行化 ownedIds 与 assignedIds 查询)
- Data-access`getAdminClasses` / `getTeacherClasses` / `getGradeManagedClasses` / `getStudentClasses` / `getClassDetails` / `getClassStudents` / `getClassSchedule` / `getClassHomeworkInsights` / `getGradeHomeworkInsights` / `getStudentsSubjectScores` / `verifyTeacherOwnsClass` / `getTeacherIdsByClassIds`(获取多个班级的所有教师 ID班主任 + 任课教师,跨模块接口,供 messaging 模块调用)(✅ P0-5 已修复classSchedule 写函数 createClassScheduleItem/updateClassScheduleItem/deleteClassScheduleItem 已迁移至 scheduling/data-access-class-schedule.tsclasses 模块仅保留 classSchedule 读函数;✅ P2 已修复:`getAccessibleClassIdsForTeacher` 使用 `Promise.all` 并行化 ownedIds 与 assignedIds 查询)
- Schema`CreateTeacherClassSchema` / `UpdateTeacherClassSchema` / `DeleteTeacherClassSchema` / `CreateAdminClassSchema` / `UpdateAdminClassSchema` / `DeleteAdminClassSchema` / `CreateGradeClassSchema` / `UpdateGradeClassSchema` / `DeleteGradeClassSchema` / `CreateClassScheduleItemSchema` / `UpdateClassScheduleItemSchema` / `DeleteClassScheduleItemSchema` / `EnrollStudentByEmailSchema`
**依赖关系**
- 依赖:`shared/*``@/auth``school`(✅ P1-1 已修复:通过 school data-access.isGradeHead/isGradeManager/findGradeIdByHeadAndName`homework`(✅ P0-7 已修复:通过 `homework/data-access-classes` 暴露的函数获取作业数据,不再直查 homework/exams 表)
- 被依赖:`exams`/`homework`/`grades`/`attendance`/`scheduling`/`dashboard`(通过 data-accessP0-4 已修复)/`parent`/`course-plans`/`users`(✅ P1-1 已修复8+ 处直查 classes 表改为通过 classes data-access
- 被依赖:`exams`/`homework`/`grades`/`attendance`/`scheduling`/`dashboard`(通过 data-accessP0-4 已修复)/`parent`/`course-plans`/`users`(✅ P1-1 已修复8+ 处直查 classes 表改为通过 classes data-access/`messaging`(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId支持学生/家长给班级教师发消息)
**已知问题**
- ✅ P0-1 已修复:`data-access.ts` 已拆分为 5 个文件data-access/data-access-stats/data-access-schedule/data-access-students/data-access-admin所有文件均 ≤800 行
@@ -787,11 +823,11 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**导出函数**
- Actions`sendMessageAction` / `getMessagesAction` / `getMessageAction` / `deleteMessageAction` / `getNotificationsAction` / `markNotificationReadAction` / `markAllNotificationsReadAction` / `getNotificationPreferencesAction` / `updateNotificationPreferencesAction`
- Data-access`getMessages` / `getMessageById` / `getMessageThread` / `createMessage` / `markMessageAsRead` / `deleteMessage` / `getUnreadMessageCount` / `getRecipients`(通知 CRUD 通过 re-export 从 notifications 模块重导出,保持向后兼容)
- Notification-preferencesre-export shim实际逻辑在 `notifications/preferences.ts`
- Data-access`getMessages` / `getMessageById` / `getMessageThread` / `createMessage` / `markMessageAsRead` / `deleteMessage` / `getUnreadMessageCount` / `getRecipients`按 DataScope 过滤可发送对象class_taught 教师→学生、grade_managed 年级管理员→教师/学生、all 管理员、class_members 学生→自己班级的任课教师/班主任、children 家长→孩子的班主任/任课教师;通过 classes data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID通知 CRUD 通过 re-export 从 notifications 模块重导出,保持向后兼容)
- Notification-preferences~~re-export shim实际逻辑在 `notifications/preferences.ts`~~ ✅ P0-b 已修复:`notification-preferences.ts` 文件已删除(通知模块去重),消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`
**依赖关系**
- 依赖:`shared/*``@/auth``notifications`(✅ P0-4 / P1-5 已修复:通过 `sendNotification` dispatcher 发送通知,通知 CRUD 和偏好已迁移至 notifications 模块)
- 依赖:`shared/*``@/auth``notifications`(✅ P0-4 / P1-5 已修复:通过 `sendNotification` dispatcher 发送通知,通知 CRUD 和偏好已迁移至 notifications 模块)`classes`(通过 data-access.getTeacherIdsByClassIds/getStudentActiveClassId 获取班级教师 ID支持学生 class_members 和家长 children 数据范围)、`users`(通过 data-access.getUserNamesByIds 获取用户显示名称)
- 被依赖:`notifications`(✅ 已消除反向依赖)、`settings`(通知偏好表单)、`layout`(通知下拉)
**已知问题**
@@ -802,13 +838,14 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ P1 已修复:~~`markMessageAsReadAction` / `deleteMessageAction` / `getMessageDetailAction` 缺少 Zod 校验~~ 已添加 `MessageIdSchema` 校验 messageId 参数
- ✅ P1 已修复:~~`updateNotificationPreferencesAction` 缺少 Zod 校验~~ 已添加 `UpdateNotificationPreferencesSchema` 校验 8 个布尔字段
- ✅ P2 已修复:`data-access.ts` 中 3 处 `or(...)!` 非空断言清理为安全守卫(条件 push
- ✅ P0-b 已修复:~~`notification-preferences.ts` re-export shim 文件~~ 已删除通知模块去重8 个消费方改为直接从 `@/modules/notifications/preferences` 导入 `getNotificationPreferences` / `upsertNotificationPreferences`,消除 messaging 模块对通知偏好的冗余 re-export 层
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 276 | 9 个 Server Action通知相关 Action 委托 notifications 模块) |
| `data-access.ts` | 199 | 私信 CRUD + re-export 通知 CRUD向后兼容 |
| `notification-preferences.ts` | 11 | re-export shim实际逻辑在 notifications/preferences.ts |
| ~~`notification-preferences.ts`~~ | ~~11~~ | ~~re-export shim~~ ✅ P0-b 已删除(消费方改为直接从 notifications/preferences 导入 |
| `schema.ts` | 41 | 私信发送校验 + messageId 校验 + 通知偏好更新校验 |
| `types.ts` | 72 | 私信类型 + re-export 通知类型(向后兼容) |
@@ -918,6 +955,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**已知问题**
- ✅ P2-13 已修复:~~所有函数 try-catch 吞错误返回空数组/null~~ 所有 catch 块已添加 `console.error` 输出错误上下文
- ✅ P2 已修复:`getFileAttachmentsWithFilters``or(...)!` 非空断言清理为安全守卫
- ✅ P2 已修复:~~`getFileAttachmentsWithFilters``conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注
- ⚠️ P2`actions.ts`data-access 被路由直接调用
- ✅ 职责单一,不跨模块查询
@@ -962,22 +1000,28 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**职责**:家长视角的子女数据聚合与展示。
**导出函数**
- Data-access`getChildren` / `getChildBasicInfo` / `getChildDashboardData`(✅ P2 已修复:`getChildBasicInfo` 使用 `Promise.all` 并行化 gradeOptions 与 classId 查询,并添加 `ChildBasicInfo` 显式返回类型;`getChildBasicInfo` 使用 `React.cache()` 包装实现请求级 memoization
- Data-access`getChildren` / `getChildBasicInfo` / `getChildDashboardData` / `getParentDashboardData` / `verifyParentChildRelation`(✅ P2 已修复:`getChildBasicInfo` 使用 `Promise.all` 并行化 gradeName 与 activeClass 查询;新增 `verifyParentChildRelation` 同时按 parentId + studentId 过滤,防止跨家庭信息泄露;新增 `getStudentActiveClass` 一次 JOIN 返回 classId + className新增 `getGradeNameById` 替代全量 `getGradeOptions`
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(合理)、`homework`(合理)、`grades`(合理)
- 依赖:`shared/*``@/auth``classes`(合理)、`homework`(合理)、`grades`(合理)`users`(合理)、`school`(合理)
- 被依赖:无
**已知问题**
- ✅ P2 已修复:~~`getChildBasicInfo` 多次串行查询,可优化为 join~~ 改为使`Promise.all` 并行化 gradeOptions 与 classId 查询
- ✅ P1 已修复:~~`app/(dashboard)/parent/children/[studentId]/page.tsx` 直接访问 DB违反三层架构~~ 改为`verifyParentChildRelation` data-access 函数
- ✅ P1 已修复:~~权限校验未加 parentId 条件,存在信息泄露风险~~ `verifyParentChildRelation` 同时按 parentId + studentId 过滤
- ✅ P2 已修复:~~`getChildBasicInfo` 多次串行查询~~ 改为 `Promise.all` 并行化,并使用 `getStudentActiveClass` 一次 JOIN
- ✅ P2 已修复:~~`getGradeOptions` 全量查询效率低~~ 改为 `getGradeNameById` 按 ID 查询
- ✅ P2 已修复:~~`buildHomeworkSummary``[...assignments].sort()` 不必要拷贝~~ 改为 `toSorted()`
- ✅ P2 已修复:~~`in7Days` 死代码~~ 已删除
- ✅ 职责单一,正确复用其他模块 data-access
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `data-access.ts` | 234 | 子女关系 + 仪表盘数据聚合 |
| `types.ts` | 57 | 类型定义 |
| `components/*` | 7 文件 | 子女卡片/详情/仪表盘 |
| `data-access.ts` | 227 | 子女关系 + 仪表盘数据聚合 + 关系校验 |
| `types.ts` | 67 | 类型定义(含 JSDoc |
| `lib/utils.ts` | 7 | 模块共享工具函数getInitials |
| `components/*` | 8 文件 | 子女卡片/详情/仪表盘/共享数据页 |
---
@@ -987,24 +1031,29 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**导出函数**
- Actions`getElectiveCoursesAction` / `createElectiveCourseAction` / `updateElectiveCourseAction` / `deleteElectiveCourseAction` / `getStudentSelectionsAction` / `selectCourseAction` / `dropCourseAction` / `runLotteryAction` / `getAvailableCoursesForStudentAction`
- Data-access`getElectiveCourses` / `getElectiveCourseById` / `createElectiveCourse` / `updateElectiveCourse` / `deleteElectiveCourse` / `selectCourse` / `dropCourse` / `runLottery` / `getStudentSelections` / `getAvailableCoursesForStudent`
- Data-access`getElectiveCourses` / `getElectiveCourseById` / `createElectiveCourse` / `updateElectiveCourse` / `deleteElectiveCourse` / `openSelection` / `closeSelection` / `buildCourseSelect` / `mapCourseRow` / `resolveCourseDisplayNames` / `CourseCoreRow`P3 新增导出,供 data-access-selections 复用)
- Data-access-operations`selectCourse` / `dropCourse` / `runLottery`
- Data-access-selections`getCourseSelections` / `getStudentSelections` / `getStudentGradeId` / `getAvailableCoursesForStudent`
**依赖关系**
- 依赖:`shared/*``@/auth`
- 依赖:`shared/*``@/auth``school`(✅ P3 已修复:通过 school data-access.getSubjectOptions/getGradeOptions 获取科目/年级名称,不再直查 subjects/grades 表)、`users`(✅ P3 已修复:通过 users data-access.getUserNamesByIds 获取教师姓名,不再直查 users 表)、`classes`(通过 classes data-access.getStudentActiveGradeId 获取学生年级)
- 被依赖:无
**已知问题**
- ⚠️ P1`data-access.ts``data-access-selections.ts` 重复定义 `mapCourseRow`/`buildCourseSelect`60 行重复)
- ⚠️ P2`runLottery` 使用 `Math.random()`,结果不可复现
- ⚠️ P2`selectCourse` FCFS 模式存在并发超卖风险
- P1 已修复:~~`buildCourseSelect` 跨模块 join users/subjects/grades 表~~ 改为只查 electiveCourses 表,通过 `resolveCourseDisplayNames` 调用 school/users data-access 获取显示名称
- ✅ P1 已修复:~~`getSubjectOptions` 本地直查 subjects 表且与 school 模块重复~~ 删除本地实现,改用 `school/data-access.getSubjectOptions`
- ✅ P1 已修复:~~`selectCourse`/`dropCourse` 缺事务包裹~~ 改为 `db.transaction` 包裹FCFS 模式下使用 `FOR UPDATE` 行锁防止并发超卖
- ✅ P2 已修复:~~`mapCourseRow` 在 data-access.ts 与 data-access-selections.ts 重复定义~~ 抽取到 data-access.ts 统一导出data-access-selections.ts 复用
- ✅ P2 已修复:~~`runLottery` 使用 `sort(() => Math.random() - 0.5)` 有偏 shuffle~~ 改为 Fisher-Yates 无偏洗牌算法
- ✅ P2 已修复:~~`selectCourse` FCFS 并发超卖风险~~ 使用 `db.transaction` + `.for("update")` 行锁
- ✅ 权限校验完整ELECTIVE_MANAGE/SELECT/READ
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 304 | 11 个 Server Action |
| `data-access.ts` | 242 | 课程 CRUD + scope 过滤 |
| `data-access-operations.ts` | 217 | 选课操作select/drop/lottery |
| `data-access.ts` | 250 | 课程 CRUD + scope 过滤 + 共享映射函数P3 重构:移除跨模块 join通过 school/users data-access 获取显示名称) |
| `data-access-operations.ts` | 245 | 选课操作select/drop/lotteryP3 重构:事务包裹 + FOR UPDATE 锁 + Fisher-Yates 洗牌 |
| `data-access-selections.ts` | 189 | 选课记录查询 |
| `schema.ts` | 132 | Zod 校验 |
| `types.ts` | 108 | 类型定义 + 标签常量 |
@@ -1028,6 +1077,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ P0-6 已修复:~~事件上报存在 Server Action 与 REST API 双通道重复~~ 删除 `/api/proctoring/event` REST 路由(移至 deletes/Server Action `recordProctoringEventAction` 为唯一规范路径
- ✅ P1-1 已修复:~~跨模块直查 `exams`/`examSubmissions`/`users`~~ 改为通过 exams/users data-access 函数获取数据
- ✅ P2 已修复:`actions.ts` 不再直接 import `db``examSubmissions`submission 归属校验已下沉到 data-access`recordProctoringEventAction` 改用 `requirePermission(EXAM_SUBMIT)` 并增加 `revalidatePath`
- ✅ P2 已修复:~~`getStudentProctoringStatuses` 串行查询getUserNamesByIds 后再查事件)~~ 改为 `Promise.all` 并行拉取学生姓名与事件记录
**文件清单**
| 文件 | 行数 | 职责 |
@@ -1057,7 +1107,10 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**已知问题**
- ✅ P1-1 已修复:~~`updateMasteryFromSubmission` 跨模块直查 4 张表(与 exams/homework/questions 紧耦合)~~ 改为调用 `exams/data-access.getExamSubmissionWithAnswers``questions/data-access.getKnowledgePointsForQuestions`
- ⚠️ P2`data-access-reports.ts` 有未使用代码(`round2`
- P2 已修复:~~`data-access-reports.ts` 有未使用代码(`round2` + `void round2`~~ 已删除死代码
- ✅ P2 已修复:~~`updateMasteryFromSubmission` 循环内串行 await upsert~~ 改为 `Promise.all` 并行执行所有 upsert
- ✅ P2 已修复:~~`getClassMasterySummary` 串行查询className → studentIds → userMap → masteryRows~~ 改为两组 `Promise.all` 并行className+studentIdsuserMap+masteryRows
- ✅ P2 已修复:~~`getDiagnosticReports``conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注
- ⚠️ P2班级报告将生成者 ID 存入 `studentId` 字段schema 设计缺陷 workaround
- ✅ 与 grades 模块无职责重叠grades 管分数diagnostic 管知识点掌握度)
@@ -1081,6 +1134,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- Actions`getAiProvidersAction` / `createAiProviderAction` / `updateAiProviderAction` / `deleteAiProviderAction` / `testAiProviderAction`
- Actions-password`changePasswordAction`(✅ P1 已修复:使用 `requirePermission(USER_PROFILE_UPDATE)` + Zod 校验 + DB 操作下沉到 data-access
- Data-access`getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange`P1 新增,从 actions 下沉)
- Components`SettingsView`P2-a 新增:统一设置页布局,消除 admin/teacher/student 三个设置视图的重复布局4 标签页 General/Notifications/Appearance/Security角色差异通过 `description` / `backHref` / `generalExtra` 三个 props 注入3 个消费方admin/teacher/student 设置页)
- Types`AiProviderSummary` / `AiProviderName` / `AiProviderExisting`P1 新增,从 actions.ts 迁出)
**依赖关系**
@@ -1092,6 +1146,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ P1 已修复:~~无 `data-access.ts``actions.ts` 直接使用 `db`~~ 新建 `data-access.ts`,所有 DB 操作已下沉
- ✅ P1 已修复:~~`changePasswordAction` 使用 `requireAuth()` 无 Zod 校验~~ 改为 `requirePermission(USER_PROFILE_UPDATE)` + `ChangePasswordSchema` Zod 校验 + 并行查询优化
- ✅ P2 已修复:`actions-password.ts` 删除本地 `normalizeBcryptHash`,统一复用 `shared/lib/bcrypt-utils.normalizeBcryptHash`,消除重复代码
- ✅ P2-a 已修复:~~admin/teacher/student 三个设置视图重复布局~~ 新增 `SettingsView` 统一设置页布局4 标签页 + 角色差异通过 props 注入3 个设置页改为消费 `SettingsView`
- ⚠️ P2`notification-preferences-form.tsx` 跨模块 UI 依赖
- ✅ 密码修改有速率限制
- ✅ AI Provider 操作有 `AI_CONFIGURE` 权限校验
@@ -1103,6 +1158,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `actions-password.ts` | 107 | 修改密码P1 已修复requirePermission + Zod + data-access |
| `data-access.ts` | 175 | AI Provider CRUD + 密码修改 DB 操作P1 新增) |
| `types.ts` | 16 | 类型定义P1 新增AiProviderSummary 等) |
| `components/settings-view.tsx` | 117 | SettingsView 统一设置页布局P2-a 新增4 标签页 + props 注入角色差异) |
| `components/*` | 8 文件 | 通用设置 + AI 配置 + 密码 + 主题 + 通知偏好 |
---
@@ -1167,22 +1223,47 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**已知问题**
- ⚠️ P2与 classes 模块的 `schedule-view.tsx`/`schedule-filters.tsx` 可能功能重叠
- ✅ 纯 UI 模块,数据由页面通过 classes data-access 获取
- ✅ 认证模式已统一:所有 student 页面使用 `getCurrentStudentUser()`users 模块)或 `getAuthContext()`shared 模块),不再直接调用 `auth()``getDemoStudentUser()`
**文件清单**
| 文件 | 职责 |
|------|------|
| `components/student-courses-view.tsx` | 学生课程视图 |
| `components/student-courses-view.tsx` | 学生课程视图(含 `ClassCard` memo 组件 + 加入班级表单,使用 `useTransition` |
| `components/student-schedule-filters.tsx` | 课表筛选器 |
| `components/student-schedule-view.tsx` | 学生课表视图 |
**路由文件清单**`app/(dashboard)/student/`
| 文件 | 职责 |
|------|------|
| `dashboard/page.tsx` + `loading.tsx` | 学生仪表盘 + 骨架屏 |
| `attendance/page.tsx` + `loading.tsx` | 学生考勤 + 骨架屏 |
| `diagnostic/page.tsx` + `loading.tsx` | 学情诊断 + 骨架屏 |
| `elective/page.tsx` + `loading.tsx` | 选课中心 + 骨架屏 |
| `grades/page.tsx` + `loading.tsx` | 我的成绩 + 骨架屏 |
| `learning/assignments/page.tsx` + `loading.tsx` | 作业列表(含 `AssignmentCard` 组件)+ 骨架屏 |
| `learning/assignments/[assignmentId]/page.tsx` + `loading.tsx` | 作业作答/复习 + 骨架屏 |
| `learning/courses/page.tsx` + `loading.tsx` | 课程列表 + 骨架屏 |
| `learning/textbooks/page.tsx` + `loading.tsx` | 教材列表 + 骨架屏 |
| `learning/textbooks/[id]/page.tsx` + `loading.tsx` | 教材阅读 + 骨架屏 |
| `schedule/page.tsx` + `loading.tsx` | 课表 + 骨架屏 |
| `error.tsx` | 路由组错误边界(提供"重试"按钮) |
---
## 2.27 lesson-preparation备课模块
**职责**:教师备课,基于教材章节创建课案(Block 编辑器),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。
**职责**:教师备课,基于教材章节创建课案(**节点图编辑器 React Flow**),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。
> 架构变更2026-06-21编辑器从列表式BlockRenderer + @dnd-kit升级为节点图式NodeEditor + @xyflow/react。数据结构从 v1blocks 数组)升级到 v2nodes + edges 节点图),旧数据通过 `migrateV1ToV2()` 自动迁移。
**数据结构**
- v1已废弃仅向后兼容读取`{ version: 1, blocks: Block[] }`
- v2当前`{ version: 2, nodes: LessonPlanNode[]; edges: LessonPlanEdge[] }`
- `LessonPlanNode``Block` + `position: { x, y }`(画布坐标)
- `LessonPlanEdge``{ id, source, target, sourceHandle?, targetHandle? }`(节点间连线)
**导出函数**
- Data-access`data-access.ts``getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent`
- Data-access`data-access.ts``getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2`v1→v2 迁移blocks 数组转换为 nodes + 线性 edges/ `normalizeDocument`(规范化:确保 content 为 v2 格式,兼容旧数据)
- Data-access-versions`data-access-versions.ts``getLessonPlanVersions` / `createLessonPlanVersion` / `getVersionContent` / `revertToVersion` / `pruneAutoVersions`
- Data-access-templates`data-access-templates.ts``getLessonPlanTemplates` / `saveAsTemplate` / `deletePersonalTemplate`
- Data-access-knowledge`data-access-knowledge.ts``getLessonPlansByKnowledgePoint` / `getLessonPlansByQuestion`
@@ -1191,21 +1272,23 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- Actions`getLessonPlansAction` / `getLessonPlanByIdAction` / `createLessonPlanAction` / `updateLessonPlanAction` / `saveLessonPlanVersionAction` / `getLessonPlanVersionsAction` / `revertLessonPlanVersionAction` / `deleteLessonPlanAction` / `duplicateLessonPlanAction` / `getLessonPlanTemplatesAction` / `saveAsTemplateAction` / `deleteTemplateAction` / `suggestKnowledgePointsAction` / `publishLessonPlanHomeworkAction` / `getKnowledgePointOptionsAction`
**依赖关系**
- 依赖:`shared/*``@/auth``shared/lib/ai``textbooks`(只读章节/知识点树)、`questions`(创建/查询题目)、`exams`(创建 exam 草稿)、`homework`(创建作业下发)、`classes`(查询教师班级)、`files`(附件)
- 依赖:`shared/*``@/auth``shared/lib/ai``@xyflow/react`(节点图编辑器)、`textbooks`(只读章节/知识点树)、`questions`(创建/查询题目)、`exams`(创建 exam 草稿)、`homework`(创建作业下发)、`classes`(查询教师班级)、`files`(附件)
- 被依赖:无
**已知问题**
- ✅ 通过对方 data-access 调用跨模块数据,无直查跨模块表
- ✅ data-access 按职责拆分为 4 个文件data-access/data-access-versions/data-access-templates/data-access-knowledge
- ✅ actions 按职责拆分为 4 个文件actions/actions-publish/actions-ai/actions-kp
- ✅ 编辑器架构升级NodeEditorReact Flow 画布)+ NodeEditPanel侧边内容编辑面板+ LessonNode自定义节点组件支持节点拖拽、连线、画布缩放
- ⚠️ `block-renderer.tsx` 标记为 @deprecated(已被 NodeEditor 替代,保留用于向后兼容)
**文件清单**
| 文件 | 职责 |
|------|------|
| `types.ts` | 类型定义 |
| `types.ts` | 类型定义(含 v1/v2 文档类型、LessonPlanNode、LessonPlanEdge |
| `constants.ts` | 常量定义 |
| `schema.ts` | Zod 验证 |
| `data-access.ts` | 课案 CRUD + 模板查询 + 初始内容构建 |
| `data-access.ts` | 课案 CRUD + 模板查询 + 初始内容构建 + v1→v2 迁移migrateV1ToV2 / normalizeDocument |
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理) |
| `data-access-templates.ts` | 个人模板 CRUD |
| `data-access-knowledge.ts` | 按知识点/题目反查课案 |
@@ -1216,22 +1299,25 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `publish-service.ts` | 发布作业服务(编排 homework/exams/classes |
| `ai-suggest.ts` | AI 知识点建议服务 |
| `seed-templates.ts` | 模板种子数据 |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook(基于 zustand支持 nodes/edges 操作addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode |
| `components/lesson-plan-list.tsx` | 课案列表 |
| `components/lesson-plan-card.tsx` | 课案卡片 |
| `components/lesson-plan-filters.tsx` | 课案筛选器 |
| `components/lesson-plan-editor.tsx` | 课案编辑器 |
| `components/block-renderer.tsx` | Block 渲染器 |
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel |
| `components/node-editor.tsx` | **节点图画布**React Flow自定义 LessonNode支持拖拽/连线/缩放) |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**(选中节点后编辑标题/数据) |
| `components/nodes/lesson-node.tsx` | **自定义节点组件**(按 BlockType 显示图标/颜色,含 Handle 连接点) |
| `components/block-renderer.tsx` | ⚠️ @deprecated Block 渲染器(已被 NodeEditor 替代,保留向后兼容) |
| `components/template-picker.tsx` | 模板选择器 |
| `components/version-history-drawer.tsx` | 版本历史抽屉 |
| `components/knowledge-point-picker.tsx` | 知识点选择器 |
| `components/question-bank-picker.tsx` | 题库选择器 |
| `components/inline-question-editor.tsx` | 内联题目编辑器 |
| `components/publish-homework-dialog.tsx` | 发布作业对话框 |
| `components/blocks/rich-text-block.tsx` | 富文本 Block |
| `components/blocks/text-study-block.tsx` | 课文研读 Block |
| `components/blocks/exercise-block.tsx` | 练习 Block |
| `components/blocks/reflection-block.tsx` | 反思 Block |
| `components/blocks/rich-text-block.tsx` | 富文本 Block(被 NodeEditPanel 复用) |
| `components/blocks/text-study-block.tsx` | 课文研读 Block(被 NodeEditPanel 复用) |
| `components/blocks/exercise-block.tsx` | 练习 Block(被 NodeEditPanel 复用) |
| `components/blocks/reflection-block.tsx` | 反思 Block(被 NodeEditPanel 复用) |
---
@@ -1417,9 +1503,9 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
| P2-10 | school 模块审计日志不一致(仅 school 实体记录) | school |
| ~~P2-11~~ | ~~`announcements` 死代码 `void wasPublished`~~ ✅ 已修复(代码中已不存在) | announcements |
| ~~P2-12~~ | ~~`announcements` 权限模式不一致requireAuth vs requirePermission~~ ✅ 已修复 | announcements |
| P2-13 | `files` try-catch 吞错误 | files |
| P2-14 | `elective` runLottery 使用 Math.random | elective |
| P2-15 | `elective` selectCourse FCFS 并发超卖风险 | elective |
| P2-13 | ~~`files` try-catch 吞错误~~ ✅ 已修复(所有 catch 块已添加 console.errorconditions 隐式 any[] 改为 SQL[] | files |
| ~~P2-14~~ | ~~`elective` runLottery 使用 Math.random~~ ✅ 已修复(改为 Fisher-Yates 无偏洗牌) | elective |
| ~~P2-15~~ | ~~`elective` selectCourse FCFS 并发超卖风险~~ ✅ 已修复db.transaction + FOR UPDATE 行锁) | elective |
| P2-16 | `diagnostic` 班级报告 studentId 字段复用 | diagnostic |
| ~~P2-17~~ | ~~`layout` 用权限反推角色~~ ✅ 已修复(`app-sidebar.tsx` 改用 `hasRole()` 判断角色) | layout |
| ~~P2-18~~ | ~~`scheduling/actions.ts` 末尾 re-export data-access~~ ✅ 已修复(移除 re-export4 个页面改为从 `data-access` 导入) | scheduling |
@@ -1491,7 +1577,7 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
| **grades** | ✅ | ✅ | ✅外键 | ✅外键 | - | - | ✅data-access | ✅data-access | - | ✅data-access | - | - | - | - | - |
| **dashboard** | ✅ | ✅ | ✅data-access | ✅data-access | ✅data-access | ✅data-access | ✅data-access | - | - | ✅data-access | - | - | - | - | - |
| **users** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | - | - | - |
| **messaging** | ✅ | ✅ | - | - | - | - | - | - | - | - | - | - | ✅dispatcher | - | - |
| **messaging** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | ✅dispatcher | - | - |
| **notifications** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | - | - | - |
| **attendance** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | - | - | - | - | - | - |
| **scheduling** | ✅ | ✅ | - | - | - | - | ✅data-access | - | - | ✅data-access | - | - | - | - | - |
@@ -1538,7 +1624,7 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
6.`auth-guard.ts` 中通过 `classSubjectTeachers` 查询教师关联的 classIds构建 `DataScope.class_taught`
### `permission`
1.`shared/types/permissions.ts``Permissions` 常量定义(54 个权限点)
1.`shared/types/permissions.ts``Permissions` 常量定义(61 个权限点)
2.`shared/lib/permissions.ts` 中通过 `ROLE_PERMISSIONS` 映射角色到权限列表
3.`auth.ts` JWT callback 中通过 `resolvePermissions(roleNames)` 合并多角色权限,存入 JWT
4.`proxy.ts` middleware 中通过 `token.permissions` 检查路由访问权限
@@ -1620,6 +1706,11 @@ formatFileSize(bytes: number): string
// shared/lib/utils.ts
cn(...inputs: ClassValue[]): string
formatDate(date: string | Date, locale?: string): string
getSearchParam(params: SearchParams, key: string): string | undefined
formatNumber(v: number | null | undefined, digits?: number): string
// shared/lib/search-params.ts (re-export from utils.ts)
getParam(params: SearchParams, key: string): string | undefined // = getSearchParam
```
### 业务模块核心 Actions

View File

@@ -5,7 +5,7 @@
"generatedAt": "2026-06-17",
"formatVersion": "1.1",
"rule": "每次文件修改后须同步更新本文件",
"lastUpdate": "P2-6/P2-7/P2-8/P2-11/P2-17/P2-18 已修复proxy.ts 改用 Permissions 常量替代硬编码字符串useA11yId 文件已不存在use-aria-live.ts 已在 hooks/ 目录schema.ts 分节编号重新编号为连续 1-24消除 8b/14b/乱序问题announcements 死代码 void wasPublished 已不存在layout/app-sidebar.tsx 改用 hasRole() 判断角色不再用权限反推角色scheduling/actions.ts 移除末尾 re-export data-access4 个页面改为从 data-access 直接导入P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 模块P0-2 已修复shared/lib ↔ auth 循环依赖已解决,新增 shared/lib/session.ts 单一入口P1-6 已修复http-utils.ts 统一 IP/UA 提取P0-1/P0-2/P0-3/P0-4/P0-5/P0-7/P0-8 已修复P1-2 已修复actions 层 DB 操作下沉到 data-accessP2-2/P2-3/P2-12/P2-20 已修复"
"lastUpdate": "P0-b/P1-a/P1-b/P1-c/P2-a/P2-b/P3-a/P3-b/P3-c/P3-d 共享组件抽取重构已同步:新增 shared 层 UI 组件StatCard/StatItem/ChipNav/PageHeader/FilterBar+FilterSearchInput+FilterResetButton、图表组件ChartCardShell/TrendLineChart/SimpleBarChart/ComparisonRadarChart、课表组件ScheduleList+ScheduleListItem、题库组件QuestionBankFilters、工具函数downloadBase64File/downloadBlob/getInitials/formatDateForFile新增 settings 模块 SettingsView 组件;删除 messaging/notification-preferences.tsP0-b 通知模块去重re-export shim 已移除,消费方改为直接从 notifications/preferences 导入);P2-6/P2-7/P2-8/P2-11/P2-17/P2-18 已修复proxy.ts 改用 Permissions 常量替代硬编码字符串useA11yId 文件已不存在use-aria-live.ts 已在 hooks/ 目录schema.ts 分节编号重新编号为连续 1-24消除 8b/14b/乱序问题announcements 死代码 void wasPublished 已不存在layout/app-sidebar.tsx 改用 hasRole() 判断角色不再用权限反推角色scheduling/actions.ts 移除末尾 re-export data-access4 个页面改为从 data-access 直接导入P1-1 已修复:所有跨模块直查已改为通过对方 data-access 接口homework/grades/parent/diagnostic/elective/proctoring/notifications/scheduling/classes 模块P0-2 已修复shared/lib ↔ auth 循环依赖已解决,新增 shared/lib/session.ts 单一入口P1-6 已修复http-utils.ts 统一 IP/UA 提取P0-1/P0-2/P0-3/P0-4/P0-5/P0-7/P0-8 已修复P1-2 已修复actions 层 DB 操作下沉到 data-accessP2-2/P2-3/P2-12/P2-20 已修复"
},
"architectureOverview": {
"layers": [
@@ -231,6 +231,7 @@
"FILE_READ",
"COURSE_PLAN_READ",
"ATTENDANCE_READ",
"MESSAGE_SEND",
"MESSAGE_READ",
"MESSAGE_DELETE",
"DIAGNOSTIC_READ",
@@ -360,6 +361,37 @@
"textbooks"
]
},
{
"name": "getParam",
"file": "lib/search-params.ts",
"signature": "getParam(params: SearchParams, key: string): string | undefined",
"params": {
"params": "Next.js searchParams 对象",
"key": "参数键名"
},
"purpose": "规范化 Next.js 15+ searchParams 访问string | string[] | undefined → string | undefinedre-export 自 utils.ts 的 getSearchParam",
"deps": [
"shared/lib/utils.getSearchParam"
],
"usedBy": [
"teacher/attendance/page.tsx",
"teacher/attendance/sheet/page.tsx",
"teacher/attendance/stats/page.tsx",
"teacher/classes/schedule/page.tsx",
"teacher/classes/students/page.tsx",
"teacher/course-plans/page.tsx",
"teacher/diagnostic/page.tsx",
"teacher/elective/page.tsx",
"teacher/exams/all/page.tsx",
"teacher/grades/page.tsx",
"teacher/grades/analytics/page.tsx",
"teacher/grades/entry/page.tsx",
"teacher/grades/stats/page.tsx",
"teacher/homework/assignments/page.tsx",
"teacher/questions/page.tsx",
"teacher/textbooks/page.tsx"
]
},
{
"name": "parseAiChatPayload",
"file": "lib/ai/payload-parser.ts",
@@ -795,6 +827,55 @@
"usedBy": [
"待扩展"
]
},
{
"name": "getInitials",
"file": "lib/utils.ts",
"signature": "getInitials(name: string | null | undefined): string",
"purpose": "从用户姓名提取首字母缩写最多2字符用于头像 fallback",
"deps": [],
"usedBy": [
"shared/components/ui/avatar.tsx",
"modules/dashboard/components/*-dashboard/*-header.tsx",
"modules/parent/components/child-card.tsx"
]
},
{
"name": "formatDateForFile",
"file": "lib/utils.ts",
"signature": "formatDateForFile(d?: Date): string",
"purpose": "格式化日期为 YYYY-MM-DD 用于文件名P1-c/P2-c 重构:从 grades/export.ts、audit/actions.ts、api/export/route.ts、users/actions.ts 四处重复实现抽取)",
"deps": [],
"usedBy": [
"grades/actions.exportGradesAction",
"audit/actions.exportAuditLogsAction",
"api/export/route",
"users/actions.exportUsersAction"
]
},
{
"name": "downloadBase64File",
"file": "lib/download.ts",
"signature": "downloadBase64File(base64: string, filename: string, mimeType?: string): void",
"purpose": "客户端下载 Base64 编码文件(默认 MIME 为 Excel xlsxP1-c 重构从 grades/export-button、users/user-import-dialog 两处重复实现抽取",
"deps": [
"shared/lib/download.downloadBlob"
],
"usedBy": [
"grades/components/export-button.tsx",
"users/components/user-import-dialog.tsx"
]
},
{
"name": "downloadBlob",
"file": "lib/download.ts",
"signature": "downloadBlob(blob: Blob, filename: string): void",
"purpose": "客户端下载 Blob 对象(创建临时 URL + a 标签点击 + revokeP1-c 重构从 audit/audit-log-export-button 抽取",
"deps": [],
"usedBy": [
"shared/lib/download.downloadBase64File",
"audit/components/audit-log-export-button.tsx"
]
}
],
"hooks": [
@@ -940,6 +1021,261 @@
"usedBy": [
"settings/components/notification-preferences-form.tsx"
]
},
{
"name": "StatCard",
"file": "components/ui/stat-card.tsx",
"props": "{ title, value, icon?, description?, color?, highlight?, href?, isLoading?, valueClassName? }",
"purpose": "统计卡片(标题+数值+图标+描述+跳转+骨架屏P1-a 重构从 8 处重复实现抽取teacher/student/admin dashboard stats、class-overview-stats、grade insights 等)",
"internalDeps": [
"Card",
"CardHeader",
"CardTitle",
"CardContent",
"Link",
"cn",
"Skeleton"
],
"usedBy": [
"dashboard/components/teacher-dashboard/teacher-stats.tsx",
"dashboard/components/student-dashboard/student-stats-grid.tsx",
"dashboard/components/admin-dashboard/admin-dashboard.tsx",
"classes/components/class-detail/class-overview-stats.tsx",
"app/(dashboard)/admin/school/grades/insights/page.tsx",
"app/(dashboard)/management/grade/insights/page.tsx"
]
},
{
"name": "StatItem",
"file": "components/ui/stat-item.tsx",
"props": "{ label, value, icon?, hint?, valueClassName? }",
"purpose": "紧凑统计项label+icon+value+hint用于统计面板网格P1-a 重构从 attendance-stats-card、grade-stats-card 两处重复实现抽取",
"internalDeps": [
"cn"
],
"usedBy": [
"attendance/components/attendance-stats-card.tsx",
"grades/components/grade-stats-card.tsx"
]
},
{
"name": "ChipNav",
"file": "components/ui/chip-nav.tsx",
"props": "{ options: ChipNavOption[], currentId, buildHref: (id) => string, size?: 'sm'|'xs', allOption?, className? }",
"purpose": "芯片导航组(通过 URL search params 切换筛选维度Link 跳转P1-b 重构从 stats-class-selector、attendance-stats-class-selector、analytics-filters 三处重复实现抽取",
"internalDeps": [
"Link",
"cn"
],
"usedBy": [
"grades/components/stats-class-selector.tsx",
"attendance/components/attendance-stats-class-selector.tsx",
"grades/components/analytics-filters.tsx"
]
},
{
"name": "PageHeader",
"file": "components/ui/page-header.tsx",
"props": "{ title, description?, icon?: ComponentType, actions?: ReactNode, className? }",
"purpose": "页面头部(标题+描述+icon+actions响应式布局移动端纵向桌面端横向P2-b 重构从 admin-dashboard、profile/page、settings/security/page 三处内联头部抽取",
"internalDeps": [
"cn"
],
"usedBy": [
"dashboard/components/admin-dashboard/admin-dashboard.tsx",
"app/(dashboard)/profile/page.tsx",
"app/(dashboard)/settings/security/page.tsx",
"modules/settings/components/settings-view.tsx"
]
},
{
"name": "FilterBar",
"file": "components/ui/filter-bar.tsx",
"props": "{ children, hasFilters?, onReset?, layout?: 'default'|'wrap'|'between', gapClassName?, className?, resetClassName? }",
"purpose": "筛选栏容器(统一布局壳 + Reset 按钮P3-b 重构从 exam/textbook/question/audit-log/login-log filters 五处重复布局抽取。URL 状态管理方式nuqs/router/callback由各模块自行处理",
"internalDeps": [
"Button",
"cn"
],
"usedBy": [
"exams/components/exam-filters.tsx",
"textbooks/components/textbook-filters.tsx",
"questions/components/question-filters.tsx",
"audit/components/audit-log-filters.tsx",
"audit/components/login-log-filters.tsx"
]
},
{
"name": "FilterSearchInput",
"file": "components/ui/filter-bar.tsx",
"props": "{ value, onChange, placeholder?, className?, inputClassName? }",
"purpose": "筛选栏搜索框(带 Search 图标的 InputP3-b 重构从 exam/textbook/question filters 三处重复搜索框抽取",
"internalDeps": [
"Input",
"Search (lucide-react)",
"cn"
],
"usedBy": [
"exams/components/exam-filters.tsx",
"textbooks/components/textbook-filters.tsx",
"questions/components/question-filters.tsx"
]
},
{
"name": "FilterResetButton",
"file": "components/ui/filter-bar.tsx",
"props": "{ onClick, className? }",
"purpose": "筛选栏重置按钮Reset + X 图标P3-b 重构从 6 个 filter 文件中重复的 Reset 按钮抽取",
"internalDeps": [
"Button",
"X (lucide-react)",
"cn"
],
"usedBy": [
"FilterBar内部使用"
]
},
{
"name": "ChartCardShell",
"file": "components/charts/chart-card-shell.tsx",
"props": "{ title, description?, icon?, iconClassName?, titleClassName?, isEmpty?, emptyTitle?, emptyDescription?, emptyIcon?, emptyClassName?, children, className?, contentClassName? }",
"purpose": "图表卡片外壳Card + CardHeader + EmptyState + CardContent 统一结构P3-c 重构从 8 个图表文件重复的 Card 包装抽取",
"internalDeps": [
"Card",
"CardHeader",
"CardTitle",
"CardDescription",
"CardContent",
"EmptyState",
"cn"
],
"usedBy": [
"grades/components/grade-trend-chart.tsx",
"grades/components/grade-distribution-chart.tsx",
"grades/components/class-comparison-chart.tsx",
"grades/components/subject-comparison-chart.tsx",
"dashboard/components/teacher-dashboard/teacher-grade-trends.tsx",
"dashboard/components/student-dashboard/student-grades-card.tsx",
"parent/components/child-grade-summary.tsx",
"diagnostic/components/mastery-radar-chart.tsx"
]
},
{
"name": "TrendLineChart",
"file": "components/charts/trend-line-chart.tsx",
"props": "{ data, series: TrendLineSeries[], xKey?, yDomain?, yTickFormatter?, xTickFormatter?, heightClassName?, margin?, yWidth?, tooltipClassName?, tooltipLabelKey?, className? }",
"purpose": "趋势折线图LineChart 统一配置CartesianGrid + XAxis + YAxis + ChartTooltip + LineP3-c 重构从 4 个 LineChart 文件grade-trend-chart、teacher-grade-trends、student-grades-card、child-grade-summary几乎逐行相同的配置抽取",
"internalDeps": [
"ChartContainer",
"ChartTooltip",
"ChartTooltipContent",
"CartesianGrid",
"Line",
"LineChart",
"XAxis",
"YAxis",
"cn"
],
"usedBy": [
"grades/components/grade-trend-chart.tsx",
"dashboard/components/teacher-dashboard/teacher-grade-trends.tsx",
"dashboard/components/student-dashboard/student-grades-card.tsx",
"parent/components/child-grade-summary.tsx"
]
},
{
"name": "SimpleBarChart",
"file": "components/charts/simple-bar-chart.tsx",
"props": "{ data, bars: BarSeries[], xKey, yDomain?, yAllowDecimals?, yTickFormatter?, xTickFormatter?, xTruncateLength?, yWidth?, heightClassName?, margin?, showLegend?, tooltipClassName?, tooltipFormatter?, cellColors?, className? }",
"purpose": "柱状图BarChart 统一配置CartesianGrid + XAxis + YAxis + ChartTooltip + BarP3-c 重构从 grade-distribution-chart单 Bar + Cell 分桶着色)和 class-comparison-chart多 Bar + Legend抽取",
"internalDeps": [
"ChartContainer",
"ChartTooltip",
"ChartTooltipContent",
"Bar",
"BarChart",
"CartesianGrid",
"Legend",
"XAxis",
"YAxis",
"Cell",
"cn"
],
"usedBy": [
"grades/components/grade-distribution-chart.tsx",
"grades/components/class-comparison-chart.tsx"
]
},
{
"name": "ComparisonRadarChart",
"file": "components/charts/comparison-radar-chart.tsx",
"props": "{ data, series: RadarSeries[], angleKey, angleTickFormatter?, angleTickFontSize?, domain?, tickCount?, showLegend?, heightClassName?, tooltipClassName?, className?, gridStrokeDasharray?, gridStrokeOpacity? }",
"purpose": "对比雷达图RadarChart 统一配置PolarGrid + PolarAngleAxis + PolarRadiusAxis + ChartTooltip + RadarP3-c 重构从 subject-comparison-chart双 RadaraverageScore + passRate和 mastery-radar-chart双 Radarstudent + classAverage含条件 Legend抽取",
"internalDeps": [
"ChartContainer",
"ChartTooltip",
"ChartTooltipContent",
"PolarAngleAxis",
"PolarGrid",
"PolarRadiusAxis",
"Radar",
"RadarChart",
"Legend",
"cn"
],
"usedBy": [
"grades/components/subject-comparison-chart.tsx",
"diagnostic/components/mastery-radar-chart.tsx"
]
},
{
"name": "ScheduleList",
"file": "components/schedule/schedule-list.tsx",
"props": "{ items: ScheduleListItemData[], variant?: 'separator'|'card', spacingClassName?, renderTrailing?, className? }",
"purpose": "课表列表(课程+时间+地点+班级徽章P3-a 重构从 student-today-schedule-card、child-schedule-card、student-schedule-view 三处逐行复制的列表项渲染抽取。支持 separator分隔线和 card卡片两种变体",
"internalDeps": [
"ScheduleListItem",
"cn"
],
"usedBy": [
"dashboard/components/student-dashboard/student-today-schedule-card.tsx",
"parent/components/child-schedule-card.tsx",
"student/components/student-schedule-view.tsx"
]
},
{
"name": "ScheduleListItem",
"file": "components/schedule/schedule-list.tsx",
"props": "{ item: ScheduleListItemData, variant?: 'separator'|'card', trailing?, className? }",
"purpose": "课表列表项单条课程渲染course + Clock + MapPin + BadgeP3-a 重构从 3 个课表文件中重复的列表项抽取",
"internalDeps": [
"Badge",
"Clock (lucide-react)",
"MapPin (lucide-react)",
"cn"
],
"usedBy": [
"ScheduleList内部使用"
]
},
{
"name": "QuestionBankFilters",
"file": "components/question/question-bank-filters.tsx",
"props": "{ search, onSearchChange, type, onTypeChange, difficulty, onDifficultyChange, layout?: 'default'|'compact', className? }",
"purpose": "题库筛选栏(搜索+题型+难度P3-d 重构从 exam-assemblycompact 布局)和 question-bank-pickerdefault 布局,同时将原生 HTML input/select 迁移到 shadcn Input/Select两处重复筛选栏抽取。状态管理方式由调用方自行处理",
"internalDeps": [
"Select",
"SelectContent",
"SelectItem",
"SelectTrigger",
"SelectValue",
"FilterSearchInput",
"cn"
],
"usedBy": [
"exams/components/exam-assembly.tsx",
"lesson-preparation/components/question-bank-picker.tsx"
]
}
],
"constants": [
@@ -1029,10 +1365,22 @@
"所有actions"
]
},
{
"name": "Role",
"file": "types/permissions.ts",
"definition": "Role = 'admin' | 'teacher' | 'student' | 'parent' | 'grade_head' | 'teaching_head'",
"usedBy": [
"auth-guard",
"permissions",
"proxy",
"next-auth.d.ts",
"use-permission"
]
},
{
"name": "DataScope",
"file": "types/permissions.ts",
"definition": "DataScope = { type: 'all' } | { type: 'owned'; userId: string } | { type: 'class_taught'; classIds: string[]; subjectIds?: string[] } | { type: 'grade_managed'; gradeIds: string[] } | { type: 'class_members' } | { type: 'children'; childrenIds: string[] }",
"definition": "DataScope = { type: 'all' } | { type: 'owned'; userId: string } | { type: 'class_members'; classIds: string[] } | { type: 'grade_managed'; gradeIds: string[] } | { type: 'class_taught'; classIds: string[]; subjectIds?: string[] } | { type: 'children'; childrenIds: string[] }",
"usedBy": [
"auth-guard",
"exams/data-access",
@@ -1045,7 +1393,7 @@
{
"name": "AuthContext",
"file": "types/permissions.ts",
"definition": "AuthContext = { userId: string; roles: string[]; permissions: Permission[]; dataScope: DataScope }",
"definition": "AuthContext = { userId: string; roles: Role[]; permissions: Permission[]; dataScope: DataScope }",
"usedBy": [
"auth-guard",
"所有调用requirePermission的Server Action"
@@ -2676,6 +3024,22 @@
"exam-form.tsx"
]
}
],
"utils": [
{
"name": "normalizeStructure",
"file": "utils/normalize-structure.ts",
"type": "function",
"signature": "(nodes: unknown) => ExamNode[]",
"purpose": "将持久化的 exam.structureunknown JSON运行时校验并归一化为类型安全的 ExamNode[](类型守卫模式,无 as 断言;递归处理 group children保证 id 唯一)",
"deps": [
"@paralleldrive/cuid2.createId",
"exams/components/assembly/selected-question-list.ExamNode"
],
"usedBy": [
"teacher/exams/[id]/build/page.tsx"
]
}
]
}
},
@@ -4260,6 +4624,14 @@
"usedBy": [
"grades/data-access-analytics"
]
},
{
"name": "getTeacherIdsByClassIds",
"signature": "(classIds: string[]) => Promise<string[]>",
"purpose": "获取多个班级的所有教师 ID班主任 + 任课教师,跨模块接口)",
"usedBy": [
"messaging/data-access.getRecipients"
]
}
],
"schema": [
@@ -5369,8 +5741,8 @@
{
"name": "getAiProviderSummaries",
"permission": "AI_CONFIGURE",
"signature": "() => Promise<AiProviderSummary[]>",
"purpose": "获取AI Provider列表P1 已修复DB 操作下沉到 data-access.getAiProviderSummaries",
"signature": "() => Promise<ActionState<AiProviderSummary[]>>",
"purpose": "获取AI Provider列表P1 已修复DB 操作下沉到 data-access.getAiProviderSummariesv3 已修复:返回值统一为 ActionState",
"deps": [
"data-access.getAiProviderSummaries"
]
@@ -6499,13 +6871,14 @@
"name": "getFileAttachmentsWithFilters",
"signature": "(params: FileAttachmentQueryParams) => Promise<FileAttachment[]>",
"file": "data-access.ts",
"purpose": "按 mimeType精确或前缀匹配与 searchoriginalName/filename 模糊匹配)筛选文件列表,支持 limit/offset 分页",
"purpose": "按 mimeType精确或前缀匹配与 searchoriginalName/filename 模糊匹配)筛选文件列表,支持 limit/offset 分页v3 修复conditions 显式标注 SQL[] 类型,消除隐式 any[]",
"deps": [
"shared.db",
"shared.db.schema.fileAttachments",
"drizzle-orm.like",
"drizzle-orm.or",
"drizzle-orm.and"
"drizzle-orm.and",
"drizzle-orm.SQL"
],
"usedBy": [
"app/(dashboard)/admin/files/page.tsx"
@@ -7234,11 +7607,12 @@
"name": "formatDateForFile",
"signature": "(d?: Date) => string",
"file": "export.ts",
"purpose": "格式化日期为 YYYY-MM-DD 用于文件名",
"deps": [],
"usedBy": [
"actions.exportGradesAction"
]
"purpose": "⚠️ P1-c/P2-c 已迁移:本地实现已删除,改为从 @/shared/lib/utils 导入。此条目保留仅作历史记录",
"deps": [
"shared/lib/utils.formatDateForFile"
],
"usedBy": [],
"migratedTo": "shared/lib/utils.formatDateForFile"
}
],
"components": [
@@ -7321,6 +7695,30 @@
"recharts",
"shared/components/ui/chart"
]
},
{
"name": "AnalyticsFilters",
"file": "components/analytics-filters.tsx",
"purpose": "成绩分析页筛选器(班级、科目、年级 Link 筛选按钮组,含 focus-visible 焦点样式)",
"deps": [
"next/link",
"shared/lib/utils.cn"
],
"usedBy": [
"teacher/grades/analytics/page.tsx"
]
},
{
"name": "StatsClassSelector",
"file": "components/stats-class-selector.tsx",
"purpose": "统计页班级+科目筛选器Link 筛选按钮组,含 focus-visible 焦点样式)",
"deps": [
"next/link",
"shared/lib/utils.cn"
],
"usedBy": [
"teacher/grades/stats/page.tsx"
]
}
]
}
@@ -8232,12 +8630,16 @@
"name": "getRecipients",
"signature": "(ctx: AuthContext) => Promise<RecipientOption[]>",
"file": "data-access.ts",
"purpose": "按 DataScope 过滤可发送对象列表class_taught教师→学生、grade_managed年级管理员→教师/学生、all管理员、class_members学生→自己班级的任课教师/班主任、children家长→孩子的班主任/任课教师)",
"deps": [
"shared.db",
"shared.db.schema.users",
"shared.db.schema.classEnrollments",
"shared.db.schema.classes",
"shared.db.schema.grades"
"shared.db.schema.grades",
"classes.data-access.getTeacherIdsByClassIds",
"classes.data-access.getStudentActiveClassId",
"users.data-access.getUserNamesByIds"
],
"usedBy": [
"getRecipientsAction",
@@ -9307,6 +9709,18 @@
"name": "AttendanceRulesForm",
"file": "components/attendance-rules-form.tsx",
"purpose": "考勤规则配置表单(班级选择器、迟到/早退阈值、自动标记勾选)"
},
{
"name": "AttendanceStatsClassSelector",
"file": "components/attendance-stats-class-selector.tsx",
"purpose": "考勤统计页班级筛选器Link 筛选按钮组,含 focus-visible 焦点样式)",
"deps": [
"next/link",
"shared/lib/utils.cn"
],
"usedBy": [
"teacher/attendance/stats/page.tsx"
]
}
]
}
@@ -9961,7 +10375,7 @@
{
"name": "getStudentProctoringStatuses",
"signature": "(examId: string) => Promise<StudentProctoringStatus[]>",
"purpose": "获取所有学生监考状态",
"purpose": "获取所有学生监考状态v3 优化Promise.all 并行执行 getUserNamesByIds 与事件聚合查询)",
"usedBy": [
"actions.getProctoringDashboardAction",
"teacher/exams/[id]/proctoring/page.tsx"
@@ -10102,7 +10516,7 @@
"name": "updateMasteryFromSubmission",
"signature": "(submissionId: string) => Promise<void>",
"file": "data-access.ts",
"purpose": "从提交答案更新掌握度按知识点聚合正确率onDuplicateKeyUpdate upsert",
"purpose": "从提交答案更新掌握度按知识点聚合正确率onDuplicateKeyUpdate upsertv3 优化Promise.all 并行执行多个知识点 upsert",
"deps": [
"shared.db",
"shared.db.schema.examSubmissions",
@@ -10118,7 +10532,7 @@
"name": "getClassMasterySummary",
"signature": "(classId: string) => Promise<ClassMasterySummary | null>",
"file": "data-access.ts",
"purpose": "获取班级掌握度摘要(学生数、平均掌握度、知识点统计、需重点关注学生)",
"purpose": "获取班级掌握度摘要(学生数、平均掌握度、知识点统计、需重点关注学生v3 优化:两阶段 Promise.all 并行查询班级信息+学生 ID、用户名+掌握度",
"deps": [
"shared.db",
"shared.db.schema.classes",
@@ -10182,7 +10596,7 @@
"name": "getDiagnosticReports",
"signature": "(filters: DiagnosticReportQueryParams) => Promise<DiagnosticReportWithDetails[]>",
"file": "data-access-reports.ts",
"purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名)",
"purpose": "查询诊断报告列表(可按 studentId/reportType/status/period 过滤,含学生名和生成者名v3 修复conditions 显式标注 SQL[] 类型,移除 round2 死代码",
"deps": [
"shared.db",
"shared.db.schema.learningDiagnosticReports",
@@ -10787,13 +11201,35 @@
]
},
{
"name": "getSubjectOptions",
"name": "buildCourseSelect",
"file": "data-access.ts",
"signature": "() => Promise<{id, name}[]>",
"purpose": "获取学科选项(按 order, name 排序",
"signature": "() => query builder",
"purpose": "构建 electiveCourses 表查询(仅查询本表字段,不跨表 JOINv3 重构:移除跨模块 LEFT JOIN名称解析改由 resolveCourseDisplayNames 异步聚合",
"usedBy": [
"admin/elective/create/page.tsx",
"admin/elective/[id]/edit/page.tsx"
"data-access.getElectiveCourses",
"data-access.getElectiveCourseById",
"data-access-selections.getAvailableCoursesForStudent"
]
},
{
"name": "mapCourseRow",
"file": "data-access.ts",
"signature": "(row: CourseCoreRow, display: {teacherName?, subjectName?, gradeName?}) => ElectiveCourseWithDetails",
"purpose": "将核心行 + 显示名映射为 ElectiveCourseWithDetailsv3 抽取:消除 data-access 与 data-access-selections 重复代码)",
"usedBy": [
"data-access.getElectiveCourses",
"data-access.getElectiveCourseById",
"data-access-selections.getAvailableCoursesForStudent"
]
},
{
"name": "resolveCourseDisplayNames",
"file": "data-access.ts",
"signature": "(rows: CourseCoreRow[]) => Promise<{teacherName?, subjectName?, gradeName?}[]>",
"purpose": "并行聚合教师名users.getUserNamesByIds、学科school.getSubjectOptions、年级school.getGradeOptions返回每行的显示名映射v3 重构:替代跨模块 LEFT JOIN",
"usedBy": [
"data-access.getElectiveCourses",
"data-access.getElectiveCourseById"
]
},
{
@@ -10838,7 +11274,7 @@
"name": "runLottery",
"file": "data-access-operations.ts",
"signature": "(courseId: string) => Promise<{enrolled: number, waitlist: number}>",
"purpose": "抽签录取(随机打乱 selected 记录,前 capacity 名 enrolled其余 waitlist课程 status=closed",
"purpose": "抽签录取(Fisher-Yates 无偏洗牌 selected 记录,前 capacity 名 enrolled其余 waitlist课程 status=closedv3 修复:替换 sort(Math.random) 有偏洗牌",
"usedBy": [
"actions.runLotteryAction"
]
@@ -10847,7 +11283,7 @@
"name": "selectCourse",
"file": "data-access-operations.ts",
"signature": "(courseId: string, studentId: string, priority?: number) => Promise<{status: CourseSelectionStatus, message: string}>",
"purpose": "学生选课(校验课程状态/时间窗口/重复选课FCFS 模式即时 enrolled/waitlistlottery 模式 selected",
"purpose": "学生选课(校验课程状态/时间窗口/重复选课FCFS 模式即时 enrolled/waitlistlottery 模式 selectedv3 修复db.transaction 包裹 + .for('update') 锁课程行防 FCFS 超卖",
"usedBy": [
"actions.selectCourseAction"
]
@@ -10856,7 +11292,7 @@
"name": "dropCourse",
"file": "data-access-operations.ts",
"signature": "(courseId: string, studentId: string) => Promise<void>",
"purpose": "学生退课status=droppedFCFS 模式自动递补 waitlist 首位)",
"purpose": "学生退课status=droppedFCFS 模式自动递补 waitlist 首位v3 修复db.transaction 包裹 + .for('update') 锁课程行保证递补一致性",
"usedBy": [
"actions.dropCourseAction"
]
@@ -10893,6 +11329,12 @@
"file": "types.ts",
"definition": "ElectiveCourse & { teacherName?, subjectName?, gradeName? }"
},
{
"name": "CourseCoreRow",
"type": "type",
"file": "data-access.ts",
"definition": "buildCourseSelect 返回行的推断类型v3 新增:供 mapCourseRow/resolveCourseDisplayNames 共享)"
},
{
"name": "CourseSelection",
"type": "interface",
@@ -11025,7 +11467,7 @@
},
"lesson_preparation": {
"path": "src/modules/lesson-preparation",
"description": "教师备课模块:基于教材章节创建课案(Block 编辑器),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布",
"description": "教师备课模块:基于教材章节创建课案(节点图编辑器 React Flowv2 nodes+edges 数据结构),支持模板、版本管理、知识点标注、题目创建/拉取、作业发布。编辑器从列表式BlockRenderer + @dnd-kit升级为节点图式NodeEditor + @xyflow/react旧 v1 数据通过 migrateV1ToV2() 自动迁移",
"exports": {
"dataAccess": [
{
@@ -11046,7 +11488,7 @@
{
"name": "updateLessonPlanContent",
"file": "data-access.ts",
"purpose": "更新课案内容(Block JSON"
"purpose": "更新课案内容(v2 nodes+edges JSON"
},
{
"name": "softDeleteLessonPlan",
@@ -11066,7 +11508,17 @@
{
"name": "buildInitialContent",
"file": "data-access.ts",
"purpose": "基于模板构建初始课案内容"
"purpose": "基于模板构建初始课案内容v2 nodes+edges"
},
{
"name": "migrateV1ToV2",
"file": "data-access.ts",
"purpose": "v1→v2 迁移:将旧 blocks 数组转换为 nodes + 线性 edges节点按网格布局"
},
{
"name": "normalizeDocument",
"file": "data-access.ts",
"purpose": "规范化:确保 content 为 v2 格式,兼容旧 v1 数据(自动调用 migrateV1ToV2"
},
{
"name": "getLessonPlanVersions",
@@ -11229,7 +11681,8 @@
"homework",
"classes",
"files",
"shared/lib/ai"
"shared/lib/ai",
"@xyflow/react"
],
"files": [
"types.ts",
@@ -11251,6 +11704,9 @@
"components/lesson-plan-card.tsx",
"components/lesson-plan-filters.tsx",
"components/lesson-plan-editor.tsx",
"components/node-editor.tsx",
"components/node-edit-panel.tsx",
"components/nodes/lesson-node.tsx",
"components/block-renderer.tsx",
"components/template-picker.tsx",
"components/version-history-drawer.tsx",
@@ -12028,6 +12484,7 @@
"shared": [
"db",
"auth-guard.requireAuth",
"auth-guard.getAuthContext",
"db.schema.parentStudentRelations",
"types"
],
@@ -12041,14 +12498,13 @@
"classes": [
"data-access.getStudentClasses",
"data-access.getStudentSchedule",
"data-access.getClassNameById",
"data-access.getStudentActiveClassId"
"data-access.getStudentActiveClass"
],
"grades": [
"data-access.getStudentGradeSummary"
],
"school": [
"data-access.getGradeOptions"
"data-access.getGradeNameById"
],
"users": [
"data-access.getUserBasicInfo",
@@ -12060,7 +12516,8 @@
"dependsOn": [
"shared",
"auth",
"notifications"
"notifications",
"classes"
],
"uses": {
"shared": [
@@ -12087,6 +12544,10 @@
"data-access.getUnreadNotificationCount (via re-export)",
"preferences.getNotificationPreferences (via re-export)",
"preferences.upsertNotificationPreferences (via re-export)"
],
"classes": [
"data-access.getTeacherIdsByClassIds",
"data-access.getStudentActiveClassId"
]
}
},
@@ -12332,6 +12793,11 @@
"files": [
"data-access.createFileAttachment",
"data-access.getFileAttachmentsByTarget"
],
"external": [
"@xyflow/reactReact Flow 节点图编辑器ReactFlow/Background/Controls/MiniMap/Handle/applyNodeChanges/applyEdgeChanges",
"@paralleldrive/cuid2节点 ID 生成)",
"zustand编辑器状态管理"
]
}
}
@@ -12449,6 +12915,12 @@
"type": "data-access",
"description": "✅ P0-4 / P1-5 已修复messaging 通过 sendNotification dispatcher 发送通知,通知 CRUD 和偏好通过 re-export 保持向后兼容"
},
{
"from": "messaging",
"to": "classes",
"type": "data-access",
"description": "getRecipients 通过 classes data-access.getTeacherIdsByClassIds / getStudentActiveClassId 获取班级教师 ID支持 class_members学生和 children家长数据范围"
},
{
"from": "classes",
"to": "homework",
@@ -13456,9 +13928,11 @@
"component": "StudentDashboardView",
"type": "server",
"dataAccess": [
"dashboard/data-access (student)",
"homework/data-access.getStudentDashboardGrades",
"classes/data-access.getStudentClasses"
"users/data-access.getCurrentStudentUser",
"classes/data-access.getStudentClasses",
"classes/data-access.getStudentSchedule",
"homework/data-access.getStudentHomeworkAssignments",
"homework/data-access.getStudentDashboardGrades"
],
"permission": "homework:submit"
},
@@ -13467,13 +13941,14 @@
"type": "server",
"module": "homework",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"homework/data-access.getStudentHomeworkAssignments"
],
"permission": "homework:submit"
},
"/student/learning/assignments/[assignmentId]": {
"component": "学生作答/复习",
"type": "client",
"type": "server",
"module": "homework",
"actions": [
"startHomeworkSubmissionAction",
@@ -13481,6 +13956,7 @@
"submitHomeworkAction"
],
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"homework/data-access.getStudentHomeworkTakeData"
],
"permission": "homework:submit"
@@ -13488,6 +13964,10 @@
"/student/learning/courses": {
"component": "StudentCoursesView",
"type": "server",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"classes/data-access.getStudentClasses"
],
"permission": "homework:submit"
},
"/student/learning/textbooks": {
@@ -13495,18 +13975,20 @@
"type": "server",
"module": "textbooks",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"textbooks/data-access.getTextbooks"
],
"permission": "textbook:read"
},
"/student/learning/textbooks/[id]": {
"component": "学生教材阅读(只读)",
"type": "client",
"type": "server",
"module": "textbooks",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"textbooks/data-access.getTextbookById",
"getChaptersByTextbookId",
"getKnowledgePointsByTextbookId"
"textbooks/data-access.getChaptersByTextbookId",
"textbooks/data-access.getKnowledgePointsByTextbookId"
],
"permission": "textbook:read"
},
@@ -13515,6 +13997,8 @@
"type": "server",
"module": "classes",
"dataAccess": [
"users/data-access.getCurrentStudentUser",
"classes/data-access.getStudentClasses",
"classes/data-access.getStudentSchedule"
],
"permission": "homework:submit"
@@ -13524,7 +14008,7 @@
"type": "server",
"module": "grades",
"dataAccess": [
"grades/actions.getStudentGradeSummaryAction"
"grades/data-access.getStudentGradeSummary"
],
"permission": "grade_record:read"
},
@@ -13540,7 +14024,7 @@
},
"/student/diagnostic": {
"component": "StudentDiagnosticView",
"type": "client",
"type": "server",
"module": "diagnostic",
"dataAccess": [
"diagnostic/data-access.getStudentMasterySummary (ctx.userId)",