16 KiB
错题本模块审计报告
审计日期: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 算法说明、自动采集机制
- 文件清单、路由清单、数据库表
但存在以下不一致:
005_architecture_data.json的uses.shared仍列出examSubmissions、submissionAnswers、homeworkSubmissions等表,实际上这些已通过跨模块 data-access 接口访问(data-access-collection.ts),不再直接查询- JSON 未记录
adaptive-practice依赖,但error-book-detail-dialog.tsx直接 import 了createPracticeSessionAction - JSON 未记录
ai模块依赖,但error-book-detail-dialog.tsx直接 import 了AiErrorBookAnalysis组件
二、现存问题与原因分析
2.1 【P0】跨模块直接依赖(违反三层架构规则)
项目规则:「模块间只能通过对方 data-access 通信,禁止跨模块直接查询数据库表」「该模块必须作为独立功能单元」
| 位置 | 违规内容 | 原因 | 后果 |
|---|---|---|---|
| error-book-detail-dialog.tsx | import { AiErrorBookAnalysis } from "@/modules/ai/components/ai-error-book-analysis" |
error-book 组件直接 import ai 模块组件 | 模块强耦合,无法独立测试/部署 |
| error-book-detail-dialog.tsx | import { createPracticeSessionAction } from "@/modules/adaptive-practice/actions" |
error-book 组件直接 import adaptive-practice 的 Server Action | 模块强耦合,违反依赖注入原则 |
| add-error-book-dialog.tsx | 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直接 importarchiveErrorBookItemAction、deleteErrorBookItemAction、updateErrorBookNoteActionadd-error-book-dialog.tsx直接 importcreateErrorBookItemActionreview-buttons.tsx直接 importreviewErrorBookItemAction
无法在不启动整个应用的情况下 mock 这些依赖,违反可测试性原则。
2.10 【P2】权限校验位置不统一
getAllStudentIds() 在 data-access 层通过 roles.name === "student" 查询,虽然不在前端,但角色名字符串硬编码在查询中。应使用 shared/types/permissions 中的角色常量。
三、行业差距对比
3.1 与优秀 K12 产品的差距
| 功能 | 我们 | 智学网 | 猿题库 | 钉钉教育 | 差距影响 |
|---|---|---|---|---|---|
| 智能复习队列 | ✅ SM-2 | ✅ | ✅ | ❌ | 基本持平 |
| 复习提醒通知 | ❌ | ✅ 推送 | ✅ 推送 | ✅ | 学生不知道何时复习 |
| 错题趋势图 | ❌(类型已定义未实现) | ✅ | ✅ | ❌ | 无法看到进步趋势 |
| 导出/打印错题 | ❌ | ❌ | 无法离线复习 | ||
| 批量操作 | ❌ | ✅ | ✅ | ❌ | 管理大量错题效率低 |
| 班级平均对比 | ❌ | ✅ | ✅ | ❌ | 学生不知道自己水平 |
| 复习日历视图 | ❌ | ✅ | ✅ | ❌ | 无法直观看到复习安排 |
| 错题来源详情跳转 | ❌ | ✅ | ✅ | ❌ | 无法回看原试卷/作业 |
| 知识点掌握度雷达图 | ❌ | ✅ | ✅ | ❌ | 无法多维度看薄弱点 |
| 游戏化激励(连续复习天数) | ❌ | ✅ | ✅ | ❌ | 学生缺乏复习动力 |
| 智能推题(基于错题变式) | ✅(已接入 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 需要修改的节点
-
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 访问)
- 移除
-
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
- 新增
-
004_architecture_impact_map.md第 2.28 节:- 文件清单中
data-access.ts行数更新为拆分后的两个文件 - 依赖关系新增 ai / adaptive-practice 的直接依赖说明(标注为"待解耦")
- 文件清单中
5.2 架构图待补充项
- 拆分后的
data-access-analytics.ts文件信息 ErrorBookService接口定义(如实施 P2-2)- i18n 翻译文件结构更新