feat: exam actions and data safety fixes
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
- **SubmissionAnswers**: 链接到特定题目的单个答案。
|
||||
|
||||
### 2.2 `structure` 字段
|
||||
为了支持层级布局(如章节/分组),我们在 `exams` 表中引入了一个 JSON 列 `structure`。这作为考试布局的“单一事实来源”(Source of Truth)。
|
||||
为了支持层级布局(如章节/分组),我们在 `exams` 表中引入了一个 JSON 列 `structure`。它作为“布局/呈现层”的单一事实来源(Source of Truth),用于渲染分组与排序;而 `exam_questions` 仍然承担题目关联、外键完整性与索引查询职责。
|
||||
|
||||
**JSON Schema:**
|
||||
```typescript
|
||||
@@ -26,6 +26,9 @@ type ExamNode = {
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 `description` 元数据字段(当前实现)
|
||||
当前版本将部分元数据(如 `subject/grade/difficulty/totalScore/durationMin/tags/scheduledAt`)以 JSON 字符串形式存入 `exams.description`,并在数据访问层解析后提供给列表页展示与筛选。
|
||||
|
||||
## 3. 组件架构
|
||||
|
||||
### 3.1 组卷(构建器)
|
||||
@@ -52,6 +55,13 @@ type ExamNode = {
|
||||
- **状态**: 在提交前管理本地更改。
|
||||
- **Actions**: `gradeSubmissionAction` 更新 `submissionAnswers` 并将总分聚合到 `examSubmissions`。
|
||||
|
||||
### 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 创建与构建考试
|
||||
@@ -72,6 +82,27 @@ type ExamNode = {
|
||||
- 教师输入分数(上限为满分)和反馈。
|
||||
4. **提交**: 服务器更新单个答案记录并重新计算提交总分。
|
||||
|
||||
### 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 混合存储策略
|
||||
@@ -87,4 +118,46 @@ type ExamNode = {
|
||||
- 面向未来(现代 React Hooks 模式)。
|
||||
|
||||
### 5.3 Server Actions
|
||||
所有变更操作(保存草稿、发布、评分)均使用 Next.js Server Actions,以确保类型安全并自动重新验证缓存。
|
||||
所有变更操作(保存草稿、发布、复制、删除、评分)均使用 Next.js Server Actions,以确保类型安全并自动重新验证缓存。
|
||||
|
||||
已落地的 Server Actions:
|
||||
- `createExamAction`
|
||||
- `updateExamAction`
|
||||
- `duplicateExamAction`
|
||||
- `deleteExamAction`
|
||||
- `gradeSubmissionAction`
|
||||
|
||||
## 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")`
|
||||
- `revalidatePath("/teacher/exams/grading")`
|
||||
|
||||
### 6.4 数据访问层(Data Access)
|
||||
位于 `src/modules/exams/data-access.ts`,对外提供与页面/组件解耦的查询函数。
|
||||
|
||||
- `getExams(params)`: 支持按 `q/status` 在数据库侧过滤;`difficulty` 因当前存储在 `description` JSON 中,采用内存过滤
|
||||
- `getExamById(id)`: 查询 exam 及其 `exam_questions`,并返回 `structure` 以用于构建器 Hydrate
|
||||
- `getExamSubmissions()`: 为阅卷列表提供 submissions 数据
|
||||
|
||||
Reference in New Issue
Block a user