275 lines
16 KiB
Markdown
275 lines
16 KiB
Markdown
# 错题本模块审计报告
|
||
|
||
> 审计日期:2026-06-24
|
||
> 审计范围:`src/modules/error-book/` 及 `src/app/(dashboard)/{student,teacher,parent,admin}/error-book/`
|
||
> 审计依据:项目规则 `docs/standards/coding-standards.md`、架构影响地图 `004`/`005`
|
||
|
||
---
|
||
|
||
## 一、现有实现概要
|
||
|
||
### 1.1 文件分布
|
||
|
||
错题本模块位于 `src/modules/error-book/`,包含以下文件:
|
||
|
||
| 文件 | 行数 | 职责 |
|
||
|------|------|------|
|
||
| `actions.ts` | 341 | 9 个 Server Actions(列表/详情/统计/增删改/复习/采集) |
|
||
| `data-access.ts` | **1029** | 数据访问层(学生 CRUD + 教师/管理员分析查询 + 跨模块接口) |
|
||
| `data-access-collection.ts` | 170 | 自动采集逻辑(考试/作业错题采集) |
|
||
| `schema.ts` | 51 | 4 个 Zod 验证 schema |
|
||
| `types.ts` | 245 | 11 个类型定义 + 状态映射常量 |
|
||
| `sm2-algorithm.ts` | 177 | SM-2 间隔重复算法(纯函数) |
|
||
| `sm2-algorithm.test.ts` | - | 39 个单元测试 |
|
||
| `components/` | 17 个文件 | UI 组件(学生卡片/列表/筛选/详情/图表等) |
|
||
|
||
### 1.2 路由分布
|
||
|
||
| 路由 | 角色 | 文件完整性 |
|
||
|------|------|-----------|
|
||
| `/student/error-book` | 学生 | page + loading + error ✅ |
|
||
| `/teacher/error-book` | 教师 | page + loading + error ✅ |
|
||
| `/parent/error-book` | 家长 | page + loading + error ✅ |
|
||
| `/admin/error-book` | 管理员 | page + loading + error ✅ |
|
||
|
||
### 1.3 架构图覆盖情况
|
||
|
||
架构影响地图 `004_architecture_impact_map.md` 第 2.28 节已覆盖该模块,记录了:
|
||
- 模块职责、导出函数、权限点、DataScope 行级权限
|
||
- SM-2 算法说明、自动采集机制
|
||
- 文件清单、路由清单、数据库表
|
||
|
||
**但存在以下不一致**:
|
||
1. `005_architecture_data.json` 的 `uses.shared` 仍列出 `examSubmissions`、`submissionAnswers`、`homeworkSubmissions` 等表,实际上这些已通过跨模块 data-access 接口访问(`data-access-collection.ts`),不再直接查询
|
||
2. JSON 未记录 `adaptive-practice` 依赖,但 `error-book-detail-dialog.tsx` 直接 import 了 `createPracticeSessionAction`
|
||
3. JSON 未记录 `ai` 模块依赖,但 `error-book-detail-dialog.tsx` 直接 import 了 `AiErrorBookAnalysis` 组件
|
||
|
||
---
|
||
|
||
## 二、现存问题与原因分析
|
||
|
||
### 2.1 【P0】跨模块直接依赖(违反三层架构规则)
|
||
|
||
**项目规则**:「模块间只能通过对方 data-access 通信,禁止跨模块直接查询数据库表」「该模块必须作为独立功能单元」
|
||
|
||
| 位置 | 违规内容 | 原因 | 后果 |
|
||
|------|---------|------|------|
|
||
| [error-book-detail-dialog.tsx](file:///e:/Desktop/CICD/src/modules/error-book/components/error-book-detail-dialog.tsx#L40) | `import { AiErrorBookAnalysis } from "@/modules/ai/components/ai-error-book-analysis"` | error-book 组件直接 import ai 模块组件 | 模块强耦合,无法独立测试/部署 |
|
||
| [error-book-detail-dialog.tsx](file:///e:/Desktop/CICD/src/modules/error-book/components/error-book-detail-dialog.tsx#L41) | `import { createPracticeSessionAction } from "@/modules/adaptive-practice/actions"` | error-book 组件直接 import adaptive-practice 的 Server Action | 模块强耦合,违反依赖注入原则 |
|
||
| [add-error-book-dialog.tsx](file:///e:/Desktop/CICD/src/modules/error-book/components/add-error-book-dialog.tsx#L26) | `import { getQuestionsAction } from "@/modules/questions/actions"` | error-book 组件直接 import questions 模块的 Action | 应通过 data-access 或注入接口调用 |
|
||
|
||
### 2.2 【P0】i18n 国际化严重遗漏
|
||
|
||
**项目规则**:「所有用户可见文本必须适配 i18n(使用 next-intl),提取翻译键」
|
||
|
||
虽然 `error-book.json` 翻译文件已存在(123 行),但**大量组件仍使用硬编码中文**:
|
||
|
||
| 组件 | 硬编码文本示例 | 行数 |
|
||
|------|--------------|------|
|
||
| `error-book-item-card.tsx` | "题目内容"、"难度"、"掌握度"、"复习 X 次"、"需复习"、"下次"、"未学习"、"入门"... | 10+ 处 |
|
||
| `error-book-detail-dialog.tsx` | "题目"、"我的答案"、"正确答案"、"AI 智能分析"、"复习自评"、"学习笔记"、"错误原因标签"、"复习历史"、"归档"、"删除"、"添加于"... | 20+ 处 |
|
||
| `error-book-filters.tsx` | "搜索笔记内容..."、"状态"、"来源"、"复习"、"全部状态"、"全部来源"... | 8+ 处 |
|
||
| `add-error-book-dialog.tsx` | "手动添加"、"添加错题"、"选择题目"、"学习笔记(可选)"、"错误原因标签"、"取消"、"添加"... | 10+ 处 |
|
||
| `review-buttons.tsx` | "重来"、"困难"、"良好"、"简单" 及描述文案(i18n 已有翻译但未使用) | 8 处 |
|
||
| `error-book-stats-cards.tsx` | "错题总数"、"待学习"、"学习中"、"已掌握"、"待复习" 及描述 | 10 处 |
|
||
| `analytics-stats-cards.tsx` | "覆盖学生"、"错题总数"、"平均掌握率"、"待复习"、"涉及知识点" 及子文案 | 10+ 处 |
|
||
| `subject-tabs.tsx` | "全部学科"、"待复习" | 2 处 |
|
||
| `class-filter.tsx` | "全部班级"、"错题"、"待复习" | 3 处 |
|
||
| `top-wrong-questions.tsx` | "高频错题"、"高频错题 Top 10"、"暂无高频错题"、"人错"、"人已掌握"、"掌握率" | 8 处 |
|
||
| `knowledge-point-weakness-chart.tsx` | "薄弱知识点 Top X"、"错题数"、"所属章节"、"错题数"、"已掌握"、"掌握率" | 8+ 处 |
|
||
| `chapter-weakness-chart.tsx` | "章节错题分布(哪些课在错)"、"错题数"、"已掌握"、"掌握率"、"知识点数"、"薄弱知识点" | 10+ 处 |
|
||
| `class-error-bar-chart.tsx` | "各班级错题数对比"、"错题总数"、"学生数"、"人均错题"、"平均掌握率"、"待复习" | 6+ 处 |
|
||
| `subject-distribution-chart.tsx` | "各学科错题分布"、"错题数"、"已掌握"、"掌握率" | 4+ 处 |
|
||
| `grouped-student-error-table.tsx` | "未分班"、"人"、"人有错题"、"错题总数"、"平均掌握率"、"学生" 及表头 | 15+ 处 |
|
||
| `class-error-overview.tsx` | "覆盖学生"、"错题总数"、"平均掌握率"、"薄弱知识点"、"学科错题分布" 等 | 15+ 处 |
|
||
| `error-book-list.tsx` | "错题本为空"、"查看详情" | 2 处 |
|
||
| `teacher/error-book/page.tsx` | "错题分析"、"按学科、班级查看学生的错题统计与薄弱知识点" 等 | 8+ 处 |
|
||
|
||
**总计约 150+ 处硬编码中文文本**,违反 i18n 规则。
|
||
|
||
### 2.3 【P0】类型安全问题(违反 TypeScript 严格模式)
|
||
|
||
**项目规则**:「禁止 `any`、禁止 `as` 断言(除类型收窄外)」
|
||
|
||
| 文件 | 行号 | 违规代码 | 类型 |
|
||
|------|------|---------|------|
|
||
| `data-access.ts` | 77 | `row.sourceType as ErrorBookItem["sourceType"]` | `as` 断言 |
|
||
| `data-access.ts` | 82 | `row.knowledgePointIds as string[] \| null` | `as` 断言 |
|
||
| `data-access.ts` | 90 | `row.errorTags as string[] \| null` | `as` 断言 |
|
||
| `data-access.ts` | 133 | `or(...)!` | 非空断言 |
|
||
| `data-access.ts` | 175 | `row as unknown as Parameters<typeof mapRowToItem>[0]` | 双重断言 |
|
||
| `data-access.ts` | 222 | 同上 | 双重断言 |
|
||
| `data-access.ts` | 657 | `row.knowledgePointIds as string[] \| null` | `as` 断言 |
|
||
| `data-access.ts` | 792 | 同上 | `as` 断言 |
|
||
| `actions.ts` | 63 | `params.status as "new" \| "learning" \| ...` | `as` 断言 |
|
||
| `actions.ts` | 69 | `params.sourceType as "exam" \| "homework" \| ...` | `as` 断言 |
|
||
| `error-book-item-card.tsx` | 31 | `node as Record<string, unknown>` | `as` 断言 |
|
||
| `error-book-detail-dialog.tsx` | 61 | `content as Record<string, unknown>` | `as` 断言 |
|
||
| `add-error-book-dialog.tsx` | 48 | `node as Record<string, unknown>` | `as` 断言 |
|
||
| `top-wrong-questions.tsx` | 26 | `node as Record<string, unknown>` | `as` 断言 |
|
||
| `knowledge-point-weakness-chart.tsx` | 77 | `payload as unknown as {...}` | 双重断言 |
|
||
| `chapter-weakness-chart.tsx` | 74 | `payload as unknown as {...}` | 双重断言 |
|
||
| `class-error-bar-chart.tsx` | 76 | `payload as unknown as {...}` | 双重断言 |
|
||
| `subject-distribution-chart.tsx` | 77 | `payload as unknown as {...}` | 双重断言 |
|
||
|
||
### 2.4 【P1】data-access.ts 超过 1000 行硬性上限
|
||
|
||
**项目规则**:「硬性上限:任何文件不超过 1000 行,超过必须拆分」
|
||
|
||
`data-access.ts` 当前 **1029 行**,超出硬性上限。该文件混合了:
|
||
- 学生端 CRUD(`getErrorBookItems`、`createErrorBookItem`、`recordReview` 等)
|
||
- 教师/管理员分析查询(`getStudentErrorBookSummaries`、`getKnowledgePointWeakness`、`getChapterWeakness`、`getClassErrorOverviews`、`getSubjectErrorOverviews` 等)
|
||
- 跨模块查询接口(`getStudentIdsByClassIdList`、`getAllStudentIds`)
|
||
|
||
### 2.5 【P1】性能问题:全量查询 + JS 端聚合
|
||
|
||
| 函数 | 问题 | 影响 |
|
||
|------|------|------|
|
||
| `getErrorBookStats` | 查询该学生**所有**错题行到内存,再 JS 循环统计 | 学生错题多时内存/CPU 浪费 |
|
||
| `getStudentErrorBookSummaries` | 查询所有学生的所有错题行,再 JS 聚合 | 班级学生多时性能差 |
|
||
| `getKnowledgePointWeakness` | 查询所有错题行,JS 展开知识点数组再统计 | 同上 |
|
||
| `getChapterWeakness` | 同上 | 同上 |
|
||
| `getSubjectErrorDistribution` | 同上 | 同上 |
|
||
| `getClassErrorOverviews` | 同上 | 同上 |
|
||
| `getSubjectErrorOverviews` | 同上 | 同上 |
|
||
| `getTopWrongQuestionsByStudentIds` | 同上 | 同上 |
|
||
| `admin/page.tsx` | `allStudentIds.slice(0, 500)` 硬编码限制,无分页 | 超过 500 学生时数据不完整 |
|
||
|
||
**应使用 SQL `GROUP BY` + `COUNT` 聚合查询**,避免全量加载到内存。
|
||
|
||
### 2.6 【P1】a11y 可访问性缺失
|
||
|
||
**项目规则**:「可访问性(a11y):语义化标签、ARIA 属性、键盘导航」
|
||
|
||
| 位置 | 问题 |
|
||
|------|------|
|
||
| `grouped-student-error-table.tsx:162` | 使用 `<a href>` 而非 Next.js `<Link>`(违反项目记忆中的 Link 规范) |
|
||
| `subject-tabs.tsx` | `<button>` 缺少 `role="tab"` / `aria-selected` / `aria-controls` |
|
||
| `class-filter.tsx` | 同上 |
|
||
| 所有图表组件 | 缺少 `aria-label` 描述图表内容 |
|
||
| `review-buttons.tsx` | 按钮缺少 `aria-label` 描述操作 |
|
||
| `grouped-student-error-table.tsx:96` | 可展开行缺少 `aria-expanded` |
|
||
| `error-book-detail-dialog.tsx` | `<textarea>` 缺少 `aria-label` |
|
||
|
||
### 2.7 【P1】错误边界不完整
|
||
|
||
**项目规则**:「每个独立的数据区块必须用 React Error Boundary 包裹」
|
||
|
||
| 位置 | 问题 |
|
||
|------|------|
|
||
| `parent/error-book/page.tsx` | 无 `Suspense` 包裹,整个页面同步渲染 |
|
||
| 所有页面 | 仅页面级 `error.tsx`,各数据区块(统计卡片/图表/表格)无独立 Error Boundary |
|
||
| 图表组件 | recharts 渲染失败时无 fallback |
|
||
|
||
### 2.8 【P2】组件复用问题
|
||
|
||
| 问题 | 位置 | 说明 |
|
||
|------|------|------|
|
||
| `extractQuestionPreview` 函数重复 3 次 | `error-book-item-card.tsx`、`add-error-book-dialog.tsx`、`top-wrong-questions.tsx` | 应提取到 shared 工具函数 |
|
||
| `extractQuestionText` 函数重复 | `error-book-detail-dialog.tsx` | 与上面类似但实现不同 |
|
||
| `MASTERY_LEVEL_LABELS` 硬编码 | `error-book-item-card.tsx:47` | 应使用 i18n |
|
||
| `QUESTION_TYPE_LABEL` 硬编码 | `top-wrong-questions.tsx:35` | 应使用 i18n |
|
||
| `class-error-overview.tsx` 导出 `StudentErrorTable` | 似乎已被 `GroupedStudentErrorTable` 取代 | 死代码 |
|
||
| `COMMON_ERROR_TAGS` 硬编码中文 | `types.ts:55` | 应使用 i18n 键 |
|
||
|
||
### 2.9 【P2】数据服务未抽象(不可测试)
|
||
|
||
**项目规则**:「可测试性:数据获取、计算、格式化等纯逻辑全部放入纯函数或 hooks,与 UI 分离」
|
||
|
||
当前组件直接 import `actions` 和 `data-access`:
|
||
- `error-book-detail-dialog.tsx` 直接 import `archiveErrorBookItemAction`、`deleteErrorBookItemAction`、`updateErrorBookNoteAction`
|
||
- `add-error-book-dialog.tsx` 直接 import `createErrorBookItemAction`
|
||
- `review-buttons.tsx` 直接 import `reviewErrorBookItemAction`
|
||
|
||
**无法在不启动整个应用的情况下 mock 这些依赖**,违反可测试性原则。
|
||
|
||
### 2.10 【P2】权限校验位置不统一
|
||
|
||
`getAllStudentIds()` 在 data-access 层通过 `roles.name === "student"` 查询,虽然不在前端,但角色名字符串硬编码在查询中。应使用 `shared/types/permissions` 中的角色常量。
|
||
|
||
---
|
||
|
||
## 三、行业差距对比
|
||
|
||
### 3.1 与优秀 K12 产品的差距
|
||
|
||
| 功能 | 我们 | 智学网 | 猿题库 | 钉钉教育 | 差距影响 |
|
||
|------|------|--------|--------|---------|---------|
|
||
| 智能复习队列 | ✅ SM-2 | ✅ | ✅ | ❌ | 基本持平 |
|
||
| 复习提醒通知 | ❌ | ✅ 推送 | ✅ 推送 | ✅ | 学生不知道何时复习 |
|
||
| 错题趋势图 | ❌(类型已定义未实现) | ✅ | ✅ | ❌ | 无法看到进步趋势 |
|
||
| 导出/打印错题 | ❌ | ✅ PDF | ✅ PDF | ❌ | 无法离线复习 |
|
||
| 批量操作 | ❌ | ✅ | ✅ | ❌ | 管理大量错题效率低 |
|
||
| 班级平均对比 | ❌ | ✅ | ✅ | ❌ | 学生不知道自己水平 |
|
||
| 复习日历视图 | ❌ | ✅ | ✅ | ❌ | 无法直观看到复习安排 |
|
||
| 错题来源详情跳转 | ❌ | ✅ | ✅ | ❌ | 无法回看原试卷/作业 |
|
||
| 知识点掌握度雷达图 | ❌ | ✅ | ✅ | ❌ | 无法多维度看薄弱点 |
|
||
| 游戏化激励(连续复习天数) | ❌ | ✅ | ✅ | ❌ | 学生缺乏复习动力 |
|
||
| 智能推题(基于错题变式) | ✅(已接入 adaptive-practice) | ✅ | ✅ | ❌ | 基本持平 |
|
||
|
||
### 3.2 UI/UX 差距
|
||
|
||
| 差距 | 说明 | 影响角色 |
|
||
|------|------|---------|
|
||
| 学生端缺少复习仪表盘 | 当前只有列表+筛选,无"今日待复习"独立视图 | 学生 |
|
||
| 教师端缺少学生个体下钻 | 点击学生只能跳转带参数,无学生错题详情面板 | 教师 |
|
||
| 家长端无子女切换 | 多子女时用卡片展示,无 Tab 切换对比 | 家长 |
|
||
| 管理员端无年级维度 | 只有全校维度,无年级/班级下钻 | 管理员 |
|
||
| 无骨架屏一致性 | 各页面 loading.tsx 结构不一致 | 全部 |
|
||
|
||
---
|
||
|
||
## 四、改进优先级建议
|
||
|
||
### P0(紧急,影响架构合规与安全)
|
||
|
||
| 编号 | 问题 | 改进方向 |
|
||
|------|------|---------|
|
||
| P0-1 | 跨模块直接依赖 | 将 `AiErrorBookAnalysis`、`createPracticeSessionAction`、`getQuestionsAction` 改为通过 props/Context 注入,error-book 模块不直接 import 其他业务模块 |
|
||
| P0-2 | i18n 硬编码(150+ 处) | 全部提取为翻译键,扩展 `error-book.json` 翻译文件 |
|
||
| P0-3 | 类型安全(18 处 as 断言) | 使用类型守卫替代 `as`,图表 tooltip 使用泛型组件 |
|
||
|
||
### P1(重要,影响性能与可访问性)
|
||
|
||
| 编号 | 问题 | 改进方向 |
|
||
|------|------|---------|
|
||
| P1-1 | data-access.ts 超 1000 行 | 拆分为 `data-access.ts`(学生 CRUD)+ `data-access-analytics.ts`(教师/管理员分析) |
|
||
| P1-2 | 全量查询 + JS 聚合 | 改用 SQL `GROUP BY` + `COUNT` 聚合 |
|
||
| P1-3 | a11y 缺失 | 添加 ARIA 属性、语义化标签、`<Link>` 替代 `<a>` |
|
||
| P1-4 | 错误边界不完整 | 为各数据区块添加 Error Boundary |
|
||
| P1-5 | admin 无分页 | 实现分页查询或虚拟滚动 |
|
||
|
||
### P2(优化,提升可维护性与用户体验)
|
||
|
||
| 编号 | 问题 | 改进方向 |
|
||
|------|------|---------|
|
||
| P2-1 | 重复函数 | 提取 `extractQuestionPreview` 到 `shared/lib/question-content.ts`(已存在) |
|
||
| P2-2 | 数据服务未抽象 | 定义 `ErrorBookService` 接口,通过 Context 注入 |
|
||
| P2-3 | 死代码 | 删除 `class-error-overview.tsx` 中未使用的 `StudentErrorTable` |
|
||
| P2-4 | 缺失功能 | 错题趋势图、复习提醒、导出、批量操作(中长期) |
|
||
| P2-5 | 角色字符串硬编码 | `getAllStudentIds` 使用角色常量 |
|
||
|
||
---
|
||
|
||
## 五、架构图同步说明
|
||
|
||
### 5.1 需要修改的节点
|
||
|
||
1. **`005_architecture_data.json` → `error-book.uses.shared`**:
|
||
- 移除 `db.schema.examSubmissions`、`db.schema.submissionAnswers`、`db.schema.homeworkSubmissions`、`db.schema.homeworkAnswers`、`db.schema.examQuestions`、`db.schema.homeworkAssignmentQuestions`(这些已通过跨模块 data-access 访问)
|
||
|
||
2. **`005_architecture_data.json` → `error-book.dependsOn`**:
|
||
- 新增 `ai`(error-book-detail-dialog 直接依赖 ai 组件)
|
||
- 新增 `adaptive-practice`(error-book-detail-dialog 直接依赖其 Action)
|
||
- 新增 `questions`(add-error-book-dialog 直接依赖其 Action)—— 注意:questions 已在 dependsOn 中,但 uses 中记录的是 `actions.getQuestionsAction` 而非 data-access
|
||
|
||
3. **`004_architecture_impact_map.md` 第 2.28 节**:
|
||
- 文件清单中 `data-access.ts` 行数更新为拆分后的两个文件
|
||
- 依赖关系新增 ai / adaptive-practice 的直接依赖说明(标注为"待解耦")
|
||
|
||
### 5.2 架构图待补充项
|
||
|
||
- 拆分后的 `data-access-analytics.ts` 文件信息
|
||
- `ErrorBookService` 接口定义(如实施 P2-2)
|
||
- i18n 翻译文件结构更新
|