# 课程作业模块 · 前后端 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 方法
1. **静态检测**:`vite build`(生产构建)、`vue-tsc --noEmit`(类型检查)。
2. **契约逐字段对照**:前端 `types/*.ts` ↔ 后端 `model/**/*.java` 字段名与类型逐一对照。
3. **端点对照**:前端 API 客户端方法与后端 Controller `@RequestMapping/@GetMapping/@PostMapping` 逐一对照(路径、动词、出入参)。
4. **引用完整性**:已删除组件残留引用、未使用组件、未定义全局符号。
5. **运行期口径**:状态机(`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` 两个入口):
```ts
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 个字段,**全库 grep `myAssignCount|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、各作业进度表「暂无数据」。属静默错误,用户会误以为没有数据。
**修复(双管齐下)**
1. **前端降级(可验证)**:`HomeworkStats.vue` 在字段缺失时明确呈现「后端未提供该口径」而非伪装成 0;仅在 `scoreDist?.length` / `byHomework?.length` 时渲染区块。
2. **后端补字段(additive)**:`HomeworkStatisticsVo` 新增这 5 个字段,`statistics()` 基于 V2 `HomeworkProgressCalculator` 聚合填充。
---
### 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`)。
**后果**:真实环境下「全部作业」页的提交进度条、未读未交、批改状态三列恒为「—」。
**修复**
1. **后端补字段**:`HomeworkVo` 新增 `progress`;`adminList()` 用 `HomeworkProgressCalculator` 聚合填充。
2. **前端降级**:进度列在 `progress` 缺失时显示「—」(现有行为已如此),不伪装为 0。
---
### B-05 【P1】详情页「接收班级」恒显示「全部班级」
**证据链**
- `views/homework/HomeworkDetails.vue`(V1 管理分支):`接收班级:{{ detail.receiveText || "全部班级" }}`。
- 后端 `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` 四个函数在 `