19 KiB
19 KiB
题库模块审计报告
审计日期:2026-06-22 审计范围:
src/modules/questions/、src/app/(dashboard)/teacher/questions/、共享组件src/shared/components/question/架构图参考:docs/architecture/004_architecture_impact_map.md、docs/architecture/005_architecture_data.json
一、现有实现概要
1.1 文件分布
| 层 | 文件 | 行数 | 职责 |
|---|---|---|---|
| app | src/app/(dashboard)/teacher/questions/page.tsx |
129 | 教师题库列表页(Server Component) |
| app | src/app/(dashboard)/teacher/questions/loading.tsx |
29 | 骨架屏 |
| module | src/modules/questions/actions.ts |
158 | 5 个 Server Action(CRUD + 查询) |
| module | src/modules/questions/data-access.ts |
357 | 数据访问层(含跨模块接口) |
| module | src/modules/questions/types.ts |
53 | 类型定义 |
| module | src/modules/questions/schema.ts |
18 | Zod 校验 |
| module | src/modules/questions/components/question-filters.tsx |
104 | 筛选栏(自有实现) |
| module | src/modules/questions/components/question-columns.tsx |
142 | 表格列定义 |
| module | src/modules/questions/components/question-data-table.tsx |
134 | 数据表格 |
| module | src/modules/questions/components/create-question-button.tsx |
20 | 创建按钮 |
| module | src/modules/questions/components/create-question-dialog.tsx |
453 | 创建/编辑对话框 |
| module | src/modules/questions/components/question-actions.tsx |
188 | 行操作菜单 |
| shared | src/shared/components/question/question-bank-filters.tsx |
137 | 共享筛选栏(exams/lesson-prep 复用) |
1.2 数据流
teacher/questions/page.tsx (RSC)
├─ requirePermission(QUESTION_READ) ← 服务端权限校验
├─ getQuestions(params) ← 直接调用 data-access(项目规则允许)
├─ <QuestionFilters /> ← 客户端组件,调用 getKnowledgePointOptionsAction
└─ <QuestionDataTable columns data /> ← 客户端表格
└─ <QuestionActions question /> ← 行操作
├─ <CreateQuestionDialog /> ← 编辑(调用 updateQuestionAction)
└─ AlertDialog ← 删除(调用 deleteQuestionAction)
1.3 架构图记录情况
005_architecture_data.json 已记录 questions 模块的 actions / dataAccess / schema / types / components,但存在以下遗漏:
- 依赖矩阵不完整:
dependencyMatrix.questions.dependsOn仅列出["shared", "auth"],缺失textbooks。实际data-access.ts:8导入了@/modules/textbooks/data-access的getKnowledgePointOptions,属于跨模块 data-access 通信,应在依赖矩阵中记录。 - action 名称不一致:架构图记录 action 名为
createNestedQuestion,实际代码为createQuestionAction。 - 跨模块接口未记录:
getKnowledgePointsForQuestions、getQuestionsContentForErrorCollection两个跨模块接口未在架构图中记录。
二、现存问题与原因分析
P0 — 严重问题
P0-1:筛选方式不符合 K12 教学场景(用户核心痛点)
- 位置:
question-filters.tsx、page.tsx、data-access.ts:getQuestions - 问题:当前筛选仅支持「关键词搜索 + 题型 + 难度 + 扁平知识点下拉」。知识点下拉将所有教材的所有章节的所有知识点平铺在一个列表中,教师面对数百条选项无法快速定位。
- 违反规则:项目规则「交互不符合多角色使用习惯」;用户明确要求「选择一个课本 → 一个章节 → 一个课文 → 知识点」级联筛选。
- 后果:教师无法按教学进度精准筛选题目,题库实用性大打折扣,被迫退回手动翻找。
P0-2:完全缺失 i18n 国际化
- 位置:题库模块全部文件
- 问题:所有用户可见文本均为硬编码英文("Question Bank"、"Add Question"、"Search questions..."、"All Types"、"Single Choice" 等),不存在
questions.json翻译文件。 - 违反规则:项目规则「所有用户可见文本必须适配 i18n(使用 next-intl),提取翻译键」。
- 后果:无法支持中文环境,与项目其他模块(exams、textbooks、attendance 等均已 i18n)不一致。
P0-3:缺失 error.tsx 错误边界
- 位置:
src/app/(dashboard)/teacher/questions/ - 问题:仅有
loading.tsx,无error.tsx。当getQuestions或requirePermission抛错时,错误会冒泡到上层 layout,整个仪表盘白屏。 - 违反规则:项目规则「所有 student routes 必须包含 loading.tsx 和 error.tsx」(此规则虽针对 student 路由,但教师路由同样应遵循);架构原则「每个独立的数据区块必须用 React Error Boundary 包裹」。
- 后果:单次查询失败导致整页不可用,无法恢复。
P1 — 高优先级问题
P1-1:as 类型断言违反 TypeScript 严格规范
- 位置:
create-question-dialog.tsx:62—(content as { text?: unknown }).textcreate-question-dialog.tsx:71—(content as { options?: unknown }).optionscreate-question-dialog.tsx:78—(opt as { id?: unknown; value?: unknown }).idcreate-question-dialog.tsx:80—(opt as { text?: unknown; label?: unknown }).textcreate-question-dialog.tsx:82—(opt as { isCorrect?: unknown }).isCorrectquestion-columns.tsx:58—(content as { text?: unknown }).textquestion-bank-list.tsx:108-112—q.content as { text?: string }、parsed as unknown
- 问题:从
unknown到具体类型使用了as断言而非类型守卫。 - 违反规则:项目规则「禁止
as断言(除非从unknown转换或测试中,需注释原因)」。虽然这些是从unknown转换,但未使用类型守卫函数,且未注释原因。 - 后果:类型不安全,运行时可能访问不存在的属性。
P1-2:筛选栏组件重复实现
- 位置:
question-filters.tsx(模块内自有实现)vsshared/components/question/question-bank-filters.tsx(共享组件) - 问题:模块内的
QuestionFilters没有复用共享QuestionBankFilters,两者功能高度重叠(搜索 + 题型 + 难度),但实现完全独立。共享组件已被 exams 和 lesson-preparation 模块使用。 - 违反规则:项目规则「Shared components must be extracted when page duplication exceeds 90%」。
- 后果:维护两套筛选 UI,样式和行为不一致,修改需同步两处。
P1-3:客户端组件未做权限感知
- 位置:
create-question-button.tsx、question-actions.tsx - 问题:
CreateQuestionButton无条件渲染,不检查用户是否有QUESTION_CREATE权限。QuestionActions的编辑/删除按钮也不检查QUESTION_UPDATE/QUESTION_DELETE权限。 - 违反规则:项目规则「前端权限判断统一使用
usePermission().hasPermission()」。 - 后果:无权限用户看到操作按钮,点击后才在 Server Action 层被拒绝,体验差。
P1-4:题目内容 content: unknown 类型不安全
- 位置:
types.ts:27、data-access.ts、create-question-dialog.tsx、question-columns.tsx - 问题:
Question.content类型为unknown,各处用as或typeof手动解析,没有统一的类型定义和解析函数。题目内容实际有明确结构({ text, options?, answer?, explanation? }),但未类型化。 - 违反规则:项目规则「禁止
any:未知类型用unknown并做类型守卫」——虽然用了unknown,但缺少类型守卫。 - 后果:解析逻辑分散在 4+ 个文件中,重复且易错。
P1-5:无级联筛选的数据访问支持
- 位置:
data-access.ts:getQuestions参数仅有knowledgePointId,无textbookId/chapterId过滤 - 问题:要实现「课本 → 章节 → 知识点」级联筛选,需要
getQuestions支持按textbookId、chapterId过滤,以及提供getTextbookOptions、getChaptersByTextbookId等级联数据接口。当前均缺失。 - 违反规则:架构原则「可扩展性」。
- 后果:无法实现用户要求的级联筛选。
P2 — 中优先级问题
P2-1:仅支持教师角色,无多角色复用
- 位置:仅
src/app/(dashboard)/teacher/questions/存在 - 问题:admin 角色无法管理题库(虽然 admin 有
QUESTION_*全部权限),parent/student 无法查看题目。组件未抽象为可配置化多角色复用。 - 违反规则:架构原则「最大化复用:识别四个角色共用的 UI 块」。
- 后果:新增角色需复制整个页面。
P2-2:无题目预览渲染
- 位置:
question-columns.tsx:52-72、question-actions.tsx:160-163 - 问题:题目内容预览仅截取前 80 字符的纯文本或
JSON.stringify,无结构化渲染(选项列表、正确答案高亮等)。 - 违反规则:架构原则「交互不符合多角色使用习惯」。
- 后果:教师无法快速判断题目内容,需逐个点开查看。
P2-3:无批量操作
- 位置:
question-data-table.tsx有行选择 checkbox,但无批量操作 UI - 问题:表格支持多选但无批量删除、批量导出、批量移动知识点等操作。
- 违反规则:架构原则「可扩展性」。
- 后果:教师需逐条操作,效率低。
P2-4:无题目导入/导出
- 位置:缺失
- 问题:不支持从 Excel/Word 导入题目,也不支持导出。
- 违反规则:行业差距(见第三节)。
- 后果:教师无法迁移已有题库。
P2-5:create-question-dialog.tsx 接近行数上限
- 位置:
create-question-dialog.tsx(453 行) - 问题:接近 500 行组件上限。包含表单、知识点选择、选项编辑三块逻辑。
- 违反规则:项目规则「React 组件:建议 ≤ 500 行」。
- 后果:维护困难,难以测试。
P2-6:无监控埋点
- 位置:缺失
- 问题:无题目创建/删除/搜索的操作埋点接口。
- 违反规则:架构原则「监控:方案中预留关键操作埋点接口」。
- 后果:无法分析题库使用情况。
P2-7:架构图 action 名称与代码不一致
- 位置:
005_architecture_data.json:4708记录createNestedQuestion,实际代码为createQuestionAction - 问题:架构图与代码不同步。
- 违反规则:项目规则「改码必同步图」。
- 后果:架构图不可信。
三、行业差距对比
3.1 与主流 K12 产品的差距
| 能力 | 主流产品(组卷网/学科网/智学网) | 当前实现 | 差距影响 |
|---|---|---|---|
| 级联筛选 | 教材→章节→课时→知识点四级级联 | 扁平知识点下拉 | 教师无法按教学进度选题 |
| 题目预览 | 结构化渲染(题干+选项+答案+解析) | 截取 80 字符纯文本 | 无法快速判断题目内容 |
| 批量操作 | 批量删除/移动/导出 | 仅有多选 UI 无操作 | 效率低 |
| 导入导出 | Excel/Word 模板导入导出 | 无 | 无法迁移已有题库 |
| 题目难度标签 | 易/中/难 + 星级 | 数字 1-5 | 不够直观 |
| 题目来源标注 | 自编/教材/网络 | 仅记录 author | 无法追溯题目来源 |
| 题目使用统计 | 被引用次数/正确率 | 仅 childrenCount |
无法评估题目质量 |
| 题目版本管理 | 修改历史 | 无 | 无法回溯修改 |
| 富文本编辑 | 公式/图片/表格 | 纯文本 textarea | 无法编辑理科题目 |
| 多角色支持 | 教师建题/admin管理/学生练习 | 仅教师 | admin 无法管理 |
3.2 关键差距分析
-
级联筛选是最大痛点:K12 教学严格按教材章节进度推进,教师选题时必然先定位到当前教学进度对应的章节,再在该章节的知识点下筛选题目。扁平列表完全不符合此工作流。
-
题目预览缺失影响效率:教师在组卷/备课选题时,需要快速浏览多道题目的完整内容(含选项和答案),当前仅显示截断文本,必须逐个点开查看,严重影响效率。
-
无导入导出限制迁移:学校通常有存量题库(Word/Excel),无法导入意味着教师需手动重新录入所有题目,阻力极大。
四、改进优先级建议
P0 — 立即实施
| 编号 | 问题 | 改进方向 |
|---|---|---|
| P0-1 | 筛选方式不符合教学场景 | 新增「教材→章节→知识点」三级级联筛选组件,data-access 层新增 getTextbookOptionsForQuestions、getChaptersByTextbookId 接口,getQuestions 支持 textbookId/chapterId 参数 |
| P0-2 | 缺失 i18n | 创建 questions.json 翻译文件(中/英),所有组件使用 useTranslations("questions") |
| P0-3 | 缺失 error.tsx | 新增 error.tsx 错误边界 |
P1 — 本轮实施
| 编号 | 问题 | 改进方向 |
|---|---|---|
| P1-1 | as 类型断言 |
提取 parseQuestionContent 类型守卫函数到 utils/parse-content.ts,替换所有 as 断言 |
| P1-2 | 筛选栏重复 | 模块内 QuestionFilters 改为复用共享 QuestionBankFilters + 新增级联筛选扩展 |
| P1-3 | 客户端权限感知 | CreateQuestionButton、QuestionActions 使用 usePermission().hasPermission() |
| P1-4 | content 类型不安全 | 定义 QuestionContent 类型,替换 unknown |
| P1-5 | 无级联筛选数据支持 | data-access 新增级联数据接口 |
P2 — 中长期计划
| 编号 | 问题 | 改进方向 |
|---|---|---|
| P2-1 | 仅教师角色 | 抽象角色配置,新增 admin 题库页面 |
| P2-2 | 无题目预览 | 新增 QuestionPreview 组件,结构化渲染 |
| P2-3 | 无批量操作 | 新增批量操作工具栏 |
| P2-4 | 无导入导出 | 中长期:Excel 模板导入导出 |
| P2-5 | dialog 行数 | 拆分为 QuestionFormFields + KnowledgePointSelector + OptionsEditor |
| P2-6 | 无监控埋点 | 预留 trackQuestionEvent 接口 |
| P2-7 | 架构图不同步 | 同步 action 名称、依赖矩阵、跨模块接口 |
五、架构图同步说明
需要同步更新以下内容:
5.1 005_architecture_data.json
- 依赖矩阵:
dependencyMatrix.questions.dependsOn添加"textbooks",uses添加textbooks: ["data-access.getKnowledgePointOptions"] - action 名称:
createNestedQuestion→createQuestionAction - 跨模块接口:在
dataAccess中补充getKnowledgePointsForQuestions、getQuestionsContentForErrorCollection - 新增 data-access 函数:
getTextbookOptionsForQuestions、getChaptersForCascade、getQuestions参数新增textbookId/chapterId - 新增组件:
QuestionCascadeFilter、QuestionPreview、QuestionContentRenderer - 新增 utils:
utils/parse-content.ts
5.2 004_architecture_impact_map.md
- 题库模块章节:更新筛选方式说明(级联筛选)、新增 i18n 说明
- 依赖关系:补充 questions → textbooks 依赖
- 组件清单:新增级联筛选、预览组件
六、实施记录
以下改进已在本次审计中实施(含中长期计划,全部完成):
P0 修复
- P0-1:新增「教材→章节→知识点」三级级联筛选,data-access 支持
textbookId/chapterId参数 - P0-2:创建
questions.jsoni18n 翻译文件(中/英),所有组件迁移到useTranslations - P0-3:新增
error.tsx错误边界(teacher + admin)
P1 修复
- P1-1:提取
parseQuestionContent类型守卫,替换所有as断言 - P1-2:模块筛选栏复用共享组件 + 级联扩展(QuestionCascadeFilter)
- P1-3:客户端组件添加
usePermission()权限感知 - P1-4:定义
QuestionContent类型 - P1-5:data-access 新增级联数据接口(getTextbookOptions/getChapterOptions/getKnowledgePointOptionsByChapter)
P2 修复
- P2-5:拆分 create-question-dialog.tsx(453→281 行,拆分 KnowledgePointSelector + OptionsEditor)
- P2-6:预留
trackQuestionEvent埋点接口(no-op 实现) - P2-7:同步架构图(004 + 005)
P2 中长期计划(已全部实施)
- P2-1:多角色支持 — 新增
admin/questions页面(page.tsx + error.tsx + loading.tsx),复用全部 questions 模块组件,权限感知自动适配 - P2-2:题目结构化预览 — 新增
QuestionContentRenderer组件,支持题干/选项/答案/解析结构化渲染,单选圆点/多选方框视觉区分,正确答案高亮,已集成到查看详情对话框 - P2-3:批量操作 — 新增
deleteQuestionsBatchdata-access +deleteQuestionsBatchActionServer Action +BatchOperations组件,表格行选择后显示批量删除工具栏,权限感知,事务保证原子性 - P2-4:导入导出 — 新增
exportQuestions/importQuestionsdata-access +exportQuestionsAction/importQuestionsActionServer Action +ImportExportButtons组件,支持 JSON 格式导出下载和文件上传导入,权限感知,导入前预览确认
新增文件清单
| 文件 | 行数 | 职责 |
|---|---|---|
src/app/(dashboard)/admin/questions/page.tsx |
136 | 管理员题库页面(复用 questions 模块组件) |
src/app/(dashboard)/admin/questions/error.tsx |
29 | 管理员题库错误边界 |
src/app/(dashboard)/admin/questions/loading.tsx |
29 | 管理员题库加载骨架屏 |
src/modules/questions/components/question-content-renderer.tsx |
110+ | 题目结构化渲染(题干/选项/答案/解析) |
src/modules/questions/components/batch-operations.tsx |
110+ | 批量操作工具栏(批量删除) |
src/modules/questions/components/import-export-buttons.tsx |
180+ | 导入导出组件(JSON 格式) |
修改文件清单
| 文件 | 变更 |
|---|---|
src/modules/questions/data-access.ts |
新增 deleteQuestionsBatch/exportQuestions/importQuestions + 类型定义 |
src/modules/questions/actions.ts |
新增 deleteQuestionsBatchAction/exportQuestionsAction/importQuestionsAction,所有 Action 增加 revalidatePath("/admin/questions") |
src/modules/questions/components/question-data-table.tsx |
集成 BatchOperations 组件,支持 getRowId + 行选择 |
src/modules/questions/components/question-bank-results-client.tsx |
传入 getRowId 支持批量操作 |
src/modules/questions/components/question-actions.tsx |
查看详情改用 QuestionContentRenderer 结构化渲染 |
src/app/(dashboard)/teacher/questions/page.tsx |
新增 ImportExportButtons |
src/app/(dashboard)/admin/questions/page.tsx |
新增 ImportExportButtons |
src/shared/i18n/messages/zh-CN/questions.json |
新增 batch + importExport 命名空间 |
src/shared/i18n/messages/en/questions.json |
新增 batch + importExport 命名空间 |
验证结果
- ✅ TypeScript:questions 模块零错误(
npx tsc --noEmit仅余其他模块预存错误) - ✅ ESLint:questions 模块 + admin/questions + teacher/questions 零错误零警告(
--max-warnings=0) - ✅ 架构图同步:004 + 005 已更新