Redirect /teacher/exams/grading* to /teacher/homework/submissions; remove exam grading UI/actions/data-access; add homework student workflow and update design docs.
162 lines
7.5 KiB
Markdown
162 lines
7.5 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
|
||
|
||
## 7. 变更记录(合并 Homework)
|
||
|
||
**日期**:2025-12-31
|
||
|
||
- 移除 Exams grading 入口与实现:删除阅卷 UI、server action、data-access 查询。
|
||
- Exams grading 路由改为重定向到 Homework submissions。
|