Files
CICD/docs/design/005_exam_module_implementation.md

9.2 KiB
Raw 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

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。