13 KiB
作业模块实现设计文档(Homework Module)
日期: 2025-12-31
模块: Homework (src/modules/homework)
1. 概述
作业模块提供“由试卷派发作业”的完整生命周期:
- 教师从已存在的 Exam 派发 Homework Assignment(冻结当时的结构与题目引用)
- 指定作业目标学生(Targets)
- 学生开始一次作答(Submission),保存答案(Answers),并最终提交
- 教师在提交列表中查看并批改(按题给分/反馈,汇总总分)
核心目标是:在不破坏 Exam 本体数据的前提下,为作业提供可追溯、可批改、可统计的独立域模型。
说明(合并调整):教师端“阅卷/批改”统一通过 Homework submissions 完成,/teacher/exams/grading* 相关路由已重定向到 /teacher/homework/submissions。
2. 数据架构
2.1 核心实体
homework_assignments: 作业实例(从 exam 派生)homework_assignment_questions: 作业与题目关系(score/order)homework_assignment_targets: 作业目标学生列表homework_submissions: 学生作业尝试(attempt_no/status/时间/是否迟交)homework_answers: 每题答案(answer_content/score/feedback)
数据库变更记录见:schema-changelog.md
2.2 设计要点:冻结 Exam → Homework Assignment
homework_assignments.source_exam_id保存来源 Examhomework_assignments.structure在 publish 时复制exams.structure(冻结当时的呈现结构)- 题目关联使用
homework_assignment_questions(仍引用questions表,作业侧记录分值与顺序)
3. 路由与页面
3.1 教师端
/teacher/homework/assignments: 作业列表
实现:assignments/page.tsx/teacher/homework/assignments/create: 从 Exam 派发作业
实现:create/page.tsx/teacher/homework/assignments/[id]: 作业详情
实现:[id]/page.tsx/teacher/homework/assignments/[id]/submissions: 作业提交列表(按作业筛选)
实现:[id]/submissions/page.tsx/teacher/homework/submissions: 全部提交列表
实现:submissions/page.tsx/teacher/homework/submissions/[submissionId]: 批改页
实现:[submissionId]/page.tsx
关联重定向:
/teacher/exams/grading→/teacher/homework/submissions
实现:grading/page.tsx/teacher/exams/grading/[submissionId]→/teacher/homework/submissions
实现:grading/[submissionId]/page.tsx
3.2 学生端
/student/learning/assignments: 作业列表
实现:page.tsx/student/learning/assignments/[assignmentId]: 作答页(开始/保存/提交)
实现:[assignmentId]/page.tsx
4. 数据访问层(Data Access)
数据访问位于:data-access.ts
4.1 教师侧查询
getHomeworkAssignments:作业列表(可按 creatorId/ids)getHomeworkAssignmentById:作业详情(含目标人数、提交数统计)getHomeworkSubmissions:提交列表(可按 assignmentId/classId/creatorId)getHomeworkSubmissionDetails:提交详情(题目内容 + 学生答案 + 分值/顺序)
4.2 学生侧查询
getStudentHomeworkAssignments(studentId):只返回“已派发给该学生、已发布、且到达 availableAt”的作业getStudentHomeworkTakeData(assignmentId, studentId):进入作答页所需数据(assignment + 当前/最近 submission + 题目列表 + 已保存答案)
4.3 开发模式用户选择(Demo)
为了在未接入真实 Auth 的情况下可演示学生端页面,提供:
getDemoStudentUser():优先选取最早创建的 student;若无 student,则退化到任意用户
5. Server Actions
实现位于:actions.ts
5.1 教师侧
createHomeworkAssignmentAction:从 exam 创建 assignment;可写入 targets;可选择 publish(默认 true)gradeHomeworkSubmissionAction:按题写入 score/feedback,并汇总写入 submission.score 与 status=graded
5.2 学生侧
startHomeworkSubmissionAction:创建一次 submission(attemptNo + startedAt),并校验:- assignment 已发布
- student 在 targets 中
- availableAt 已到
- 未超过 maxAttempts
saveHomeworkAnswerAction:保存/更新某题答案(upsert 到 homework_answers)submitHomeworkAction:提交作业(校验 dueAt/lateDueAt/allowLate,写入 submittedAt/isLate/status=submitted)
6. UI 组件
6.1 教师批改视图
- HomeworkGradingView
- 左侧:学生答案只读展示
- 右侧:按题录入分数与反馈,并提交批改
6.2 学生作答视图
- HomeworkTakeView
- Start:开始一次作答
- Save:按题保存
- Submit:提交(提交前会先保存当前题目答案)
- 题型支持:
text/judgment/single_choice/multiple_choice
题目 content 约定与题库一致:{ text, options?: [{ id, text, isCorrect? }] }(作答页仅消费 id/text)。
7. 类型定义
类型位于:types.ts
- 教师侧:
HomeworkAssignmentListItem/HomeworkSubmissionDetails等 - 学生侧:
StudentHomeworkAssignmentListItem/StudentHomeworkTakeData等
8. 校验
npm run typecheck: 通过npm run lint: 0 errors(仓库其他位置存在 warnings,与本模块新增功能无直接关联)
9. 部署与环境变量(CI/CD)
9.1 本地开发
- 本地开发使用项目根目录的
.env提供DATABASE_URL .env仅用于本机开发,不应写入真实生产库凭据
9.2 CI 构建与部署(Gitea)
工作流位于:ci.yml
- 构建阶段(
npm run build)不依赖数据库连接:作业相关页面在构建时不会静态预渲染执行查库 - 部署阶段通过
docker run -e DATABASE_URL=...在运行时注入数据库连接串 - 需要在 Gitea 仓库 Secrets 配置
DATABASE_URL(生产环境 MySQL 连接串) - CI 中关闭 Next.js telemetry:设置
NEXT_TELEMETRY_DISABLED=1
9.3 Next.js 渲染策略(避免 build 阶段查库)
作业模块相关页面在渲染时会进行数据库查询,因此显式标记为动态渲染以避免构建期预渲染触发数据库连接:
- 教师端作业列表:assignments/page.tsx
10. 实现更新(2026-01-05)
10.1 教师端作业详情页组件化(按 Vertical Slice 拆分)
将 /teacher/homework/assignments/[id] 页面调整为“只负责组装”,把可复用展示逻辑下沉到模块内组件:
- 页面组装:page.tsx
- 题目错误概览卡片(overview):homework-assignment-question-error-overview-card.tsx
- 题目错误明细卡片(details):homework-assignment-question-error-details-card.tsx
- 试卷预览/错题工作台容器卡片:homework-assignment-exam-content-card.tsx
10.2 题目点击联动:试卷预览 ↔ 错题详情
在“试卷预览”中点击题目后,右侧联动展示该题的统计与错答列表(按学生逐条展示,不做合并):
- 工作台(选择题目、拼装左右面板):homework-assignment-exam-error-explorer.tsx
- 试卷预览面板(可选中题目):homework-assignment-exam-preview-pane.tsx
- 错题详情面板(错误人数/错误率/错答列表):homework-assignment-question-error-detail-panel.tsx
10.3 统计数据增强:返回逐学生错答
为满足“错答列表逐条展示学生姓名 + 答案”的需求,作业统计查询返回每题的错答明细(包含学生信息):
- 数据访问:getHomeworkAssignmentAnalytics
- 类型定义:types.ts
10.4 加载优化:Client Wrapper 动态分包
由于 next/dynamic({ ssr: false }) 不能在 Server Component 内使用,工作台动态加载通过 Client wrapper 进行隔离:
- Client wrapper:homework-assignment-exam-error-explorer-lazy.tsx
- 入口卡片(Server Component,渲染 wrapper):homework-assignment-exam-content-card.tsx
10.5 校验
npm run lint: 通过npm run typecheck: 通过npm run build: 通过
11. 学生成绩图表与排名(2026-01-06)
11.1 目标
在学生主页(Dashboard)展示:
- 最近已批改作业的成绩趋势(百分比折线)
- 最近若干次已批改作业明细(标题、得分、时间)
- 班级排名(基于班级内作业总体得分百分比)
11.2 数据访问与计算口径
数据由 Homework 模块统一提供聚合查询,避免页面层拼 SQL:
- 新增查询:getStudentDashboardGrades
trend:取该学生所有graded提交中“每个 assignment 最新一次”的集合,按时间升序取最近 10 个recent:对trend再按时间降序取最近 5 条,用于表格展示maxScore:通过homework_assignment_questions汇总每个 assignment 的总分(SUM(score))percentage:score / maxScore * 100ranking:- 班级选择:取该学生最早创建的一条 active enrollment 作为当前班级
- 班级作业集合:班级内所有学生的 targets 合并得到 assignment 集合
- 计分口径:班级内“每个学生 × 每个 assignment”取最新一次 graded 提交,累加得分与满分,得到总体百分比
- 排名:按总体百分比降序排序(百分比相同按 studentId 作为稳定排序因子)
11.3 类型定义
为 Dashboard 聚合数据提供显式类型:
- types.ts
StudentHomeworkScoreAnalyticsStudentRankingStudentDashboardGradeProps
11.4 页面与组件接入
- 学生主页页面负责“取数 + 计算基础计数 + 传参”:
- student/dashboard/page.tsx
- 取数:
getStudentDashboardGrades(student.id) - 传入:
<StudentDashboard grades={grades} />
- 展示组件负责渲染卡片:
- student-view.tsx
- 趋势图:使用内联
svg polyline渲染折线,避免引入额外图表依赖
11.5 校验
npm run lint: 通过npm run typecheck: 通过npm run build: 通过