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:
406
bugs/teacher_web_test_post_audit.json
Normal file
406
bugs/teacher_web_test_post_audit.json
Normal file
File diff suppressed because one or more lines are too long
1673
bugs/teacher_web_test_post_audit.md
Normal file
1673
bugs/teacher_web_test_post_audit.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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 新增:年级仪表盘维度3,exams 表有直接 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 新增:年级仪表盘维度3,exams 表有直接 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-service(V3-8 新增):`getExamAnalytics`(cache 包装,聚合考试所有作业的已批改提交,计算平均分/及格率/分数段分布/逐题错误率与难度等级,对标智学网考试分析)+ `ExamAnalyticsSummary` 类型
|
||||
- Types(✅ 2026-06-24 新增成绩录入相关类型):`ExamQuestionItem`(试卷中单个题目的精简结构 { id, order, score, type })/ `ExamForGradeEntry`(成绩录入用的试卷详情,含题目列表)/ `ExamOptionForEntry`(成绩录入页试卷选择器选项 { id, title, subjectName, gradeName, questionCount, totalScore })
|
||||
- Components(V3-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-access,P0-4 已修复)、`proctoring`(✅ P1-1 已修复:通过 exams data-access)、`diagnostic`(✅ P1-1 已修复:通过 exams data-access)
|
||||
- 被依赖:`homework`(通过 sourceExamId 外键,合理)、`dashboard`(通过 data-access,P0-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 Action(P1-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 新增 getExamsByGradeId;2026-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 新增:成绩录入草稿 CRUD,upsert + 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 新增:成绩录入草稿 CRUD,upsert + 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/deleteGradeDraftAction;v4-P1-6:createGradeRecordAction/batchCreateGradeRecordsAction 新增通知;v4-P1-12:exportGradesAction 新增 studentId 参数;v3-P3-2 新增:bulkDeleteGradeRecordsAction 批量删除;v4-P2-6:assertClassInScope 迁移至 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/deleteGradeDraftAction;v4-P1-6:createGradeRecordAction/batchCreateGradeRecordsAction 新增通知;v4-P1-12:exportGradesAction 新增 studentId 参数;v3-P3-2 新增:bulkDeleteGradeRecordsAction 批量删除;v4-P2-6:assertClassInScope 迁移至 lib/scope-check.ts;2026-06-24 新增:batchCreateGradeRecordsByExamAction 按试卷录入每题得分) |
|
||||
| `actions-analytics.ts` | 170 | 5 个分析 Action(含 Zod 校验,P3 修复:handleActionError + assertClassInScope 校验;v4-P2-6:assertClassInScope 改从 ./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 应用 buildScopeClassFilter;v3-P2 新增:getExamOptionsForGrades/getSchoolWideGradeSummary;getGradeTrend/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-10:createDefaultBuckets 改为内部函数;P3-24:buildGradeTrendPoints 使用 isGradeTrendType 类型守卫替代 as 断言) |
|
||||
| `export.ts` | 290+ | Excel 导出(v2-P1-5 修复:传递 currentUserId 到 data-access;P3 修复:适配 PaginatedGradeRecords 结构 + 传递 scope;P3-6:复用 stats-service.computeAverageScore 替代局部 avg;P3-7:硬编码中文改用 next-intl getTranslations;v4-P1-12 新增:exportStudentGradeRecordsToExcel 家长视角单学生导出) |
|
||||
| `schema.ts` | 113+ | Zod 校验(含 12 个查询 schema;P3 修复:score .max(1000) + records .max(500) + 补全查询字段;v3-P2 新增:grade_drafts 表定义第 1444-1469 行) |
|
||||
| `schema.ts` | 130+ | Zod 校验(含 12 个查询 schema;P3 修复: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/normalize;P3-26:buildScopeClassFilter 迁移至 scope-filter.ts) |
|
||||
| `lib/scope-filter.ts` | 56 | DB 行级权限过滤(buildScopeClassFilter;P3-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/SchoolWideGradeSummary;2026-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-4:i18n;v3-P3-5 新增:显著性分析区域,基于极差和样本量的经验规则判断班级间差异,含可折叠详细分析) |
|
||||
| `components/grade-trend-chart.tsx` | 59 | 趋势图(v2-P1-4:i18n) |
|
||||
| `components/grade-record-form.tsx` | 177 | 录入表单(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;P3 修复:safeActionCall 包装提交) |
|
||||
| `components/batch-grade-entry.tsx` | 500+ | 批量录入(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;P3 修复:safeActionCall + localStorage 安全检查 + 区分未录入与录入 0;v3-P2 新增:接入服务端草稿 saveGradeDraftAction/getGradeDraftAction/deleteGradeDraftAction;v3-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 htmlFor;v2-P1-4 i18n;P3 safeActionCall + localStorage 安全检查;v3-P2 服务端草稿;v3-P3-1 下载 CSV 模板;v4-P3-2 新手引导均已替换) |
|
||||
| `components/grade-filters.tsx` | 76 | 过滤器(v2-P1-4:i18n) |
|
||||
| `components/student-grade-summary.tsx` | 107 | 学生成绩摘要(v2-P1-4:i18n) |
|
||||
| `components/export-button.tsx` | 79 | 导出按钮(v2-P1-4:i18n;P3 修复: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 新增:管理员系统设置 CRUD,4 分类 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 选择器 + 可见性/归属 Badge,isAdmin 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-plans);student 导航新增「我的课案」(/student/lesson-plans);parent 导航新增「孩子课案」(/parent/lesson-plans)
|
||||
|
||||
> 架构变更(2026-06-24,V3 深度审计修复):
|
||||
> - **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-24,V3 深度审计修复续):
|
||||
> - **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→v2(migrateV1ToV2)/ v2→v3(migrateV2ToV3)/ 规范化(normalizeDocument,兼容 v1/v2/v3)/ 初始内容(buildInitialContent)/ 默认骨架(buildDefaultSkeleton,10 节点 + 正文节点)/ 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 节点 + 锚点回调;V3:ctx 新增 anchorableNodes/onCreateNewNode 字段)/ toRfEdges(区分 anchor/flow 边透明度;V3:锚点边颜色使用 getNodeColor(anchor.nodeId) 替代硬编码,anchorId 存入 edge.data)/ fromRfEdges(V3:从 e.data.anchorId 读取 anchorId,回退到 className 判断) |
|
||||
| `config/block-registry.tsx` | **配置驱动**:BLOCK_REGISTRY 注册表 + BlockRenderer(switch 渲染 11 种定制节点 + textbook_content) |
|
||||
| `providers/lesson-plan-provider.tsx` | **Provider + Context(P1-5/P1-7/P2-4/V2-6)**:LessonPlanProvider 注入数据服务/角色配置/埋点;定义 LessonPlanDataService 接口、4 个角色配置(TEACHER/ADMIN/STUDENT/PARENT)、ROLE_CONFIGS 注册表、LessonPlanTracker 接口 + noopTracker;hooks:useLessonPlanContextSafe(返回 null 不抛错)/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe(V2-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-3;V2-1:抛出 `LessonPlanDataError` 错误码;V2-3:mapRowToLessonPlan/mapRowToListItem/mapRowToTemplate 显式映射 + isLessonPlanStatus/isTemplateType/isTemplateScope 类型守卫;V3:新增 publishLessonPlan/unpublishLessonPlan 函数) |
|
||||
| `data-access-versions.ts` | 版本管理(创建/查询/回滚/清理;V2-3:mapRowToVersion 显式映射) |
|
||||
| `data-access-templates.ts` | 个人模板 CRUD(V2-3:mapRowToTemplate 显式映射 + 类型守卫) |
|
||||
| `data-access-knowledge.ts` | 按知识点/题目反查课案(V2-3:显式字段映射替代 `as unknown as`) |
|
||||
| `actions.ts` | 课案 CRUD/版本/模板 Server Actions(V2-1:getTranslations i18n + 错误码捕获;V2-2:createLessonPlanAction 传入 translateTitle 翻译 SYSTEM_TEMPLATES) |
|
||||
| `actions-publish.ts` | 发布作业 Server Action(V2-1:getTranslations i18n + PUBLISH_ERROR_KEY_MAP 错误码映射;V3:新增 publishLessonPlanAction/unpublishLessonPlanAction,requirePermission(LESSON_PLAN_PUBLISH)) |
|
||||
| `actions-ai.ts` | AI 知识点建议 Server Action(V2-1:i18n + 错误码) |
|
||||
| `actions-kp.ts` | 知识点选项 Server Action(V2-1:i18n + 错误码) |
|
||||
| `publish-service.ts` | 发布作业服务(编排 homework/exams/classes,通过对方 data-access 调用,无直查跨模块表;V2-1:抛出 `PublishServiceError` 错误码;V2-3:显式字段映射替代 `as unknown as`) |
|
||||
| `lib/rf-mappers.ts` | **纯函数**:toRfNodes(V3:移除冗余 `as` 断言,TypeScript 判别联合自动收窄)/ toRfEdges / fromRfEdges |
|
||||
| `config/block-registry.tsx` | **配置驱动**:BLOCK_REGISTRY 注册表 + BlockRenderer(V3:使用 `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 接口 + noopTracker;hooks:useLessonPlanContextSafe/useLessonPlanContext/useRoleConfig/useLessonPlanService/useLessonPlanTracker/useLessonPlanTrackerSafe |
|
||||
| `providers/lesson-plan-provider-setup.tsx` | **页面层 Provider 设置包装组件(V3 续新增)**:LessonPlanProviderSetup 自动注入默认数据服务(createDefaultDataService)和角色配置(TEACHER_ROLE_CONFIG),3 个 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 Actions(V3:所有 action 使用 `translateFieldErrors` 翻译 Zod 错误;`duplicateLessonPlanAction` 传入 i18n 翻译的副本后缀;`getLessonPlanByIdAction` 返回类型改为 `ActionState<{ plan: LessonPlan }>`) |
|
||||
| `actions-publish.ts` | 发布作业 Server Action(V3:actions 层注入 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-6:duplicate/archive 调用 tracker.track;V3:新增 viewMode prop(teacher/student/parent/admin/gradeHead),动态跳转链接 + 发布/撤回按钮) |
|
||||
| `components/lesson-plan-filters.tsx` | 课案筛选器(i18n 已接入;V2-5:3 个表单元素 label htmlFor 关联) |
|
||||
| `components/lesson-plan-editor.tsx` | 课案编辑器(编排 NodeEditor + NodeEditPanel,i18n 已接入;V2-6:handleManualSave 调用 tracker.track;V3:顶部工具栏显示教材/章节标题指示器;V3:新增 initialStatus prop + 发布/撤回按钮(AlertDialog 确认)) |
|
||||
| `components/lesson-plan-readonly-view.tsx` | **只读画布组件**(V3 新增):复用 React Flow(nodesDraggable=false, nodesConnectable=false),供学生/家长/管理员/教研组长查看已发布课案 |
|
||||
| `components/node-editor.tsx` | **节点图画布**(React Flow,使用 lib/rf-mappers + lib/node-summary 纯函数,i18n 已接入;V2-4:MiniMap 复用 getNodeColor;V2-5:role=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/getNodeColor,i18n 已接入) |
|
||||
| `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` | 课案编辑器 Hook(V3:`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` | **只读画布组件**(V3:MiniMap 使用 `getNodeColor` 替代硬编码颜色) |
|
||||
| `components/node-editor.tsx` | **节点图画布**(V3:MiniMap `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-6:create 调用 tracker.track;V3:加载并显示个人模板(调用 getLessonPlanTemplatesAction),分区显示系统/个人模板) |
|
||||
| `components/version-history-drawer.tsx` | 版本历史抽屉(i18n 已接入;V2-6:revert 调用 tracker.track) |
|
||||
| `components/knowledge-point-picker.tsx` | 知识点选择器(i18n 已接入) |
|
||||
| `components/question-bank-picker.tsx` | 题库选择器(i18n 已接入) |
|
||||
| `components/inline-question-editor.tsx` | 内联题目编辑器(i18n 已接入;V2-5:type/difficulty select label htmlFor 关联) |
|
||||
| `components/publish-homework-dialog.tsx` | 发布作业对话框(i18n 已接入;V2-6:publish 调用 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.reload,i18n 已接入;V2-5:purpose 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` | 教学目标 Block(V3 续:select onChange 使用 `isObjectiveDimension` 类型守卫替代 `as` 断言) |
|
||||
| `components/blocks/key-point-block.tsx` | 重难点 Block(V3 续:select onChange 使用 `isKeyPointType` 类型守卫替代 `as` 断言) |
|
||||
| `components/blocks/import-block.tsx` | 导入 Block(V3 续:select onChange 使用 `isImportMethod` 类型守卫替代 `as` 断言) |
|
||||
| `components/blocks/new-teaching-block.tsx` | 新授 Block |
|
||||
| `components/blocks/summary-block.tsx` | 总结 Block |
|
||||
| `components/blocks/homework-block.tsx` | 作业 Block(V3 续:select onChange 使用 `isHomeworkType` 类型守卫替代 `as` 断言) |
|
||||
| `components/blocks/blackboard-block.tsx` | 板书 Block(V3 续:select onChange 使用 `isBlackboardLayout` 类型守卫替代 `as` 断言) |
|
||||
| `components/blocks/exercise-block.tsx` | 练习 Block(V3 续:select onChange 使用 `isExercisePurpose` 类型守卫替代 `as` 断言) |
|
||||
| `components/blocks/reflection-block.tsx` | 反思 Block(V3 续: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 | 班级错题数对比柱状图(recharts,tooltip 显示学生数/人均/掌握率) |
|
||||
| `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
@@ -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 课案
|
||||
- 教研组长可查看本年级教师备课
|
||||
- 锚点交互完整可用(选中文本 → 选择节点 → 创建锚点)
|
||||
- 锚点边颜色与关联节点一致
|
||||
Reference in New Issue
Block a user