diff --git a/.gitignore b/.gitignore index c00039c..6ccf44d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ yarn.lock # scratch .temp + +# 改动前本地备份(版本迭代时人工留存,不进版本库) +.backup/ diff --git a/bug检测与修复.md b/bug检测与修复.md new file mode 100644 index 0000000..69360d8 --- /dev/null +++ b/bug检测与修复.md @@ -0,0 +1,333 @@ +# 课程作业模块 · 前后端 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` 四个函数在 ` - - diff --git a/src/components/HomeworkUpload.vue b/src/components/HomeworkUpload.vue deleted file mode 100644 index c3f8e3d..0000000 --- a/src/components/HomeworkUpload.vue +++ /dev/null @@ -1,116 +0,0 @@ - - - - - diff --git a/src/components/ReviewModal.vue b/src/components/ReviewModal.vue index 1f0d9bc..79cf137 100644 --- a/src/components/ReviewModal.vue +++ b/src/components/ReviewModal.vue @@ -2,8 +2,8 @@ (cur.value?.attachments || []).filter((a) => (a.fileType || "").toLowerCase() === "image")); const fileAtts = computed(() => (cur.value?.attachments || []).filter((a) => (a.fileType || "").toLowerCase() !== "image")); -/** 附件展示地址:fileUrl 已是 /api/file/Preview?id= 相对路径;绝对地址原样 */ +/** 附件展示地址:fileUrl 可能是 /api/file/Preview?id= 相对路径(需拼 serviceHost),绝对地址原样 */ function attUrl(a: AttachmentItem): string { - if (/^(https?:)?\/\//.test(a.fileUrl) || a.fileUrl.startsWith("data:")) return a.fileUrl; - return a.fileUrl; + const u = a.fileUrl || ""; + if (/^(https?:)?\/\//.test(u) || u.startsWith("data:")) return u; + return `${serviceBase}${u}`; } function attDownUrl(a: AttachmentItem): string { - return a.fileId ? `/api/file/Down?id=${a.fileId}` : a.fileUrl; + return a.fileId ? `${serviceBase}/api/file/Down?id=${a.fileId}` : attUrl(a); } const imgZoom = reactive({ open: false, url: "" }); diff --git a/src/components/UserCheckModal.vue b/src/components/UserCheckModal.vue deleted file mode 100644 index 49e6a31..0000000 --- a/src/components/UserCheckModal.vue +++ /dev/null @@ -1,446 +0,0 @@ - - - - - diff --git a/src/types/homework.ts b/src/types/homework.ts index 2e69f45..f399c86 100644 --- a/src/types/homework.ts +++ b/src/types/homework.ts @@ -1,17 +1,11 @@ /** - * 课程作业前端类型契约(P1-7) + * 课程作业 V1 冻结接口类型契约(M5 后待评估退役,见 m5-plan §2.3)。 * - * 与后端 VO/DTO 一一对齐: - * - HomeworkVo → 后端 model/homework/HomeworkVo.java(Jackson 默认驼峰序列化) - * - HomeworkDetailVo → 后端 model/homework/HomeworkDetailVo.java(@JsonProperty 保留旧字段名) - * - BasePageResult → 后端 model/BasePageResult.java - * - HomeworkPageResult extends BasePageResult → 后端 model/homework/HomeworkPageResult.java - * - UpFileResultVo → 后端 model/homework/UpFileResultVo.java(@JsonProperty) - * - UpFileVo → 后端 model/homework/UpFileVo.java - * - InitializeRedVo → 后端 model/homework/InitializeRedVo.java(@JsonProperty) - * - HomeworkStatisticsVo → 后端 model/homework/HomeworkStatisticsVo.java - * - HomeworkRequest → 后端 model/homework/HomeworkRequest.java - * - HomeworkQuery → 后端 model/homework/HomeworkQuery.java + BasePageQuery.java + * 仅服务未切换页面:/homeworklist(SendList/Delete)、/homeworklist/detail + * (Detail/GetNextPrev/ReadDetail)、/stats(Statis)、/list/new?fid= 转发回显 + * (GetDetail/ChaoSend)。V2 契约在 types/homeworkV2.ts,两者互不引用。 + * 与后端 VO/DTO 一一对齐:HomeworkVo/HomeworkDetailVo(@JsonProperty 保留旧字段名)、 + * BasePageResult、UpFileResultVo/UpFileVo、HomeworkStatisticsVo、HomeworkQuery。 */ // ============================ 分页外壳 ============================ @@ -26,12 +20,6 @@ export interface BasePageResult { hasNext: boolean; } -/** 作业分页结果(对齐 HomeworkPageResult.java,扩展 unreadCount) */ -export interface HomeworkPageResult extends BasePageResult { - /** 当前用户未读作业总数 */ - unreadCount: number; -} - // ============================ 作业列表项 ============================ /** 作业视图对象(对齐 HomeworkVo.java,Jackson 默认驼峰序列化) */ @@ -159,13 +147,6 @@ export interface UpFileVo { topicType: string; } -// ============================ 一键已读 ============================ - -/** 一键已读结果(对齐 InitializeRedVo.java,@JsonProperty) */ -export interface InitializeRedVo { - count: number; -} - // ============================ 上一篇/下一篇 ============================ /** 上一篇/下一篇作业ID */ @@ -212,14 +193,11 @@ export interface HomeworkStatisticsVo { scoreDist?: { label: string; key: string; count: number }[]; } -// ============================ 课程作业 v1:提交 / 批改 ============================ +// ============================ 课程作业 v1 状态与进度 ============================ /** 作业流程状态 */ export type HomeworkStatus = "draft" | "collecting" | "closed" | "done"; -/** 单个提交状态 */ -export type SubmitStatus = "unsubmitted" | "submitted" | "graded" | "needRedo" | "redone"; - /** 提交/批改进度 */ export interface HomeworkProgress { /** 应交人数 */ @@ -242,111 +220,8 @@ export interface HomeworkProgress { gradeRate: number; } -/** 提交名单项(Submissions 接口返回,精简) */ -export interface SubmissionBrief { - subId: number; - studentId: number; - studentName: string; - submitStatus: SubmitStatus; - submittedAt: string | null; - hasGrade: boolean; - score: number | null; - level: string | null; - isExcellent: boolean; -} - -/** 提交名单按班级分组 */ -export interface SubmissionGroup { - className: string; - students: SubmissionBrief[]; -} - -/** 某作业提交情况结果 */ -export interface SubmissionsResultVo { - homeworkId: number; - title: string; - subject: string; - deadline: string | null; - hwStatus: HomeworkStatus; - hwStatusDesc: string; - summary: HomeworkProgress; - groups: SubmissionGroup[]; -} - -/** 批改结果 */ -export interface GradeResult { - score: number | null; - level: string | null; - comment: string; - isExcellent: boolean; - markedImages: string[]; -} - -/** 单份提交详情(批改工作台用) */ -export interface SubmissionDetailVo { - subId: number; - homeworkId: number; - homeworkTitle: string; - subject: string; - studentId: number; - studentName: string; - classId: string; - className: string; - submitStatus: SubmitStatus; - submittedAt: string | null; - text: string; - images: string[]; - attachments: string[]; - grade: GradeResult | null; -} - -/** 批改入参 */ -export interface GradeRequest { - subId: number; - /** grade 保存批改 / redo 要求订正 / excellent 标为优秀 */ - action?: "grade" | "redo" | "excellent"; - score?: number | string; - level?: string; - comment?: string; - isExcellent?: boolean; - markedImages?: string[]; -} - // ============================ 请求入参 ============================ -/** 作业发布/编辑入参(对齐 HomeworkRequest.java) */ -export interface HomeworkRequest { - title: string; - content?: string; - /** 学科 */ - subject?: string; - /** 截止时间 yyyy-MM-dd HH:mm:ss */ - deadline?: string; - /** 提交方式:text/image/attach/none */ - submitType?: string[] | string; - type?: number; - deptId?: string; - /** 是否允许逾期补交 */ - allowLate?: boolean; - /** 截止提醒:如 ["1d","1h"] */ - remindBefore?: string[]; - /** 存草稿:true 则不发布 */ - draft?: boolean; - deptName?: string; - topmost?: number; - /** 接收对象:"all" 或 班级 id 逗号串(receiveType=class) */ - receiveList?: string; - /** 接收对象类型:class=班级 */ - receiveType?: string; - /** 附件文件ID列表(逗号分隔串) */ - fileIds?: string; - timingSendTime?: string; - /** 短信发送标记 */ - smsFlag?: number; - /** 转发源作业ID */ - sourceId?: number; -} - /** 作业列表查询参数(对齐 HomeworkQuery.java + BasePageQuery.java) */ export interface HomeworkQuery { pageIndex?: number; @@ -379,73 +254,3 @@ export interface SchoolTreeNode { [key: string]: any; } -// ============================ 班级 ============================ - -/** 年级→班级树节点(叶子=班级,id 为数字班级 id,附学生数) */ -export interface ClassTreeNode { - id: number; - name: string; - isParent: boolean; - studentCount?: number; - children?: ClassTreeNode[]; - [key: string]: any; -} - -// ============================ v2 学生端 ============================ - -/** 当前视角角色(家长即登录学生账号,不单列) */ -export type ViewerRole = "teacher" | "student"; - -/** 学生对某作业的提交/批改态 */ -export interface StudentSubmissionInfo { - subId: number; - submitStatus: SubmitStatus; - submittedAt: string | null; - text: string; - score: number | null; - level: string | null; - comment: string; - isExcellent: boolean; - images?: string[]; - attachments?: string[]; - grade?: GradeResult | null; -} - -/** 学生视角作业项 */ -export interface StudentHomeworkVo { - id: number; - title: string; - subject: string; - publisherName: string; - deptName: string; - deadline: string | null; - createTime: string | null; - content: string; - submitType: string[]; - viewOnly: boolean; - hwStatus?: HomeworkStatus; - allowLate?: boolean; - my: StudentSubmissionInfo | null; -} - -/** 今日/我的作业列表 */ -export interface MyHomeworksVo { - buckets: { todo: number; submitted: number; graded: number; redo: number; all: number }; - list: StudentHomeworkVo[]; -} - -/** 我的某作业详情(作业信息 + 我的提交/批改) */ -export interface MySubmissionVo { - homework: { id: number; title: string; subject: string; content: string; deadline: string | null; submitType: string[]; publisherName: string; deptName: string }; - my: StudentSubmissionInfo | null; -} - -/** 学生提交作业入参 */ -export interface SubmitRequest { - serid: number; - text?: string; - /** 图片 URL / dataURL 列表(逗号串或数组) */ - images?: string[] | string; - /** 附件 id 列表(逗号串或数组) */ - attachments?: string[] | string; -} diff --git a/src/types/homeworkV2.ts b/src/types/homeworkV2.ts index 865e6fe..ded2ef6 100644 --- a/src/types/homeworkV2.ts +++ b/src/types/homeworkV2.ts @@ -240,6 +240,8 @@ export interface StudentHomeworkV2Vo { submitType: string[]; hwStatus: HwStatusV2; hwStatusDesc: string; + /** 截止后是否允许逾期补交:0 否 / 1 是(CLOSED 时决定是否渲染「补交」入口) */ + allowLate?: number; my: StudentMySubmission | null; } diff --git a/src/views/homework/HomeworkStats.vue b/src/views/homework/HomeworkStats.vue index 1a6cf71..7e9794c 100644 --- a/src/views/homework/HomeworkStats.vue +++ b/src/views/homework/HomeworkStats.vue @@ -23,12 +23,20 @@ onMounted(load); /* 分数分布条 */ const maxDist = computed(() => Math.max(1, ...(stat.value?.scoreDist || []).map((d) => d.count))); const BAR_COLOR: Record = { excellent: "#19be6b", good: "#2d8cf0", pass: "#ff9900", fail: "#ed4014" }; +/** 后端是否提供课程作业口径数据(老部署未升级时避免把「未返回」伪装成 0) */ +const hasScoreDist = computed(() => (stat.value?.scoreDist?.length ?? 0) > 0); +const hasByHomework = computed(() => (stat.value?.byHomework?.length ?? 0) > 0); + +/** 卡片值:字段缺失显示「—」而非 0,避免被误读为「确实没有作业」 */ +function cardValue(v: number | undefined | null, suffix = ""): string { + return v === undefined || v === null ? "—" : `${v}${suffix}`; +} const cards = computed(() => [ - { label: "我布置的作业", value: stat.value?.myAssignCount ?? 0 }, - { label: "待批改总量", value: stat.value?.pendingGrade ?? 0 }, - { label: "平均提交率", value: (stat.value?.avgSubmitRate ?? 0) + "%" }, - { label: "作业总数", value: stat.value?.total ?? 0 }, + { label: "我布置的作业", value: cardValue(stat.value?.myAssignCount) }, + { label: "待批改总量", value: cardValue(stat.value?.pendingGrade) }, + { label: "平均提交率", value: cardValue(stat.value?.avgSubmitRate, "%") }, + { label: "作业总数", value: cardValue(stat.value?.total) }, ]); const HW_STATUS_TEXT: Record = { draft: "草稿", collecting: "收集中", closed: "已截止", done: "已完成" }; @@ -52,7 +60,7 @@ const HW_STATUS_TEXT: Record = { draft: "草稿", collecting: "
成绩分布(已批改提交)
-
+
{{ d.label }}
@@ -61,12 +69,14 @@ const HW_STATUS_TEXT: Record = { draft: "草稿", collecting: "
{{ d.count }}
+
暂无可统计的批阅分数(后端未返回成绩分布口径)
我布置的作业 · 提交与批改
+
暂无需要收集提交记录的作业(后端未返回按作业进度口径)
@@ -124,6 +135,11 @@ const HW_STATUS_TEXT: Record = { draft: "草稿", collecting: " margin-bottom: 12px; } } +.block-empty { + color: #a6aab0; + font-size: 13px; + padding: 12px 0; +} .dist { .dist-row { display: flex; diff --git a/src/views/homework/NewHomework.vue b/src/views/homework/NewHomework.vue index b4005fd..8f63f9c 100644 --- a/src/views/homework/NewHomework.vue +++ b/src/views/homework/NewHomework.vue @@ -72,7 +72,7 @@ async function loadClasses() { function toggleAll(v: boolean) { if (v) selClasses.value = [...allClassIds.value]; } -onMounted(loadClasses); +/* 班级树由下方统一初始化入口按序加载(fillFromV2 依赖 allClassIds 判定「全部班级」) */ const ruleValidate = { title: [ @@ -147,6 +147,9 @@ function fillFromV2(d: HomeworkV2Vo) { } onMounted(async () => { + // 先加载班级树再回显:fillFromV2 用 allClassIds 判定「全部班级」, + // 若并行执行则 allClassIds 仍为空,勾选态会被误判为「未全选」 + await loadClasses(); if (!(isEdit.value || fid.value > 0)) return; try { if (isEdit.value) { diff --git a/src/views/homework/StudentHomeworks.vue b/src/views/homework/StudentHomeworks.vue index 49dd584..3dfe80a 100644 --- a/src/views/homework/StudentHomeworks.vue +++ b/src/views/homework/StudentHomeworks.vue @@ -272,8 +272,8 @@ function zoom(url: string) {
- -