# 作业模块实现设计文档(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](file:///c:/Users/xiner/Desktop/CICD/docs/db/schema-changelog.md#L34-L77) ### 2.2 设计要点:冻结 Exam → Homework Assignment - `homework_assignments.source_exam_id` 保存来源 Exam - `homework_assignments.structure` 在 publish 时复制 `exams.structure`(冻结当时的呈现结构) - 题目关联使用 `homework_assignment_questions`(仍引用 `questions` 表,作业侧记录分值与顺序) --- ## 3. 路由与页面 ### 3.1 教师端 - `/teacher/homework/assignments`: 作业列表 实现:[assignments/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/page.tsx) - `/teacher/homework/assignments/create`: 从 Exam 派发作业 实现:[create/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/create/page.tsx) - `/teacher/homework/assignments/[id]`: 作业详情 实现:[[id]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/page.tsx) - `/teacher/homework/assignments/[id]/submissions`: 作业提交列表(按作业筛选) 实现:[[id]/submissions/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/submissions/page.tsx) - `/teacher/homework/submissions`: 全部提交列表 实现:[submissions/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/submissions/page.tsx) - `/teacher/homework/submissions/[submissionId]`: 批改页 实现:[[submissionId]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/submissions/%5BsubmissionId%5D/page.tsx) 关联重定向: - `/teacher/exams/grading` → `/teacher/homework/submissions` 实现:[grading/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/exams/grading/page.tsx) - `/teacher/exams/grading/[submissionId]` → `/teacher/homework/submissions` 实现:[grading/[submissionId]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/exams/grading/%5BsubmissionId%5D/page.tsx) ### 3.2 学生端 - `/student/learning/assignments`: 作业列表 实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/student/learning/assignments/page.tsx) - `/student/learning/assignments/[assignmentId]`: 作答页(开始/保存/提交) 实现:[[assignmentId]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/student/learning/assignments/%5BassignmentId%5D/page.tsx) --- ## 4. 数据访问层(Data Access) 数据访问位于:[data-access.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/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](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/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](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-grading-view.tsx) - 左侧:学生答案只读展示 - 右侧:按题录入分数与反馈,并提交批改 ### 6.2 学生作答视图 - [HomeworkTakeView](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-take-view.tsx) - Start:开始一次作答 - Save:按题保存 - Submit:提交(提交前会先保存当前题目答案) - 题型支持:`text` / `judgment` / `single_choice` / `multiple_choice` 题目 content 约定与题库一致:`{ text, options?: [{ id, text, isCorrect? }] }`(作答页仅消费 `id/text`)。 --- ## 7. 类型定义 类型位于:[types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/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](file:///c:/Users/xiner/Desktop/CICD/.gitea/workflows/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](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/page.tsx) --- ## 10. 实现更新(2026-01-05) ### 10.1 教师端作业详情页组件化(按 Vertical Slice 拆分) 将 `/teacher/homework/assignments/[id]` 页面调整为“只负责组装”,把可复用展示逻辑下沉到模块内组件: - 页面组装:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/page.tsx) - 题目错误概览卡片(overview):[homework-assignment-question-error-overview-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-overview-card.tsx) - 题目错误明细卡片(details):[homework-assignment-question-error-details-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-details-card.tsx) - 试卷预览/错题工作台容器卡片:[homework-assignment-exam-content-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-content-card.tsx) ### 10.2 题目点击联动:试卷预览 ↔ 错题详情 在“试卷预览”中点击题目后,右侧联动展示该题的统计与错答列表(按学生逐条展示,不做合并): - 工作台(选择题目、拼装左右面板):[homework-assignment-exam-error-explorer.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-error-explorer.tsx) - 试卷预览面板(可选中题目):[homework-assignment-exam-preview-pane.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-preview-pane.tsx) - 错题详情面板(错误人数/错误率/错答列表):[homework-assignment-question-error-detail-panel.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-detail-panel.tsx) ### 10.3 统计数据增强:返回逐学生错答 为满足“错答列表逐条展示学生姓名 + 答案”的需求,作业统计查询返回每题的错答明细(包含学生信息): - 数据访问:[getHomeworkAssignmentAnalytics](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/data-access.ts) - 类型定义:[types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/types.ts) ### 10.4 加载优化:Client Wrapper 动态分包 由于 `next/dynamic({ ssr: false })` 不能在 Server Component 内使用,工作台动态加载通过 Client wrapper 进行隔离: - Client wrapper:[homework-assignment-exam-error-explorer-lazy.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-error-explorer-lazy.tsx) - 入口卡片(Server Component,渲染 wrapper):[homework-assignment-exam-content-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/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](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/data-access.ts) - `trend`:取该学生所有 `graded` 提交中“每个 assignment 最新一次”的集合,按时间升序取最近 10 个 - `recent`:对 `trend` 再按时间降序取最近 5 条,用于表格展示 - `maxScore`:通过 `homework_assignment_questions` 汇总每个 assignment 的总分(SUM(score)) - `percentage`:`score / maxScore * 100` - `ranking`: - 班级选择:取该学生最早创建的一条 active enrollment 作为当前班级 - 班级作业集合:班级内所有学生的 targets 合并得到 assignment 集合 - 计分口径:班级内“每个学生 × 每个 assignment”取最新一次 graded 提交,累加得分与满分,得到总体百分比 - 排名:按总体百分比降序排序(百分比相同按 studentId 作为稳定排序因子) ### 11.3 类型定义 为 Dashboard 聚合数据提供显式类型: - [types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/types.ts) - `StudentHomeworkScoreAnalytics` - `StudentRanking` - `StudentDashboardGradeProps` ### 11.4 页面与组件接入 - 学生主页页面负责“取数 + 计算基础计数 + 传参”: - [student/dashboard/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/student/dashboard/page.tsx) - 取数:`getStudentDashboardGrades(student.id)` - 传入:`` - 展示组件负责渲染卡片: - [student-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/dashboard/components/student-view.tsx) - 趋势图:使用内联 `svg polyline` 渲染折线,避免引入额外图表依赖 ### 11.5 校验 - `npm run lint`: 通过 - `npm run typecheck`: 通过 - `npm run build`: 通过