192 lines
9.2 KiB
Markdown
192 lines
9.2 KiB
Markdown
# 考试模块实现设计文档
|
||
|
||
## 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-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。
|