# 题库模块审计报告

> 审计日期：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 }).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 已更新
