# 题库模块审计报告 > 审计日期: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(项目规则允许) ├─ ← 客户端组件,调用 getKnowledgePointOptionsAction └─ ← 客户端表格 └─ ← 行操作 ├─ ← 编辑(调用 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 }).text` - `create-question-dialog.tsx:71` — `(content as { options?: unknown }).options` - `create-question-dialog.tsx:78` — `(opt as { id?: unknown; value?: unknown }).id` - `create-question-dialog.tsx:80` — `(opt as { text?: unknown; label?: unknown }).text` - `create-question-dialog.tsx:82` — `(opt as { isCorrect?: unknown }).isCorrect` - `question-columns.tsx:58` — `(content as { text?: unknown }).text` - `question-bank-list.tsx:108-112` — `q.content as { text?: string }`、`parsed as unknown` - **问题**:从 `unknown` 到具体类型使用了 `as` 断言而非类型守卫。 - **违反规则**:项目规则「禁止 `as` 断言(除非从 `unknown` 转换或测试中,需注释原因)」。虽然这些是从 `unknown` 转换,但未使用类型守卫函数,且未注释原因。 - **后果**:类型不安全,运行时可能访问不存在的属性。 #### P1-2:筛选栏组件重复实现 - **位置**:`question-filters.tsx`(模块内自有实现)vs `shared/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 关键差距分析 1. **级联筛选是最大痛点**:K12 教学严格按教材章节进度推进,教师选题时必然先定位到当前教学进度对应的章节,再在该章节的知识点下筛选题目。扁平列表完全不符合此工作流。 2. **题目预览缺失影响效率**:教师在组卷/备课选题时,需要快速浏览多道题目的完整内容(含选项和答案),当前仅显示截断文本,必须逐个点开查看,严重影响效率。 3. **无导入导出限制迁移**:学校通常有存量题库(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` 1. **依赖矩阵**:`dependencyMatrix.questions.dependsOn` 添加 `"textbooks"`,`uses` 添加 `textbooks: ["data-access.getKnowledgePointOptions"]` 2. **action 名称**:`createNestedQuestion` → `createQuestionAction` 3. **跨模块接口**:在 `dataAccess` 中补充 `getKnowledgePointsForQuestions`、`getQuestionsContentForErrorCollection` 4. **新增 data-access 函数**:`getTextbookOptionsForQuestions`、`getChaptersForCascade`、`getQuestions` 参数新增 `textbookId`/`chapterId` 5. **新增组件**:`QuestionCascadeFilter`、`QuestionPreview`、`QuestionContentRenderer` 6. **新增 utils**:`utils/parse-content.ts` ### 5.2 `004_architecture_impact_map.md` 1. 题库模块章节:更新筛选方式说明(级联筛选)、新增 i18n 说明 2. 依赖关系:补充 questions → textbooks 依赖 3. 组件清单:新增级联筛选、预览组件 --- ## 六、实施记录 以下改进已在本次审计中实施(含中长期计划,全部完成): ### P0 修复 - [x] P0-1:新增「教材→章节→知识点」三级级联筛选,data-access 支持 `textbookId`/`chapterId` 参数 - [x] P0-2:创建 `questions.json` i18n 翻译文件(中/英),所有组件迁移到 `useTranslations` - [x] P0-3:新增 `error.tsx` 错误边界(teacher + admin) ### P1 修复 - [x] P1-1:提取 `parseQuestionContent` 类型守卫,替换所有 `as` 断言 - [x] P1-2:模块筛选栏复用共享组件 + 级联扩展(QuestionCascadeFilter) - [x] P1-3:客户端组件添加 `usePermission()` 权限感知 - [x] P1-4:定义 `QuestionContent` 类型 - [x] P1-5:data-access 新增级联数据接口(getTextbookOptions/getChapterOptions/getKnowledgePointOptionsByChapter) ### P2 修复 - [x] P2-5:拆分 create-question-dialog.tsx(453→281 行,拆分 KnowledgePointSelector + OptionsEditor) - [x] P2-6:预留 `trackQuestionEvent` 埋点接口(no-op 实现) - [x] P2-7:同步架构图(004 + 005) ### P2 中长期计划(已全部实施) - [x] P2-1:多角色支持 — 新增 `admin/questions` 页面(page.tsx + error.tsx + loading.tsx),复用全部 questions 模块组件,权限感知自动适配 - [x] P2-2:题目结构化预览 — 新增 `QuestionContentRenderer` 组件,支持题干/选项/答案/解析结构化渲染,单选圆点/多选方框视觉区分,正确答案高亮,已集成到查看详情对话框 - [x] P2-3:批量操作 — 新增 `deleteQuestionsBatch` data-access + `deleteQuestionsBatchAction` Server Action + `BatchOperations` 组件,表格行选择后显示批量删除工具栏,权限感知,事务保证原子性 - [x] P2-4:导入导出 — 新增 `exportQuestions`/`importQuestions` data-access + `exportQuestionsAction`/`importQuestionsAction` Server 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 已更新