22 KiB
课程作业模块 · 前后端 Bug 检测与修复计划任务
| 项 | 内容 |
|---|---|
| 前端 | E:\YHXT\view\CourseHomework(Vue3 + Vite + TS + view-ui-plus + alova) |
| 后端 | E:\YHXT\java\ClassHomeWork(Spring Boot 2.7 + JPA,/{platform}/… 统一前缀,platform=api) |
| 检测日期 | 2026-09-16 |
| 检测方式 | 静态检视 + 契约逐字段对照 + 构建/类型检查 |
| 文档性质 | 计划任务文档(检测项清单)+ 执行记录(修复与回归结果) |
0. 检测范围与方法
0.1 范围
| 层 | 覆盖对象 |
|---|---|
| 前端契约层 | src/types/homework.ts、src/types/homeworkV2.ts |
| 前端 API 层 | src/api/module/{homework,teacherHomework,studentHomework,notification}.ts、src/utils/alova/* |
| 前端页面 | src/views/homework/*.vue(9 个页面) |
| 前端组件/路由 | src/components/{ReviewWorkbench,ReviewModal,AttachmentUpload,layout}.vue、src/router/routes.ts、src/utils/menuRoute.ts、src/App.vue |
| Mock | src/assets/mock/homeworkapi.ts |
| 后端接口面 | controller/api/{TeacherHomeworkV2,StudentHomework,Notification,Homework,File,BaseApi}Controller |
| 后端模型 | model/homework/*、model/v2/*、model/BasePage* |
| 后端服务 | service/impl/{TeacherHomeworkV2,StudentHomework,Homework,HomeworkProgressCalculator}Service |
0.2 方法
- 静态检测:
vite build(生产构建)、vue-tsc --noEmit(类型检查)。 - 契约逐字段对照:前端
types/*.ts↔ 后端model/**/*.java字段名与类型逐一对照。 - 端点对照:前端 API 客户端方法与后端 Controller
@RequestMapping/@GetMapping/@PostMapping逐一对照(路径、动词、出入参)。 - 引用完整性:已删除组件残留引用、未使用组件、未定义全局符号。
- 运行期口径:状态机(
hwStatus五态 /submitStatus五态 /reviewStatus三态)在前后端的判定一致性。
0.3 检测项清单(共 24 项)
| 编号 | 检测项 | 方法 |
|---|---|---|
| C-01 | 生产构建是否通过 | vite build --mode base |
| C-02 | TS 类型检查是否通过 | vue-tsc --noEmit |
| C-03 | 已删除组件是否仍被引用 | grep 残留引用 |
| C-04 | 是否存在未使用的死组件 | 反向引用计数 |
| C-05 | 前端定义的接口路径后端是否真实存在 | 端点对照 |
| C-06 | 路径参数名是否与后端 @PathVariable 一致 |
端点对照 |
| C-07 | @RequestBody 是否用 POST/PUT + JSON |
端点对照 |
| C-08 | 分页外壳字段是否一致 | BasePageResult 对照 |
| C-09 | 分页入参语义是否一致(pageIndex 起始值) | BasePageQuery 对照 |
| C-10 | V2 作业 VO 字段是否一致 | HomeworkV2Vo 对照 |
| C-11 | V2 学生 VO 字段是否一致 | StudentHomeworkVo/DetailVo 对照 |
| C-12 | 学生矩阵 VO 字段是否一致 | StudentMatrixVo 对照 |
| C-13 | 提交版本 VO 字段是否一致 | SubmissionVersionVo 对照 |
| C-14 | 通知 VO / 未读数是否一致 | NotificationPageResult 对照 |
| C-15 | V1 作业列表 VO 字段是否一致 | HomeworkVo 对照 |
| C-16 | V1 详情 VO 字段是否一致 | HomeworkDetailVo 对照 |
| C-17 | V1 统计 VO 字段是否一致 | HomeworkStatisticsVo 对照 |
| C-18 | 时间格式是否一致(全局 yyyy-MM-dd HH:mm:ss) |
JacksonConfig + 前端解析 |
| C-19 | 附件 URL 是否可解析(相对/绝对、contextPath) | 各页面 previewUrl/downUrl |
| C-20 | 状态机判定是否前后端一致(可提交/可撤回/可编辑) | 页面 canSubmit* vs Service 校验 |
| C-21 | 是否存在重复函数声明/无界缓存 | 代码检视 |
| C-22 | 组件挂载期是否存在异步竞态 | 代码检视 |
| C-23 | 构建/发布配置是否自洽(outDir、env、产物) | vite.config.ts + .env.* |
| C-24 | 工作区未提交改动是否引入噪音 | git status/diff |
1. 检测结论摘要
| 编号 | 问题 | 级别 | 影响面 | 状态 |
|---|---|---|---|---|
| B-01 | 学生端缺少 allowLate 字段,导致「截止后补交」能力在真实环境完全失效 |
P0 | 学生列表/详情/快捷提交 | 已修复 |
| B-02 | ReviewWorkbench 附件 URL 未拼 serviceHost,线上图片/下载 404 |
P0 | 教师批阅工作台 | 已修复 |
| B-03 | HomeworkStatisticsVo 前端声明 5 个幻影字段,后端从未返回 → 统计页 4 张卡片恒为 0/0%/空表 |
P0 | /stats 作业统计 |
已修复(前端降级)+ 后端补字段 |
| B-04 | HomeworkVo 前端声明 progress 字段,后端从未返回 → 「全部作业」页 3 列进度恒为「—」 |
P0 | /homeworklist 全部作业 |
已修复(前端降级)+ 后端补字段 |
| B-05 | HomeworkDetailVo 前端声明 receiveText,后端从未返回 → 详情页「接收班级」恒显示「全部班级」 |
P1 | /homeworklist/detail |
已修复(后端补字段 + 前端兜底) |
| B-06 | TeacherHomeworks.vue 4 个行操作函数重复声明 |
P1 | /list 我布置的 |
已修复 |
| B-07 | TeacherHomeworks.vue moreActions Map 每次重渲染累积、永不回收 |
P1 | /list 我布置的 |
已修复 |
| B-08 | NewHomework.vue 编辑回显与班级树加载存在竞态,导致「全部班级」勾选态误判 |
P1 | /list/new 发作业 |
已修复 |
| B-09 | src/api/index.ts 引用未定义的 GlobalFetch(),且 baseURL 硬编码远程域名 |
P2 | 潜在全局 auto-import 污染 | 已修复 |
| B-10 | 7 个死组件 + 1 个 index copy.vue 残留,auto-import 扫描目录存在误引入风险 |
P2 | 构建体积/可维护性 | 已记录(不删除) |
| B-11 | .backup/ 未纳入 .gitignore,污染 git status |
P2 | 版本管理 | 已修复 |
| B-12 | routes.ts 出现仅换行符差异的伪改动 |
P2 | 版本管理噪音 | 已记录 |
| B-13 | baseApi.setRole 指向后端不存在的 /api/base/SetRole,真实环境每次跨角色路由切换都会 404 |
P2 | 控制台噪音 | 已记录(设计如此) |
| B-14 | vue-tsc 在 Node 24 下崩溃(Search string not found: "/supportedTSExtensions = .*(?=;)/"),类型门禁实际失效 |
P1 | 全部 TS 代码 | 已记录(需换 Node 18/20 或升级 vue-tsc) |
| C-01 | 生产构建通过(39.66s,exit 0) | — | — | 通过 |
| C-02 | TS 类型检查无法执行(见 B-14) | — | — | 阻塞 |
| C-05~C-07 | V2 端点 28 个 + 通知 3 个 + V1 冻结端点路径/动词与后端一致 | — | — | 通过 |
| C-08/C-09 | BasePageResult 六字段、pageIndex 从 1 起前后端一致 |
— | — | 通过 |
| C-10~C-14 | V2 作业/学生/矩阵/版本/通知 VO 字段与后端逐一对应 | — | — | 通过 |
| C-18 | 后端 JacksonConfig 全局 yyyy-MM-dd HH:mm:ss(无 T),前端一律 String(v).slice() 截取,一致 |
— | — | 通过 |
| C-23 | outDir=dist 直出、.env.dev/.env.mock 仅本地生效、产物不含环境值,自洽 |
— | — | 通过 |
最严重的三条(P0)本质是同一个病根:前端契约(types/*.ts)比后端实际返回多声明的字段,被 TypeScript 视为可选(?:),又被 vue-tsc 崩溃掩盖,于是「编译通过、构建成功、mock 模式下一切正常」——只有连真实后端才会表现为静默的空值/功能失效。mock(homeworkapi.ts)为了让页面「跑得起来」,主动补齐了这些后端并不存在的字段,进一步掩盖了问题。
2. 缺陷详情与修复方案
B-01 【P0】学生端 allowLate 字段缺失 → 截止后补交能力失效
证据链
- 后端
model/v2/StudentHomeworkVo.java字段仅:id/title/subject/teacherName/deadlineAt/submitType/hwStatus/hwStatusDesc/my,无allowLate。 - 后端实体
entity/Homework.java:188确有allow_late列(private Integer allowLate = 0),说明数据存在、只是学生端 VO 未透出。 - 后端
service/impl/StudentHomeworkService.java#copyBase()(411~433 行)逐字段赋值,未赋值 allowLate。 - 前端
views/homework/StudentHomeworks.vue:110:if (x.hwStatus === "CLOSED") return x.allowLate === 1; - 前端
views/homework/HomeworkAccept.vue:104:if (d.hwStatus === "CLOSED") return d.allowLate === 1;;第 307 行提示语v-if="detail.allowLate !== 1"。 - 前端类型
types/homeworkV2.ts的StudentHomeworkV2Vo也未声明allowLate(TS 应报错,被 B-14 掩盖)。 - mock 学生列表/详情响应同样未输出
allowLate。
后果
x.allowLate 恒为 undefined → undefined === 1 恒 false。因此后端即便 allow_late=1(doSubmit 允许 LATE_SUBMITTED),前端也永远不渲染「补交作业」按钮,并恒显示「作业已截止,如需补交请联系老师」。这是真实环境下可直接复现的功能性缺陷——接口允许、UI 拦住。
修复
| 文件 | 改动 |
|---|---|
ClassHomeWork/model/v2/StudentHomeworkVo.java |
新增 private Integer allowLate;(含注释) |
ClassHomeWork/service/impl/StudentHomeworkService.java |
copyBase() 增加 vo.setAllowLate(h.getAllowLate()); |
前端 types/homeworkV2.ts |
StudentHomeworkV2Vo 新增 allowLate?: number; |
前端 assets/mock/homeworkapi.ts |
学生列表与详情响应补 allowLate: n.allowLate ?? 0 |
B-02 【P0】ReviewWorkbench 附件 URL 未拼 serviceHost → 线上 404
证据链
components/AttachmentUpload.vue上传成功后写入fileUrl: "/api/file/Preview?id=" + fileid(相对路径)。views/homework/StudentHomeworks.vue、HomeworkAccept.vue、Submissions.vue展示时均用serviceBase(config.serviceHost)拼接相对路径。- 但
components/ReviewWorkbench.vue(教师批阅工作台,被Grade.vue与ReviewModal.vue共用,即/grade与/submissions两个入口):function attUrl(a: AttachmentItem): string { if (/^(https?:)?\/\//.test(a.fileUrl) || a.fileUrl.startsWith("data:")) return a.fileUrl; return a.fileUrl; // ← 相对路径原样返回,未拼 serviceBase } function attDownUrl(a: AttachmentItem): string { return a.fileId ? `/api/file/Down?id=${a.fileId}` : a.fileUrl; // ← 同样缺前缀 }
后果
本地 serviceHost 为空(走 vite 同源代理)时正常;线上 serviceHost 为基础平台域名且应用部署于 /{contextPath}/front/,相对路径 /api/... 会被解析到站点根而非后端 contextPath,导致批阅时学生作答图片裂图、附件下载 404。教师端核心链路(看图给分)受阻。
修复:ReviewWorkbench.vue 引入 @/assets/config/host.config,按 config.isMock ? "" : config.serviceHost 前缀拼接(与其它页面同一范式)。
B-03 【P0】统计页幻影字段 → 4 张卡片恒为 0
证据链
- 前端
types/homework.ts的HomeworkStatisticsVo声明:myAssignCount?、pendingGrade?、avgSubmitRate?、byHomework?、scoreDist?。 - 后端
model/homework/HomeworkStatisticsVo.java实际字段仅:total、publishedCount、totalReadRecords、unreadForCurrentUser、byPublisher、byMonth。 - 后端
service/impl/HomeworkService.java#statistics()(702~756 行)只 set 这 6 个字段,全库 grepmyAssignCount|avgSubmitRate|scoreDist|byHomework在src/main/java下零命中。 views/homework/HomeworkStats.vue直接消费:4 张卡片myAssignCount/pendingGrade/avgSubmitRate/total、成绩分布stat.scoreDist、各作业进度表stat.byHomework。- mock
/api/homework/Statis(homeworkapi.ts:1319)主动补齐了这 5 个字段 → mock 模式下页面完整,真实后端下全空。
后果:真实环境 /stats 页显示「我布置的作业 0 / 待批改总量 0 / 平均提交率 0%」、成绩分布条全 0、各作业进度表「暂无数据」。属静默错误,用户会误以为没有数据。
修复(双管齐下)
- 前端降级(可验证):
HomeworkStats.vue在字段缺失时明确呈现「后端未提供该口径」而非伪装成 0;仅在scoreDist?.length/byHomework?.length时渲染区块。 - 后端补字段(additive):
HomeworkStatisticsVo新增这 5 个字段,statistics()基于 V2HomeworkProgressCalculator聚合填充。
B-04 【P0】「全部作业」页进度列恒空
证据链
- 前端
types/homework.ts的HomeworkVo声明progress?: HomeworkProgress | null,HomeworkList.vue用progressCell / gradeCell渲染「提交进度 / 未读未交 / 批改」三列,并读g.read、g.submitRate、g.pendingGrade、g.unread。 - 后端
model/homework/HomeworkVo.java没有progress字段;service/impl/HomeworkService.java#toVo()(903~920 行)只回填deadline/submitType/hwStatus/hwStatusDesc/read/readCount/deptName。 #adminList()(345~362 行,即/homework/api/homework/SendList)只额外填充UpFile附件串,未填 progress。/homeworklist路由与菜单modId=3均在生效(routes.ts、config/menu.ts)。
后果:真实环境下「全部作业」页的提交进度条、未读未交、批改状态三列恒为「—」。
修复
- 后端补字段:
HomeworkVo新增progress;adminList()用HomeworkProgressCalculator聚合填充。 - 前端降级:进度列在
progress缺失时显示「—」(现有行为已如此),不伪装为 0。
B-05 【P1】详情页「接收班级」恒显示「全部班级」
证据链
views/homework/HomeworkDetails.vue(V1 管理分支):<Tag color="blue">接收班级:{{ detail.receiveText || "全部班级" }}</Tag>。- 后端
model/homework/HomeworkDetailVo.java无receiveText(字段:serid/title/Contents/DeptID/DeptName/TeaName/EditDate/numcount/readnumcount/ReceiveList/DsfsTime/isDsfs/ChaoSend/tlist);全库grep receiveText src/main/java零命中。 - 后端实际提供
ReceiveList(值"ALL"或 id 串)与tlist。
修复:后端 HomeworkDetailVo 新增 receiveText 并在 getDetailMap() 填充可读文本(「全部班级」或班级名串);前端保留 receiveText || "全部班级" 兜底。
B-06 / B-07 【P1】TeacherHomeworks.vue 重复声明与无界缓存
证据链
viewDetail / viewSubmissions / toGrade / edit四个函数在<script setup>中各定义两次(同一模块作用域重复function声明)。const moreActions = new Map<string, () => void>()、let moreSeq = 0位于模块级;pubColumns是computed,每次依赖变化重算都会调用mItem()递增moreSeq并写入 Map,Map 只增不减。
后果:重复声明在 JS 中后者覆盖前者(静默),但违反类型检查(TS Duplicate function implementation),也让维护者误判生效分支;Map 在长时间停留/频繁翻页的列表页持续增长(内存缓慢泄漏)。
修复:删除重复的 4 个函数定义;moreActions 改为每次 pubColumns 重算时重建(局部 Map),避免无界增长。
B-08 【P1】NewHomework.vue 编辑回显与班级树竞态
证据链
onMounted(loadClasses)与onMounted(async () => { … await teacherApi.detail(); fillFromV2(d); })两次注册,loadClasses()内部await teacherApi.classTree(),不保证先于详情回显完成。fillFromV2()依赖allClassIds.value判断「是否全部班级」:if (cls.length && cls.length === allClassIds.value.length && cls.every(c => allClassIds.value.includes(c))) allClasses.value = true;- 若
allClassIds仍为空(length === 0),条件恒不成立 → 即使该作业确实覆盖全部班级,也渲染为**未勾选「全部班级」**且多选框中班级列表为空(丢失勾选态)。
修复:把初始化串行为「先 await loadClasses(),再拉详情并 fillFromV2()」,消除竞态。
B-09 【P2】src/api/index.ts 引用未定义符号
证据链:src/api/index.ts 顶部 createAlova({ … requestAdapter: GlobalFetch() … }),但该文件未导入也未定义 GlobalFetch;且 baseURL: 'http://base.yunhaixingtong.com/homework' 硬编码远程域名,与 host.config.ts 的运行时配置体系冲突。该文件未被任何模块引用,但 vite.config.ts 中 ViteAutoImport({ dirs: ["src/api"] }) 会扫描该目录并生成全局声明(auto-imports.d.ts 中可见 apiDefinitions/createApis/mountApis/withConfigType),存在被误自动引入、进而在运行时抛 GlobalFetch is not defined 的风险。
修复:删除该废弃入口文件(页面统一走 api/module/*,src/api/index.ts 与 apiDefinitions.ts/createApis.ts 为 alova gen 遗留)。
B-10 / B-11 / B-12 / B-13 / B-14 其余记录项
| 编号 | 内容 | 处置 |
|---|---|---|
| B-10 | 死组件:BschImport.vue、GroupTreeSearchPanel.vue、MyTreeSearch.vue、TablePageAlova.vue、TablePageAlovaNew.vue、UploadFile.vue、UploadImg.vue、my_tree.vue、TreeNode/index copy.vue 反向引用均为 0 |
不删除(可能为后续页面预留),仅登记 |
| B-11 | .backup/ 未在 .gitignore,git status 出现 ?? .backup/20260916-m5-v2-mock/ |
追加忽略规则 |
| B-12 | src/router/routes.ts 被 git status 标记为已修改,但 git diff 内容为空(仅 CRLF/LF 差异) |
记录;建议统一 core.autocrlf 或提交时按内容判定 |
| B-13 | App.vue 每次跨角色路由切换调用 baseApi.setRole → 后端无 /api/base/SetRole,真实环境 404(已被 try/catch 吞掉) |
设计如此(仅 mock 用),保留但已在前端注释标明 |
| B-14 | vue-tsc@1.8.27 + Node 24 崩溃,npm run build 不含类型检查 → TS 契约门禁失效(正是 B-01/B-03/B-04 长期未被发现的原因) |
需将 Node 降到 18/20 或升级 vue-tsc;建议在 CI 增加类型检查步骤 |
3. 执行计划(修复顺序与验证)
| 步骤 | 动作 | 验证方式 |
|---|---|---|
| S1 | 修复 B-01(后端 VO + Service;前端类型 + mock) | 后端字段对照;前端构建 |
| S2 | 修复 B-02(ReviewWorkbench 拼 serviceHost) |
前端构建;代码走查三条附件路径 |
| S3 | 修复 B-04 / B-05(后端 HomeworkVo.progress、HomeworkDetailVo.receiveText + 服务填充) |
字段对照 + 前端构建 |
| S4 | 修复 B-03(统计 VO 补字段 + 前端降级展示) | 字段对照 + 前端构建 |
| S5 | 修复 B-06 / B-07(TeacherHomeworks.vue 去重、Map 本地化) |
前端构建 |
| S6 | 修复 B-08(NewHomework.vue 串行初始化) |
前端构建 |
| S7 | 修复 B-09(删除废弃 src/api/index.ts) |
全局 grep 无引用;前端构建 |
| S8 | 修复 B-11(.gitignore 追加 .backup/) |
git status 干净 |
| S9 | 回归:vite build --mode base 全绿 |
exit code 0 |
| S10 | 更新本文档「执行记录」并汇总遗留风险 | 文档 |
约束说明:本机无 Maven、无 JDK(仅 JRE 8),后端 Java 改动无法本地编译验证,仅做「纯增量、机械式」修改(新增字段 + 赋值 + 新注入),需在装有 Maven 的环境中执行 mvn -q clean compile 复核。
4. 执行记录
本节由执行过程回填,逐项记录改动文件与验证结果。
S1 · B-01 学生端 allowLate 透出
ClassHomeWork/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java:新增allowLate字段ClassHomeWork/src/main/java/com/yunhai/homework/service/impl/StudentHomeworkService.java:copyBase()补vo.setAllowLate(...)src/types/homeworkV2.ts:StudentHomeworkV2Vo新增allowLate?: numbersrc/assets/mock/homeworkapi.ts:学生列表 + 学生详情响应补allowLate
S2 · B-02 批阅工作台附件地址
src/components/ReviewWorkbench.vue:引入host.config,attUrl/attDownUrl统一补serviceHost
S3 · B-04 / B-05 后端 V1 VO 补字段
HomeworkVo.java:新增progress(model/v2/ProgressVo)HomeworkDetailVo.java:新增receiveTextHomeworkService.java:adminList()填progress;getDetailMap()填receiveText
S4 · B-03 统计口径
HomeworkStatisticsVo.java:新增myAssignCount/pendingGrade/avgSubmitRate/byHomework/scoreDistHomeworkService.java:statistics()聚合填充src/views/homework/HomeworkStats.vue:字段缺失时降级为「—/暂未提供」,不再伪装为 0
S5 · B-06 / B-07 我布置的页
src/views/homework/TeacherHomeworks.vue:删除 4 个重复函数;moreActions改为列重算时重建
S6 · B-08 发作业页竞态
src/views/homework/NewHomework.vue:初始化改为await loadClasses()→ 详情回显串行
S7 · B-09 废弃入口
- 删除
src/api/index.ts(含GlobalFetch未定义、硬编码 baseURL)
S8 · B-11 忽略规则
.gitignore追加.backup/
S9 · 回归验证
见 §5。
5. 回归验证结果
| 验证项 | 命令 | 结果 |
|---|---|---|
| 生产构建 | npx vite build --mode base |
见下 |
| 端点/契约复核 | 字段对照脚本 | 见下 |
| Java 编译 | mvn clean compile |
未执行(本机无 Maven/JDK) |
6. 遗留风险与后续建议
- 类型门禁形同虚设(B-14,最高优先):本次 B-01/B-03/B-04 三条 P0 全部是「类型声明比后端多」的静默错配。恢复
vue-tsc可运行(Node 18/20 或升级vue-tsc)并纳入 CI,是防止同类问题复发的根本手段。 - mock 与真实后端口径漂移:
homeworkapi.ts主动补齐了后端不存在的字段,使 mock 模式「看起来一切正常」,掩盖真实缺陷。建议以「后端 VO 为唯一真源」生成/校验 mock。 - 后端改动未经编译验证:S1/S3/S4 涉及 Java 文件,本机无 Maven/JDK,需在构建环境跑
mvn -q clean compile复核;若编译不通过,回退范围仅限本次新增的字段与注入。 /stats与「全部作业」仍建议评估退役:这两页属 V1 冻结面,其课程作业口径字段(提交进度/批改/成绩分布)在 V2 已有权威实现(teacherApi.stats/published的progress)。长期建议整体切 V2,避免在 V1 VO 上持续打补丁。GitHub换行符噪音(B-12):建议统一core.autocrlf或在.gitattributes中固定*.ts text eol=lf,避免无内容变更的文件反复出现在 diff 中。