# 题库模块审计报告
> 审计日期: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 已更新