# 错题本模块审计报告

> 审计日期：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 翻译文件结构更新
