Files
CICD/docs/design/005_exam_module_implementation.md
SpecialX 13e91e628d
Some checks failed
CI / build-and-test (push) Failing after 3m34s
CI / deploy (push) Has been skipped
Merge exams grading into homework
Redirect /teacher/exams/grading* to /teacher/homework/submissions; remove exam grading UI/actions/data-access; add homework student workflow and update design docs.
2025-12-31 11:59:03 +08:00

7.5 KiB
Raw Permalink Blame History

考试模块实现设计文档

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:

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): 负责解析 queryq/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_questionsexam_submissionssubmission_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/archivedquestionsJson(可选),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_questionsexam_submissionssubmission_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。