docs: update architecture map and add lesson-preparation usage fixes design

- Update architecture impact map (004) and data (005) with new modules

- Add lesson-preparation usage fixes design spec

- Add teacher web test post-audit report
This commit is contained in:
SpecialX
2026-06-24 12:01:35 +08:00
parent 9d87388524
commit e4254f0f8e
5 changed files with 3139 additions and 107 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -113,7 +113,7 @@
│ │ data-access │
┌─┴────────┐ │ │
│ grades │◀────┘ 仅外键引用(合理)
│ (成绩) │
│ (成绩) │◀──── exams data-access✅ 2026-06-24 新增getExamsForGradeEntry/getExamForGradeEntry 按试卷录入成绩)
└────┬─────┘
│ ✅ P1-1 已修复
│ 通过 classes/school/users data-access
@@ -499,7 +499,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `lib/ai.ts` | 9 | 向后兼容重导出P2-2 已拆分到 `ai/` 目录) |
| `lib/ai/payload-parser.ts` | 78 | 请求负载解析 |
| `lib/ai/api-key-crypto.ts` | 28 | API Key 加密/解密 |
| `lib/ai/provider-config.ts` | 61 | Provider 配置查询 |
| `lib/ai/provider-config.ts` | 132 | Provider 配置查询V3.1 增强:基于 session 用户的可见性/所有权校验public + own private 过滤) |
| `lib/ai/client.ts` | 58 | AI 客户端创建与调用 |
| `lib/ai/errors.ts` | 8 | 错误格式化 |
| `lib/ai/index.ts` | 5 | 聚合导出 |
@@ -541,15 +541,16 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**导出函数**
- Actions`createExamAction` / `createAiExamAction` / `previewAiExamAction` / `regenerateAiQuestionAction` / `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` / `getExamsByGradeIdAction`(✅ v4-P2-7 新增年级仪表盘维度3按 gradeId 查询年级下所有考试 + 提交统计EXAM_READ 权限)(✅ P1-2 已修复actions 层不再直接访问 DB全部下沉到 data-access
- Data-access`getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` / `getExamCreatorId` / `updateExamWithQuestions` / `deleteExamById` / `duplicateExam` / `getExamPreview` / `getExamSubjects` / `getExamGrades` / `getExamsByGradeId`(✅ v4-P2-7 新增年级仪表盘维度3exams 表有直接 gradeId 字段,配合 examSubmissions 聚合提交数/已评分数/平均分,支持 scope 行级过滤)(后 8 个为 P1-2 新增)
- Data-access`getExams` / `getExamById` / `persistExamDraft` / `persistAiGeneratedExamDraft` / `buildExamDescription` / `resolveSubjectGradeNames` / `getExamCreatorId` / `updateExamWithQuestions` / `deleteExamById` / `duplicateExam` / `getExamPreview` / `getExamSubjects` / `getExamGrades` / `getExamsByGradeId`(✅ v4-P2-7 新增年级仪表盘维度3exams 表有直接 gradeId 字段,配合 examSubmissions 聚合提交数/已评分数/平均分,支持 scope 行级过滤)(后 8 个为 P1-2 新增)/ `getExamsForGradeEntry`(✅ 2026-06-24 新增:按 scope 过滤试卷列表,只返回有题目的试卷,供成绩录入页试卷选择器使用,返回 id/title/subjectName/gradeName/questionCount/totalScore/ `getExamForGradeEntry`(✅ 2026-06-24 新增获取单个试卷详情含题目列表innerJoin questions 获取 type含 scope 校验,返回 id/title/subjectId/gradeId/totalScore/questions[{id,order,score,type}],供 grades 模块按试卷录入成绩使用)
- AI Pipeline`generateAiCreateDraftFromSource` / `generateAiPreviewData` / `regenerateAiQuestionByInstruction`
- Utils`normalizeStructure`v3 新增:将持久化的 `exam.structure` unknown JSON 运行时校验并归一化为类型安全的 `ExamNode[]`,类型守卫模式无 `as` 断言,从 `teacher/exams/[id]/build/page.tsx` 提取)
- Stats-serviceV3-8 新增):`getExamAnalytics`cache 包装,聚合考试所有作业的已批改提交,计算平均分/及格率/分数段分布/逐题错误率与难度等级,对标智学网考试分析)+ `ExamAnalyticsSummary` 类型
- Types✅ 2026-06-24 新增成绩录入相关类型):`ExamQuestionItem`(试卷中单个题目的精简结构 { id, order, score, type }/ `ExamForGradeEntry`(成绩录入用的试卷详情,含题目列表)/ `ExamOptionForEntry`(成绩录入页试卷选择器选项 { id, title, subjectName, gradeName, questionCount, totalScore }
- ComponentsV3-8 新增):`ExamAnalyticsDashboard`(考试分析仪表盘:汇总卡片+分数段分布+逐题分析表)
**依赖关系**
- 依赖:`shared/*``@/auth``questions`(✅ P0-1 已修复:通过 data-access.createQuestionWithRelations`classes`(✅ P0-2 已修复:通过 data-access.getClassGradeIdsByClassIds`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions`homework`V3-8 新增stats-service 通过 `homework/data-access.getHomeworkAssignmentsByExamId` / `getGradedSubmissionsByExamId` 获取作业与提交数据,合理跨模块调用)
- 被依赖:`homework`(通过 sourceExamId 外键,合理)、`dashboard`(通过 data-accessP0-4 已修复)、`proctoring`(✅ P1-1 已修复:通过 exams data-access`diagnostic`(✅ P1-1 已修复:通过 exams data-access
- 被依赖:`homework`(通过 sourceExamId 外键,合理)、`dashboard`(通过 data-accessP0-4 已修复)、`proctoring`(✅ P1-1 已修复:通过 exams data-access`diagnostic`(✅ P1-1 已修复:通过 exams data-access`grades`(✅ 2026-06-24 新增:通过 data-access.getExamsForGradeEntry/getExamForGradeEntry 获取试卷列表和详情供按试卷录入成绩使用)
**已知问题**
- ✅ P0-1 已修复:~~`persistAiGeneratedExamDraft` 直接 insert 到 `questions` 表~~ 改为调用 `questions/data-access.createQuestionWithRelations`,通过 ID 映射保持 structure 引用一致
@@ -565,9 +566,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|------|------|------|
| `actions.ts` | 691 | 10 个 Server ActionP1-2 已修复,无直接 DB 操作) |
| `ai-pipeline.ts` | 857 | AI 出题管线(超限) |
| `data-access.ts` | 473 | 考试 CRUD含 P1-2 新增 7 个写/查询函数P0-1/P0-2 已修复:通过 questions/classes data-access 跨模块通信) |
| `data-access.ts` | 560+ | 考试 CRUD含 P1-2 新增 7 个写/查询函数P0-1/P0-2 已修复:通过 questions/classes data-access 跨模块通信v4-P2-7 新增 getExamsByGradeId2026-06-24 新增 getExamsForGradeEntry/getExamForGradeEntry 供 grades 模块按试卷录入成绩 |
| `stats-service.ts` | - | V3-8 新增:考试分析数据聚合(`getExamAnalytics` + `ExamAnalyticsSummary` 类型) |
| `types.ts` | 31 | 类型定义 |
| `types.ts` | 50+ | 类型定义2026-06-24 新增ExamQuestionItem/ExamForGradeEntry/ExamOptionForEntry 供成绩录入使用) |
| `hooks/use-exam-preview.ts` | 295 | 预览 Hook |
| `utils/normalize-structure.ts` | 57 | v3 新增exam.structure 运行时校验与归一化(从 build/page.tsx 提取) |
| `components/exam-analytics-dashboard.tsx` | - | V3-8 新增:考试分析仪表盘组件 |
@@ -725,16 +726,16 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
**职责**:成绩分析(录入/查询/统计/导出/趋势对比分析)。
**导出函数**
- Actions`getGradeRecordsAction` / `createGradeRecordAction`v4-P1-6 增强:成绩录入后通知学生和家长,调用 `notifyGradeEntered`/ `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction`v4-P1-12 增强:新增可选 `studentId` 参数,支持按学生导出,家长视角调用 `exportStudentGradeRecordsToExcel`,校验 studentId 属于家长子女)/ `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getGradeDistributionByGradeIdAction`(✅ v4-P2-7 新增年级仪表盘维度1按 gradeId 查询年级整体 + 按班级拆分的成绩分布GRADE_RECORD_READ 权限)/ `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction`v4-P1-6 增强:批量成绩录入后通知学生和家长)/ `saveGradeDraftAction` / `getGradeDraftAction` / `deleteGradeDraftAction`(✅ v3-P2 新增:成绩录入草稿 Server Actions分别使用 GRADE_RECORD_MANAGE/GRADE_RECORD_READ/GRADE_RECORD_MANAGE 权限)。注:`assertClassInScope` 原位于 actions.ts✅ P3 新增导出:班级 scope 校验工具,供 actions-analytics 复用),✅ v4-P2-6 修复:因 "use server" 文件要求所有 export 为 async`assertClassInScope` 是同步函数,已迁移至独立文件 `lib/scope-check.ts`actions.ts 与 actions-analytics.ts 均从 `./lib/scope-check` 导入
- Data-access`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getGradeDistributionByGradeId`(✅ v4-P2-7 新增年级仪表盘维度1通过 getClassesByGradeId 获取年级下所有班级inArray 查询成绩记录,复用 computeGradeDistribution/computeGradeStats 纯函数,返回整体分布 + 按班级拆分)/ `getRankingTrend` / `PaginatedGradeRecords`(✅ P3 新增:分页结果接口 `{ records, total }`/ `saveGradeDraft` / `getGradeDraft` / `deleteGradeDraft`(✅ v3-P2 新增:成绩录入草稿 CRUDupsert + 24 小时过期)/ `getExamOptionsForGrades` / `getSchoolWideGradeSummary`(✅ v3-P2 新增:考试选项查询 + 全校各年级成绩汇总,管理员视图按年级聚合平均分/及格率/优秀率/学生数/班级数,加权平均计算全校汇总)
- Types✅ v3-P2 新增,✅ v4-P2-7 新增年级分布类型):`SchoolWideGradeSummaryItem` / `SchoolWideGradeSummary` / `GradeDraftData`(草稿数据接口)/ `GradeDistributionByGradeResult`(✅ v4-P2-7 新增:年级维度成绩分布结果,含 overall 整体分布 + stats 统计 + byClass 按班级拆分数组)/ `GradeDistributionByGradeClassItem`(✅ v4-P2-7 新增按班级拆分的分布项classId/className/distribution/stats
- Actions`getGradeRecordsAction` / `createGradeRecordAction`v4-P1-6 增强:成绩录入后通知学生和家长,调用 `notifyGradeEntered`/ `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction`v4-P1-12 增强:新增可选 `studentId` 参数,支持按学生导出,家长视角调用 `exportStudentGradeRecordsToExcel`,校验 studentId 属于家长子女)/ `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getGradeDistributionByGradeIdAction`(✅ v4-P2-7 新增年级仪表盘维度1按 gradeId 查询年级整体 + 按班级拆分的成绩分布GRADE_RECORD_READ 权限)/ `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction`v4-P1-6 增强:批量成绩录入后通知学生和家长)/ `batchCreateGradeRecordsByExamAction`(✅ 2026-06-24 新增:按试卷批量录入每题得分,流程 requirePermission → getExamForGradeEntry scope 校验 → assertClassInScope → safeJsonParse → BatchGradeEntryByExamSchema 校验 → batchCreateGradeRecordsByExam 单事务写入 → updateMasteryFromExamScore → notifyGradeEntered → revalidatePath返回 gradeRecordId 列表供撤销)/ `saveGradeDraftAction` / `getGradeDraftAction` / `deleteGradeDraftAction`(✅ v3-P2 新增:成绩录入草稿 Server Actions分别使用 GRADE_RECORD_MANAGE/GRADE_RECORD_READ/GRADE_RECORD_MANAGE 权限)。注:`assertClassInScope` 原位于 actions.ts✅ P3 新增导出:班级 scope 校验工具,供 actions-analytics 复用),✅ v4-P2-6 修复:因 "use server" 文件要求所有 export 为 async`assertClassInScope` 是同步函数,已迁移至独立文件 `lib/scope-check.ts`actions.ts 与 actions-analytics.ts 均从 `./lib/scope-check` 导入
- Data-access`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getGradeDistributionByGradeId`(✅ v4-P2-7 新增年级仪表盘维度1通过 getClassesByGradeId 获取年级下所有班级inArray 查询成绩记录,复用 computeGradeDistribution/computeGradeStats 纯函数,返回整体分布 + 按班级拆分)/ `getRankingTrend` / `PaginatedGradeRecords`(✅ P3 新增:分页结果接口 `{ records, total }`/ `saveGradeDraft` / `getGradeDraft` / `deleteGradeDraft`(✅ v3-P2 新增:成绩录入草稿 CRUDupsert + 24 小时过期)/ `getExamOptionsForGrades` / `getSchoolWideGradeSummary`(✅ v3-P2 新增:考试选项查询 + 全校各年级成绩汇总,管理员视图按年级聚合平均分/及格率/优秀率/学生数/班级数,加权平均计算全校汇总)/ `batchCreateGradeRecordsByExam`(✅ 2026-06-24 新增:按试卷批量录入成绩,单事务写入 grade_records + grade_record_answers + 投影到 exam_submissions(status=graded) + submission_answers(answerContent=null),使错题集/成绩分析等下游模块无需改造即可读取教师录入的成绩,返回 gradeRecordId 列表供撤销)
- Types✅ v3-P2 新增,✅ v4-P2-7 新增年级分布类型,✅ 2026-06-24 新增按试卷录入类型`SchoolWideGradeSummaryItem` / `SchoolWideGradeSummary` / `GradeDraftData`(草稿数据接口)/ `GradeDistributionByGradeResult`(✅ v4-P2-7 新增:年级维度成绩分布结果,含 overall 整体分布 + stats 统计 + byClass 按班级拆分数组)/ `GradeDistributionByGradeClassItem`(✅ v4-P2-7 新增按班级拆分的分布项classId/className/distribution/stats/ `GradeRecordAnswer`(✅ 2026-06-24 新增:成绩记录-题目得分明细,对应 grade_record_answers 表)/ `BatchGradeEntryByExamQuestion`(✅ 2026-06-24 新增:按试卷录入时单个题目得分 { questionId, score }/ `BatchGradeEntryByExamItem`(✅ 2026-06-24 新增:按试卷录入时单个学生所有题目得分 { studentId, answers }
- Lib✅ P1-2 新增,✅ P3 更新签名,✅ P3-26 拆分,✅ v4-P2-6 新增 scope-check`toNumber` / `normalize`(位于 `lib/grade-utils.ts``buildScopeClassFilter(scope, currentUserId?)`P3-26 从 grade-utils.ts 迁移至 `lib/scope-filter.ts`P3 修复:`class_members` scope 内置 studentId 过滤,需传入 currentUserId 参数);`assertClassInScope(scope: DataScope, classId: string): string | null`(✅ v4-P2-6 从 actions.ts 迁移至 `lib/scope-check.ts`:校验 classId 是否在 scope 允许范围内,供 actions.ts 与 actions-analytics.ts 复用。迁移原因actions.ts 是 "use server" 文件要求所有 export 为 async而 assertClassInScope 是同步函数)
- Stats-service✅ P1-1 新增):`computeGradeStats` / `computeAverageScore` / `buildGradeTrendPoints` / `computeTrendAverage` / `computeClassComparisonStats` / `computeSubjectComparisonStats` / `computeGradeDistribution` / `buildRankingTrendPoints`(从 3 个 data-access 文件抽取的纯函数,使数据层专注 DB I/O统计逻辑可独立测试
- Export✅ v4-P1-12 新增):`exportGradeRecordsToExcel` / `exportClassGradeReportToExcel` / `exportStudentGradeRecordsToExcel`v4-P1-12 新增:导出单个学生成绩单家长视角,仅含成绩明细 + 个人统计不含班级数据scope 为 children 自动按 studentId 过滤)/ `formatDateForFile`(已迁移至 shared/lib/utils
- Components✅ P1-5 新增):`WidgetBoundary`Error Boundary + Suspense + Skeleton 组合,含 a11y 属性)/ `SchoolWideSummaryCard`(✅ v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格)/ `ScoreCell`(✅ v4-P1-7 新增:成绩单元格组件,根据得分率着色——红<60%/黄60-84%/绿≥85%,使用语义化 Tailwind 类名避免动态拼接fullScore<=0 时不着色)
**依赖关系**
- 依赖:`shared/*``@/auth``classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds
- 依赖:`shared/*``@/auth``classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getClassNamesByIds/getActiveStudentIdsByClassId/getStudentActiveClassId/getClassesByGradeId/getClassGradeIdsByClassIds)、`school`(✅ P1-1 已修复:通过 school data-access.getSubjectOptions/getGradeOptions`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds`exams`(✅ 2026-06-24 新增:通过 exams data-access.getExamsForGradeEntry/getExamForGradeEntry 获取试卷列表和详情供按试卷录入成绩使用)
- 被依赖:`parent`(通过 data-access合理`dashboard`
**已知问题**
@@ -784,22 +785,23 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- ✅ v3-P3-2 改进2026-06-23`grade-record-list.tsx` 新增多选复选框(全选/单选)+ 批量删除工具栏 + 批量删除确认对话框;新增 `bulkDeleteGradeRecords` data-access 函数(使用 inArray 一次性删除避免 N+1+ `bulkDeleteGradeRecordsAction` Server Action限制单次最多 500 条)
- ✅ v4-P3-2 改进2026-06-23`batch-grade-entry.tsx` 顶部新增可折叠新手引导提示框4 步使用说明),使用 localStorage 记住用户关闭状态避免重复显示
- ✅ v4-P2-6 修复2026-06-23~~`assertClassInScope` 是同步函数但位于 "use server" 文件 actions.ts 中~~ Next.js 要求 "use server" 文件中所有 export 必须为 async同步 export 会导致构建错误。修复:将 `assertClassInScope` 迁移至独立文件 `lib/scope-check.ts`(含 `import "server-only"`actions.ts 与 actions-analytics.ts 均从 `./lib/scope-check` 导入
- ✅ 2026-06-24 重新设计:批量录入从"只录总分"改为"按试卷录入每题得分"。新增 `grade_record_answers` 表存储每题得分明细(迁移 0010_grade_record_answers.sql`batchCreateGradeRecordsByExam` data-access 单事务写入 grade_records + grade_record_answers + 投影到 exam_submissions(status=graded) + submission_answers(answerContent=null),使错题集/成绩分析等下游模块无需改造即可读取教师录入的成绩(学生只看到对错,不知道学生答案)。`BatchGradeEntryByExam` 组件完全重写为 Excel 式表格(行=学生,列=题目,末列=总分自动计算支持多行多列粘贴、Enter 跳下一行、Tab 跳下一格、分数校验、撤销机制sessionStorage 5 分钟有效)。新增对 `exams` 模块的依赖getExamsForGradeEntry/getExamForGradeEntry。i18n 新增 batchByExam 章节zh-CN/en ~35 键)
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | 670+ | 19 个 Server Action含 Zod 校验,含 v2-P1-5 安全修复assertClassInScope + 行级 scope 校验P3 修复handleActionError + safeJsonParse + scope 传递 + DB 层分页v3-P2 新增saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftActionv4-P1-6createGradeRecordAction/batchCreateGradeRecordsAction 新增通知v4-P1-12exportGradesAction 新增 studentId 参数v3-P3-2 新增bulkDeleteGradeRecordsAction 批量删除v4-P2-6assertClassInScope 迁移至 lib/scope-check.ts |
| `actions.ts` | 770+ | 20 个 Server Action含 Zod 校验,含 v2-P1-5 安全修复assertClassInScope + 行级 scope 校验P3 修复handleActionError + safeJsonParse + scope 传递 + DB 层分页v3-P2 新增saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftActionv4-P1-6createGradeRecordAction/batchCreateGradeRecordsAction 新增通知v4-P1-12exportGradesAction 新增 studentId 参数v3-P3-2 新增bulkDeleteGradeRecordsAction 批量删除v4-P2-6assertClassInScope 迁移至 lib/scope-check.ts2026-06-24 新增batchCreateGradeRecordsByExamAction 按试卷录入每题得分 |
| `actions-analytics.ts` | 170 | 5 个分析 Action含 Zod 校验P3 修复handleActionError + assertClassInScope 校验v4-P2-6assertClassInScope 改从 ./lib/scope-check 导入) |
| `data-access.ts` | 450+ | 成绩 CRUD + 统计 + 草稿(含 v2-P2-9 修复recorderName 批量查询P3 修复PaginatedGradeRecords 接口 + DB 层分页 + 事务 + 存在性检查 + scope 过滤 + 并列排名v3-P2 新增saveGradeDraft/getGradeDraft/deleteGradeDraft + GradeDraftData 接口v3-P3-2 新增bulkDeleteGradeRecords 使用 inArray 批量删除) |
| `data-access.ts` | 600+ | 成绩 CRUD + 统计 + 草稿 + 按试卷录入(含 v2-P2-9 修复recorderName 批量查询P3 修复PaginatedGradeRecords 接口 + DB 层分页 + 事务 + 存在性检查 + scope 过滤 + 并列排名v3-P2 新增saveGradeDraft/getGradeDraft/deleteGradeDraft + GradeDraftData 接口v3-P3-2 新增bulkDeleteGradeRecords 使用 inArray 批量删除2026-06-24 新增batchCreateGradeRecordsByExam 单事务写入 grade_records + grade_record_answers + 投影到 exam_submissions/submission_answers |
| `data-access-analytics.ts` | 200+ | 趋势/对比分析P3 修复getClassComparison 应用 buildScopeClassFilterv3-P2 新增getExamOptionsForGrades/getSchoolWideGradeSummarygetGradeTrend/getClassComparison/getSubjectComparison/getGradeDistribution 新增 semester/examId 可选参数) |
| `data-access-ranking.ts` | 83 | 排名查询P3 修复getRankingTrend 接受 scope 参数 + class_taught 校验) |
| `stats-service.ts` | 285 | 统计计算纯函数P1-1 新增8 个纯函数 + 2 个常量 + 2 个接口P3-10createDefaultBuckets 改为内部函数P3-24buildGradeTrendPoints 使用 isGradeTrendType 类型守卫替代 as 断言) |
| `export.ts` | 290+ | Excel 导出v2-P1-5 修复:传递 currentUserId 到 data-accessP3 修复:适配 PaginatedGradeRecords 结构 + 传递 scopeP3-6复用 stats-service.computeAverageScore 替代局部 avgP3-7硬编码中文改用 next-intl getTranslationsv4-P1-12 新增exportStudentGradeRecordsToExcel 家长视角单学生导出) |
| `schema.ts` | 113+ | Zod 校验(含 12 个查询 schemaP3 修复score .max(1000) + records .max(500) + 补全查询字段v3-P2 新增grade_drafts 表定义第 1444-1469 行) |
| `schema.ts` | 130+ | Zod 校验(含 12 个查询 schemaP3 修复score .max(1000) + records .max(500) + 补全查询字段v3-P2 新增grade_drafts 表定义第 1444-1469 行2026-06-24 新增BatchGradeEntryByExamSchema 按试卷录入校验 + BatchGradeEntryByExamQuestionSchema/ItemSchema 子 schema导出 BatchGradeEntryByExamInput 类型 |
| `lib/grade-utils.ts` | 20 | 公共工具函数toNumber/normalizeP3-26buildScopeClassFilter 迁移至 scope-filter.ts |
| `lib/scope-filter.ts` | 56 | DB 行级权限过滤buildScopeClassFilterP3-26 从 grade-utils.ts 迁移v2-P2-2 修复:改用 classes data-access 子查询P3 修复:新增 currentUserId 参数) |
| `lib/scope-check.ts` | 34 | v4-P2-6 新增:班级 scope 校验工具assertClassInScope 同步函数,从 actions.ts 迁移至此独立文件以避开 "use server" 文件要求 export 必须为 async 的限制;含 `import "server-only"` |
| `types.ts` | 168+ | 类型定义v3-P2 新增SchoolWideGradeSummaryItem/SchoolWideGradeSummary |
| `types.ts` | 200+ | 类型定义v3-P2 新增SchoolWideGradeSummaryItem/SchoolWideGradeSummary2026-06-24 新增GradeRecordAnswer/BatchGradeEntryByExamQuestion/BatchGradeEntryByExamItem |
| `components/widget-boundary.tsx` | 136 | Widget 边界组件P1-5 新增v2-P1-1 已在 3 个页面应用) |
| `components/school-wide-summary-card.tsx` | - | v3-P2 新增管理员全校成绩汇总卡片4 个统计卡片 + 各年级对比表格) |
| `components/score-cell.tsx` | 41 | v4-P1-7 新增:成绩单元格组件,根据得分率着色(红<60%/黄60-84%/绿≥85%),使用语义化 Tailwind 类名 |
@@ -810,7 +812,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| `components/class-comparison-chart.tsx` | 194 | 班级对比图v2-P1-4i18nv3-P3-5 新增:显著性分析区域,基于极差和样本量的经验规则判断班级间差异,含可折叠详细分析) |
| `components/grade-trend-chart.tsx` | 59 | 趋势图v2-P1-4i18n |
| `components/grade-record-form.tsx` | 177 | 录入表单v2-P2-7 修复Label htmlForv2-P1-4i18nP3 修复safeActionCall 包装提交) |
| `components/batch-grade-entry.tsx` | 500+ | 批量录入(v2-P2-7 修复:Label htmlForv2-P1-4i18nP3 修复:safeActionCall + localStorage 安全检查 + 区分未录入与录入 0v3-P2 新增:接入服务端草稿 saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftActionv3-P3-1 新增:下载 CSV 录入模板按钮含学生姓名列表v4-P3-2 新增可折叠新手引导提示框localStorage 记住关闭状态 |
| `components/batch-grade-entry.tsx` | 600+ | 2026-06-24 完全重写:导出名改为 BatchGradeEntryByExam按试卷录入每题得分。Excel 式表格(行=学生,列=题目,末列=总分自动计算)。交互:试卷选择器(按 scope 过滤)→ 班级选择器(按试卷 gradeId 过滤);多行多列 Excel 粘贴Tab 分隔Enter 跳下一行同一列Tab 跳下一格;实时统计(已录入/总数/均分/最高/最低分数校验超过题目满分标红撤销机制sessionStorage 5 分钟有效。Props: exams/classes/classGradeMap/exam/students/defaultExamId?/defaultClassId?(原 v2-P2-7 Label htmlForv2-P1-4 i18nP3 safeActionCall + localStorage 安全检查v3-P2 服务端草稿v3-P3-1 下载 CSV 模板v4-P3-2 新手引导均已替换 |
| `components/grade-filters.tsx` | 76 | 过滤器v2-P1-4i18n |
| `components/student-grade-summary.tsx` | 107 | 学生成绩摘要v2-P1-4i18n |
| `components/export-button.tsx` | 79 | 导出按钮v2-P1-4i18nP3 修复safeActionCall 包装导出操作) |
@@ -1632,21 +1634,21 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
## 2.23 settings设置模块— V3 AI 配置统一入口
**职责**:系统设置(学校信息/安全策略/文件上传/通知配置)+ AI Provider 管理 + 密码修改 + 个人资料 + 主题偏好 + 通知偏好 + 个人信息页(学生/教师概览。V3 将 AI Provider 配置统一到 `/admin/ai-settings` 独立页面,移除 `/settings?tab=ai` 标签页和考试页面内嵌弹窗。
**职责**:系统设置(学校信息/安全策略/文件上传/通知配置)+ AI Provider 管理 + 密码修改 + 个人资料 + 主题偏好 + 通知偏好 + 个人信息页(学生/教师概览。V3 将 AI Provider 配置统一到 `/admin/ai-settings` 独立页面,移除 `/settings?tab=ai` 标签页和考试页面内嵌弹窗。V3.1 新增 public/private 可见性模型:管理员发布 public Provider 全员可用,普通用户可创建 private Provider 仅本人可见。
**导出函数**
- Actions`getAiProviderSummaries` / `upsertAiProviderAction` / `testAiProviderAction` / `deleteAiProviderAction`V3 新增删除能力
- Actions`getAiProviderSummaries`V3.1 增强:按用户身份过滤 public + own private/ `upsertAiProviderAction`V3.1 增强visibility 字段,非管理员强制 private/ `testAiProviderAction` / `deleteAiProviderAction`V3 新增删除能力)/ `canConfigurePublicAiProvider`V3.1 新增:检查当前用户是否拥有 AI_CONFIGURE 权限
- Actions-password`changePasswordAction`(✅ P1 已修复:使用 `requirePermission(USER_PROFILE_UPDATE)` + Zod 校验 + DB 操作下沉到 data-access
- Actions-avatar`updateUserAvatarAction` / `removeUserAvatarAction`(✅ P2-8 新增:头像上传/删除,复用 `/api/upload` 路由)
- Actions-notifications`sendTestNotificationAction`(✅ P2-10 新增:发送测试通知,占位实现待接入真实通知服务)
- Actions-system-settings`getAdminSystemSettingsAction` / `saveAdminSystemSettingsAction`(✅ P0-3 新增:管理员系统设置 CRUD4 分类 Zod 校验)
- Actions-security`getSecurityCenterAction` / `toggleTwoFactorAction`(✅ P2-9 新增2FA 状态查询/切换 + 最近登录历史)
- Data-access`getAiProviderSummaries` / `countDefaultAiProviders` / `getAiProviderForUpdate` / `updateAiProvider` / `createAiProvider` / `deleteAiProvider`V3 新增:事务删除 + 自动转移默认)/ `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange`P1 新增,从 actions 下沉)
- Data-access`getAiProviderSummaries`(管理员视图,返回全部)/ `getAiProviderSummariesForUser`V3.1 新增:用户视图,返回 public + own private/ `countDefaultAiProviders` / `getAiProviderForUpdate`V3.1 增强:可选 userId 做所有权校验)/ `updateAiProvider`V3.1 增强visibility 字段)/ `createAiProvider`V3.1 增强visibility 字段)/ `deleteAiProvider`V3 新增:事务删除 + 自动转移默认V3.1 增强:可选 userId 做所有权校验/ `getUserPasswordHash` / `getPasswordSecurityByUserId` / `updateUserPassword` / `upsertPasswordSecurityOnPasswordChange`P1 新增,从 actions 下沉)
- Data-access-system-settings`getSystemSettingsByCategory` / `getAllSystemSettings` / `getSystemSetting` / `upsertSystemSetting` / `upsertSystemSettings`(✅ P0-3 新增system_settings 表 CRUD键值对存储模式
- Components`SettingsView`统一设置页布局V3 移除 AI 标签页后为 4 标签页 General/Notifications/Appearance/Security角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入Tab URL 持久化;每个 TabsContent 包裹 `SettingsSectionErrorBoundary` + `Suspense` 骨架屏)、`SettingsServiceProvider` / `useSettingsService`Context 注入 `SettingsService` 接口,解耦组件对 users/messaging actions 的直接依赖)、`SettingsSectionErrorBoundary`(分区 Error Boundary局部失败不影响整页`QuickLinksCard`快捷链接卡片i18n 键驱动)、`ProfileStudentOverview` / `ProfileStudentOverviewSkeleton`(学生概览异步 Server Component + 骨架屏)、`ProfileTeacherOverview` / `ProfileTeacherOverviewSkeleton`(教师概览异步 Server Component + 骨架屏)、`AdminSettingsView`(✅ P0-3 已修复:从 mock 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表)、`AvatarUpload`(✅ P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18n`SecurityCenterCard`(✅ P2-9 新增2FA 开关 + 最近登录历史卡片)、`ThemePreferencesCard`(✅ P2-11 已增强:集成 `LocaleSwitcher` 语言切换)、`AiProviderSettingsCard`V3 增强:新增删除按钮 + AlertDialog 确认,统一在 `/admin/ai-settings` 页面渲染
- Components`SettingsView`统一设置页布局V3 移除 AI 标签页后为 4 标签页 General/Notifications/Appearance/Security角色差异通过 `resolveRoleSettingsConfig` 配置驱动 + `generalExtra` props 注入Tab URL 持久化;每个 TabsContent 包裹 `SettingsSectionErrorBoundary` + `Suspense` 骨架屏)、`SettingsServiceProvider` / `useSettingsService`Context 注入 `SettingsService` 接口,解耦组件对 users/messaging actions 的直接依赖)、`SettingsSectionErrorBoundary`(分区 Error Boundary局部失败不影响整页`QuickLinksCard`快捷链接卡片i18n 键驱动)、`ProfileStudentOverview` / `ProfileStudentOverviewSkeleton`(学生概览异步 Server Component + 骨架屏)、`ProfileTeacherOverview` / `ProfileTeacherOverviewSkeleton`(教师概览异步 Server Component + 骨架屏)、`AdminSettingsView`(✅ P0-3 已修复:从 mock 改为真实数据层,通过 Server Actions 加载/保存到 system_settings 表)、`AvatarUpload`(✅ P2-8 新增:头像上传/预览/删除客户端组件,文件验证 + i18n`SecurityCenterCard`(✅ P2-9 新增2FA 开关 + 最近登录历史卡片)、`ThemePreferencesCard`(✅ P2-11 已增强:集成 `LocaleSwitcher` 语言切换)、`AiProviderSettingsCard`V3 增强:新增删除按钮 + AlertDialog 确认V3.1 增强visibility 选择器 + 可见性/归属 BadgeisAdmin prop 控制公开选项currentUserId prop 标识"我的"
- Config`ROLE_SETTINGS_CONFIG` / `resolveRoleSettingsConfig`(配置驱动角色 → 设置视图映射,新增角色只需添加条目)
- Lib`buildStudentOverviewData` / `computeStudentStats` / `sortUpcomingAssignments` / `filterTodaySchedule` / `toWeekday`(纯数据计算函数,与 UI 分离,便于单元测试)
- Types`AiProviderSummary` / `AiProviderName` / `AiProviderExisting` / `SettingsService` / `ProfileService` / `NotificationPreferenceService`(服务接口定义,用于依赖注入解耦)
- Types`AiProviderSummary`V3.1 增强:新增 visibility/createdBy 字段)/ `AiProviderName` / `AiProviderVisibility`V3.1 新增:'public' | 'private'/ `AiProviderExisting`V3.1 增强:新增 visibility/createdBy 字段)/ `SettingsService` / `ProfileService` / `NotificationPreferenceService`(服务接口定义,用于依赖注入解耦)
**依赖关系**
- 依赖:`shared/*`(含 `shared/lib/bcrypt-utils`)、`@/auth``messaging`(页面层通过 `SettingsService` 接口注入,组件层不直接 import`users`(页面层通过 `SettingsService` 接口注入)、`classes` / `homework` / `dashboard`ProfileStudentOverview 异步组件获取学生概览数据)、`notifications`(页面层获取通知偏好)
@@ -1821,7 +1823,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
- `FlowEdge`:流程连线(教学节点 → 教学节点)
**导出函数**
- Data-access`data-access.ts``getLessonPlans` / `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument`v3 规范化,兼容 v1/v2 旧数据)/ `buildDefaultSkeleton`v3 默认 10 节点骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById` / `publishLessonPlan`V3 新增,设置 status=published/ `unpublishLessonPlan`V3 新增,设置 status=draft仅 published 课案)
- Data-access`data-access.ts``getLessonPlans`V4查询后按 textbookId+chapterId+creatorId 聚合版本,返回代表项 + versionCount + versions 摘要数组)/ `getLessonPlanById` / `createLessonPlan` / `updateLessonPlanContent` / `softDeleteLessonPlan` / `duplicateLessonPlan` / `getTemplateById` / `buildInitialContent` / `migrateV1ToV2` / `normalizeDocument`v3 规范化,兼容 v1/v2 旧数据)/ `buildDefaultSkeleton`v3 默认 10 节点骨架)/ `getTextbooksForPicker` / `getChaptersForPicker` / `findChapterById` / `publishLessonPlan`V3 新增,设置 status=published/ `unpublishLessonPlan`V3 新增,设置 status=draft仅 published 课案)
- Lib`lib/document-migration.ts``defaultDataForType` / `migrateV1ToV2` / `migrateV2ToV3` / `normalizeDocument` / `buildInitialContent` / `buildDefaultSkeleton` / `isTextbookContentNode` / `isAnchorEdge` / `getAnchorsForNode` / `getActiveAnchorIds` / `getAnchorEdges`
- Lib`lib/anchor-injector.ts``markdownToPlainText` / `injectPlaceholders` / `parseAnchoredText` / `toCircledNumber` / `getNextPointIndex` / `relocateAnchors` / `getAnchorColor`
- Lib`lib/node-summary.ts``getNodeSummary` / `getTextbookContentSummary` / `getNodeColor` / `NODE_COLORS`
@@ -1876,53 +1878,82 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
> - **V3-9 新增路由**`/student/lesson-plans`、`/student/lesson-plans/[planId]/view`、`/parent/lesson-plans`、`/parent/lesson-plans/[planId]/view`、`/admin/lesson-plans`、`/admin/lesson-plans/[planId]/view`
> - **V3-10 导航变更**admin 导航新增「课案管理」(/admin/lesson-plansstudent 导航新增「我的课案」(/student/lesson-plansparent 导航新增「孩子课案」(/parent/lesson-plans
> 架构变更2026-06-24V3 深度审计修复):
> - **P0-1 admin 直查 DB 修复**`admin/lesson-plans/page.tsx` 移除 `import { db }` 和 `import { lessonPlans }`,改用 data-access 新增的 `getLessonPlanStats()` 函数(返回 total/published/draft/archived 统计)
> - **P0-2 publish-service 硬编码中文 + as 断言修复**`publish-service.ts` 移除 `as ExerciseBlockData`/`as typeof validTypes[number]` 断言,改用 `lib/type-guards.ts` 的 `isExerciseBlockData`/`isValidQuestionType` 类型守卫;移除硬编码中文标题/描述,改为接受 `homeworkTitle`/`homeworkDescription` 参数由 actions 层 i18n 翻译后传入
> - **P0-3 duplicateLessonPlan 硬编码修复**`duplicateLessonPlan` 接受 `duplicateSuffix` 参数(默认 " - Copy"),由 actions 层传入 `t("error.duplicateSuffix")`
> - **P0-4 schema Zod 错误消息 i18n 化**`schema.ts` 所有错误消息从硬编码中文改为 i18n 键(如 `error.titleRequired`);新增 `lib/i18n-errors.ts``translateFieldErrors`/`safeParseWithI18n`)在 actions 层翻译 Zod 错误
> - **P0-5 loading/error 边界补全**:为 6 个路由新增 12 个 `loading.tsx` 和 `error.tsx` 文件teacher/lesson-plans、teacher/lesson-plans/new、teacher/lesson-plans/[planId]/edit、admin/lesson-plans、admin/lesson-plans/[planId]/view、student/lesson-plans、student/lesson-plans/[planId]/view、parent/lesson-plans、parent/lesson-plans/[planId]/view
> - **P1-2 组件完全通过 service 调用**`lesson-plan-card.tsx`/`lesson-plan-list.tsx`/`lesson-plan-editor.tsx` 移除所有直接 `import { xxxAction } from "../actions"`,改为通过 `useLessonPlanContextSafe()` 获取 service 调用;`LessonPlanDataService` 接口扩展 5 个方法getLessonPlanById/updateLessonPlan/saveLessonPlanVersion/publishLessonPlan/unpublishLessonPlan`default-data-service.ts` 实现扩展
> - **P1-3 as 断言修复**:新增 `lib/type-guards.ts` 集中类型守卫11 种 BlockData 类型守卫 + 节点/题目类型守卫);`block-registry.tsx` 所有 `as XxxBlockData` 替换为类型守卫;`node-edit-panel.tsx` 移除冗余 `as LessonPlanNode`TypeScript 判别联合自动收窄);`node-editor.tsx` MiniMap `nodeColor` 使用类型守卫替代 `as { node?: AnyLessonPlanNode }``rf-mappers.ts` 移除冗余 `as TextbookContentNode`/`as LessonPlanNode``use-lesson-plan-editor.ts` `updateNode` patch 类型改为 `Omit<Partial<Block>, "type">` 防止类型变更
> - **P1-4 MiniMap 颜色硬编码修复**`lesson-plan-readonly-view.tsx` MiniMap 移除硬编码 `#455a64`/`#1976d2`,改用 `getNodeColor(nodeData.type)` 与编辑器保持一致
> - **i18n 错误码扩展**`zh-CN/lesson-preparation.json` 和 `en/lesson-preparation.json` 新增 `publish.homeworkTitle`/`publish.homeworkDescription`/`error.invalidQuestionType`/`error.duplicateSuffix`/`error.titleRequired`/`error.titleTooLong`/`error.templateRequired`/`error.invalidDate`/`error.classRequired` 等键
> 架构变更2026-06-24V3 深度审计修复续):
> - **P0 硬编码中文修复6处**`actions.ts` 的 `getLessonPlanByIdAction`/`revertLessonPlanVersionAction` 错误消息改为 i18n 键(`t("error.notFound")`/`t("error.versionNotFound")``data-access-versions.ts` 的 `revertToVersion` 接受 `revertLabel` 参数替代硬编码 `` `回退到 v${versionNo}` ```lesson-plan-error-boundary.tsx` 重写为包装组件模式(内部类组件接受 `errorText`/`retryText` props外部函数组件通过 `useTranslations` 注入 i18n 文案);`lesson-plan-provider.tsx` 的 `useLessonPlanContext` 错误消息改为英文
> - **P1 组件完全通过 service 调用5个组件迁移**`version-history-drawer.tsx`/`template-picker.tsx`/`knowledge-point-picker.tsx`/`publish-homework-dialog.tsx`/`question-bank-picker.tsx` 移除所有直接 `import { xxxAction } from "../actions"`,改用 `useLessonPlanContextSafe()` 获取 service 调用;`LessonPlanDataService` 接口扩展 7 个方法createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions+ 6 个导出类型TextbookPickerOption/ChapterPickerOption/KnowledgePointOption/PublishHomeworkInput/QuestionPickerParams/QuestionPickerItem`default-data-service.ts` 实现扩展;新增 `providers/lesson-plan-provider-setup.tsx`(页面层 Provider 设置包装组件自动注入默认数据服务和角色配置3 个页面teacher/lesson-plans/page、teacher/lesson-plans/new/page、teacher/lesson-plans/[planId]/edit/page用 `LessonPlanProviderSetup` 包裹;`question-bank-picker.tsx` 新增 `isQuestionType` 类型守卫替代 `as QuestionType` 断言
> - **P2 文件拆分**`textbook-content-node.tsx` 从 578 行拆分为 3 个文件——主文件471行+ `anchor-node-selector.tsx`AnchorNodeSelector 组件62行+ `textbook-segments.tsx`renderSegments 函数75行
> - **P3 as 断言修复8处**`textbook-content-node.tsx` 的 `as unknown as TextbookContentNodeProps` 替换为 `isTextbookContentNodePropsData` 类型守卫7 个 block 组件blackboard/import/exercise/objective/key-point/homework/reflection的 select onChange `as` 断言替换为类型守卫——`lib/type-guards.ts` 新增 7 个字段值类型守卫isBlackboardLayout/isImportMethod/isExercisePurpose/isObjectiveDimension/isKeyPointType/isHomeworkType/isReflectionAspect
**文件清单**
| 文件 | 职责 |
|------|------|
| `types.ts` | 类型定义(含 v1/v2/v3 文档类型、TextbookContentNode、LessonPlanNode、NodeAnchor、AnchorEdge、FlowEdge、11 种 BlockData 接口) |
| `constants.ts` | 常量定义 |
| `schema.ts` | Zod 验证 |
| `schema.ts` | Zod 验证V3错误消息改为 i18n 键,如 `error.titleRequired` |
| `lib/type-guards.ts` | **集中类型守卫V3 新增)**11 种 BlockData 类型守卫isRichTextBlockData/isTextStudyBlockData/isExerciseBlockData/isObjectiveBlockData/isKeyPointBlockData/isImportBlockData/isNewTeachingBlockData/isSummaryBlockData/isHomeworkBlockData/isBlackboardBlockData/isReflectionBlockData+ 节点类型守卫isTextbookContentNode/isLessonPlanNode+ 题目类型守卫isValidQuestionType+ 基础类型守卫isLessonPlanStatus/isTemplateType/isTemplateScope/isBlockType+ **Block 字段值类型守卫V3 续新增)**isBlackboardLayout/isImportMethod/isExercisePurpose/isObjectiveDimension/isKeyPointType/isHomeworkType/isReflectionAspect用于 select onChange 替代 `as` 断言) |
| `lib/i18n-errors.ts` | **Zod 错误 i18n 翻译辅助V3 新增)**`translateFieldErrors`(将 Zod fieldErrors 中的 i18n 键翻译为实际消息)/ `safeParseWithI18n`(安全解析 Zod 结果并返回带翻译的 ActionState 错误格式) |
| `lib/document-migration.ts` | **纯函数**v1→v2migrateV1ToV2/ v2→v3migrateV2ToV3/ 规范化normalizeDocument兼容 v1/v2/v3/ 初始内容buildInitialContent/ 默认骨架buildDefaultSkeleton10 节点 + 正文节点)/ defaultDataForType / 工具函数isTextbookContentNode/isAnchorEdge/getAnchorsForNode/getActiveAnchorIds/getAnchorEdges |
| `lib/anchor-injector.ts` | **纯函数**锚点注入算法markdownToPlainText/injectPlaceholders/parseAnchoredText/toCircledNumber/getNextPointIndex/relocateAnchors/getAnchorColor |
| `lib/node-summary.ts` | **纯函数**getNodeSummary支持 11 种节点类型)+ getTextbookContentSummary + NODE_COLORS + getNodeColor |
| `lib/rf-mappers.ts` | **纯函数**toRfNodes支持 textbook_content 节点 + 锚点回调V3ctx 新增 anchorableNodes/onCreateNewNode 字段)/ toRfEdges区分 anchor/flow 边透明度V3锚点边颜色使用 getNodeColor(anchor.nodeId) 替代硬编码anchorId 存入 edge.data/ fromRfEdgesV3从 e.data.anchorId 读取 anchorId回退到 className 判断) |
| `config/block-registry.tsx` | **配置驱动**BLOCK_REGISTRY 注册表 + BlockRendererswitch 渲染 11 种定制节点 + textbook_content |
| `providers/lesson-plan-provider.tsx` | **Provider + ContextP1-5/P1-7/P2-4/V2-6**LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置TEACHER/ADMIN/STUDENT/PARENT、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTrackerhooksuseLessonPlanContextSafe(返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafeV2-6 新增,返回 noopTracker 不抛错) |
| `services/default-data-service.ts` | **默认数据服务实现**createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现,测试可替换为 mock |
| `data-access.ts` | 课案 CRUD + 模板查询migrateV1ToV2/normalizeDocument/buildInitialContent 从 lib/ 导入并 re-export 保持向后兼容buildScopeCondition 按 scope 类型精确过滤 P0-3V2-1抛出 `LessonPlanDataError` 错误码V2-3mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫V3新增 publishLessonPlan/unpublishLessonPlan 函数) |
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理V2-3mapRowToVersion 显式映射 |
| `data-access-templates.ts` | 个人模板 CRUDV2-3mapRowToTemplate 显式映射 + 类型守卫 |
| `data-access-knowledge.ts` | 按知识点/题目反查课案V2-3显式字段映射替代 `as unknown as` |
| `actions.ts` | 课案 CRUD/版本/模板 Server ActionsV2-1getTranslations i18n + 错误码捕获V2-2createLessonPlanAction 传入 translateTitle 翻译 SYSTEM_TEMPLATES |
| `actions-publish.ts` | 发布作业 Server ActionV2-1getTranslations i18n + PUBLISH_ERROR_KEY_MAP 错误码映射V3新增 publishLessonPlanAction/unpublishLessonPlanActionrequirePermission(LESSON_PLAN_PUBLISH) |
| `actions-ai.ts` | AI 知识点建议 Server ActionV2-1i18n + 错误码) |
| `actions-kp.ts` | 知识点选项 Server ActionV2-1i18n + 错误码) |
| `publish-service.ts` | 发布作业服务(编排 homework/exams/classes通过对方 data-access 调用无直查跨模块表V2-1抛出 `PublishServiceError` 错误码V2-3显式字段映射替代 `as unknown as` |
| `lib/rf-mappers.ts` | **纯函数**toRfNodesV3移除冗余 `as` 断言TypeScript 判别联合自动收窄)/ toRfEdges / fromRfEdges |
| `config/block-registry.tsx` | **配置驱动**BLOCK_REGISTRY 注册表 + BlockRendererV3使用 `lib/type-guards.ts` 类型守卫替代所有 `as XxxBlockData` 断言 |
| `providers/lesson-plan-provider.tsx` | **Provider + Context**LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口V3扩展 5 个方法 getLessonPlanById/updateLessonPlan/saveLessonPlanVersion/publishLessonPlan/unpublishLessonPlan**V3 续扩展 7 个方法 createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions + 6 个导出类型**、4 个角色配置、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTrackerhooksuseLessonPlanContextSafe/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe |
| `providers/lesson-plan-provider-setup.tsx` | **页面层 Provider 设置包装组件V3 续新增)**LessonPlanProviderSetup 自动注入默认数据服务createDefaultDataService和角色配置TEACHER_ROLE_CONFIG3 个 teacher 页面用此组件包裹 |
| `services/default-data-service.ts` | **默认数据服务实现**createDefaultDataService() 包装 Server Actions 为 LessonPlanDataService 实现V3实现扩展的 5 个方法;**V3 续扩展 7 个方法 createLessonPlan/getTextbooksForPicker/getChaptersForPicker/getLessonPlanTemplates/getKnowledgePointOptions/publishLessonPlanHomework/getQuestions**),测试可替换为 mock |
| `data-access.ts` | 课案 CRUD + 模板查询V3新增 `getLessonPlanStats()` 统计函数 + `LessonPlanStats` 接口;`duplicateLessonPlan` 接受 `duplicateSuffix` 参数消除硬编码 |
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理 |
| `data-access-templates.ts` | 个人模板 CRUD |
| `data-access-knowledge.ts` | 按知识点/题目反查课案 |
| `actions.ts` | 课案 CRUD/版本/模板 Server ActionsV3所有 action 使用 `translateFieldErrors` 翻译 Zod 错误;`duplicateLessonPlanAction` 传入 i18n 翻译的副本后缀;`getLessonPlanByIdAction` 返回类型改为 `ActionState<{ plan: LessonPlan }>` |
| `actions-publish.ts` | 发布作业 Server ActionV3actions 层注入 i18n 翻译的作业标题/描述/日期标签;新增 `INVALID_QUESTION_TYPE` 错误码映射 |
| `actions-ai.ts` | AI 知识点建议 Server Action |
| `actions-kp.ts` | 知识点选项 Server Action |
| `publish-service.ts` | 发布作业服务V3移除 `as` 断言改用类型守卫;移除硬编码中文,接受 `homeworkTitle`/`homeworkDescription` 参数;新增 `INVALID_QUESTION_TYPE` 错误码) |
| `ai-suggest.ts` | AI 知识点建议服务 |
| `seed-templates.ts` | 模板种子数据 |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 Hook基于 zustand支持 nodes/edges/anchors 操作addNode/updateNode/updateNodePosition/removeNode/connect/disconnect/setEdges/selectNode + 锚点操作 addAnchor/removeAnchor/updateAnchor + 正文节点操作 updateTextbookContent/getTextbookContentNode实时拖动 |
| `components/lesson-plan-list.tsx` | 课案列表(i18n 已接入V3新增 viewMode prop支持 teacher/student/parent/admin/gradeHead 多角色视图 |
| `components/lesson-plan-card.tsx` | 课案卡片(i18n 已接入V2-6duplicate/archive 调用 tracker.trackV3新增 viewMode propteacher/student/parent/admin/gradeHead动态跳转链接 + 发布/撤回按钮 |
| `components/lesson-plan-filters.tsx` | 课案筛选器i18n 已接入V2-53 个表单元素 label htmlFor 关联) |
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPaneli18n 已接入V2-6handleManualSave 调用 tracker.trackV3顶部工具栏显示教材/章节标题指示器V3新增 initialStatus prop + 发布/撤回按钮AlertDialog 确认) |
| `components/lesson-plan-readonly-view.tsx` | **只读画布组件**V3 新增):复用 React FlownodesDraggable=false, nodesConnectable=false供学生/家长/管理员/教研组长查看已发布课案 |
| `components/node-editor.tsx` | **节点图画布**React Flow使用 lib/rf-mappers + lib/node-summary 纯函数i18n 已接入V2-4MiniMap 复用 getNodeColorV2-5role=application + 键盘导航配置V3注册 textbook_content 节点类型 + 锚点回调 + 实时拖动 |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**配置驱动渲染 Block通过 BlockRenderer + LessonPlanErrorBoundary 包裹i18n 已接入V3处理 textbook_content 节点教学节点类型收窄V3选中 textbook_content 时显示操作提示 + 锚点列表(含删除功能) |
| `components/nodes/lesson-node.tsx` | **自定义教学节点组件**(使用 lib/node-summary 的 getNodeSummary/getNodeColori18n 已接入) |
| `components/nodes/textbook-content-node.tsx` | **正文节点组件**V3 新增ReactMarkdown 渲染正文 + 锚点注入 + 文本选择range 锚定)+ 点击位置point 锚定)+ 缩放控制 + 锚点浮动菜单V3新增 props `anchorableNodes`, `onCreateNewNode`AnchorNodeSelector 重写为节点列表+创建新节点选项 |
| `components/lesson-plan-error-boundary.tsx` | **错误边界**LessonPlanErrorBoundary 类组件,支持 fallback 和 onError 回调 |
| `hooks/use-lesson-plan-editor.ts` | 课案编辑器 HookV3`updateNode` patch 类型改为 `Omit<Partial<Block>, "type">` 防止类型变更 |
| `components/lesson-plan-list.tsx` | 课案列表(V3完全通过 service 调用,移除直接 import `getLessonPlansAction` |
| `components/lesson-plan-card.tsx` | 课案卡片(V3完全通过 service 调用,移除直接 import actions |
| `components/lesson-plan-filters.tsx` | 课案筛选器 |
| `components/lesson-plan-editor.tsx` | 课案编辑器(V3完全通过 service 调用,移除直接 import 5 个 actions所有操作改为 `service.updateLessonPlan`/`service.saveLessonPlanVersion`/`service.getLessonPlanById`/`service.publishLessonPlan`/`service.unpublishLessonPlan` |
| `components/lesson-plan-readonly-view.tsx` | **只读画布组件**V3MiniMap 使用 `getNodeColor` 替代硬编码颜色) |
| `components/node-editor.tsx` | **节点图画布**V3MiniMap `nodeColor` 使用类型守卫替代 `as { node?: AnyLessonPlanNode }` 断言 |
| `components/node-edit-panel.tsx` | **侧边内容编辑面板**V3移除冗余 `as LessonPlanNode` 断言TypeScript 判别联合自动收窄 |
| `components/nodes/lesson-node.tsx` | **自定义教学节点组件** |
| `components/nodes/textbook-content-node.tsx` | **正文节点组件**V3 续:拆分为 3 文件,主文件 471 行;`as unknown as` 替换为 `isTextbookContentNodePropsData` 类型守卫) |
| `components/nodes/anchor-node-selector.tsx` | **锚点节点选择器V3 续新增,从 textbook-content-node.tsx 抽取)**:渲染可锚定教学节点列表 + 关联到选中节点 + 创建新节点选项 |
| `components/nodes/textbook-segments.tsx` | **锚点段落渲染函数V3 续新增,从 textbook-content-node.tsx 抽取)**renderSegments 遍历 segments 数组渲染文本/区间锚点/点锚点 |
| `components/lesson-plan-error-boundary.tsx` | **错误边界**V3 续:重写为包装组件模式——内部类组件接受 `errorText`/`retryText` props外部函数组件通过 `useTranslations` 注入 i18n 文案) |
| `components/lesson-plan-skeleton.tsx` | **骨架屏**VersionListSkeleton/QuestionBankSkeleton/KnowledgePointSkeleton/LessonPlanListSkeleton |
| `components/block-renderer.tsx` | ⚠️ @deprecated Block 渲染器(已被 NodeEditor 替代,保留向后兼容) |
| `components/template-picker.tsx` | 模板选择器(i18n 已接入V2-6create 调用 tracker.trackV3加载并显示个人模板调用 getLessonPlanTemplatesAction分区显示系统/个人模板 |
| `components/version-history-drawer.tsx` | 版本历史抽屉(i18n 已接入V2-6revert 调用 tracker.track |
| `components/knowledge-point-picker.tsx` | 知识点选择器(i18n 已接入 |
| `components/question-bank-picker.tsx` | 题库选择器(i18n 已接入 |
| `components/inline-question-editor.tsx` | 内联题目编辑器i18n 已接入V2-5type/difficulty select label htmlFor 关联) |
| `components/publish-homework-dialog.tsx` | 发布作业对话框(i18n 已接入V2-6publish 调用 tracker.track |
| `components/blocks/rich-text-block.tsx` | 富文本 Block(被 NodeEditPanel 复用i18n 已接入) |
| `components/blocks/text-study-block.tsx` | 课文研读 Block(被 NodeEditPanel 复用i18n 已接入) |
| `components/blocks/exercise-block.tsx` | 练习 Block被 NodeEditPanel 复用,使用 router.refresh 替代 window.location.reloadi18n 已接入V2-5purpose select label 关联 + 题目列表 ul/li 语义化 |
| `components/blocks/reflection-block.tsx` | 反思 Block被 NodeEditPanel 复用i18n 已接入 |
| `components/template-picker.tsx` | 模板选择器(V3 续:完全通过 service 调用,移除直接 import 4 个 actions |
| `components/version-history-drawer.tsx` | 版本历史抽屉(V3 续:完全通过 service 调用,移除直接 import 2 个 actions |
| `components/knowledge-point-picker.tsx` | 知识点选择器(V3 续:完全通过 service 调用,移除直接 import actions-kp |
| `components/question-bank-picker.tsx` | 题库选择器(V3 续:完全通过 service 调用,移除跨模块直接 import `@/modules/questions/actions`;新增 `isQuestionType` 类型守卫替代 `as QuestionType` |
| `components/inline-question-editor.tsx` | 内联题目编辑器 |
| `components/publish-homework-dialog.tsx` | 发布作业对话框(V3 续:完全通过 service 调用,移除直接 import actions-publish |
| `components/blocks/rich-text-block.tsx` | 富文本 Block |
| `components/blocks/text-study-block.tsx` | 课文研读 Block |
| `components/blocks/objective-block.tsx` | 教学目标 BlockV3 续select onChange 使用 `isObjectiveDimension` 类型守卫替代 `as` 断言 |
| `components/blocks/key-point-block.tsx` | 重难点 BlockV3 续select onChange 使用 `isKeyPointType` 类型守卫替代 `as` 断言 |
| `components/blocks/import-block.tsx` | 导入 BlockV3 续select onChange 使用 `isImportMethod` 类型守卫替代 `as` 断言) |
| `components/blocks/new-teaching-block.tsx` | 新授 Block |
| `components/blocks/summary-block.tsx` | 总结 Block |
| `components/blocks/homework-block.tsx` | 作业 BlockV3 续select onChange 使用 `isHomeworkType` 类型守卫替代 `as` 断言) |
| `components/blocks/blackboard-block.tsx` | 板书 BlockV3 续select onChange 使用 `isBlackboardLayout` 类型守卫替代 `as` 断言) |
| `components/blocks/exercise-block.tsx` | 练习 BlockV3 续select onChange 使用 `isExercisePurpose` 类型守卫替代 `as` 断言) |
| `components/blocks/reflection-block.tsx` | 反思 BlockV3 续select onChange 使用 `isReflectionAspect` 类型守卫替代 `as` 断言) |
---
@@ -1977,28 +2008,36 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
| 文件 | 行数 | 职责 |
|------|------|------|
| `actions.ts` | ~180 | 9 个 Server Actions全部使用 `requirePermission()` + `ActionState<T>` |
| `data-access.ts` | ~960 | 16 个数据访问函数 + 自动采集逻辑SM-2 算法已提取到独立模块) |
| `data-access.ts` | ~1280 | 19 个数据访问函数 + 自动采集逻辑SM-2 算法已提取到独立模块,支持 subjectId 过滤、章节维度、班级分组、学科概览 |
| `sm2-algorithm.ts` | ~180 | SM-2 间隔重复算法(独立纯函数模块,可替换,支持时间注入测试) |
| `sm2-algorithm.test.ts` | ~280 | SM-2 算法单元测试39 个测试用例,覆盖所有函数和边界条件) |
| `schema.ts` | ~60 | 4 个 Zod 验证 schema |
| `types.ts` | ~120 | 6 个类型定义 + 状态映射常量 + 错误标签常量 |
| `types.ts` | ~245 | 11 个类型定义 + 状态映射常量 + 错误标签常量(新增 ChapterWeakness/ClassErrorOverview/SubjectErrorOverview |
| `components/error-book-stats-cards.tsx` | ~80 | 5 个统计卡片(总数/待学习/学习中/已掌握/待复习) |
| `components/analytics-stats-cards.tsx` | ~110 | 教师/管理员分析视图 5 个统计卡片(覆盖学生/错题总数/平均掌握率/待复习/知识点数) |
| `components/error-book-filters.tsx` | ~100 | 筛选栏(搜索/状态/来源/待复习),使用 nuqs |
| `components/error-book-item-card.tsx` | ~150 | 错题卡片(预览/标签/笔记/掌握度/操作) |
| `components/review-buttons.tsx` | ~80 | 4 按钮复习面板again/hard/good/easy |
| `components/error-book-detail-dialog.tsx` | ~250 | 详情对话框(题目/答案/复习/笔记/历史) |
| `components/error-book-list.tsx` | ~60 | 网格列表 |
| `components/add-error-book-dialog.tsx` | ~180 | 手动添加对话框(题库选择 + 标签) |
| `components/class-error-overview.tsx` | ~200 | 班级错题概览(教师/管理员视图 |
| `components/subject-tabs.tsx` | ~95 | 学科切换 Tab显示每个学科错题概览URL 参数持久化 |
| `components/class-filter.tsx` | ~85 | 班级筛选器显示每个班级错题数和待复习数URL 参数持久化) |
| `components/class-error-bar-chart.tsx` | ~115 | 班级错题数对比柱状图rechartstooltip 显示学生数/人均/掌握率) |
| `components/subject-distribution-chart.tsx` | ~110 | 学科错题分布柱状图管理员视图recharts |
| `components/knowledge-point-weakness-chart.tsx` | ~150 | 知识点薄弱度横向柱状图(按掌握率红/黄/绿着色,显示章节归属) |
| `components/chapter-weakness-chart.tsx` | ~165 | 章节错题分布横向柱状图(哪些课在错,含 Top 3 薄弱知识点) |
| `components/grouped-student-error-table.tsx` | ~180 | 按班级分组的学生错题表格(可展开/折叠,显示每个学生的错题详情) |
| `components/class-error-overview.tsx` | ~200 | 班级错题概览(旧版,保留兼容) |
| `components/top-wrong-questions.tsx` | ~80 | 高频错题列表Top 10 |
**路由清单**
| 路由 | 文件 | 说明 |
|------|------|------|
| `/student/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 学生错题本(统计/筛选/列表/手动添加/详情复习) |
| `/teacher/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 教师错题分析(班级概览/薄弱知识点/学科分布/高频错题) |
| `/teacher/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 教师错题分析(学科Tab/班级筛选/统计卡片/班级对比图/章节错题图/知识点薄弱度图/按班级分组学生表/高频错题) |
| `/parent/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 家长错题本(子女错题统计/薄弱知识点/高频错题) |
| `/admin/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 管理员错题分析(全校错题统计/薄弱知识点/学科分布/高频错题) |
| `/admin/error-book` | `page.tsx` + `loading.tsx` + `error.tsx` | 管理员错题分析(学科Tab/统计卡片/学科分布图/章节错题图/知识点薄弱度图/按班级分组学生Top50/高频错题) |
**数据库表**
| 表 | 说明 |
@@ -2150,6 +2189,138 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
---
## 2.30 adaptive-practice专项练习模块— ✅ 新增(核心教学链路闭环)
**职责**:实现"错题集 → 知识点 → 专项出题 → 学生答题 → 自动判分 → 教师/年级主任宏观数据分析"的完整闭环。支持四种出题策略错题变式、知识点专项、薄弱章节、AI 推荐。提供教师端与年级主任端的宏观数据分析工作台。
**架构定位**
- 位于 `modules/` 层,严格遵循三层架构
- 通过 `data-access.ts` 提供 CRUD 操作,通过 `data-access-strategy.ts` 实现出题策略,通过 `data-access-analytics.ts` 提供教师/年级分析查询
- 跨模块通信:通过 `modules/error-book/data-access` 获取错题数据,通过 `modules/questions/data-access` 获取题目数据,通过 `modules/classes/data-access` 获取班级/学生数据,通过 `modules/users/data-access` 获取用户数据
- Server Actions 通过 `requirePermission()` 校验 `ADAPTIVE_PRACTICE_READ` / `ADAPTIVE_PRACTICE_MANAGE` 权限
**核心导出**
| 类型 | 名称 | 文件 | 说明 |
|------|------|------|------|
| **DB Schema** | `practiceSessions` | `shared/db/schema.ts` | 练习会话表(学生 ID/类型/状态/统计) |
| **DB Schema** | `practiceAnswers` | `shared/db/schema.ts` | 练习答题记录表(含变式题支持) |
| **Server Actions** | `getPracticeSessionsAction` | `modules/adaptive-practice/actions.ts` | 获取练习列表权限ADAPTIVE_PRACTICE_READ |
| **Server Actions** | `getPracticeSessionDetailAction` | `modules/adaptive-practice/actions.ts` | 获取练习详情权限ADAPTIVE_PRACTICE_READ |
| **Server Actions** | `getPracticeStatsAction` | `modules/adaptive-practice/actions.ts` | 获取练习统计权限ADAPTIVE_PRACTICE_READ |
| **Server Actions** | `createPracticeSessionAction` | `modules/adaptive-practice/actions.ts` | 创建练习会话权限ADAPTIVE_PRACTICE_MANAGE |
| **Server Actions** | `submitPracticeAnswerAction` | `modules/adaptive-practice/actions.ts` | 提交答案+自动判分权限ADAPTIVE_PRACTICE_MANAGE |
| **Server Actions** | `completePracticeSessionAction` | `modules/adaptive-practice/actions.ts` | 完成练习权限ADAPTIVE_PRACTICE_MANAGE |
| **Server Actions** | `abandonPracticeSessionAction` | `modules/adaptive-practice/actions.ts` | 放弃练习权限ADAPTIVE_PRACTICE_MANAGE |
| **Data Access** | `getPracticeSessions` / `getPracticeSessionById` | `data-access.ts` | 学生端 CRUD |
| **Data Access** | `createPracticeSession` | `data-access.ts` | 创建会话(事务:会话+答题记录) |
| **Data Access** | `submitPracticeAnswer` | `data-access.ts` | 提交答案+自动判分 |
| **Data Access** | `autoGradeAnswer` | `data-access.ts` | 自动判分逻辑(选择题/判断题自动,填空题返回 null |
| **Strategy** | `selectQuestionsForPractice` | `data-access-strategy.ts` | 出题策略主入口 |
| **Strategy** | `selectForErrorVariant` | `data-access-strategy.ts` | 错题变式策略 |
| **Strategy** | `selectForKnowledgePoint` | `data-access-strategy.ts` | 知识点专项策略 |
| **Strategy** | `selectForWeakChapter` | `data-access-strategy.ts` | 薄弱章节策略 |
| **Strategy** | `selectForAiRecommended` | `data-access-strategy.ts` | AI 推荐策略 |
| **Analytics** | `getClassPracticeStats` | `data-access-analytics.ts` | 班级练习统计 |
| **Analytics** | `getClassStudentPracticeSummaries` | `data-access-analytics.ts` | 班级学生练习摘要 |
| **Analytics** | `getGradePracticeStats` | `data-access-analytics.ts` | 年级练习统计 |
| **Analytics** | `getPracticeTypeBreakdown` | `data-access-analytics.ts` | 练习类型分布 |
| **Analytics** | `getStudentsWithoutPractice` | `data-access-analytics.ts` | 识别未参与练习学生 |
| **Analytics** | `getTeacherClassPracticeOverviews` | `data-access-analytics.ts` | 教师所教班级练习概览 |
| **Analytics** | `getClassKnowledgePointWeakness` | `data-access-analytics.ts` | 班级知识点薄弱度(基于练习答题) |
| **Analytics** | `getGradeClassPracticeComparison` | `data-access-analytics.ts` | 年级各班级练习对比 |
| **Analytics** | `getClassLearningProfile` | `data-access-analytics.ts` | 班级综合学习画像(跨模块整合) |
| **Analytics** | `getGradePracticeOverview` | `data-access-analytics.ts` | 年级综合练习统计 |
| **Component** | `PracticeStarter` | `components/practice-starter.tsx` | 练习发起器(四种模式选择) |
| **Component** | `PracticeSessionView` | `components/practice-session-view.tsx` | 答题界面(逐题作答+自动判分) |
| **Component** | `PracticeHistory` | `components/practice-history.tsx` | 练习历史列表 |
| **Component** | `PracticeStatsCards` | `components/practice-stats-cards.tsx` | 学生端统计卡片 |
| **Component** | `PracticeOverviewStatsCards` | `components/practice-overview-stats-cards.tsx` | 教师/年级端统计卡片 — 新增 |
| **Component** | `ClassPracticeComparisonTable` | `components/class-practice-comparison-table.tsx` | 班级练习对比表格 — 新增 |
| **Component** | `PracticeTypeBreakdownChart` | `components/practice-type-breakdown-chart.tsx` | 练习类型分布柱状图 — 新增 |
| **Component** | `ClassKnowledgePointWeaknessChart` | `components/class-knowledge-point-weakness-chart.tsx` | 知识点薄弱度柱状图 — 新增 |
| **Component** | `StudentPracticeRankingTable` | `components/student-practice-ranking-table.tsx` | 学生练习排名表格 — 新增 |
| **Component** | `InactiveStudentsAlert` | `components/inactive-students-alert.tsx` | 未参与练习学生提醒 — 新增 |
**集成点**
| 业务模块 | 集成组件 | 页面 | 说明 |
|---------|---------|------|------|
| error-book | `createPracticeSessionAction` | `student/error-book` 详情弹窗 | 错题详情页"发起变式练习"按钮 |
| student | `PracticeStarter` / `PracticeSessionView` | `student/practice` / `student/practice/[sessionId]` | 学生端练习入口与答题 |
| teacher | `PracticeOverviewStatsCards` 等 | `teacher/practice` | 教师端专项练习分析 — 新增 |
| management | `PracticeOverviewStatsCards` 等 | `management/grade/practice` | 年级主任宏观数据分析 — 新增 |
**依赖关系**
- `modules/adaptive-practice``shared/db`schema: practiceSessions/practiceAnswers/questions/knowledgePoints 等)
- `modules/adaptive-practice``shared/lib/auth-guard`(权限校验)
- `modules/adaptive-practice``shared/types/permissions`(权限常量)
- `modules/adaptive-practice``shared/types/action-state`(返回值类型)
- `modules/adaptive-practice``modules/classes/data-access`getActiveStudentIdsByClassId/getClassNameById/getClassesByGradeId/getClassIdsByGradeIds/getStudentIdsByClassIds
- `modules/adaptive-practice``modules/users/data-access`getUserIdsByGradeId/getUserNamesByIds
- `modules/adaptive-practice``modules/questions/data-access`(题目查询,出题策略使用)
- `modules/adaptive-practice``modules/error-book/data-access`(错题数据,错题变式策略使用)
- `app/(dashboard)/student/practice``modules/adaptive-practice`(学生端页面)
- `app/(dashboard)/teacher/practice``modules/adaptive-practice`(教师端分析页面)— 新增
- `app/(dashboard)/management/grade/practice``modules/adaptive-practice`(年级主任分析页面)— 新增
- `modules/error-book/components/error-book-detail-dialog``modules/adaptive-practice/actions`(变式练习入口)
**权限点**
- `ADAPTIVE_PRACTICE_READ`查看练习数据admin/teacher/student/parent/grade_head/teaching_head 均有)
- `ADAPTIVE_PRACTICE_MANAGE`:创建/提交/完成/放弃练习(仅 student 有)
**自动判分逻辑**
- `single_choice`:学生答案 ID 与正确答案 ID 完全匹配
- `multiple_choice`:学生答案 ID 集合与正确答案 ID 集合完全匹配(集合比对)
- `judgment`:学生布尔答案与正确布尔答案一致
- `text`(填空题):不自动判分,返回 `null`,待教师批阅
**出题策略**
- `error_variant`:按难度升序选取原题(确保 AI 不可用时也能练习)
- `knowledge_point`:按知识点+难度筛选,随机抽取
- `weak_chapter`:排除已答题目,避免重复
- `ai_recommended`:从 AI 推荐知识点抽题
**文件清单**
| 文件 | 行数 | 职责 |
|------|------|------|
| `modules/adaptive-practice/types.ts` | ~143 | 类型定义PracticeType/PracticeSourceMeta 联合类型等) |
| `modules/adaptive-practice/schema.ts` | ~75 | Zod 验证 schema |
| `modules/adaptive-practice/data-access.ts` | ~470 | CRUD 操作+自动判分逻辑 |
| `modules/adaptive-practice/data-access-strategy.ts` | ~310 | 四种出题策略实现 |
| `modules/adaptive-practice/data-access-analytics.ts` | ~630 | 教师/年级宏观数据分析查询 |
| `modules/adaptive-practice/actions.ts` | ~230 | 7 个 Server Actions含权限校验 |
| `modules/adaptive-practice/components/practice-starter.tsx` | ~270 | 练习发起器 |
| `modules/adaptive-practice/components/practice-session-view.tsx` | ~420 | 答题界面 |
| `modules/adaptive-practice/components/practice-history.tsx` | ~100 | 练习历史列表 |
| `modules/adaptive-practice/components/practice-stats-cards.tsx` | ~80 | 学生端统计卡片 |
| `modules/adaptive-practice/components/practice-overview-stats-cards.tsx` | ~95 | 教师/年级端统计卡片 — 新增 |
| `modules/adaptive-practice/components/class-practice-comparison-table.tsx` | ~95 | 班级练习对比表格 — 新增 |
| `modules/adaptive-practice/components/practice-type-breakdown-chart.tsx` | ~120 | 练习类型分布柱状图 — 新增 |
| `modules/adaptive-practice/components/class-knowledge-point-weakness-chart.tsx` | ~150 | 知识点薄弱度柱状图 — 新增 |
| `modules/adaptive-practice/components/student-practice-ranking-table.tsx` | ~130 | 学生练习排名表格 — 新增 |
| `modules/adaptive-practice/components/inactive-students-alert.tsx` | ~75 | 未参与练习学生提醒 — 新增 |
| `app/(dashboard)/student/practice/page.tsx` | - | 学生端练习列表页 |
| `app/(dashboard)/student/practice/[sessionId]/page.tsx` | - | 学生端答题页 |
| `app/(dashboard)/teacher/practice/page.tsx` | ~250 | 教师端专项练习分析页 — 新增 |
| `app/(dashboard)/teacher/practice/loading.tsx` | - | 教师端加载骨架 — 新增 |
| `app/(dashboard)/teacher/practice/error.tsx` | - | 教师端错误边界 — 新增 |
| `app/(dashboard)/management/grade/practice/page.tsx` | ~140 | 年级主任宏观数据分析页 — 新增 |
| `app/(dashboard)/management/grade/practice/loading.tsx` | - | 年级端加载骨架 — 新增 |
| `app/(dashboard)/management/grade/practice/error.tsx` | - | 年级端错误边界 — 新增 |
**i18n**
- 翻译文件:`shared/i18n/messages/{locale}/practice.json`
- 命名空间:`practice`
- 键:`starter.*``session.*``result.*``history.*``stats.*``types.*``status.*``teacher.*`(含 overview/classComparison/typeBreakdown/knowledgePointWeakness/studentRanking/inactiveStudents`grade.*`(含 overview/classComparison
**数据库表**
- `practice_sessions`练习会话表id/studentId/subjectId/practiceType/sourceMeta/status/totalQuestions/answeredQuestions/correctCount/startedAt/completedAt
- `practice_answers`练习答题记录表id/sessionId/studentId/questionId/variantContent/isVariant/orderIndex/status/studentAnswer/isCorrect/score/maxScore
---
# 第三部分:已知架构问题和技术债
## 3.1 P0 严重问题(必须立即修复)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,195 @@
# 备课模块使用问题修复设计
> **日期**2026-06-23
> **前置文档**[2026-06-22-lesson-preparation-anchor-canvas-design.md](./2026-06-22-lesson-preparation-anchor-canvas-design.md)
> **审计来源**:深度分析备课模块现状后发现的使用问题
## 一、问题背景
备课模块 v3 锚点画布重构已完成,但实际使用中存在 15 个问题3 个 P0 阻塞性 + 6 个 P1 严重 + 6 个 P2 一般),导致核心流程断裂、角色使用不完整。
### 核心闭环断裂
```
教师备课 → 发布课案 → 学生查看 → 家长查看
↑ ❌ P0-1 ❌ P0-2/3 ❌ P0-2/3
无"发布"action 无权限/路由 无权限/路由
```
## 二、功能定位
### 备课模块核心闭环
1. **教师备课**:选定教材+章节 → 锚点画布编辑 → 保存版本 → **发布**
2. **学生查看**:按班级可见 published 课案,只读画布视图
3. **家长查看**:通过孩子关系自动可见 published 课案
4. **教研监督**:教研组长/年级主任查看本年级教师备课(只读)
5. **管理员管理**:全校课案列表 + 统计
## 三、角色使用矩阵
| 角色 | 权限 | 路由 | 视图 |
|------|------|------|------|
| 教师 | CREATE/READ/UPDATE/DELETE/PUBLISH | `/teacher/lesson-plans` | 完整编辑画布 + 发布按钮 |
| 学生 | READ仅 published | `/student/lesson-plans/[planId]/view` | 只读画布视图 |
| 家长 | READ仅孩子的 published | `/parent/lesson-plans/[planId]/view` | 只读画布视图 |
| 管理员 | 全部 | `/admin/lesson-plans` | 全校课案列表 + 统计 |
| 教研组长 | READ本年级教师 | `/grade-head/lesson-plans` | 本年级教师课案列表(只读) |
| 年级主任 | READ本年级教师 | `/teaching-head/lesson-plans` | 同上 |
### 学生查看视图设计
**只读画布视图**:复用 React Flow配置
- `nodesDraggable={false}`
- `nodesConnectable={false}`
- `elementsSelectable={true}`(可点击查看节点详情)
- `panOnDrag={true}``zoomOnScroll={true}`(可缩放平移)
保留正文+节点+锚点的完整视觉关系,学生可缩放平移但不可编辑。
### 发布机制
- 发布到班级所有学生,家长通过孩子关系自动可见
- 状态流转:`draft``published`(发布)→ `draft`(撤回)或 `archived`(归档)
- 发布后学生/家长立即可见,撤回后立即不可见
## 四、修复范围P0+P1 优先)
### P0 阻塞性问题
| 编号 | 问题 | 修复方案 |
|------|------|----------|
| P0-1 | 无"发布课案"action | 新增 `publishLessonPlanAction` / `unpublishLessonPlanAction` |
| P0-2 | 学生/家长无权限 | 给 student/parent 添加 `LESSON_PLAN_READ` 权限 |
| P0-3 | 无学生/家长/管理员路由 | 新增路由 + 导航入口 |
### P1 严重问题
| 编号 | 问题 | 修复方案 |
|------|------|----------|
| P1-1 | 锚点节点选择器占位 | 完善 `AnchorNodeSelector`,接收节点列表 + 实现"锚定到新节点" |
| P1-2 | 锚点偏移计算不可靠 | 改用 `markdownToPlainText` 搜索定位,支持跨段落 |
| P1-3 | 边回写丢失 anchorId | anchorId 存入 `edge.data``fromRfEdges` 从 data 读取 |
| P1-4 | 锚点边颜色无区分 | 使用 `getNodeColor(anchor.nodeId)` |
| P1-5 | 教研组长无权限 | 给 grade_head/teaching_head 添加 `LESSON_PLAN_READ` 权限 |
| P1-6 | 个人模板不显示 | TemplatePicker 调用 `getLessonPlanTemplatesAction` 合并显示 |
### P2 一般问题(本次顺带修复)
| 编号 | 问题 | 修复方案 |
|------|------|----------|
| P2-1 | 正文节点侧边面板提示误导 | 改为"请在画布上直接操作正文"或显示锚点列表 |
| P2-5 | 默认骨架节点位置重叠 | 增大列间距,避免与正文节点重叠 |
## 五、实施阶段
### 阶段 1权限与路由基础P0-2、P0-3、P1-5
**权限修改**`shared/lib/permissions.ts`
- student 数组添加 `Permissions.LESSON_PLAN_READ`
- parent 数组添加 `Permissions.LESSON_PLAN_READ`
- grade_head 数组添加 `Permissions.LESSON_PLAN_READ`
- teaching_head 数组添加 `Permissions.LESSON_PLAN_READ`
**路由新增**
- `app/(dashboard)/student/lesson-plans/page.tsx` — 课案列表(按班级 published
- `app/(dashboard)/student/lesson-plans/[planId]/view/page.tsx` — 只读画布
- `app/(dashboard)/parent/lesson-plans/page.tsx` — 课案列表(按孩子 published
- `app/(dashboard)/parent/lesson-plans/[planId]/view/page.tsx` — 只读画布
- `app/(dashboard)/admin/lesson-plans/page.tsx` — 全校课案列表 + 统计
- `app/(dashboard)/admin/lesson-plans/[planId]/view/page.tsx` — 查看任意课案
**导航入口**`shared/lib/navigation.ts``modules/layout/config/navigation.ts`
- student 导航添加"我的课案"
- parent 导航添加"孩子课案"
- admin 导航添加"课案管理"
**只读画布组件**
- 新建 `components/lesson-plan-readonly-view.tsx`
- 复用 React Flow配置只读模式
- 复用 `toRfNodes` / `toRfEdges` 渲染
### 阶段 2发布机制P0-1
**data-access 新增**`data-access.ts`
```typescript
export async function publishLessonPlan(planId: string, userId: string): Promise<void>
export async function unpublishLessonPlan(planId: string, userId: string): Promise<void>
```
**actions 新增**`actions.ts`
```typescript
export async function publishLessonPlanAction(planId: string): Promise<ActionState<string>>
export async function unpublishLessonPlanAction(planId: string): Promise<ActionState<string>>
```
**UI 修改**
- `LessonPlanEditor` 工具栏添加"发布"/"撤回发布"按钮
- `LessonPlanCard` 添加"发布"/"撤回"操作菜单项
- 发布时显示确认对话框
### 阶段 3锚点交互完善P1-1、P1-2、P1-3、P1-4
**P1-1 完善 AnchorNodeSelector**
- 接收 `doc.nodes` 列表(过滤掉 textbook_content
- 渲染节点下拉选择器(显示节点标题 + 颜色标识)
- 实现"锚定到新节点":弹出节点类型选择 → 创建节点 → 自动锚定
**P1-2 修复锚点偏移计算**
- `handleMouseUp` 改用 `Range.toString()` 获取选中文本
-`markdownToPlainText(content)` 中搜索定位 start/end 偏移
- 支持跨段落、跨行内元素的选择
**P1-3 修复 fromRfEdges anchorId 丢失**
- `toRfEdges` 时将 `anchorId` 存入 `edge.data.anchorId`
- `fromRfEdges``e.data?.anchorId` 读取
**P1-4 修复锚点边颜色**
- `rf-mappers.ts` 导入 `getNodeColor`
- `toRfEdges``stroke: getNodeColor(anchor.nodeId)`
### 阶段 4模板与体验P1-6 + P2
**P1-6 TemplatePicker 显示个人模板**
- 初始化时调用 `getLessonPlanTemplatesAction()`
- 合并系统模板和个人模板展示
- 个人模板标记"个人"徽章
**P2-1 修复正文节点侧边面板**
- `NodeEditPanel` 选中 textbook_content 时显示锚点列表
- 或显示提示"请在画布上直接操作正文,点击正文选中文本可创建锚点"
**P2-5 优化默认骨架节点位置**
- 增大列间距,左列 x=80右列 x=900
- 正文节点居中 (500, 250)
## 六、数据模型变更
无数据模型变更。发布机制仅修改 `lessonPlans.status` 字段(已存在)。
## 七、i18n 键新增
### zh-CN / en grades 命名空间lesson-preparation.json
- `action.publish` / `action.unpublish`
- `action.publishConfirm` / `action.unpublishConfirm`
- `action.publishSuccess` / `action.unpublishSuccess`
- `status.published` / `status.draft` / `status.archived`
- `readonly.title` / `readonly.hint`
- `admin.title` / `admin.stats` / `admin.totalPlans` / `admin.publishedPlans`
- `gradeHead.title`
- `anchor.selectNode` / `anchor.createNode` / `anchor.nodeTypePrompt`
## 八、架构文档同步
- `004_architecture_impact_map.md`:补充路由、权限、新组件
- `005_architecture_data.json`:补充 routes、permissions、exports
## 九、验证标准
- `npx tsc --noEmit` 零错误
- `npm run lint` 零错误
- 教师可发布课案,学生/家长可查看 published 课案
- 教研组长可查看本年级教师备课
- 锚点交互完整可用(选中文本 → 选择节点 → 创建锚点)
- 锚点边颜色与关联节点一致