> ⚠️ **已归档文档**
> 本文档记录的是考试模块的实现设计（含与作业模块合并的调整说明）。
> 当前实现已演进，最新架构与组件清单详见 [004 架构影响地图](../architecture/004_architecture_impact_map.md) 的 exams 模块章节。
> 保留用于历史参考，不再维护。

---

# 考试模块实现设计文档

## 1. 概述
考试模块用于教师侧的"试卷制作与管理"，覆盖创建考试、组卷（支持嵌套分组）、发布/归档等流程。

**说明（合并调整）**：与“作业（Homework）”模块合并后，考试模块不再提供“阅卷/评分（grading）”与提交流转；教师批改统一在 Homework 的 submissions 中完成。

## 2. 数据架构

### 2.1 核心实体
- **Exams**: 根实体，包含元数据（标题、时间安排）和结构信息。
- **ExamQuestions**: 关系链接，用于查询题目的使用情况（扁平化表示）。
- **ExamSubmissions**: （历史/保留）学生的考试尝试记录；当前 UI/路由不再使用。
- **SubmissionAnswers**: （历史/保留）链接到特定题目的单个答案；当前 UI/路由不再使用。

### 2.2 `structure` 字段
为了支持层级布局（如章节/分组），我们在 `exams` 表中引入了一个 JSON 列 `structure`。它作为“布局/呈现层”的单一事实来源（Source of Truth），用于渲染分组与排序；而 `exam_questions` 仍然承担题目关联、外键完整性与索引查询职责。

**JSON Schema:**
```typescript
type ExamNode = {
  id: string;              // 节点的唯一 UUID
  type: 'group' | 'question';
  title?: string;          // 'group' 类型必填
  questionId?: string;     // 'question' 类型必填
  score?: number;          // 在此考试上下文中的分值
  children?: ExamNode[];   // 'group' 类型的递归子节点
}
```

### 2.3 `description` 元数据字段（当前实现）
当前版本将部分元数据（如 `subject/grade/difficulty/totalScore/durationMin/tags/scheduledAt`）以 JSON 字符串形式存入 `exams.description`，并在数据访问层解析后提供给列表页展示与筛选。

## 3. 组件架构

### 3.1 组卷（构建器）
位于 `/teacher/exams/[id]/build`。

- **`ExamAssembly` (客户端组件)**
  - 管理 `structure` 状态树。
  - 处理“添加题目”、“添加章节”、“移除”和“重新排序”操作。
  - 实时计算总分和进度。
- **`StructureEditor` (客户端组件)**
  - 基于 `@dnd-kit` 构建。
  - 提供嵌套的可排序（Sortable）界面。
  - 支持在组内/组间拖拽题目（当前优化为 2 层深度）。
- **`QuestionBankList`**
  - 可搜索/筛选的可用题目列表。
  - “添加”操作将节点追加到结构树中。

### 3.2 阅卷界面（已下线）
原阅卷路由 `/teacher/exams/grading` 与 `/teacher/exams/grading/[submissionId]` 已移除业务能力并重定向到 Homework：
- `/teacher/exams/grading*` → `/teacher/homework/submissions`

### 3.3 列表页（All Exams）
位于 `/teacher/exams/all`。

- **Page (RSC)**: 负责解析 query（`q/status/difficulty`）并调用数据访问层获取 exams。
- **`ExamFilters` (客户端组件)**: 使用 URL query 驱动筛选条件。
- **`ExamDataTable` (客户端组件)**: 基于 TanStack Table 渲染列表，并在 actions 列中渲染 `ExamActions`。

## 4. 关键工作流

### 4.1 创建与构建考试
1.  **创建**: 教师输入基本信息（标题、科目）。数据库创建记录（草稿状态）。
2.  **构建**:
    - 教师打开“构建”页面。
    - 服务器从数据库 Hydrate（注水）`initialStructure`。
    - 教师从题库拖拽题目到结构树。
    - 教师创建章节（分组）。
    - **保存**: 同时提交 `questionsJson`（扁平化，用于索引）和 `structureJson`（树状，用于布局）到 `updateExamAction`。
3.  **发布**: 状态变更为 `published`。

### 4.2 阅卷/批改流程（迁移到 Homework）
教师批改统一在 Homework 模块完成：
- 提交列表：`/teacher/homework/submissions`
- 批改页：`/teacher/homework/submissions/[submissionId]`

### 4.3 考试管理（All Exams Actions）
位于 `/teacher/exams/all` 的表格行级菜单。

1. **Publish / Move to Draft / Archive**
   - 客户端组件 `ExamActions` 触发 `updateExamAction`，传入 `examId` 与目标 `status`。
   - 服务器更新 `exams.status`，并对 `/teacher/exams/all` 执行缓存再验证。

2. **Duplicate**
   - 客户端组件 `ExamActions` 触发 `duplicateExamAction`，传入 `examId`。
   - 服务器复制 `exams` 记录并复制关联的 `exam_questions`。
   - 新考试以 `draft` 状态创建，复制结构（`exams.structure`），并清空排期信息（`startTime/endTime`，以及 description 中的 `scheduledAt`）。
   - 成功后跳转到新考试的构建页 `/teacher/exams/[id]/build`。

3. **Delete**
   - 客户端组件 `ExamActions` 触发 `deleteExamAction`，传入 `examId`。
   - 服务器删除 `exams` 记录；相关表（如 `exam_questions`、`exam_submissions`、`submission_answers`）通过外键级联删除。
   - 成功后刷新列表。

4. **Edit / Build**
   - 当前统一跳转到 `/teacher/exams/[id]/build`。

## 5. 技术决策

### 5.1 混合存储策略
我们在存储考试题目时采用了 **混合方法**：
- **关系型 (`exam_questions`)**: 用于“查找所有使用题目 X 的考试”查询和外键约束。
- **文档型 (`exams.structure`)**: 用于渲染嵌套 UI 和保留任意排序/分组。
*理由*: 这结合了 SQL 的完整性和 NoSQL 在 UI 布局上的灵活性。

### 5.2 拖拽功能
使用 `@dnd-kit` 代替旧库，因为：
- 更好的无障碍支持（键盘支持）。
- 模块化架构（Sensors, Modifiers）。
- 面向未来（现代 React Hooks 模式）。

### 5.3 Server Actions
所有变更操作（保存草稿、发布、复制、删除）均使用 Next.js Server Actions，以确保类型安全并自动重新验证缓存。

已落地的 Server Actions：
- `createExamAction`
- `updateExamAction`
- `duplicateExamAction`
- `deleteExamAction`

## 6. 接口与数据影响

### 6.1 `updateExamAction`
- **入参（FormData）**: `examId`（必填），`status`（可选：draft/published/archived），`questionsJson`（可选），`structureJson`（可选）
- **行为**:
  - 若传入 `questionsJson`：先清空 `exam_questions` 再批量写入，`order` 由数组顺序决定；未传入则不触碰 `exam_questions`
  - 若传入 `structureJson`：写入 `exams.structure`；未传入则不更新该字段
  - 若传入 `status`：写入 `exams.status`
- **缓存**: `revalidatePath("/teacher/exams/all")`

### 6.2 `duplicateExamAction`
- **入参（FormData）**: `examId`（必填）
- **行为**:
  - 复制一条 `exams`（新 id、新 title：追加 “(Copy)”、`status` 强制为 `draft`）
  - `startTime/endTime` 置空；同时尝试从 `description` JSON 中移除 `scheduledAt`
  - 复制 `exam_questions`（保留 questionId/score/order）
  - 复制 `exams.structure`
- **缓存**: `revalidatePath("/teacher/exams/all")`

### 6.3 `deleteExamAction`
- **入参（FormData）**: `examId`（必填）
- **行为**:
  - 删除 `exams` 记录
  - 依赖外键级联清理关联数据：`exam_questions`、`exam_submissions`、`submission_answers`
- **缓存**:
  - `revalidatePath("/teacher/exams/all")`

### 6.4 数据访问层（Data Access）
位于 `src/modules/exams/data-access.ts`，对外提供与页面/组件解耦的查询函数。

- `getExams(params)`: 支持按 `q/status` 在数据库侧过滤；`difficulty` 因当前存储在 `description` JSON 中，采用内存过滤
- `getExamById(id)`: 查询 exam 及其 `exam_questions`，并返回 `structure` 以用于构建器 Hydrate

### 6.5 `getExamPreviewAction` (新增)
- **入参**: `examId` (string)
- **行为**:
  - 查询指定 exam 及其关联的 questions (通过 `exam_questions` 关系)。
  - 返回完整的 `structure` (JSON 树) 和扁平化的 `questions` 列表。
  - 用于预览弹窗的数据加载。

## 7. 变更记录

**日期**：2026-03-03

- **题库列表稳定性**:
  - 题库卡片对题目 content/type 做解析兜底，避免异常数据导致运行时崩溃。
  - 主要修改： [question-bank-list.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/assembly/question-bank-list.tsx)

**日期**：2026-01-12 (当前)

- **列表页优化 (`/teacher/exams/all`)**:
  - 移除了冗余的 "All Exams" 页面标题和描述。
  - 重构了表格列 (`ExamColumns`)：
    - 合并标题、标签、科目、年级为 "Exam Info" 列。
    - 合并题目数、总分、时长为 "Stats" 列。
    - 合并创建时间和预定时间为 "Date" 列。
    - 优化了状态 (Status) 和难度 (Difficulty) 的视觉样式 (Badge, Progress bar)。
  - 优化了表格分页和布局 (`ExamDataTable`)。

- **预览功能增强**:
  - 新增直接预览功能：在操作列添加了 "View" (眼睛图标) 按钮。
  - 点击 "View" 触发 `getExamPreviewAction` 获取完整试卷数据。
  - 弹窗 (`Dialog`) 直接展示试卷内容 (`ExamPaperPreview`)，移除了冗余的头部描述，优化了滚动体验。
  - 修复了可访问性问题 (DialogTitle)。

- **组卷页面升级 (`/teacher/exams/[id]/build`)**:
  - **布局重构**: 扩展工作区高度，调整左右面板比例 (2:1)，优化头部信息展示和进度可视化。
  - **题库增强**: 实现了基于 Server Action (`getQuestionsAction`) 的分页加载和服务器端筛选，提升大数据量下的性能；优化了搜索和筛选器 UI。
  - **预览优化**: 移除了内联预览，改为通过 "Preview" 按钮触发弹窗预览，避免干扰编辑流。
  - **视觉降噪**: 移除了页面顶部冗余的标题和描述。

**日期**：2025-12-31

- 移除 Exams grading 入口与实现：删除阅卷 UI、server action、data-access 查询。
- Exams grading 路由改为重定向到 Homework submissions。
