From 58656da983f60c01619a1d75103bf3f348e6f1f4 Mon Sep 17 00:00:00 2001 From: SpecialX <47072643+wangxiner55@users.noreply.github.com> Date: Tue, 23 Jun 2026 00:13:03 +0800 Subject: [PATCH] =?UTF-8?q?feat(textbooks):=20=E7=9F=A5=E8=AF=86=E5=9B=BE?= =?UTF-8?q?=E8=B0=B1=E5=8A=9F=E8=83=BD=E5=85=A8=E9=9D=A2=E9=87=8D=E6=9E=84?= =?UTF-8?q?=20=E2=80=94=20=E5=89=8D=E7=BD=AE=E4=BE=9D=E8=B5=96=20+=20dagre?= =?UTF-8?q?=20=E5=B8=83=E5=B1=80=20+=20React=20Flow=20=E5=8F=AF=E8=A7=86?= =?UTF-8?q?=E5=8C=96=20+=20=E5=B8=88=E7=94=9F=E5=8F=8C=E8=A7=86=E8=A7=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将教材模块图谱从基本无用状态升级为完整知识图谱可视化系统。 数据层:新增 knowledgePointPrerequisites 表(复合主键+双外键 cascade);新增 data-access-graph.ts(server-only)知识点关联聚合、学生/班级掌握度查询;utils.ts 新增 hasCycleAfterAddingEdge(DFS 循环依赖检测)。 业务层:3 个新 Server Action(getKnowledgeGraphDataAction 三视图模式、createPrerequisiteAction 含循环检测、deletePrerequisiteAction);graph-layout.ts 重写为 dagre 分层有向图布局。 视图层:knowledge-graph.tsx 重写为 React Flow 主组件(全书视图+搜索高亮+关联节点高亮+章节着色);4 个新组件(graph-kp-node/graph-prerequisite-edge/graph-toolbar/graph-node-detail-panel);use-graph-data.ts 派生值模式避免 effect 中 setState。 架构:严格三层架构,客户端通过 Server Action 间接访问 server-only 数据层;权限校验+ i18n 全覆盖;架构文档 004/005 同步。 测试:utils.test.ts 新增 5 个循环检测测试,graph-layout.test.ts 重写 5 个 dagre 布局测试,全部 30 个教材模块单元测试通过。 附带提交 drizzle/0005 error-book 迁移文件以保持 journal 一致性。 --- .../004_architecture_impact_map.md | 329 +- docs/architecture/005_architecture_data.json | 814 +- .../plans/2026-06-22-knowledge-graph.md | 2266 +++++ .../2026-06-22-knowledge-graph-design.md | 338 + drizzle/0004_calm_sandman.sql | 11 + drizzle/0005_messy_pride.sql | 48 + drizzle/meta/0004_snapshot.json | 7644 ++++++++++++++++ drizzle/meta/0005_snapshot.json | 8001 +++++++++++++++++ drizzle/meta/_journal.json | 21 + src/modules/textbooks/actions.ts | 295 +- .../textbooks/components/graph-kp-node.tsx | 91 + .../components/graph-node-detail-panel.tsx | 182 + .../components/graph-prerequisite-edge.tsx | 45 + .../textbooks/components/graph-toolbar.tsx | 86 + .../textbooks/components/knowledge-graph.tsx | 320 +- .../textbooks/components/textbook-reader.tsx | 352 +- src/modules/textbooks/data-access-graph.ts | 203 + src/modules/textbooks/data-access.ts | 53 +- src/modules/textbooks/graph-layout.test.ts | 93 +- src/modules/textbooks/graph-layout.ts | 182 +- src/modules/textbooks/hooks/use-graph-data.ts | 69 + src/modules/textbooks/schema.ts | 16 + src/modules/textbooks/types.ts | 61 + src/modules/textbooks/utils.test.ts | 85 + src/modules/textbooks/utils.ts | 96 + src/shared/db/schema.ts | 103 + src/shared/i18n/messages/en/textbooks.json | 74 +- src/shared/i18n/messages/zh-CN/textbooks.json | 74 +- 28 files changed, 21377 insertions(+), 575 deletions(-) create mode 100644 docs/superpowers/plans/2026-06-22-knowledge-graph.md create mode 100644 docs/superpowers/specs/2026-06-22-knowledge-graph-design.md create mode 100644 drizzle/0004_calm_sandman.sql create mode 100644 drizzle/0005_messy_pride.sql create mode 100644 drizzle/meta/0004_snapshot.json create mode 100644 drizzle/meta/0005_snapshot.json create mode 100644 src/modules/textbooks/components/graph-kp-node.tsx create mode 100644 src/modules/textbooks/components/graph-node-detail-panel.tsx create mode 100644 src/modules/textbooks/components/graph-prerequisite-edge.tsx create mode 100644 src/modules/textbooks/components/graph-toolbar.tsx create mode 100644 src/modules/textbooks/data-access-graph.ts create mode 100644 src/modules/textbooks/hooks/use-graph-data.ts diff --git a/docs/architecture/004_architecture_impact_map.md b/docs/architecture/004_architecture_impact_map.md index 105acd8..aab621d 100644 --- a/docs/architecture/004_architecture_impact_map.md +++ b/docs/architecture/004_architecture_impact_map.md @@ -636,15 +636,19 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:教材与知识体系管理(教材/章节树形结构、知识点 CRUD、Markdown 内容编辑、知识图谱)。 **导出函数**: -- Actions(11 个,均为写操作;读操作由 RSC 页面直接调用 data-access):`createTextbookAction` / `updateTextbookAction` / `deleteTextbookAction` / `createChapterAction` / `updateChapterContentAction` / `deleteChapterAction` / `reorderChaptersAction` / `createKnowledgePointAction` / `updateKnowledgePointAction` / `deleteKnowledgePointAction` / `getKnowledgePointsByChapterAction` -- Data-access:`getTextbooks` / `getTextbookById` / `getChaptersByTextbookId` / `getKnowledgePointsByChapterId` / `getKnowledgePointsByTextbookId` / `createTextbook` / `updateTextbook` / `deleteTextbook` / `createChapter` / `updateChapterContent` / `deleteChapter` / `createKnowledgePoint` / `updateKnowledgePoint` / `deleteKnowledgePoint` / `reorderChapters` / `getTextbooksDashboardStats` / `getKnowledgePointOptions`(跨模块接口,供 questions 调用)/ `getTextbooksWithScope`(P1-1 新增:按数据范围获取教材列表,学生端强制按年级过滤)/ `verifyChapterBelongsToTextbook`(P0-4 新增:资源归属校验)/ `verifyKnowledgePointBelongsToTextbook`(P0-4 新增:资源归属校验)/ `getSubjectLabelKey` / `getGradeLabelKey`(i18n 标签键) +- Actions(14 个,均为写操作;读操作由 RSC 页面直接调用 data-access):`createTextbookAction` / `updateTextbookAction` / `deleteTextbookAction` / `createChapterAction` / `updateChapterContentAction` / `deleteChapterAction` / `reorderChaptersAction` / `createKnowledgePointAction` / `updateKnowledgePointAction` / `deleteKnowledgePointAction` / `getKnowledgePointsByChapterAction` / `getKnowledgeGraphDataAction`(✅ Task 7 新增:知识图谱数据查询,支持 structure/student-mastery/class-mastery 三种视图)/ `createPrerequisiteAction`(✅ Task 7 新增:声明前置依赖,含循环检测)/ `deletePrerequisiteAction`(✅ Task 7 新增:删除前置依赖) +- Data-access:`getTextbooks` / `getTextbookById` / `getChaptersByTextbookId` / `getKnowledgePointsByChapterId` / `getKnowledgePointsByTextbookId` / `createTextbook` / `updateTextbook` / `deleteTextbook` / `createChapter` / `updateChapterContent` / `deleteChapter` / `createKnowledgePoint` / `updateKnowledgePoint` / `deleteKnowledgePoint` / `reorderChapters` / `getTextbooksDashboardStats` / `getKnowledgePointOptions`(跨模块接口,供 questions 调用)/ `getTextbooksWithScope`(P1-1 新增:按数据范围获取教材列表,学生端强制按年级过滤)/ `verifyChapterBelongsToTextbook`(P0-4 新增:资源归属校验)/ `verifyKnowledgePointBelongsToTextbook`(P0-4 新增:资源归属校验)/ `createPrerequisite`(✅ Task 7 新增:创建前置依赖)/ `deletePrerequisite`(✅ Task 7 新增:删除前置依赖)/ `getPrerequisiteEdgesForTextbook`(✅ Task 7 新增:获取教材下所有前置依赖边,用于循环检测)/ `getSubjectLabelKey` / `getGradeLabelKey`(i18n 标签键) +- Data-access-graph(✅ Task 5 新增,图谱只读查询):`getKnowledgePointsWithRelations`(知识点+依赖+题目数聚合查询)/ `getStudentKpMastery`(学生掌握度)/ `getClassKpMastery`(班级平均掌握度)/ `getPrerequisitesForKp` / `getSuccessorsForKp` - Constants(✅ 新增):`SUBJECTS` / `GRADES` / `SUBJECT_COLORS` / `getSubjectColor` / `getSubjectLabelKey` / `getGradeLabelKey` -- Utils(✅ 新增,纯函数 + 单测):`sortChapters` / `buildChapterTree` / `buildChapterIndex` / `findChapterParent` / `filterKnowledgePointsByChapter` / `normalizeOptional` / `highlightKnowledgePoints` + - ✅ v1 测试修复:`SUBJECTS` 新增 `Chinese` 学科、`GRADES` 新增 `Grade 1`/`Grade 2`,与 seed 数据对齐 + - ✅ v1 测试修复:`SUBJECT_COLORS` 新增 `Chinese` 主题色(rose) +- Utils(✅ 新增,纯函数 + 单测):`sortChapters` / `buildChapterTree` / `buildChapterIndex` / `findChapterParent` / `filterKnowledgePointsByChapter` / `normalizeOptional` / `highlightKnowledgePoints` / `hasCycleAfterAddingEdge`(Task 4 新增:循环依赖检测,DFS 算法) - Graph-layout(✅ 新增,纯函数 + 单测):`computeGraphLayout` +- Components(✅ Task 13 新增):`GraphNodeDetailPanel` — 知识图谱节点详情侧边面板,展示知识点名称/描述/掌握度/关联题目/前置后置知识点,支持跳转与前置关系增删 - Analytics(✅ 新增):`TextbookAnalytics` / `TextbookAnalyticsProvider` / `useTextbookAnalytics` **依赖关系**: -- 依赖:`shared/*`、`@/auth` +- 依赖:`shared/*`、`@/auth`、`@xyflow/react`(知识图谱可视化)、`@dagrejs/dagre`(图谱分层布局算法) - 被依赖:`questions`(✅ P1-1 已修复:通过 textbooks data-access)、`exams`(通过类型)、`dashboard`(通过 data-access,P0-4 已修复) - ✅ UI 层跨模块依赖已解耦:`textbooks/components/knowledge-point-dialogs.tsx` 不再直接 import questions 模块,改为通过 render prop 注入创建题目入口 @@ -661,6 +665,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" - ✅ P1 Error Boundary 已补齐:新增 `section-error-boundary.tsx` - ✅ P1 知识点列表/弹窗重复实现已清理:移除无调用方的 `knowledge-point-panel.tsx` - ✅ P1 学科/年级选项统一:抽取到 `constants.ts`(`SUBJECTS` / `GRADES` / `SUBJECT_COLORS`) +- ✅ v1 测试修复:`textbook-reader.tsx` 移除 `SheetTrigger`(错误置于 `Sheet` 外部导致 `DialogTrigger must be used within Dialog`),改用受控 `Button` + `onClick` 打开移动端抽屉 +- ✅ v1 测试修复:新增 `teacher-textbook-reader.tsx` 客户端包装组件,解决 Server Component 向 Client Component 传递函数 prop(`renderQuestionCreator`)违反 Next.js 序列化约束的问题 +- ✅ v1 测试修复:`scripts/seed.ts` 教材 subject/grade 改为英文 value(与 `constants.ts` 对齐),消除 i18n `MISSING_MESSAGE` 错误 - ✅ P1 纯逻辑已导出并补单测:新增 `utils.ts` / `graph-layout.ts` 及对应 `.test.ts` - ⚠️ i18n 覆盖率约 95%(`chapter-sidebar-list` 已接入,`actions.ts` 已接入,`section-error-boundary` 默认值已改英文) - ⚠️ 类型断言残留 3 处 `as string` @@ -669,17 +676,25 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| -| `actions.ts` | 377 | 11 个 Server Action(写操作,含 Zod 校验 + 资源归属校验) | -| `data-access.ts` | 619 | 教材/章节/知识点 CRUD + 跨模块查询接口 + 资源归属校验 + 数据范围过滤 | -| `types.ts` | 45 | 类型定义 | -| `schema.ts` | 64 | Zod 校验 | -| `constants.ts` | 91 | 学科/年级常量与颜色映射(✅ 新增) | -| `utils.ts` | 181 | 章节树构建/排序/查找等纯函数(✅ 新增,含单测) | -| `graph-layout.ts` | 141 | 知识图谱布局计算纯函数(✅ 新增,含单测) | +| `actions.ts` | 502 | 14 个 Server Action(写操作,含 Zod 校验 + 资源归属校验 + 知识图谱查询/前置依赖管理) | +| `data-access.ts` | 586 | 教材/章节/知识点 CRUD + 跨模块查询接口 + 资源归属校验 + 数据范围过滤 + 前置依赖 CRUD | +| `data-access-graph.ts` | 184 | 知识图谱只读查询(✅ Task 5 新增:知识点关联聚合、学生/班级掌握度、前置后置知识点,标记 `server-only`) | +| `types.ts` | 94 | 类型定义(含知识图谱类型:GraphViewMode/MasteryInfo/KpWithRelations/GraphNodeData/GraphEdgeData/KnowledgeGraphData/MasteryLevel) | +| `schema.ts` | 62 | Zod 校验(含 CreatePrerequisiteSchema/DeletePrerequisiteSchema) | +| `constants.ts` | 99 | 学科/年级常量与颜色映射(✅ 新增;v1 测试修复:新增 Chinese/Grade 1/Grade 2) | +| `utils.ts` | 203 | 章节树构建/排序/查找等纯函数 + 循环检测(✅ 新增,含单测) | +| `graph-layout.ts` | 105 | 知识图谱布局计算纯函数(✅ Task 8 重写为 dagre 集成,输出 React Flow 格式,含单测) | | `analytics.tsx` | 43 | 教材分析 Context/Provider/Hook(✅ 新增) | | `hooks/use-knowledge-point-actions.ts` | 121 | 知识点操作 Hook | | `hooks/use-text-selection.ts` | 57 | 文本选区捕获 Hook | -| `components/*` | 12 文件 | 教材编辑/知识图谱组件(新增 `section-error-boundary.tsx`) | +| `hooks/use-graph-data.ts` | 58 | 知识图谱数据加载 Hook(✅ Task 11 新增:派生值模式避免 effect 中 setState) | +| `components/teacher-textbook-reader.tsx` | 41 | 教师端 TextbookReader 客户端包装(✅ v1 测试修复:解决 Server→Client 函数 prop 序列化问题) | +| `components/knowledge-graph.tsx` | 249 | React Flow 知识图谱主组件(✅ Task 10/15 重写:全书视图 + 搜索高亮 + 关联节点高亮 + 章节着色) | +| `components/graph-kp-node.tsx` | 80 | React Flow 自定义节点(✅ Task 9 新增:知识点名称+题目数徽章+掌握度进度条) | +| `components/graph-prerequisite-edge.tsx` | 40 | React Flow 自定义边(✅ Task 9 新增:虚线+箭头表示前置依赖) | +| `components/graph-toolbar.tsx` | 77 | 图谱工具栏(✅ Task 9 新增:视图模式切换 + 搜索 + 重置视图) | +| `components/graph-node-detail-panel.tsx` | 171 | 节点详情侧边面板(✅ Task 13 新增:描述/掌握度/关联题目/前置后置列表) | +| `components/*` | 17 文件 | 教材编辑/知识图谱组件(新增 `section-error-boundary.tsx`、`teacher-textbook-reader.tsx`、5 个 graph-* 组件) | --- @@ -688,9 +703,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:成绩分析(录入/查询/统计/导出/趋势对比分析)。 **导出函数**: -- Actions:`getGradeRecordsAction` / `createGradeRecordAction` / `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction` / `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction` -- Data-access:`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getRankingTrend` -- Lib(✅ P1-2 新增):`toNumber` / `normalize` / `buildScopeClassFilter`(从 3 个 data-access 文件抽取的公共工具函数) +- Actions:`getGradeRecordsAction` / `createGradeRecordAction` / `updateGradeRecordAction` / `deleteGradeRecordAction` / `exportGradesAction` / `getGradeTrendAction` / `getClassComparisonAction` / `getSubjectComparisonAction` / `getGradeDistributionAction` / `getClassRankingAction` / `getRankingTrendAction` / `getGradeRecordByIdAction` / `getClassGradeStatsAction` / `getStudentGradeSummaryAction` / `batchCreateGradeRecordsAction` / `assertClassInScope`(✅ P3 新增导出:班级 scope 校验工具,供 actions-analytics 复用) +- Data-access:`getGradeRecords` / `getStudentGradeSummary` / `getClassRanking` / `getClassStudentsForEntry` / `getClassGradeStats` / `getClassGradeStatsWithMeta` / `getGradeTrend` / `getClassComparison` / `getSubjectComparison` / `getGradeDistribution` / `getRankingTrend` / `PaginatedGradeRecords`(✅ P3 新增:分页结果接口 `{ records, total }`) +- Lib(✅ P1-2 新增,✅ P3 更新签名):`toNumber` / `normalize` / `buildScopeClassFilter(scope, currentUserId?)`(P3 修复:`class_members` scope 内置 studentId 过滤,需传入 currentUserId 参数) - Stats-service(✅ P1-1 新增):`computeGradeStats` / `computeAverageScore` / `buildGradeTrendPoints` / `computeTrendAverage` / `computeClassComparisonStats` / `computeSubjectComparisonStats` / `computeGradeDistribution` / `buildRankingTrendPoints`(从 3 个 data-access 文件抽取的纯函数,使数据层专注 DB I/O,统计逻辑可独立测试) - Components(✅ P1-5 新增):`WidgetBoundary`(Error Boundary + Suspense + Skeleton 组合,含 a11y 属性) @@ -712,21 +727,50 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" - ✅ actions 层无直接 DB 访问(标杆) - ✅ data-access 按职责拆分为 3 个文件(标杆) - ✅ P2 已修复:`export.ts` 中 `scoreMap.get(r.studentId)!` 非空断言清理为安全守卫(`if (!subjMap) continue`) +- ✅ P3 修复(2026-06-22):~~`schema.ts` 缺少输入边界约束~~ score 字段添加 `.max(1000)`,records 数组添加 `.max(500)`,查询 schema 补全 studentId/semester/examId 字段 +- ✅ P3 修复(2026-06-22):~~`buildScopeClassFilter` 的 `class_members` scope 未应用 studentId 过滤~~ 新增 `currentUserId` 参数,`class_members` scope 内置 `eq(gradeRecords.studentId, currentUserId)` 过滤 +- ✅ P3 修复(2026-06-22):~~`getGradeRecords` 内存分页(全量查询后切片)~~ 改为 DB 层分页(limit/offset),并行查询总数与当前页数据,返回 `PaginatedGradeRecords { records, total }` +- ✅ P3 修复(2026-06-22):~~`batchCreateGradeRecords` 非原子操作~~ 包裹 `db.transaction` 确保原子性 +- ✅ P3 修复(2026-06-22):~~`updateGradeRecord`/`deleteGradeRecord` 未检查记录是否存在~~ 新增存在性检查,不存在时抛 `NotFoundError("成绩记录")` +- ✅ P3 修复(2026-06-22):~~`getClassGradeStats`/`getStudentGradeSummary`/`getClassRanking`/`getClassStudentsForEntry`/`getClassGradeStatsWithMeta` 缺少 scope 过滤~~ 所有函数新增 `scope?`/`currentUserId?` 参数,应用 `buildScopeClassFilter`;`getStudentGradeSummary`/`getClassStudentsForEntry` 对 `class_taught` scope 校验学生/班级归属 +- ✅ P3 修复(2026-06-22):~~`getClassRanking` 未处理并列排名~~ 相同平均分获得相同名次,下一名次跳过占位 +- ✅ P3 修复(2026-06-22):~~`getClassComparison`/`getRankingTrend` 缺少 scope 过滤~~ 应用 `buildScopeClassFilter`;`getRankingTrend` 对 `class_taught` scope 校验 +- ✅ P3 修复(2026-06-22):~~actions.ts/actions-analytics.ts catch 块直接返回 `e.message`~~ 改用 `handleActionError(e)` 统一错误处理;`batchCreateGradeRecordsAction` 使用 `safeJsonParse` 解析 JSON +- ✅ P3 修复(2026-06-22):~~actions-analytics.ts 缺少 class scope 校验~~ `getGradeTrendAction`/`getSubjectComparisonAction`/`getGradeDistributionAction` 新增 `assertClassInScope` 校验 +- ✅ P3 修复(2026-06-22):~~组件直接 try/catch 调用 Server Action~~ 改用 `safeActionCall` 包装器(onError/onFinally 回调);`batch-grade-entry.tsx` localStorage 访问包裹 `typeof window !== "undefined"` 检查;区分"未录入"与"录入 0" +- ✅ P3 修复(2026-06-22):~~`grade-trend-card.tsx` 排序未处理 NaN 日期、除法未检查 fullScore > 0~~ 新增日期有效性检查与 `fullScore > 0` 守卫 +- ✅ P3 修复(2026-06-22):~~页面文件缺少 scope 传递~~ teacher/grades/page.tsx 使用 DB 层分页;analytics/page.tsx 添加 EmptyState;entry/page.tsx 与 stats/page.tsx 过滤 class_taught scope 班级;student/grades/page.tsx 与 parent/grades/page.tsx 传递 `ctx.dataScope` 到 data-access **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| -| `actions.ts` | 359 | 7 个 Server Action(含 Zod 校验) | -| `actions-analytics.ts` | 175 | 5 个分析 Action(含 Zod 校验) | -| `data-access.ts` | 361 | 成绩 CRUD + 统计 | -| `data-access-analytics.ts` | 266 | 趋势/对比分析 | -| `data-access-ranking.ts` | 96 | 排名查询 | -| `stats-service.ts` | - | 统计计算纯函数(P1-1 新增:8 个纯函数 + 2 个常量 + 2 个接口) | -| `export.ts` | 214 | Excel 导出 | -| `schema.ts` | 100 | Zod 校验(含 12 个查询 schema) | -| `lib/grade-utils.ts` | 46 | 公共工具函数(toNumber/normalize/buildScopeClassFilter) | -| `components/widget-boundary.tsx` | - | Widget 边界组件(P1-5 新增) | -| `types.ts` | - | 类型定义 | +| `actions.ts` | 396 | 15 个 Server Action(含 Zod 校验,含 v2-P1-5 安全修复:assertClassInScope + 行级 scope 校验;P3 修复:handleActionError + safeJsonParse + scope 传递 + DB 层分页) | +| `actions-analytics.ts` | 170 | 5 个分析 Action(含 Zod 校验,P3 修复:handleActionError + assertClassInScope 校验) | +| `data-access.ts` | 428 | 成绩 CRUD + 统计(含 v2-P2-9 修复:recorderName 批量查询;P3 修复:PaginatedGradeRecords 接口 + DB 层分页 + 事务 + 存在性检查 + scope 过滤 + 并列排名) | +| `data-access-analytics.ts` | 200 | 趋势/对比分析(P3 修复:getClassComparison 应用 buildScopeClassFilter) | +| `data-access-ranking.ts` | 83 | 排名查询(P3 修复:getRankingTrend 接受 scope 参数 + class_taught 校验) | +| `stats-service.ts` | 279 | 统计计算纯函数(P1-1 新增:8 个纯函数 + 2 个常量 + 2 个接口) | +| `export.ts` | 189 | Excel 导出(v2-P1-5 修复:传递 currentUserId 到 data-access;P3 修复:适配 PaginatedGradeRecords 结构 + 传递 scope) | +| `schema.ts` | 113 | Zod 校验(含 12 个查询 schema;P3 修复:score .max(1000) + records .max(500) + 补全查询字段) | +| `lib/grade-utils.ts` | 66 | 公共工具函数(toNumber/normalize/buildScopeClassFilter,v2-P2-2 修复:改用 classes data-access 子查询;P3 修复:buildScopeClassFilter 新增 currentUserId 参数) | +| `components/widget-boundary.tsx` | 136 | Widget 边界组件(P1-5 新增,v2-P1-1 已在 3 个页面应用) | +| `components/grade-trend-card.tsx` | 69 | 趋势卡片(v2-P2-9 修复:a11y;v2-P1-4:i18n;P3 修复:NaN 日期检查 + fullScore > 0 守卫) | +| `components/grade-record-list.tsx` | 125 | 成绩记录列表(v2-P1-4:i18n;P3 修复:safeActionCall 包装删除操作) | +| `components/grade-distribution-chart.tsx` | 100 | 分数分布图(v2-P1-4:i18n) | +| `components/subject-comparison-chart.tsx` | 62 | 科目对比图(v2-P1-4:i18n) | +| `components/class-comparison-chart.tsx` | 58 | 班级对比图(v2-P1-4:i18n) | +| `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` | 435 | 批量录入(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n;P3 修复:safeActionCall + localStorage 安全检查 + 区分未录入与录入 0) | +| `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 包装导出操作) | +| `components/analytics-filters.tsx` | 86 | 分析过滤器(v2-P1-4:i18n) | +| `components/stats-class-selector.tsx` | 40 | 统计班级选择器(v2-P1-4:i18n) | +| `components/grade-stats-card.tsx` | 74 | 统计卡片(v2-P1-4:i18n) | +| `components/class-grade-report.tsx` | 90 | 班级成绩报告(v2-P1-4:i18n) | +| `components/grade-query-filters.tsx` | 96 | 查询过滤器(v2-P2-7 修复:Label htmlFor;v2-P1-4:i18n) | +| `types.ts` | 168 | 类型定义 | --- @@ -1173,7 +1217,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **组件清单**: | 组件 | 职责 | |------|------| -| `components/announcement-list.tsx` | 公告列表(用户端,支持状态筛选;✅ V2-P1-1:纯服务端过滤,Select 切换更新 URL ?status= 触发 RSC 重新渲染) | +| `components/announcement-list.tsx` | 公告列表(用户端,支持状态筛选;✅ V2-P1-1:纯服务端过滤,Select 切换更新 URL ?status= 触发 RSC 重新渲染;✅ V3:新增 `detailHrefPrefix` prop 替代 `detailHrefBuilder` 函数 prop,解决 Next.js 16 Server→Client 序列化限制) | | `components/announcement-card.tsx` | 公告卡片(列表项) | | `components/announcement-detail.tsx` | 公告详情(只读) | | `components/announcement-form.tsx` | 公告表单(创建/编辑,✅ P1-6:条件校验由 schema superRefine 保证;✅ V2-P1-4:fieldErrors + aria-invalid 字段级错误展示) | @@ -1417,36 +1461,50 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" **职责**:知识点掌握度查询 + 诊断报告生成。 **导出函数**: -- Actions:`generateStudentDiagnosticReportAction` / `generateClassDiagnosticReportAction` / `publishDiagnosticReportAction` / `deleteDiagnosticReportAction` / `getDiagnosticReportsAction` / `getStudentMasteryAction` -- Data-access:`updateMasteryFromSubmission` / `getStudentMastery` / `getClassMasteryOverview` -- Data-access-reports:`createDiagnosticReport` / `getDiagnosticReport` / `getDiagnosticReports` / `deleteDiagnosticReport` / `publishDiagnosticReport`(✅ P2 已修复:`getDiagnosticReports` 和 `getDiagnosticReportById` 使用 `React.cache()` 包装实现请求级 memoization) -- Schema:`GenerateStudentReportSchema` / `GenerateClassReportSchema` / `PublishReportSchema` / `DeleteReportSchema` / `GetDiagnosticReportsSchema` / `GetDiagnosticReportByIdSchema` +- Actions:`generateStudentReportAction` / `generateClassReportAction` / `publishReportAction` / `deleteReportAction`(v2-P2-3 修复:删除死代码 `getDiagnosticReportsAction` / `getDiagnosticReportByIdAction`,页面直接调用 data-access 并自行权限校验) +- Data-access:`updateMasteryFromSubmission`(v2-P1-8 修复:累积模式;v2-P2-5 修复:db.transaction 包裹)/ `getStudentMastery` / `getStudentMasterySummary` / `getClassMasterySummary`(v2-P2-4 修复:totalStudents 语义 + 班级平均掌握度按学生平均)/ `getKnowledgePointStats`(v2-P1-7 修复:页面先查班级再传参) +- Data-access-reports:`generateDiagnosticReport` / `generateClassDiagnosticReport`(v2-P2-6 修复:校验掌握度数据)/ `getDiagnosticReports` / `getDiagnosticReportById` / `publishDiagnosticReport` / `deleteDiagnosticReport`(✅ P2 已修复:使用 `React.cache()` 包装实现请求级 memoization) +- Stats-service(✅ v2-P1-6 新增):`serializeMasteryWithKp` / `computeAverageMastery` / `classifyStrengthsWeaknesses` / `buildStudentMasterySummary` / `aggregateClassMastery` / `computeKpStats` / `computeClassAverageMastery` / `buildStudentsNeedingAttention` / `buildClassMasterySummary` / `buildStudentReportContent` / `buildClassReportContent` / `computeMasteryLevel` / `serializeMastery`(从 data-access / data-access-reports 抽取的纯统计函数) +- Schema:`GenerateStudentReportSchema` / `GenerateClassReportSchema` / `PublishReportSchema` / `DeleteReportSchema`(v2-P2-3 修复:删除死代码 `GetDiagnosticReportsSchema` / `GetDiagnosticReportByIdSchema`) **依赖关系**: -- 依赖:`shared/*`、`@/auth`、`exams`(✅ P1-1 已修复:通过 exams data-access.getExamSubmissionWithAnswers)、`questions`(✅ P1-1 已修复:通过 questions data-access.getKnowledgePointsForQuestions)、`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getActiveStudentIdsByClassId)、`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId) +- 依赖:`shared/*`、`@/auth`、`exams`(✅ P1-1 已修复:通过 exams data-access.getExamSubmissionWithAnswers)、`questions`(✅ P1-1 已修复:通过 questions data-access.getKnowledgePointsForQuestions)、`classes`(✅ P1-1 已修复:通过 classes data-access.getClassExists/getClassNameById/getActiveStudentIdsByClassId;v2-P1-7 新增 getStudentActiveClassId)、`users`(✅ P1-1 已修复:通过 users data-access.getUserNamesByIds/getUserIdsByGradeId) - 被依赖:无 **已知问题**: -- ✅ P1-1 已修复:~~`updateMasteryFromSubmission` 跨模块直查 4 张表(与 exams/homework/questions 紧耦合)~~ 改为调用 `exams/data-access.getExamSubmissionWithAnswers` 和 `questions/data-access.getKnowledgePointsForQuestions` +- ✅ P1-1 已修复:~~`updateMasteryFromSubmission` 跨模块直查 4 张表~~ 改为调用 `exams/data-access.getExamSubmissionWithAnswers` 和 `questions/data-access.getKnowledgePointsForQuestions` - ✅ P2 已修复:~~`data-access-reports.ts` 有未使用代码(`round2` + `void round2`)~~ 已删除死代码 - ✅ P2 已修复:~~`updateMasteryFromSubmission` 循环内串行 await upsert~~ 改为 `Promise.all` 并行执行所有 upsert -- ✅ P2 已修复:~~`getClassMasterySummary` 串行查询(className → studentIds → userMap → masteryRows)~~ 改为两组 `Promise.all` 并行(className+studentIds,userMap+masteryRows) +- ✅ P2 已修复:~~`getClassMasterySummary` 串行查询~~ 改为两组 `Promise.all` 并行 - ✅ P2 已修复:~~`getDiagnosticReports` 中 `conditions` 隐式 `any[]`~~ 改为显式 `SQL[]` 类型标注 -- ✅ P0-2 已修复:~~`data-access-reports.ts` 直查 `users` 表获取姓名~~ 改为通过 `users/data-access.getUserNamesByIds` 跨模块接口 -- ✅ P2-2 已修复:~~`class-diagnostic-view.tsx`/`student-diagnostic-view.tsx`/`mastery-radar-chart.tsx` 中存在 Tailwind 任意值~~ 改用标准 Tailwind 类(w-44/max-w-32/text-xs/h-96/max-w-lg) +- ✅ P0-2 已修复:~~`data-access-reports.ts` 直查 `users` 表获取姓名~~ 改为通过 `users/data-access.getUserNamesByIds` +- ✅ P2-2 已修复:~~Tailwind 任意值~~ 改用标准 Tailwind 类 - ✅ P2-1 已修复:~~图表/表格/列表缺少 a11y ARIA 属性~~ 为 5 个图表添加 `role="img"` + `aria-label`,2 个表格添加 ``,3 个列表添加 `role="list"`,3 个图标按钮添加 `aria-label` -- ✅ P2-3 已修复:~~班级报告将生成者 ID 存入 `studentId` 字段(schema 设计缺陷 workaround)~~ schema `learningDiagnosticReports.studentId` 改为可空,班级报告 `studentId` 置空,读取逻辑适配 null -- ✅ 与 grades 模块无职责重叠(grades 管分数,diagnostic 管知识点掌握度) +- ✅ P2-3 已修复:~~班级报告将生成者 ID 存入 `studentId` 字段~~ schema 改为可空,班级报告 `studentId` 置空 +- ✅ v2-P1-6 已修复:~~统计逻辑混在 data-access 层~~ 抽取 `stats-service.ts`(352 行,12 个纯函数 + 2 个常量 + 4 个接口) +- ✅ v2-P1-7 已修复:~~`getKnowledgePointStats` 无参调用~~ 页面先查 `getStudentActiveClassId` 再传参 +- ✅ v2-P1-8 已修复:~~`updateMasteryFromSubmission` 覆盖模式~~ 改为累积计算(读取已有记录后累加) +- ✅ v2-P2-3 已修复:~~死代码 `getDiagnosticReportsAction` / `getDiagnosticReportByIdAction` 全局零调用~~ 已删除,页面直接调用 data-access +- ✅ v2-P2-4 已修复:~~`totalStudents` 语义错误 + 班级平均掌握度计算偏差~~ 改为实际有掌握度记录的学生数;先算学生个人平均再取平均 +- ✅ v2-P2-5 已修复:~~多 upsert 无事务包裹~~ 使用 `db.transaction()` 保证原子性 +- ✅ v2-P2-6 已修复:~~生成报告未校验掌握度数据~~ 添加 `totalKnowledgePoints === 0` 和 `studentCount === 0` 校验 +- ✅ v2-P1-4 已修复:~~4 个组件 i18n 完全未接入~~ 全部接入 `useTranslations("diagnostic")` +- ✅ v2-P2-7 已修复:~~`report-list.tsx` 过滤器 Label 缺少 `htmlFor`~~ 添加 `htmlFor` 和 `id` +- ✅ 与 grades 模块无职责重叠 **文件清单**: | 文件 | 行数 | 职责 | |------|------|------| -| `data-access.ts` | 254 | 知识点掌握度查询 + 更新 | -| `data-access-reports.ts` | 202 | 诊断报告 CRUD | -| `actions.ts` | 172 | 6 个 Server Action(使用 Zod schema 校验) | -| `schema.ts` | 56 | Zod 校验(6 个 schema:生成/发布/删除/查询报告) | -| `types.ts` | 97 | 类型定义 | -| `components/*` | 4 文件 | 学生/班级诊断视图 + 雷达图 | +| `data-access.ts` | 179 | 知识点掌握度查询 + 更新(v2-P1-8 累积模式;v2-P2-5 事务;v2-P2-4 语义修正;v2-P1-6 改用 stats-service 纯函数) | +| `data-access-reports.ts` | 160 | 诊断报告 CRUD(v2-P2-6 校验;v2-P1-6 改用 stats-service 纯函数) | +| `stats-service.ts` | 352 | 统计计算纯函数(v2-P1-6 新增:12 个纯函数 + 2 个常量 + 4 个接口) | +| `actions.ts` | 111 | 4 个 Server Action(v2-P2-3 删除 2 个死代码读 Action) | +| `schema.ts` | 23 | Zod 校验(4 个 schema,v2-P2-3 删除 2 个死代码 schema) | +| `types.ts` | 87 | 类型定义 | +| `components/class-diagnostic-view.tsx` | 266 | 班级诊断视图(v2-P1-6 热力图 a11y;v2-P1-4 i18n) | +| `components/student-diagnostic-view.tsx` | 225 | 学生诊断视图(v2-P1-4 i18n) | +| `components/mastery-radar-chart.tsx` | 72 | 雷达图(v2-P1-4 i18n) | +| `components/report-list.tsx` | 265 | 报告列表(v2-P2-7 Label htmlFor;v2-P1-4 i18n) | --- @@ -1496,6 +1554,12 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions" - ✅ P2-9 已修复:~~无 2FA / 会话管理~~ 新增 `actions-security.ts` + `SecurityCenterCard` 组件(2FA 开关占位 + 最近登录历史来自 login_logs 表) - ✅ P2-10 已修复:~~通知偏好表单无测试通知按钮~~ 新增 `sendTestNotificationAction`,每个已启用渠道旁显示测试按钮 - ✅ P2-11 已修复:~~语言切换未集成到设置页~~ `ThemePreferencesCard` 集成 `LocaleSwitcher` 到 Appearance 标签页 +- ✅ v1.1 已修复:~~`/settings` 页面 ErrorBoundary 触发(Functions cannot be passed directly to Client Components)~~ 新增 `actions-service.ts`("use server" 文件),导出 `updateProfileAction` + `updateNotificationPreferencesAction` 两个 Server Action wrapper;`page.tsx` 直接传递 Server Action 引用;`SettingsService` 接口的 `getProfile`/`getPreferences` 改为可选 +- ✅ v1.1 已修复:~~i18n 键双重 `settings.` 前缀(MISSING_MESSAGE)~~ `role-settings-config.tsx` 中 `descriptionKey` 去掉 `settings.` 前缀 +- ✅ v1.1 已修复:~~AI 标签页 FormLabel 在 Form 上下文外使用(getFieldState null)~~ `ai-provider-settings-card.tsx` 中两处 `` 改为 `