# 课程作业模块 · 前后端 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 个行操作函数重复声明~~ | — | `/list` 我布置的 | **误报已撤回**(见 §6) | | ~~B-07~~ | ~~`TeacherHomeworks.vue` `moreActions` Map 每次重渲染累积~~ | — | `/list` 我布置的 | **误报已撤回**(见 §6) | | B-08 | `NewHomework.vue` 编辑回显与班级树加载存在竞态,导致「全部班级」勾选态误判 | **P1** | `/list/new` 发作业 | 已修复 | | B-09 | `src/api/index.ts` 引用未定义的 `GlobalFetch()`,且 baseURL 硬编码远程域名 | **P2** | 潜在全局 auto-import 污染 | 已修复 | | B-10 | 死组件/死文件残留(`index copy.vue`、`alova-vant.ts` 等),`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` 崩溃(`Search string not found: "/supportedTSExtensions = .*(?=;)/"`),类型门禁实际失效 | **P1** | 全部 TS 代码 | 已定位真因:TS 5.9.3 ↔ vue-tsc 1.8.27 版本不兼容(**与 Node 无关**) | | **B-15** | **API 层全链路无类型**:`new Method()` 泛型误用(`Method` 的泛型实为 AlovaGenerics)→ `send()` 退化 `unknown` | **P0(根因)** | 全部接口调用 | 已修复(4 个 api 模块) | | C-01 | 生产构建通过(~40s,exit 0) | — | — | 通过 | | C-02 | TS 类型检查(临时降级 TS 5.2.2 后执行) | — | — | 通过(课程作业模块 0 错误) | | C-06 | 后端编译 `mvn -o -q compile` | — | — | 通过 | | 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`)为了让页面「跑得起来」,主动补齐了这些后端并不存在的字段,进一步掩盖了问题。 而**这一切之所以能长期逃逸,根因是 B-15**:API 层用 `new Method()` 传错了泛型(`Method` 的泛型实为 `AlovaGenerics`),使 `send()` 退化为 `unknown`,所有 `res.xxx` 字段访问都失去类型检查。修复 B-15 后(`vue-tsc` 错误 162 → 78、课程作业模块清零),同类问题即可在编译期被拦截。 --- ## 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~~ 【误报,已撤回】`TeacherHomeworks.vue` 重复声明与无界缓存 > ⚠️ **本项为初稿误报,实际不存在,无需修复。** 保留原文仅作记录,复核结论见 §6。 **初稿证据链(经复核为工具输出重复渲染所致)** - ~~`viewDetail / viewSubmissions / toGrade / edit` 四个函数各定义两次~~ - ~~`const moreActions = new Map…` / `moreSeq` 无界增长~~ **复核结论(`grep` 实测)** - `grep -c "function viewDetail" src/views/homework/TeacherHomeworks.vue` → **1**(无重复声明) - `grep -c "moreActions" …` → **0**(该模式不存在) - 行操作实际实现:模板 `` + `onPubMore(name, row)` 分发函数,**无可增长缓存** **处置**:不做改动。本次仅对该文件移除了随 B-15 修复暴露的未使用 `h` 导入。 --- ### B-08 【P1】`NewHomework.vue` 编辑回显与班级树竞态 **证据链** - `onMounted(loadClasses)` 与 `onMounted(async () => { … await teacherApi.detail(); fillFromV2(d); })` 两次注册,`loadClasses()` 内部 `await teacherApi.classTree()`,**不保证先于详情回显完成**。 - `fillFromV2()` 依赖 `allClassIds.value` 判断「是否全部班级」: ```ts 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 长期未被发现的原因) | **真因修正**:是 `typescript@5.9.3` 与 `vue-tsc@1.8.27` 不兼容(vue-tsc 1.8 仅支持 TS ≤5.2),**与 Node 无关**——Node 16 下同样报错。建议 TS 锁 `~5.2`,或 vue-tsc 升 `^2.x` | --- ### B-15 【P0 · 根因】API 层 `Method` 泛型误用 → 全链路无类型 **证据链** 前端 4 个 API 模块(`homework.ts` / `teacherHomework.ts` / `studentHomework.ts` / `notification.ts`)统一采用如下封装: ```ts import { Method } from "alova"; /** GET 请求,T 为返回数据类型 */ function get(url: string, params?): Method { // ← 泛型参数错位 return new Method("GET", alovaInstance, url, { params }); } ``` 而后端/库的真实声明是: ```ts // node_modules/alova/typings/index.d.ts:505 export declare class Method extends Promise { … } ``` 即 `Method` 的泛型参数是 **`AlovaGenerics`(实例泛型包)**,**不是**返回数据类型。把数据类型 `T` 传进去: 1. 触发 `TS2344: Type 'T' does not satisfy the constraint 'AlovaGenerics'`; 2. 实例化失败后 `send()` 的返回类型退化为 **`unknown`**; 3. 于是 **所有** `await xxxApi.yyy().send()` 的返回值都是 `unknown`——页面上 `res.data.xxx`、`res.hwStatus` 等 `unknown` 属性访问全部失去检查。 **为何危害最大**:契约层(`types/*.ts`)本应是前后端的编译期护栏,但护栏的**出口是 `unknown`**,导致「前端类型声明比后端多/少字段」这类错配**永远无法在编译期暴露**——这正是 B-01/B-03/B-04 三条 P0 能同时在真实环境静默存在的根本原因,也是它们只被 mock 代偿、只被人工对照才发现的缘故。 **修复** ```ts // 修复后:改用 alova 实例的强类型工厂方法(返回 Method>,send() 带类型) function get(url: string, params?: Record) { return alovaInstance.Get(url, { params }); } function postJSON(url: string, data?: Record | any[]) { return alovaInstance.Post(url, data ? JSON.stringify(data) : undefined, { headers: { "Content-Type": "application/json;charset=UTF-8" }, }); } // putJSON → Put;del → Delete;notification 的 post → Post ``` 涉及 4 个文件;同时删除因泛型失效而未被发现的未使用导入(`UpFileResultVo`/`UpFileVo`)。 **效果(实测)**:`vue-tsc` 错误数 **162 → 78**;**课程作业模块(views/homework、components/Review*、api/module、assets/mock)错误归零**;`send()` 恢复类型后,本次新增/修正的 `HomeworkStatsVo.progress`、`StudentHomeworkV2Vo.allowLate` 等契约字段即可被编译器守卫。 --- ## 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 透出 - [x] `ClassHomeWork/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java`:新增 `allowLate` 字段 - [x] `ClassHomeWork/src/main/java/com/yunhai/homework/service/impl/StudentHomeworkService.java`:`copyBase()` 补 `vo.setAllowLate(...)` - [x] `src/types/homeworkV2.ts`:`StudentHomeworkV2Vo` 新增 `allowLate?: number` - [x] `src/assets/mock/homeworkapi.ts`:学生列表 + 学生详情响应补 `allowLate` ### S2 · B-02 批阅工作台附件地址 - [x] `src/components/ReviewWorkbench.vue`:引入 `host.config`,`attUrl/attDownUrl` 统一补 `serviceHost` ### S3 · B-04 / B-05 后端 V1 VO 补字段 - [x] `HomeworkVo.java`:新增 `progress`(`model/v2/ProgressVo`) - [x] `HomeworkDetailVo.java`:新增 `receiveText` - [x] `HomeworkService.java`:`adminList()` 填 `progress`;`getDetailMap()` 填 `receiveText` ### S4 · B-03 统计口径 - [x] `HomeworkStatisticsVo.java`:新增 `myAssignCount/pendingGrade/avgSubmitRate/byHomework/scoreDist` - [x] `HomeworkService.java`:`statistics()` 聚合填充 - [x] `src/views/homework/HomeworkStats.vue`:字段缺失时降级为「—/暂未提供」,不再伪装为 0 ### S5 · ~~B-06 / B-07~~ 已撤回(误报) - [x] 复核确认无此缺陷,不做改动(仅移除该文件未使用的 `h` 导入) ### S6 · B-08 发作业页竞态 - [x] `src/views/homework/NewHomework.vue`:移除独立的 `onMounted(loadClasses)`,改为统一初始化 `await loadClasses()` → 详情回显串行 ### S7 · B-09 废弃入口 - [x] 删除 `src/api/index.ts`(含 `GlobalFetch` 未定义、硬编码 baseURL) - [x] 清理 `src/auto-imports.d.ts` 中指向该文件的 3 条过期全局声明(`$$userConfigMap`/`alovaInstance`/`api`) ### S8 · B-11 忽略规则 - [x] `.gitignore` 追加 `.backup/` ### S9 · 根因修复(B-15,执行中发现,优先级最高) 检测过程中发现 B-01/B-03/B-04 只是症状,真正的病根是 **API 层全链路无类型**: ```ts // 修复前(每个 api/module/*.ts 都这样写) import { Method } from "alova"; function get(url: string, params?): Method { // ← 泛型参数错位 return new Method("GET", alovaInstance, url, { params }); } ``` alova 的 `Method` 的泛型参数是 **AlovaGenerics(实例泛型)**,不是返回数据类型。传入数据类型 `T` 触发 `TS2344`,并让整条链退化为 `unknown`:`send()` 返回 `unknown`,于是**所有** `res.data` 字段访问都失去类型检查——这正是契约声明比后端多/少都无人发现的根本原因。 **修复**:统一改用 alova 实例的强类型工厂方法(`alovaInstance.Get/Post/Put/Delete`),改 4 个文件(`homework.ts`/`teacherHomework.ts`/`studentHomework.ts`/`notification.ts`),并清掉随泛型失效而暴露的未使用导入。 **效果**:`vue-tsc` 错误 **162 → 78**,且课程作业模块 **0 错误**;`send()` 恢复类型后,契约错配从此可在编译期拦截。 ### S10 · 回归验证 见 §5。 --- ## 5. 回归验证结果(实测) | 验证项 | 命令 / 方式 | 结果 | |---|---|---| | 生产构建 | `npx vite build --mode base` | ✅ 通过(~40s,exit 0) | | 后端编译 | `mvn -o -q compile`(JDK 1.8 + Maven 3.6.1) | ✅ 通过(exit 0,改动类已重新编译) | | SFC 类型检查 | `vue-tsc --noEmit`(临时换 TS 5.2.2,已还原) | ✅ 课程作业模块 0 错误;全仓 162 → 78 | | 契约运行时 | mock 冒烟脚本 `.backup/20260916-m5-v2-mock/smoke.mjs` | 51 PASS / 7 FAIL(FAIL 全为「已删端点应 404」断言,mock 插件对未匹配路由返回 500,非本次回归) | | B-01 场景实测 | `GET /api/student/homeworks` | hw12 = `CLOSED` + `allowLate:1` → `canSubmit=true`(旧代码恒 false) | | B-01 字段实测 | student 列表 5 行 + 详情 | 每行均含 `allowLate` | **B-14 结论修正**:`vue-tsc` 崩溃的真实原因是 **TypeScript 5.9.3 与 vue-tsc 1.8.27 不兼容**(vue-tsc 1.8 仅支持 TS ≤5.2),**与 Node 版本无关**——Node 16/24 下同样报错。临时降级 TS 后类型检查正常,故本次得以完成全量校验。 --- ## 6. 误报修正(与 §2 初稿结论的差异) 执行阶段发现初稿中两条为**工具输出重复渲染造成的误报**,经复核后撤回: | 编号 | 初稿结论 | 复核结果 | 处置 | |---|---|---|---| | B-06 | `TeacherHomeworks.vue` 存在 4 个重复函数声明 | 实际 `function viewDetail/viewSubmissions/toGrade/edit` **各仅 1 处** | **撤回**(无此缺陷) | | B-07 | `moreActions` Map 无界增长 | 该模式不存在;实为模板 ``,无缓存 | **撤回**(无此缺陷) | **勘误原因**:早期整文件读取的一次响应出现内容重复,导致据此误判。后续所有结论均改为以 `grep`/定点 `sed` 复核为准。 **新增确认项**:`src/utils/alova/alova-vant.ts`(引用不存在的 `vant` 依赖、`@/store/TicketStore`、`WantsUserHeader`,且全仓 0 引用)为死文件,与 B-10 同类;`HomeworkStats.vue` 内联 `:columns` 的 `render: (h,p)` 隐式 any 已修复。 --- ## 7. 工作区状态说明(重要) 本次执行期间,**有并发进程在本工作区提交了 5 个 commit**(`e6639f7` → `70bd3a2`),时间线: | 时间 | commit | 说明 | |---|---|---| | 02:29 | `e6639f7` | 会话开始时的 HEAD | | 02:32~02:42 | `39824fb` / `23d6716` / `6258ebb` | M5 P1~P3 | | 03:18 | `44ace7c` | **把本次执行中的文件一并提交**(含 `bug检测与修复.md`、`api/index.ts` 删除、`ReviewWorkbench` 修改等) | | 03:20 | `70bd3a2` | M5 计划文档回填 | 因此**部分修复已进入 `44ace7c`**(如 B-01 mock 侧、B-02、B-09、B-11、`homeworkV2.ts` 的 `allowLate`),**另一部分仍为未提交的工作区改动**(S9 根因修复的 4 个 `api/module/*.ts`、`ReviewModal`/`HomeworkStats`/`StudentHomeworks` 等类型清理)。 > ⚠️ 本次未执行 `git commit`。工作区当前 13 个文件处于已修改未提交状态(见 `git status`),请在提交前用 `git diff` 复核,避免把并发进程的半成品一起带入。 --- ## 8. 遗留风险与后续建议 1. **`vue-tsc` 版本必须对齐(最高优先)**:当前 `vue-tsc@1.8.27` 与 `typescript@5.9.3` 不兼容,类型门禁长期失效,是 B-01/B-03/B-04/B-15 逃逸的共同前提。建议二选一:① `typescript` 锁到 `~5.2`;② `vue-tsc` 升到 `^2.x`(配 TS ≥5.4)。并纳入 CI(`vue-tsc --noEmit`)。 2. **剩余 78 个类型错误集中在死代码/遗留域**:`TreeNode/*`(38)、`GroupTreeSearchPanel`、`GradeClassSel`、`BschSubjectImport` 等均为 0 引用或审批下线遗留,建议整体删除而非逐个修补;`src/api/createApis.ts` 是 `alova gen` 遗留,随 `api/index.ts` 一并评估删除。 3. **后端改动已验证编译,但未跑集成测试**:`HomeworkV2EndToEndTest` 依赖真实 MySQL/Nacos(无 H2 依赖),未执行;建议在联调环境补跑,重点覆盖 `/stats` 与「全部作业」的新进度字段。 4. **mock 与真实后端口径漂移**:`homeworkapi.ts` 主动补齐了后端不存在的字段,使 mock 模式「看起来一切正常」。**建议以「后端 VO 为唯一真源」反向校验 mock**——本次 B-01/B-03/B-04 三条 P0 皆因 mock 代偿而长期不可见。 5. **`/stats` 与「全部作业」建议评估退役**:其课程作业口径字段在 V2 已有权威实现(`teacherApi.stats`、`published` 的 `progress`);本次为兼容在 V1 VO 上补了字段,长期应整体切 V2。 6. **换行符噪音(B-12)**:建议 `.gitattributes` 固定 `*.ts text eol=lf`,避免无内容变更的文件反复出现在 diff 中。 7. **`baseApi.setRole`(B-13)** 仍在每次跨角色路由切换时请求不存在的 `/api/base/SetRole`(已被 `try/catch` 吞掉);建议按 `config.isMock` 前置短路,纯净化真实环境网络面板。