feat: exam actions and data safety fixes

This commit is contained in:
SpecialX
2025-12-30 17:48:22 +08:00
parent e7c902e8e1
commit f7ff018490
27 changed files with 896 additions and 194 deletions

View File

@@ -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 数据