build / build (push) Successful in 13m15s
- v29 删除前端对 notice/ScopePreview(推送范围「已选学员数」预估)的请求: api 方法、ScopePreviewVo 类型、dev:mock 桩、ClassScopeInput/PushScopePanel 两个组件的预估 UI 一并移除。 - v30 修复发文部门/联系组名称重复且带逗号(如「校园部,校园部」并逐次翻倍): postForm 原先把参数同时拼进 URL query 与 urlencoded body,Servlet 会合并两处取值、 Spring @RequestParam String 再以「,」拼接。改为只发 body,仅在 dev:mock 下附带 query (vite-plugin-mock 不解析 urlencoded body),不影响线上。 - 修复时间筛选失效:DatePicker 的 Date 实例被 alova 直接 encodeURIComponent 成浏览器本地化 日期串(Fri Aug 21 2026 ... GMT+0800),后端按 yyyy-MM-dd 解析不了。列表侧提交前归一化为 短日期,alova-core 另加一层 Date → 本地 'yyyy-MM-dd HH:mm:ss' 的兜底归一化。 - 列表「类型 / 状态 / 阅读情况」三列改醒目样式(口径不变,仅样式): 类型改胶囊(重要=实心红、普通=浅灰弱化);状态改彩色圆点胶囊,已发布绿/定时中蓝/ 待审批橙/已驳回红/草稿灰,已撤回、置顶改实心小徽标;阅读情况改渐变进度条 + 已读/应读·百分比, 读满转绿。同步微调类型(80→90)、阅读情况(110→180)列宽。 - 同步更新《执行计划任务方案.md》v29 / v30 记录。
1806 lines
180 KiB
Markdown
1806 lines
180 KiB
Markdown
# 通知公告前端改造成「校园通知」执行计划任务方案(v2 · 2026-09-13 检查修订)
|
||
|
||
> 工程:前端 `E:\YHXT\view\CampusAnnouncement`(Vue3 + iview + alova + vite);后端 `E:\YHXT\java\CampusNotice`
|
||
> 参照物:`http://localhost:8101/api-test.html`(校园通知测试台,6 视图信息架构)
|
||
> 决策基线:对齐 api-test 能力 + 全站「通知」术语;保留 iview 左侧菜单框架;接口契约以后端 Controller 实际定义为准。
|
||
>
|
||
> 本版用途:v1 方案(信息架构/术语/API 扩展/页面清单)已大面积落地。本轮对**已改造部分做全面错误检查**——进度盘点、错误清单 E1–E8、修复计划与回归清单;修复结果回填 §七。
|
||
|
||
---
|
||
|
||
## 一、改造进度盘点(对照 v1 差距清单)
|
||
|
||
| v1 待办项 | 状态 | 说明 |
|
||
| --- | --- | --- |
|
||
| 术语全站切换 | ✅ 完成 | views/components 零残留;~~layout 菜单名映射表内旧名是映射源,应保留~~ → **v10 已删除该映射表**,栏目名以接口 `name` 为准 |
|
||
| 菜单契约风险(GetUserMod 数据不可控) | ⚠️ 定论已被 v10 推翻 | 原方案:`layout.vue` MENU_NAME_MAP 展示层改名 + `utils/menuRoute.ts` 解析器(path 精确→模块名别名→外链→modId 兜底),配错显式提示不静默跳默认页。**现方案见 v10**:改名层移除,仅保留 menuRoute 解析器(仍按原始 name/modId 兜底) |
|
||
| 草稿箱独立页 | ✅ 完成 | `DraftList.vue`(搜索/续编/提交审批弹窗/删除);菜单由 layout 注入(id 3100,挂「已发管理」后) |
|
||
| API 扩展 SaveDraft/DraftList/Submit/ScopeTree/ScopePreview/ReadReceipt/Approve+opinion | ✅ 完成 | `notice.ts` 已封装,类型对齐后端 |
|
||
| 写通知:三态动作区/回执勾选/推送范围树+预估/多步审批流 | ✅ 基本完成 | `PushScopePanel` + `ApprovalFlowEditor`(审批人池数据源、会签/或签、上移下移);**教师接收名单缺失 → E3** |
|
||
| 已发管理:状态/类型/部门/日期筛选、审批进度、定时中标注、驳回重提、阅读回执弹窗 | ✅ 完成 | `NoticeList.vue` |
|
||
| 审批中心:四 Tab/待审角标/多步进度 | ✅ 完成 | layout 60s 轮询 ApprovalCount 角标;**审批意见列死字段 → E6** |
|
||
| 我的通知:未读已读分段/类型/搜索/置顶重要徽标/回执提示 | ✅ 完成 | `NoticeAccept.vue` |
|
||
| 详情:审批轨迹+每步流程进度+四态徽标 | ✅ 完成 | `NoticeDetails.vue`;**三处字段后端不返回 → E4/E5** |
|
||
| 审核设置(审批人池 CRUD) | ⚠️ 有错误 | 页面完成,列表接口路径写错 → **E1** |
|
||
| 旧路径兼容/重定向 | ✅ 完成 | routes.ts 别名段保留 |
|
||
|
||
编译级验证:`npm run build` 通过;`vue-tsc --noEmit` 与本机 Node/TS 版本不兼容无法运行(工具链问题,非代码错误),类型契约以下一轮人工比对为准。
|
||
|
||
---
|
||
|
||
## 二、错误检查方法
|
||
|
||
1. **编译层**:vite build;
|
||
2. **契约层**:逐接口比对 `NoticeController/DictController/BaseApiController/FileController` 的 @Mapping 与 @RequestParam/@RequestBody 参数名 vs 前端 `api/module/notice.ts` 的 URL/方法/参数;
|
||
3. **数据层**:比对 `NoticeVo/NoticeDetailVo/SubmitRequest/FlowStepItem` 实际返回字段 vs 页面渲染引用与 `types/notice.ts` 声明(前端类型里存在而后端不返回的字段=死引用);
|
||
4. **交互层**:对照 api-test.html 六视图的表单字段与操作语义,找缺失能力。
|
||
|
||
## 三、检出错误清单
|
||
|
||
| # | 位置 | 现象 | 根因 | 修复 |
|
||
| --- | --- | --- | --- | --- |
|
||
| **E1** | `notice.ts` `approvers()` | 审核设置页审批人列表加载失败 | 调 `dict/Approvers`,后端为 `dict/Approver`(单数) | 前端路径改 `/Approver` |
|
||
| **E2** | `NewNotice.vue` `buildBody` | 「存为草稿」丢失已配置审批流 → 草稿箱「提交审批-沿用草稿已存流程」必失败 | flow 仅提交审批动作携带,draft 分支不带(后端 SaveDraft 支持存流程定义) | draft 分支同样携带 flow |
|
||
| **E3** | `NewNotice.vue` | 无「教师接收名单」:不能只通知教师;ScopePreview 教师数恒 0;发布被强制要求选学员范围 | 参照 api-test `cReceivers` 面板未实现(后端 receiveList 支持逗号串/JSON/ALL) | 新增表单项;buildBody 带 receiveList;校验改「范围或名单二选一」;联动 PushScopePanel 预估;编辑回显(ALL 或 JSON 数组型) |
|
||
| **E4** | `NoticeDetails.vue` 驳回/通过 Alert | 审批人/时间/意见显示空 | 用了后端不存在的 `approvalBy/approvalTime/approvalRemark` | 改从 `approvalRecords` 取末条 action=3/2(operatorName/actionTime/opinion) |
|
||
| **E5** | `NoticeDetails.vue` 接收班级标签 | 恒显「全部班级」 | 用了不存在的 `receiveText` | 按 `pushScopes` 名称拼接;ALL→全体教师;旧链路兜底;标签改「接收范围」 |
|
||
| **E6** | `ApprovalList.vue` 审批意见列 | 恒显「—」 | NoticeVo 无 `approvalRemark`(意见仅详情轨迹返回) | 删除该列;types 同步清理死字段 |
|
||
| **E7** | 后端 `DictController` | 「发送短信提醒」勾选永不出现(404 被 catch 静默) | 前端约定 `dict/Sync`;后端 service 有 `getSchModuleIsSms()` 但控制器漏暴露 | 后端补 `@GetMapping("Sync")`(3 行;需重启生效;现实现固定 false 属平台占位,UI 行为不变) |
|
||
| **E8** | `ClassCheckInput.vue`/`GradeClassSel.vue` | 无运行影响 | 死代码,引用后端不存在的 `base/GetClassTree` | 仅登记,后续清理删除 |
|
||
|
||
已知限制(不列入本轮修复):旧链路班级通知编辑回显依赖的 `detail.receiveType` 后端 Detail VO 未返回,旧数据续编范围不回填;如需支持由后端补字段。
|
||
|
||
---
|
||
|
||
## 四、修复执行顺序
|
||
|
||
1. 前端小修:E1(1 行)→ E2(1 行)→ E6(删列)+ types 死字段清理;
|
||
2. 前端中修:E4/E5(NoticeDetails computed)→ E3(NewNotice 教师名单 + 校验 + 回显 + 预估联动);
|
||
3. 后端补口:E7(DictController.Sync)+ `mvn compile` 验证(JAVA_HOME=`D:/Program Files/Java/jdk-1.8`,mvn=`D:/Program Files/Java/maven/apache-maven-3.6.1/bin/mvn.cmd`);
|
||
4. 回归:`npm run build` 重跑 + §五清单自检,结果回填 §七。
|
||
|
||
## 五、回归验证清单
|
||
|
||
- [ ] E1:审核设置页列表加载/新增/停用/删除正常
|
||
- [ ] E2:写通知→配两步审批→存为草稿→草稿箱提交审批(不重选人)→ 已发管理进度「第1/2步…」
|
||
- [ ] E3:只填教师名单不选范围可发布;范围+名单预估合计正确;ALL 全员;草稿续编回显
|
||
- [ ] E4:已驳回单详情意见/人/时间正确;通过单显示最后通过人
|
||
- [ ] E5:范围单「接收范围:xx、xx」;教师单「全体教师」;旧单「全部班级」
|
||
- [ ] E6:审批中心四 Tab 正常、无死列
|
||
- [ ] E7:重启后 `GET /api/dict/Sync` code=0
|
||
- [ ] 旧链路回归:收件箱/一键已读/撤回/置顶/转发/附件/打印 不受影响
|
||
- [ ] `npm run build` 通过;主链路冒烟(Create direct/approve、Submit、多步 Approve、Reject、Delete)
|
||
|
||
## 六、风险与边界
|
||
|
||
- E7 需 IDEA 重启服务生效(服务跑在 Debug 模式,脚本不代重启),本轮仅保证编译通过;
|
||
- E3 回显保守:仅 `ALL` 或 JSON 数组型 receiveList 回填,防旧班级 id 串误显;
|
||
- 全部修复不动鉴权/布局/alova 基建;types 仅删后端证实不返回的字段。
|
||
|
||
## 七、修复完成记录(实施时回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| E1 | ✅ 2026-09-13 | `notice.ts` approvers() → `dict/Approver` |
|
||
| E2 | ✅ 2026-09-13 | `NewNotice.vue` draft 分支 buildBody 携带 flow |
|
||
| E3 | ✅ 2026-09-13 | 教师名单表单项 + receiveList 入 body + 「范围/名单二选一」校验 + 预估联动 + 编辑回显(ALL 或 JSON 数组型) |
|
||
| E4 | ✅ 2026-09-13 | `NoticeDetails.vue` 驳回/通过 Alert 改取 approvalRecords 末条记录 |
|
||
| E5 | ✅ 2026-09-13 | 「接收范围」标签按 pushScopes/ReceiveList 计算(receiveLabel) |
|
||
| E6 | ✅ 2026-09-13 | 审批中心删除审批意见死列;types 清 approvalRemark/approvalBy/approvalTime/receiveText |
|
||
| E7 | ✅ 2026-09-13 | DictController 补 `GET Sync`,`mvn compile` 通过;**需 IDEA 重启服务生效**(当前 service 固定返回 false,勾选仍隐藏属平台占位预期) |
|
||
| E8 | 📝 登记 | ClassCheckInput/GradeClassSel 死代码,后续清理删除 |
|
||
| 构建回归 | ✅ 2026-09-13 | `npm run build` 通过(仅 chunk 体积警告,非错误);死字段引用清零 |
|
||
|
||
---
|
||
|
||
# v3(2026-09-14)· 写通知业务逻辑迁移三项
|
||
|
||
> 需求(参照 api-test 写通知视图):
|
||
> 1. **推送范围(学员)接口更换**,并**显示学员选中人数**;
|
||
> 2. **去除教师接收名单**(撤销 v2-E3 在写通知中的名单输入,仅移除发布表单入口,旧数据详情展示不动);
|
||
> 3. **选择审批人接口更换**:由「审批人池(dict/ApproverPool 账号路径)」改为「教师树(base/getTeaTree 教师ID路径)」。
|
||
|
||
## 一、契约核对(后端实况,均已确认支持)
|
||
|
||
| 能力 | api-test 用法 | 后端契约 | 前端现状(待改) |
|
||
| --- | --- | --- | --- |
|
||
| 学员范围树 | `GET base/getStuTree?toUser=false`,节点 `{id,name,type(1组/2叶子),children}` 自底向上归类 scopeType(叶=4 班级→父=3 年级→再上=2 学部→根强制=1 全校) | `BaseApiController.getStuTree`(SchTreeNodeDto 树) | `PushScopePanel` 走 `notice/ScopeTree`(后端已拼好树,但需求要求统一换 getStuTree 口径) |
|
||
| 选中学员数 | `GET notice/ScopePreview?scopes=…` → `studentCount` | `NoticeController.scopePreview(scopes, receiveList?)`,receiveList 可空 | 已有预估行「学员/教师/合计」,含教师定向口径 |
|
||
| 审批人选人树 | `GET base/getTeaTree?toUser=true`,叶子 id=teaId | `BaseApiController.getTeaTree` | `ApprovalFlowEditor` 走 `dict/ApproverPool` + UserCheckModal 账号 |
|
||
| 流程提交 | 每步 `{name,vote,approverTeaIds:[…]}` | `FlowStepItem.approverTeaIds` 与 approvers 二选一;`resolveRequestFlow` 同时服务于 Create.flow 与 Submit.steps;服务端 `bindApprovers` 按校教师名单绑定姓名 | 前端每步传 `approvers:[账号]` |
|
||
| 记忆带入 | `notice/ApproverSuggest` → teaIds;`notice/ApproverResolve?ids=` → `{id,name}` 姓名解析 | 两接口均存在 | Suggest 结果靠池内 teaId 映射账号;Resolve 未封装 |
|
||
| 编辑回显 | 详情 `approvalFlow[].approverTeaIds/approverNames/vote` | `ApprovalFlowVo` 四字段齐备 | 回显走 approvers 账号列表 |
|
||
|
||
## 二、修改计划
|
||
|
||
1. **API 层 `api/module/notice.ts`**:`baseApi` 增 `getStuTree/getTeaTree`;`noticeApi` 删 `scopeTree()`、增 `approverResolve(ids)`;`approvalSettingApi` 删 `approverPool()`(仅编辑器使用;审核设置管理页走 `approvers()` 不受影响);`scopePreview` 保留(receiveList 变可选、不再传)。
|
||
2. **类型 `types/notice.ts`**:新增 `SchTreeNode`(getStuTree/getTeaTree 节点);`NoticeRequest.flow` 项改 `{name?,vote,approverTeaIds}`;`ScopeTreeNode` 注释改为「面板视图节点(由 getStuTree 归类而来)」。
|
||
3. **`PushScopePanel.vue`**:数据源换 `base/getStuTree?toUser=false` + `toScopeNode` 归类(与 api-test 同算法,toUser=false 无学生叶子、防御性跳过 type≠1);删 `receiveList` prop 及预估联动;预估改为「已选学员 N」(N=ScopePreview.studentCount)。
|
||
4. **`NewNotice.vue`**:删「教师名单」表单项、`form.receiveList`、buildBody receiveList、编辑回显 ReceiveList 块;校验恢复「必须选择推送范围」;PushScopePanel 去 prop;审批流回显改 `approverTeaIds/approverNames`;编辑态 Submit steps 改传 `approverTeaIds`。
|
||
5. **`ApprovalFlowEditor.vue`(重写选人)**:内联教师树选择面板(搜索过滤+命中自动展开、组节点不可勾、教师叶子可多选、同教师跨组出现勾选态同步、完成选择回填当前步);步骤模型 `{vote, teaIds, names}`;勾选启用时/手动按钮带入 ApproverSuggest 记忆(经 ApproverResolve 解析姓名,未命中降级 ID);对外 v-model flow=有效步 `{vote, approverTeaIds}`。
|
||
6. **`DraftList.vue`**:提交审批 steps 改传 `approverTeaIds`;「沿用草稿已存流程」逻辑不变(不勾选=steps 缺省)。
|
||
7. 回归:`npm run build`;对照 api-test 人工走读契约。
|
||
|
||
已知限制:旧「账号路径」草稿/驳回单(approvalFlow 仅有 approvers 无 teaIds)在新编辑器中不回显,保存后流程按新 selections 覆盖(api-test 同口径);如需兼容由后端补 teaId 反查。
|
||
|
||
## 三、回归验证清单(v3)
|
||
|
||
- [ ] 写通知:范围树来自 getStuTree,勾选全校/学段/年级/班级出 chips,「已选学员 N」随勾选变化
|
||
- [ ] 写通知:无「教师名单」输入;不选范围直接发布/提交审批被拦截
|
||
- [ ] 写通知:勾选审批→教师树选人(搜索/多选/多组同步);带入上次审批人经 Resolve 显示姓名
|
||
- [ ] Create/Edit/Submit 请求体 flow/steps 为 `{name,vote,approverTeaIds}`;提交后审批中心可正常审批
|
||
- [ ] 编辑回显:教师树审批流+姓名正确;草稿箱提交审批(重选/沿用)正常
|
||
- [ ] 旧数据:详情「接收范围/全体教师」标签不受影响;审核设置页 CRUD 不受影响
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 四、实施记录(v3 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 1 API 层 | ✅ 2026-09-14 | `baseApi.getStuTree/getTeaTree`;`noticeApi` 删 `scopeTree()`、增 `approverResolve(ids)`;删 `approverPool()`;`scopePreview` 保留(receiveList 缺省空) |
|
||
| 2 类型层 | ✅ 2026-09-14 | 新增 `SchTreeNode`;`NoticeRequest.flow` 与 `SubmitRequest.steps` 改 `{name?,vote,approverTeaIds}`;`receiveList` 字段标注「发布表单已下线」 |
|
||
| 3 PushScopePanel | ✅ 2026-09-14 | 数据源 `base/getStuTree?toUser=false` + `toScopeNode` 自底向上归类(同 api-test);删 receiveList prop;预估区改「已选学员 N」(ScopePreview.studentCount,防抖 350ms,无勾选不显示) |
|
||
| 4 教师名单去除 | ✅ 2026-09-14 | NewNotice 表单项/form.receiveList/buildBody 组装/编辑回显(ALL·JSON 串回填)全删;校验恢复「请选择推送范围!」;详情 receiveLabel 与旧数据展示未动 |
|
||
| 5 ApprovalFlowEditor | ✅ 2026-09-14 | 重写为教师树路径:Modal 内联树(搜索命中过滤+自动展开、组不可勾、叶子多选、同教师跨组按 teaId 同步、完成选择回填当前步);步骤模型 `{vote,teaIds}`;带入上次=ApproverSuggest→ApproverResolve(未命中降级 ID);对外 flow=`{name,vote,approverTeaIds}` |
|
||
| 6 NewNotice 审批流 | ✅ 2026-09-14 | 回显改 `approvalFlow[].approverTeaIds/approverNames`;编辑态 Submit steps 传 `approverTeaIds`;Create/Edit flow 体同步 |
|
||
| 7 DraftList | ✅ 2026-09-14 | 提交审批弹窗 steps 传 `approverTeaIds`;不勾选=steps 缺省沿用草稿已存流程(不变) |
|
||
| 构建回归 | ✅ 2026-09-14 | `npm run build` 通过(40.2s,仅 chunk 体积警告);残留引用清零(approverPool/scopeTree/form.receiveList 无引用);ApproverResolveVo{id,name,photo}/ApproverSuggestVo{teaIds} 字段人工比对一致 |
|
||
|
||
待人工冒烟(需登录环境):§三 v3 清单逐条过;后端 `notice/ScopeTree` 接口本轮起前端不再调用(保留未删,其他端或仍用)。
|
||
|
||
---
|
||
|
||
# v4(2026-09-14)·「审批列表菜单消失」排查登记(平台侧问题,前端零改动)
|
||
|
||
## 一、现象与结论
|
||
|
||
- **现象**:左侧菜单无「审批中心」「审核设置」入口,其余菜单正常。
|
||
- **结论**:**非代码问题**。基础平台用户中心对该校全员不再返回模块 id=5(审批列表)与 id=6(审核设置),`GetUserMod` 透传后前端菜单随之消失。
|
||
|
||
## 二、排查证据链
|
||
|
||
| # | 层面 | 核查结果 |
|
||
| --- | --- | --- |
|
||
| 1 | 前端路由/菜单 | `/approval`(modId 5)、`/approval-setting`(modId 6) 路由、menu.ts 兜底、MENU_NAME_MAP(审批列表→审批中心)、图标角标逻辑全部完好;v3 改造未触碰菜单/layout |
|
||
| 2 | 后端 CampusNotice | `BaseApiController.GetUserMod` 仅透传 `YschBaseOpenApi.getUserModule(userId, schId)`,无过滤 |
|
||
| 3 | 远程用户中心实况 | 直查 `base.yunhaixingtong.com/userbase /oapi/user/modules`(复刻平台签名,脚本 `.scratch/oapi-call.mjs`):云海中学(schId=3) 全部已知账号(lijing=10008、王虎=10007、陈星1=10006、测试教师2=10045、ceshiteacher=125287)均只返回 1/2/3/21/4 五个模块,**5/6 全员缺失** |
|
||
| 4 | 时间线线索 | 平台 2026-09-10 新建「校园公告」应用(appKey 100006),疑似其后有人调整过应用/模块/角色配置导致丢失 |
|
||
|
||
## 三、修复方案(平台侧操作,前端不用发版)
|
||
|
||
1. 基础平台管理后台 → 应用管理 → 「通知公告」应用 → 模块管理:确认两模块存在且启用——**审批列表**(path `/approval`,前端显示「审批中心」)、**审核设置**(path `/approval-setting`);
|
||
2. 角色权限中为对应角色(老师/管理员)重新勾选这两个模块;
|
||
3. 恢复授权后菜单即时回来(前端已有名称映射+路径解析兜底)。
|
||
|
||
备选:平台侧无法及时修改时,可仿「草稿箱」由 layout 前端注入这两个菜单项临时兜底(偏离“菜单以平台授权为准”契约,需产品确认后实施)。
|
||
|
||
## 四、验证方式
|
||
|
||
恢复后执行 `node .scratch/oapi-call.mjs "/oapi/user/modules" "userId=10008&schId=3"`,确认返回列表包含 id=5、id=6;或直接刷新前端页面观察左侧菜单。
|
||
|
||
## 五、实施记录
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 排查登记 | ✅ 2026-09-14 | 本节;前端/后端零代码改动 |
|
||
| 平台侧重新授权 | ⏳ 待平台管理员操作 | 模块 5/6 挂回应用并授权角色 |
|
||
| 复验 | ⏳ 待平台操作后 | 按 §四 验证 |
|
||
|
||
---
|
||
|
||
# v5(2026-09-14)· 发公告「推送范围」改为「接收班级」二选一(全部班级 / 指定班级)
|
||
|
||
> 需求(参照截图,旧链路 new_notice 样式):发公告表单的范围选择由「全校/学段/年级/班级」多级树面板(PushScopePanel)改为:
|
||
> - 单选 **全部班级** / **指定班级**;
|
||
> - 指定班级 → 只读输入框(placeholder「选择通知的班级」)+「+」按钮打开**选择用户弹窗**(左侧年级→班级树勾选、右侧已选列表全选/计数、提交/取消),垃圾桶按钮清空;
|
||
> - 表单项 label 由「推送范围」改为「接收班级」。
|
||
|
||
## 一、契约核对(后端实况,均以源码确认)
|
||
|
||
| # | 结论 | 依据 |
|
||
| --- | --- | --- |
|
||
| 1 | **后端零改动**:全部班级 → `pushScopes=[{scopeType:1, scopeId:0, scopeName:"全部班级"}]`;指定班级 → `[{scopeType:4, scopeId:classId, scopeName:className}…]` | `PushScopeTypeEnum`:SCHOOL(1) 走 `getSchStuList(schSerId)` 全校(scopeId 不参与解析,api-curl-test.sh 亦用 scopeId=0 验证);CLASS(4) 走 `getClaArrStuList` 展开 |
|
||
| 2 | **弹窗班级树数据源 = `base/getStuTree?toUser=false`**,前端自底向上归出「年级→班级」两层(学校/学段层丢弃) | 后端**无 `base/GetClassTree`**(v2-E8 登记属实,ClassCheckInput 的旧数据源不可用);`BaseApiController.getStuTree` 返回组节点树 |
|
||
| 3 | mock 缺 `getStuTree`(v3 换源后未补),本轮一并补 mock(学校→学段→年级→班级),保持 mock 模式可用 | `mock/noticeapi.ts` 仅有 GetClassTree/GetSchoolAll |
|
||
| 4 | 编辑回显兼容:pushScopes 中 type=2/3(旧多级面板选的学部/年级)在树中展开为其下属班级;type=5/6 等无法映射项原样保留随表单提交,防丢范围 | 对齐 PushScopePanel.applyScopes「树外项保留」口径 |
|
||
| 5 | 旧链路数据(receiveType=class):tlist 班级 id 串 → type=4 项(树加载后解析真实班级名);tlist="all" → 全部班级 | NewNotice 既有映射逻辑延续 |
|
||
|
||
## 二、修改计划
|
||
|
||
1. **新建 `src/components/ClassScopeInput.vue`**:v-model=`PushScopeItem[]`;radio 全部班级(默认)/指定班级 + 只读输入框 + 加号(开 UserCheckModal,数据源 getStuTree 自归类的年级→班级树)/垃圾桶(清空指定班级);弹窗提交空选=回落全部班级(旧链路口径);指定班级下方保留「已选学员 N 人」预估(ScopePreview,防抖,v3 能力不回退);暴露 `setScopes()` 供编辑回显(含 type 2/3 展开与名称解析,树未就绪时缓冲)。
|
||
2. **`NewNotice.vue`**:PushScopePanel → ClassScopeInput,label 改「接收班级」;回显三分支改走 setScopes(pushScopes / receiveType=class 班级串 / tlist=all→全部班级);发布校验文案改「请选择接收班级!」。
|
||
3. **`mock/noticeapi.ts`**:补 `/api/base/getStuTree`(toUser 参数按组节点树返回,由 gradeClasses 派生)。
|
||
4. PushScopePanel.vue 退役(保留文件不删,文档登记);ClassCheckInput/GradeClassSel 死代码登记维持 v2-E8 口径。
|
||
5. 回归:`npm run build` + 本节清单人工冒烟。
|
||
|
||
## 三、回归验证清单(v5)
|
||
|
||
- [ ] 新建公告:默认「全部班级」,直接发布成功,详情接收范围显示「全部班级」
|
||
- [ ] 切「指定班级」自动开弹窗:年级树勾选级联、右侧全选/计数、搜索班级名可用;提交回填班级名顿号串
|
||
- [ ] 指定班级清空后发布被拦(「请选择接收班级!」);弹窗提交空选回落全部班级
|
||
- [ ] 编辑回显:v3 单(全校/年级/班级混合)→ 对应 radio 态与班级名正确;旧链路班级串单 → 指定班级 + 真实班级名;tlist=all 单 → 全部班级
|
||
- [ ] Create/Edit 请求体 pushScopes 为 scopeType 1/4 组合,后端详情/收件范围/应读人数正确
|
||
- [ ] mock 模式(isMock)下弹窗树可加载
|
||
- [ ] `npm run build` 通过;其余表单项(附件/审批/定时/草稿)不受影响
|
||
|
||
## 四、实施记录(v5 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 1 ClassScopeInput | ✅ 2026-09-14 | 新建 `components/ClassScopeInput.vue`:radio 全部班级(默认)/指定班级 + 只读框 + 加号弹窗/垃圾桶清空;树=base/getStuTree 自归类「年级→班级」;空选提交回落全部班级(旧链路口径);setScopes 含 type 2/3 展开为班级、type 5/6 原样保留(extras)、树未就绪缓冲;「已选学员 N 人」预估保留(ScopePreview) |
|
||
| 2 NewNotice 接入 | ✅ 2026-09-14 | PushScopePanel → ClassScopeInput;label 改「接收班级」;回显三分支(pushScopes 原样 / receiveType=class 班级串→type4+树解析真实班级名 / tlist=all→全部班级);转发空范围默认全部班级;校验文案「请选择接收班级!」 |
|
||
| 3 mock getStuTree | ✅ 2026-09-14 | 补 `/api/base/getStuTree`(学校→学段→年级→班级组节点树,由 gradeClasses 派生);**已知问题登记:mock 仍缺 `base/getTeaTree`(v3 审批选人换源后未补),mock 模式下审批流选人不可用,后续补** |
|
||
| 4 PushScopePanel 退役登记 | ✅ 2026-09-14 | 文件保留未删(无任何 import 引用,不参与构建产物);如需恢复多级树面板可直接复用 |
|
||
| 构建回归 | ✅ 2026-09-14 | `npm run build` 通过(15.68s,仅 chunk 体积警告,与历次一致);PushScopePanel/推送范围 残留引用仅剩注释与退役文件本身 |
|
||
|
||
待人工冒烟(需登录环境):§三 v5 清单逐条过(重点:真实 getStuTree 数据下年级归类是否正确、后端 Create/Edit 全部班级与指定班级的应读人数)。
|
||
|
||
---
|
||
|
||
# v6(2026-09-14)· 审批人选人弹窗改用「选择用户」穿梭式(UserCheckModal)
|
||
|
||
> 需求(参照截图):发送通知「审批 → 选择审批人」当前用的是 v3 重写的单栏内联教师树 Modal(标题「选择审批人(可多选)」)。改为复用 `UserCheckModal.vue` 的「选择用户」双栏穿梭式弹窗:左侧组织树(带复选框、父子级联)+ 中间箭头 + 右侧已选列表(全选/计数/删除选中)+ 顶部名字搜索 + 提交/取消。**审批人数据源不变,仍走教师树 `base/getTeaTree?toUser=true`(叶子 id=teaId)**。多步审批、或签/会签、记忆带入、编辑回显等能力不回退。
|
||
|
||
## 一、契约核对
|
||
|
||
| # | 结论 | 依据 |
|
||
| --- | --- | --- |
|
||
| 1 | `UserCheckModal` 可直接复用:props `sources:[{name,loader}]`(loader 返回 ztree 树)、`userids:number[]`(回显勾选);emits `on-submit` 返回 `[{id,name,group}]`(id=真实 teaId,经 `getSplitId` 还原) | 组件已用于「接收班级」弹窗,`formatTree` 无 `isParent` 时按有无 children 判组/叶子(教师叶子无 children→type 2) |
|
||
| 2 | 单数据源(教师树)→ `sources.length===1`,Tabs 不渲染,与截图一致(截图无 tab 栏,仅搜索框+双栏) | `UserCheckModal` 模板 `v-if="sources.length>1"` |
|
||
| 3 | 提交后按步回填:`steps[editingStep].teaIds = 返回 ids`,姓名写入 `teaNames` 供 chip 展示;对外 flow 仍 `{name,vote,approverTeaIds}` | 现 `collectFlow/emitChange` 不变 |
|
||
| 4 | 依赖 jQuery zTree + `requireImg` 已由「接收班级」弹窗全局引入,无需新增依赖 | `UserCheckInput/ClassScopeInput` 已用 |
|
||
| 5 | 记忆带入(ApproverSuggest→ApproverResolve)、编辑回显(setFlow+names 兜底)、多步增删/上下移、dropApprover 逐 chip 删除——均与选人弹窗解耦,保持不动 | 现组件逻辑 |
|
||
| 6 | 已知限制延续 v5:mock 缺 `base/getTeaTree`,mock 模式下审批选人弹窗树加载失败(真实环境正常);本轮不扩范围补该 mock | v5 §四登记 |
|
||
|
||
## 二、修改计划(仅 `ApprovalFlowEditor.vue`)
|
||
|
||
1. 引入 `UserCheckModal`;模板中删除内联 `<Modal title="选择审批人(可多选)">…</Modal>` 整块,替换为 `<UserCheckModal ref="pickerRef" :sources="pickerSources" :userids="editingTeaIds" @on-submit="onPickerSubmit" />`。
|
||
2. `pickerSources = [{ name: "教师", loader: () => baseApi.getTeaTree(true).send() }]`;`editingStep` 由 `let` 改 `ref(0)`;`editingTeaIds = computed(() => steps[editingStep].teaIds)`。
|
||
3. `openPicker(i)`:`editingStep=i` 后 `pickerRef.value.show()`;`onPickerSubmit(arr)`:回填当前步 teaIds、写 teaNames、`emitChange()`(UserCheckModal 提交后自关)。
|
||
4. 删除仅供内联树使用的死代码:`rawTree/rows/rowByKey/treeLoading/treeError/expanded/keyword/pickerOpen`、`buildRows/ensureTree/hitKeys/visibleRows/toggle/isTeacherChecked/onTeacherCheck/closePicker/renderFromSteps`,及 `SchTreeNode` 若不再引用则删;删除 `.tea-tree/.picker-search` 等相关 scoped 样式。
|
||
5. 保留:`teaNames/nameOf/steps/enabled/addStep/removeStep/move/dropApprover/collectFlow/emitChange/validFlow/validate/setFlow/loadSuggest/bindStepByTeaIds/watch(enabled)/onMounted/defineExpose`。
|
||
6. 回归:`npm run build`。
|
||
|
||
## 三、回归验证清单(v6)
|
||
|
||
- [ ] 勾选审批→点「选择审批人」弹出「选择用户」双栏弹窗(无 tab),左=教师组织树可勾选级联,右=已选列表带全选/计数/删除选中,顶部搜索教师名可定位勾选
|
||
- [ ] 提交后当前步生成教师 chips(姓名正确);再次打开回显已勾教师
|
||
- [ ] 多步:每步独立选人互不串;上移/下移/删除步、逐 chip ×删除、垃圾桶清空 均正常
|
||
- [ ] 「带入上次所选审批人」经 ApproverResolve 显示姓名(池外降级 ID)
|
||
- [ ] 编辑回显:教师树审批流+姓名正确还原,弹窗内可再编辑
|
||
- [ ] Create/Edit/Submit 请求体 flow/steps 仍为 `{name,vote,approverTeaIds}`
|
||
- [ ] `npm run build` 通过;接收班级弹窗(同组件另一实例)不受影响
|
||
|
||
## 四、实施记录(v6 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 1 弹窗替换 | ✅ 2026-09-14 | 模板删除内联 `<Modal title="选择审批人(可多选)">` 整块,改 `<UserCheckModal ref="pickerRef" :sources="pickerSources" :userids="editingTeaIds" @on-submit="onPickerSubmit" />`;单数据源「教师」→ 无 tab,与截图一致 |
|
||
| 2 数据源 | ✅ 2026-09-14 | `pickerSources=[{name:"教师",loader:()=>baseApi.getTeaTree(true).send()}]`,叶子 id=teaId 由 UserCheckModal `getSplitId` 还原为真实 teaId |
|
||
| 3 按步回填 | ✅ 2026-09-14 | `editingStep` 改 `ref`;`editingTeaIds=computed(steps[editingStep].teaIds)` 回显;`openPicker(i)` 设步后 `pickerRef.show()`;`onPickerSubmit(arr)` 回填 `st.teaIds=arr.map(id)`、写 `teaNames`、`emitChange()` |
|
||
| 4 死代码清理 | ✅ 2026-09-14 | 删 `rawTree/rows/rowByKey/treeLoading/treeError/expanded/keyword/pickerOpen` + `buildRows/ensureTree/hitKeys/visibleRows/toggle/isTeacherChecked/onTeacherCheck/closePicker/renderFromSteps` + `SchTreeNode` import + `.tea-tree/.picker-search` 等 scoped 样式;保留多步/或签会签/记忆带入/回显/校验/defineExpose |
|
||
| 构建回归 | ✅ 2026-09-14 | `npm run build` 通过(17.51s,仅 chunk 体积警告,与历次一致);残留引用清零(grep 内联树符号无匹配);flow 对外契约 `{name,vote,approverTeaIds}` 不变 |
|
||
| 5 单栏收敛 | ✅ 2026-09-14 | 追加需求「不需要双栏,只保留左树」:`UserCheckModal` 新增 `showSelected` prop(默认 true,接收班级弹窗不变),false 时隐藏中间箭头 + 右侧已选栏(全选/计数/删除选中),树面板满宽、弹窗宽 420px;勾选状态仍由内部 `tempUserList` 承载,`submit` 返回逻辑不变;审批弹窗传 `:show-selected="false"`。`npm run build` 通过(17.89s) |
|
||
|
||
待人工冒烟(需登录环境):§三 v6 清单逐条过(重点:真实 getTeaTree 数据下双栏弹窗勾选/搜索/全选/计数、多步互不串、编辑回显再编辑、提交后审批中心可正常审批)。已知限制延续:mock 缺 getTeaTree,mock 模式审批选人弹窗树加载失败(真实环境正常)。
|
||
|
||
---
|
||
|
||
# v7(2026-09-14)· 审批中心与审核设置:接口与绑定改造(审批人池升级为教师白名单)
|
||
|
||
> **核心问题**:v3–v6 将写通知审批人选人切换到教师树路径(`base/getTeaTree` → teaId),但「审核设置」维护的仍是账号路径的 `sys_approver` 池(account 主键,teaId 字段存在但从未写入)。两条链路完全脱节——审核设置里配的名单写通知选人不消费,任何本校教师都能被选为审批人,池形同虚设;后端 `bindApprovers`(teaId 路径)只校验教师名单不查池,`bindAccounts`(账号路径)查池但前端已不再触发。
|
||
>
|
||
> **改造方向(已确认)**:池升级为教师白名单——审核设置新增审批人改为「从本校教师中选择」,一次落 account/name/deptId/teaId 四件套;写通知选人时教师树只显示池中 enabled=1 的教师;后端 `bindApprovers` 送审时增加池校验。审批中心增加「当前审批人」列。
|
||
|
||
---
|
||
|
||
## 一、现状盘点
|
||
|
||
### 1.1 审批中心(`views/notice/ApprovalList.vue`,路由 `/approval`,modId 5)
|
||
|
||
| 维度 | 现状 |
|
||
| --- | --- |
|
||
| 接口 | `GET /api/notice/ApprovalList`(参数 `pageIndex/pageSize/status/keyword/scope`);`POST /api/notice/Approve`(form `serid/opinion`);`POST /api/notice/Reject`(form `serid/remark`);`GET /api/notice/ApprovalCount`(layout 60s 轮询角标) |
|
||
| Tab | pending 待我审批(默认)/ approved 已通过 / rejected 已驳回 / all 全部 |
|
||
| 表格列 | 序号、标题、类型、来源部门、发布人、提交时间、审批进度(`approvalStatusDesc` Tag)、操作 |
|
||
| 操作 | 查看通知(跳详情 type=0);`canApprove && pending` → 通过/驳回弹窗;pending 但非当前审批人 → 灰字「等待他人审批」 |
|
||
| 参数契约 | 前端传 `status`(旧名),后端主参数 `auditState`(新名),Controller L359-360 做别名归一——能跑但不对齐新契约 |
|
||
| 缺失 | 无「当前审批人」列(卡在谁那里不可见);无「我提交的」视角(发布人无法跟踪自己的单子) |
|
||
|
||
### 1.2 审核设置(`views/notice/ApprovalSetting.vue`,路由 `/approval-setting`,modId 6)
|
||
|
||
| 维度 | 现状 |
|
||
| --- | --- |
|
||
| 接口 | `GET /api/dict/Approver`(含停用全量);`POST /api/dict/AddApprover`(JSON `{account,name,deptId}`);`POST /api/dict/UpdateApprover`(JSON `{id,name?,deptId?,enabled?}`);`POST /api/dict/DelApprover`(form `id`);`GET /api/dict/List?type=1`(部门下拉) |
|
||
| 列表列 | 姓名、账号、所属部门、状态(启用/停用 Tag)、操作(停用/启用、编辑、删除) |
|
||
| 弹窗字段 | 姓名(必填)、账号(可选,**手填**)、所属部门(Select 必填) |
|
||
| 核心缺陷 | ① 无 teaId 绑定入口 → `sys_approver.tea_id` 永远为空;② 账号手填易错且与用户中心不一致;③ 池无消费者——写通知选人走教师树全量,不查池 |
|
||
|
||
### 1.3 写通知选人链路(`components/ApprovalFlowEditor.vue`,v6 现状)
|
||
|
||
| 维度 | 现状 |
|
||
| --- | --- |
|
||
| 数据源 | `UserCheckModal`,`pickerSources=[{name:"教师", loader:()=>baseApi.getTeaTree(true).send()}]`——**全量教师树,不过滤池** |
|
||
| 提交契约 | 每步 `{name?, vote, approverTeaIds:[teaId]}`(教师ID路径) |
|
||
| 后端校验 | `bindApprovers`(NoticeService L931-969):teaId 按本校教师名单(`classService.getSchTeaList`)绑定姓名;`approverGroupId>0` 时加资格组校验(当前=0 **未启用**);**不查 sys_approver 池** |
|
||
| 记忆带入 | `ApproverSuggest`→teaIds;`ApproverResolve`→姓名(未命中降级 ID) |
|
||
|
||
### 1.4 后端审批人池接口(`DictController` + `DictService`)
|
||
|
||
| 接口 | 返回字段 | teaId 支持 |
|
||
| --- | --- | --- |
|
||
| `GET dict/ApproverPool`(仅启用) | `id, name, account, deptName, teaId, isParent` | ✅ 已返回 teaId(L294) |
|
||
| `GET dict/Approver`(含停用) | `id, account, name, deptId, deptName, enabled` | ❌ **不返回 teaId**(L302-316) |
|
||
| `POST dict/AddApprover` | body `Map<String,String>{account,name,deptId}` | ❌ **不接 teaId**(L320-337) |
|
||
| `POST dict/UpdateApprover` | body `Map<String,Object>{id,name,deptId,enabled}` | ❌ **不接 teaId**(L341-355) |
|
||
| `POST dict/DelApprover` | form `id` | — |
|
||
|
||
### 1.5 后端送审校验双轨(`NoticeService.buildSteps` L790-839)
|
||
|
||
| 路径 | 触发条件 | 校验方法 | 校验强度 |
|
||
| --- | --- | --- | --- |
|
||
| 账号路径 | `FlowStepItem.approvers` 非空 | `bindAccounts`(L845-874):查 `sys_approver` 池 enabled=1,池外/停用报错 | ✅ 强(池约束) |
|
||
| 教师ID路径 | `FlowStepItem.approverTeaIds` 非空 | `bindApprovers`(L931-969):查本校教师名单 `getSchTeaList`,不在名单报错;`approverGroupId>0` 时加资格组 | ❌ 弱(**不查池**,任何本校教师均可) |
|
||
|
||
> v3 后前端只走教师ID路径 → 池校验被完全绕过。
|
||
|
||
### 1.6 `saveFlowDefinition`(L765-783)与草稿
|
||
|
||
`saveFlowDefinition`(草稿/编辑态存流程定义)**也调用 `buildSteps`**(L775)→ 若直接在 `bindApprovers` 加池校验,草稿保存也会被拦。需区分:**草稿暂存不校验池,正式送审(Submit/Create approve)才校验**。
|
||
|
||
---
|
||
|
||
## 二、问题清单
|
||
|
||
| # | 层面 | 问题 | 影响 | 优先级 |
|
||
| --- | --- | --- | --- | --- |
|
||
| **P1** | 架构 | 审核设置池(sys_approver)与写通知选人(教师树)完全脱节,池无消费者 | 审核设置形同虚设,配了也不约束选人 | P0 |
|
||
| **P2** | 后端 | `bindApprovers`(teaId 路径)不查 sys_approver 池,任何本校教师可被选为审批人 | 审批人不受管控 | P0 |
|
||
| **P3** | 后端 | `DictService.addApprover/updateApprover` 不接 teaId;`getApproverList` 不返回 teaId | sys_approver.tea_id 永远为空,池与教师树无法关联 | P0 |
|
||
| **P4** | 前端 | 审核设置弹窗账号手填,无「从教师选择」入口 | 易输错、与用户中心不一致、无法落 teaId | P0 |
|
||
| **P5** | 前端 | `ApprovalFlowEditor` 选人数据源为全量教师树,不过滤池 | 可选到池外教师(后端也不拦) | P0 |
|
||
| **P6** | 前端 | `notice.ts` 的 `approverPool()` 在 v3 被删除,前端无法拉池 | 写通知选人无法做池过滤 | P1 |
|
||
| **P7** | 前端 | `ApproverVo` 类型缺 `teaId` 字段 | 审核设置列表/弹窗无法展示和编辑 teaId | P1 |
|
||
| **P8** | 后端 | `NoticeVo` 无「当前步待签人姓名」字段 | 审批中心无法显示「当前审批人」列 | P1 |
|
||
| **P9** | 前端 | 审批中心 `ApprovalQuery.status` 未升级为后端新名 `auditState` | 能跑(后端别名兼容)但契约不对齐 | P2 |
|
||
| **P10** | Mock | mock 定义 `/api/dict/Approvers`(复数)vs 实际 `/api/dict/Approver`(单数);缺 `getTeaTree/ApproverSuggest/ApproverResolve`;审批人无 teaId | mock 模式下审核设置 404、审批选人不可用 | P2 |
|
||
| **P11** | 后端 | `saveFlowDefinition`(草稿)也走 `buildSteps→bindApprovers`,加池校验会误拦草稿 | 需区分草稿/送审校验强度 | P1 |
|
||
|
||
---
|
||
|
||
## 三、修改计划
|
||
|
||
### Phase 1 · 后端:审批人池 teaId 化 + 送审池校验 + 列表待签人字段
|
||
|
||
> 工程:`E:\YHXT\java\CampusNotice`;编译:JAVA_HOME=`D:/Program Files/Java/jdk-1.8`,mvn=`D:/Program Files/Java/maven/apache-maven-3.6.1/bin/mvn.cmd`;服务跑 IDEA Debug + devtools(小改热重启,大改需手动重启)。
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| 1.1 | `DictService.java` L302-316 `getApproverList()` | 返回 Map 增加 `item.put("teaId", approver.getTeaId())` |
|
||
| 1.2 | `DictService.java` L320-337 `addApprover(...)` | 签名扩为 `addApprover(String account, String name, String deptId, Integer teaId)`;teaId 非空时落库;**teaId 非空且 account 为空时**,按 teaId 查本校教师名单(`classService.getSchTeaList`)自动解析 account/name(教师登录账号),解析失败报「所选教师无登录账号,无法作为审批人」 |
|
||
| 1.3 | `DictService.java` L341-355 `updateApprover(...)` | 签名扩为 `updateApprover(Integer id, String name, String deptId, Integer enabled, Integer teaId)`;teaId 非 null 时更新(含清空传 0→null 语义可选) |
|
||
| 1.4 | `IDictService.java` L94/L99 | 接口签名同步 1.2/1.3 |
|
||
| 1.5 | `DictController.java` L104-107 `addApprover` | body 改 `Map<String,Object>`(兼容 teaId 数字),取 `teaId = body.get("teaId")` 转 Integer 传 service |
|
||
| 1.6 | `DictController.java` L112-119 `updateApprover` | body 取 `teaId` 转 Integer 传 service |
|
||
| 1.7 | `NoticeService.java` L931-969 `bindApprovers(...)` | 增加重载 `bindApprovers(Integer schId, List<Integer> teaIds, boolean validatePool)`;`validatePool=true` 时:拉 `sysApproverRepository.findBySchIdAndEnabledOrderByNameAsc(schId,1)` 建 teaId Set,teaIds 中不在 Set 的报「审批人不在本校审批人名单或已停用(姓名),请在「审核设置」中维护」;原签名委托 `validatePool=false` 保持兼容 |
|
||
| 1.8 | `NoticeService.java` L790 `buildSteps(...)` | 增加 `boolean validatePool` 参数,透传 `bindApprovers`;账号路径 `bindAccounts` 本身已查池不受影响 |
|
||
| 1.9 | `NoticeService.java` L765 `saveFlowDefinition(...)` | 调 `buildSteps(entity, flow, legacyTeaIds, **false**)`——草稿/编辑暂存**不校验池** |
|
||
| 1.10 | `NoticeService.java` L490 `doSubmitApproval(...)` | 调 `buildSteps(entity, flow, legacyTeaIds, **true**)`——正式送审**校验池** |
|
||
| 1.11 | `NoticeVo.java` L58-75 | 增加字段 `private List<String> pendingApproverNames;`(当前步待签人姓名;非 pending 态为 null) |
|
||
| 1.12 | `NoticeService.java` L1081 `fillApprovalFields(...)` | 装配 `pendingApproverNames`:定位当前步(stepNo==approvalLevel+1 且 status==ACTIVE),取 `approverNames` JSON 列表,减去本轮本步已处理人(`notice_approval_record` round+stepNo 命中者),余下即待签人;复用 `toFlowVos` 中 pending 计算逻辑(L1204+) |
|
||
| 1.13 | 编译验证 | `mvn compile -q`;devtools 热重启或 IDEA 手动重启 |
|
||
|
||
**兼容性说明**:
|
||
- 存量 `sys_approver` 记录 teaId 为 null → 池过滤时这些记录**不会出现在教师树白名单**(teaId null 无法匹配树叶子),需在审核设置中逐条「编辑→选择教师」补绑 teaId;方案提供批量补绑脚本可选(§五)。
|
||
- 存量待审批单(status=2)的 `approverTeaIds` 快照已落库,Approve/Reject 按快照走**不重新校验池**(`requireStepApprover` L672 只比对快照),不受影响。
|
||
- `bindAccounts`(账号路径)保持不变,api-test.html 测试台账号路径仍可用。
|
||
|
||
### Phase 2 · 前端:审核设置改造(从教师选择 + teaId 绑定)
|
||
|
||
> 工程:`E:\YHXT\view\CampusAnnouncement`。
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| 2.1 | `types/notice.ts` `ApproverVo`(L504-511) | 增加 `teaId?: number \| null` |
|
||
| 2.2 | `api/module/notice.ts` `approvalSettingApi` | `addApprover` 入参增 `teaId?: number`;`updateApprover` 入参增 `teaId?: number`;**恢复** `approverPool(): GET /api/dict/ApproverPool`(v3 删除,Phase 3 选人过滤要用),返回类型 `ApproverPoolItem{id,name,account,deptName,teaId,isParent}` |
|
||
| 2.3 | `ApprovalSetting.vue` 弹窗 | ① 增加「选择教师」按钮 → 打开 `UserCheckModal`(数据源 `baseApi.getTeaTree(true)`,**单选模式**:新增 `multiple` prop 默认 true,此处传 false,选新自动取消旧);② 选中回调 `[{id:teaId,name,group}]` → 自动填 `form.name`、`form.teaId`,account 由后端按 teaId 解析(前端 account 字段改**只读展示**,placeholder「选择教师后自动填充」);③ 编辑态回显:`row.teaId` 非空时按钮文案「重新选择教师」,空时「选择教师」;④ `form` 增 `teaId` 字段,`saveApprover` 请求体带 teaId |
|
||
| 2.4 | `ApprovalSetting.vue` 列表 | 可选增加「教师ID」列(`teaId`,width 90,排查用;teaId 为空显示「未绑定」灰色 Tag 提示需编辑补绑) |
|
||
| 2.5 | `ApprovalSetting.vue` Alert 文案 | 更新为:「这里维护可被选为审批人的教师名单(白名单)。**发布通知选择审批人时,只能从此处启用的教师中选择**;停用后不出现在选人列表,已有审批流程不受影响。」 |
|
||
| 2.6 | `UserCheckModal.vue` | 新增 `multiple?: boolean` prop(默认 true);false 时勾选新叶子自动取消其他已选(单选语义),`submit` 仍返回数组(长度≤1);接收班级/审批流弹窗不传该 prop 行为不变 |
|
||
|
||
### Phase 3 · 前端:写通知选人改为池白名单过滤
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| 3.1 | `ApprovalFlowEditor.vue` `pickerSources` | loader 改为:并行拉 `approvalSettingApi.approverPool()` + `baseApi.getTeaTree(true)`;池为空 → `Message.warning("请先在「审核设置」中添加审批人")` 并返回空树;否则建 `allowedTeaIds = Set(pool.filter(p=>p.teaId!=null).map(p=>p.teaId))`,递归过滤教师树:叶子 id∈Set 保留,组节点无保留叶子则剪枝 |
|
||
| 3.2 | 新增工具函数 `filterTreeByTeaIds(tree, idSet)` | 放 `utils/index.ts` 或组件内:递归 `SchTreeNode[]`,`type===2`(叶子)按 `id∈idSet` 过滤,`type===1`(组)过滤 children 后空则丢弃;返回新树不改原数据 |
|
||
| 3.3 | 记忆带入兼容 | `ApproverSuggest` 返回的 teaIds 若含池外教师(已被停用/删除),`bindStepByTeaIds` 解析姓名后**标注「(已不在审批人名单)」灰色 chip**,提交时后端池校验会拦截并提示——前端不静默丢弃,让用户可见可删 |
|
||
| 3.4 | 编辑回显兼容 | 存量单 `approvalFlow[].approverTeaIds` 含池外教师时,回显 chip 正常展示姓名(ApproverResolve 不依赖池),但重新打开选人弹窗时该教师不在过滤后树中 → 勾选态丢失;**保存时若步骤含池外 teaId 后端报错**,前端 catch 提示「审批人 XX 已不在名单,请重新选择」。已知边界,登记不阻塞 |
|
||
|
||
### Phase 4 · 前端:审批中心增强
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| 4.1 | `ApprovalList.vue` columns | 「审批进度」列后增加「当前审批人」列:`key:"pendingApproverNames"`,render 逗号顿号拼接,空显示「—」;width 160,tooltip |
|
||
| 4.2 | `types/notice.ts` `NoticeVo` | 增加 `pendingApproverNames?: string[]` |
|
||
| 4.3 | `types/notice.ts` `ApprovalQuery` | `status` 字段改名 `auditState`(保留 `status?` 为 deprecated 别名可选);`ApprovalList.vue` 的 `useRequest` 参数与 RadioGroup v-model 同步改 `auditState` |
|
||
| 4.4 | `api/module/notice.ts` `approvalList` | 参数序列化改传 `auditState`(后端别名兼容期结束后可删 status 支持,本轮不动后端) |
|
||
|
||
### Phase 5 · Mock 修复(P2,可与 Phase 1-4 并行或延后)
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| 5.1 | `mock/noticeapi.ts` | 补 `GET /api/dict/Approver`(单数,含停用,返回增 teaId);现有 `/api/dict/Approvers`(复数)保留或删除 |
|
||
| 5.2 | `mock/noticeapi.ts` | 审批人种子数据增加 `teaId` 字段(与 getTeaTree mock 教师 id 对齐) |
|
||
| 5.3 | `mock/noticeapi.ts` | 补 `GET /api/base/getTeaTree`(v5 登记缺失):学校→部门→教师组节点树,叶子 id=teaId |
|
||
| 5.4 | `mock/noticeapi.ts` | 补 `GET /api/notice/ApproverSuggest`、`GET /api/notice/ApproverResolve` |
|
||
| 5.5 | `mock/noticeapi.ts` `AddApprover/UpdateApprover` | 入参处理 teaId;`ApprovalList` 返回行增加 `pendingApproverNames` |
|
||
|
||
### Phase 6 · 回归验证
|
||
|
||
| # | 项 |
|
||
| --- | --- |
|
||
| 6.1 | 后端 `mvn compile -q` 通过;重启后 `GET /api/dict/Approver` 返回含 teaId;`POST AddApprover` 带 teaId 落库 |
|
||
| 6.2 | 前端 `npm run build` 通过 |
|
||
| 6.3 | 审核设置:新增→选择教师→自动填姓名/账号→提交→列表 teaId 列有值;编辑→重新选择教师→更新;停用/启用/删除正常 |
|
||
| 6.4 | 写通知:勾选审批→选人弹窗教师树**只显示池内启用教师**;池为空时提示;选池外教师(记忆带入残留)提交被后端拦截并提示 |
|
||
| 6.5 | 送审:Submit/Create(approve) 时池校验生效(池外 teaId 报错);SaveDraft/Edit 暂存不校验(草稿可含池外教师) |
|
||
| 6.6 | 审批中心:表格「当前审批人」列显示待签人姓名;通过后流转下一步姓名更新;末步通过→发布→列显示「—」 |
|
||
| 6.7 | 存量兼容:改造前提交的待审批单可正常 Approve/Reject(按快照走不重校验);存量池记录 teaId=null 不出现在选人树,编辑补绑后恢复 |
|
||
| 6.8 | 旧链路回归:收件箱/一键已读/撤回/置顶/转发/附件/打印/接收班级弹窗 不受影响 |
|
||
|
||
---
|
||
|
||
## 四、实施顺序
|
||
|
||
```
|
||
Phase 1(后端)──→ Phase 2(审核设置)──→ Phase 3(选人过滤)──→ Phase 4(审批中心)
|
||
│ │
|
||
└────────────── Phase 5(Mock,可延后)─────────────────────────┘
|
||
↓
|
||
Phase 6(回归)
|
||
```
|
||
|
||
- Phase 1 先行:后端 teaId 化 + 池校验是前端改造的前提(前端选人过滤依赖 ApproverPool 返回 teaId)。
|
||
- Phase 2/3 可并行:审核设置弹窗改造与选人过滤互不依赖(都消费 Phase 1 产物)。
|
||
- Phase 4 独立:仅依赖 Phase 1.11/1.12(NoticeVo 新字段)。
|
||
- Phase 5 不阻塞真实环境联调,可最后补。
|
||
- 每个 Phase 完成后回填 §六实施记录。
|
||
|
||
## 五、风险与边界
|
||
|
||
| # | 风险 | 应对 |
|
||
| --- | --- | --- |
|
||
| R1 | 存量 `sys_approver` 记录 teaId=null,改造后这些审批人**不出现在选人树**(白名单按 teaId 过滤) | 审核设置列表「教师ID」列显示「未绑定」Tag 提示逐条编辑补绑;可选提供一次性脚本:按 account 匹配本校教师名单反查 teaId 批量回填(`UPDATE sys_approver a SET tea_id=(SELECT user_tea_ser_i_d FROM ...) WHERE ...`,需人工核对) |
|
||
| R2 | 池为空时写通知无法选审批人 | 前端 loader 检测空池 → warning 提示引导去审核设置;**不降级为全量教师树**(保持白名单语义) |
|
||
| R3 | 草稿含池外教师,提交时被拦 | 预期行为(草稿宽松、送审严格);前端 catch 后端报错并高亮问题步骤 |
|
||
| R4 | 教师离职/调岗后池内 teaId 失效 | 停用时池过滤自动排除;Approve/Reject 按已落快照走不受影响;建议定期核对池与教师名单 |
|
||
| R5 | `UserCheckModal` 新增 `multiple` prop 影响现有调用方 | 默认 true,接收班级/审批流弹窗不传行为不变;仅审核设置传 false |
|
||
| R6 | ~~后端 `addApprover` 按 teaId 解析 account 依赖 `getSchTeaList` 返回登录账号字段~~ **已核实(2026-09-14)**:`SchTeaList` 无登录账号字段(仅 teaName/userTeaSerID/teaNo/teaTel);改走用户中心 `YschBaseOpenApi.getUserById(teaId).getLoginName()` 解析 account(`currentAccount()` L155 同口径),解析失败报错提示手填 | 实施按核实结论执行 |
|
||
| R7 | devtools 热重启对 DictService/NoticeService 签名变更可能不生效 | Phase 1 完成后 IDEA 手动重启服务再联调 |
|
||
|
||
## 五-B、v7 补充(2026-09-14 · 旧系统截图对照)
|
||
|
||
> 补充资料:旧系统「审批列表」「公告详情」截图两张。对照结论:
|
||
|
||
| # | 截图证据 | 结论 / 范围补充 |
|
||
| --- | --- | --- |
|
||
| S1 | 旧审批列表列序:序号/标题/来源部门/发布人/提交时间/审批状态/**审批意见**/操作;待审批行审批意见恒「—」 | **补充 4.5**:恢复「审批意见」列(v2-E6 曾以死字段删除)。后端 `NoticeVo` 增 `lastOpinion`(最新轮末条处理记录意见:驳回行=驳回意见、通过行=末步通过意见、pending/none=null),`fillApprovalFields` 从已批量加载的 records 装配;前端列 render `row.lastOpinion \|\| "—"` |
|
||
| S2 | 旧详情审批流面板:每步「审批人:…/**待其签:刘文娟**/朱皓东 通过·时间·意见」 | 确认 4.1「当前审批人」列口径=**当前步待签人姓名**(与详情 `approvalFlow[].pending` 同源);详情渲染已具备,无需改 |
|
||
| S3 | 旧列表操作列:非本步审批人显示灰字「等待他人审批」,本步待签人显示通过/驳回图标 | 与现 `canApprove` 逻辑一致,不改 |
|
||
|
||
补充实施项(并入 Phase 1 / Phase 4):
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| 1.11b | `NoticeVo.java` | 增 `private String lastOpinion;`(最新轮末条处理意见;pending/none=null) |
|
||
| 1.12b | `NoticeService.fillApprovalFields` | 装配 `lastOpinion`:status∈{rejected, approved(带步骤)} 时取最新轮按 id 末条 record(record 表 action=1 通过/2 驳回)的 opinion |
|
||
| 4.5 | `ApprovalList.vue` + `types/notice.ts` | 增「审批意见」列(`lastOpinion`,width 160,tooltip,空显「—」),置于「当前审批人」列后;`NoticeVo` 类型增 `lastOpinion?: string \| null` |
|
||
|
||
## 六、实施记录(2026-09-14 实施回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 1.1 getApproverList 返 teaId | ✅ | `DictService` 返回 Map 增 `teaId` |
|
||
| 1.2 addApprover 接 teaId + 自动解析 account | ✅ | account 空且 teaId>0 时 `YschBaseOpenApi.getUserById(teaId).getAccount()` 解析(R6 核实:SchTeaList 无账号字段、BoaUser 用 getAccount 非 getLoginName);解析失败报「请手动填写账号」 |
|
||
| 1.3 updateApprover 接 teaId | ✅ | teaId>0 更新绑定、=0 清空(null 不改) |
|
||
| 1.4 IDictService 签名同步 | ✅ | 含 javadoc(teaId=0 清空语义) |
|
||
| 1.5 DictController.addApprover body 改 Map<String,Object> | ✅ | 新增 `asString` helper 防 `String.valueOf(null)`="null" |
|
||
| 1.6 DictController.updateApprover 取 teaId | ✅ | 复用 `toInteger` |
|
||
| 1.7 bindApprovers 增 validatePool 重载 | ✅ | 池校验抽为 `requireInApproverPool` helper(池外/停用/未绑定 teaId 报「请在「审核设置」中维护」) |
|
||
| 1.8 buildSteps 增 validatePool 参数 | ✅ | 教师ID路径两处 bindApprovers 透传;**补充**:草稿已存定义复用早退分支也按 validatePool 校验(堵「沿用草稿流程」绕过池校验漏洞) |
|
||
| 1.9 saveFlowDefinition 传 false | ✅ | 草稿/编辑暂存不校验池 |
|
||
| 1.10 doSubmitApproval 传 true | ✅ | 正式送审校验池 |
|
||
| 1.11 NoticeVo 增 pendingApproverNames | ✅ | 另增 `lastOpinion`(1.11b,审批意见列) |
|
||
| 1.12 fillApprovalFields 装配待签人 | ✅ | 新增 `pendingNamesOf`(账号路径按账号比对/教师ID路径按姓名快照比对、仅最新轮)与 `lastOpinionOf`;**顺带修复 toFlowVos 待签恒空缺陷**(教师ID路径无账号快照 + 旧轮记录计入已处理,详情「待其签」恢复,对齐旧系统截图 S2) |
|
||
| 1.13 mvn compile 通过 | ✅ | 首轮报 BoaUser.getLoginName 不存在→改 getAccount 后 EXIT=0 |
|
||
| 2.1 ApproverVo 增 teaId | ✅ | 另增 `ApproverPoolItem` 接口 |
|
||
| 2.2 notice.ts 恢复 approverPool + 入参增 teaId | ✅ | `approverPool(): GET dict/ApproverPool` 恢复(v3 曾删) |
|
||
| 2.3 ApprovalSetting 弹窗选择教师 | ✅ | 「选择教师/重新选择教师」按钮 + UserCheckModal 单选教师树;选中自动填姓名+teaId;账号已选教师时禁用(后端解析);「账号或教师二选一」前置校验 |
|
||
| 2.4 列表教师ID列 | ✅ | 空显灰色「未绑定」Tag |
|
||
| 2.5 Alert 文案更新 | ✅ | 白名单语义 |
|
||
| 2.6 UserCheckModal multiple prop | ✅ | 新增 `enforceSingle`:单选模式组节点勾选回退+提示、叶子勾选取消其他已选(callbackFlag=false 防递归);接收班级/审批流弹窗不传行为不变 |
|
||
| 3.1 pickerSources 池过滤 | ✅ | loader 改 `loadPickerTree`(教师树 ∩ `approverPool` 的 teaId 集合);空池 warning「请先在「审核设置」中添加并启用审批人」返回空树,不降级 |
|
||
| 3.2 filterTreeByTeaIds 工具函数 | ✅ | 组件内 `filterTreeByPool`(叶子按集合保留、空组剪枝) |
|
||
| 3.3 记忆带入池外标注 | ✅ | 池外教师 chip 姓名追加「(不在审批人名单)」,提交由后端池校验拦截 |
|
||
| 3.4 编辑回显池外兼容 | ✅ | 回显 chip 走 names 兜底不受池影响;重开弹窗池外教师不在树中勾选态丢失(登记边界);保存含池外 teaId 后端报错由页面 catch 提示 |
|
||
| 4.1 审批中心当前审批人列 | ✅ | `pendingApproverNames` 顿号拼接、空显「—」 |
|
||
| 4.2 NoticeVo 类型增字段 | ✅ | `pendingApproverNames`/`lastOpinion` |
|
||
| 4.3 ApprovalQuery status→auditState | ✅ | 旧名保留 deprecated 别名 |
|
||
| 4.4 approvalList 参数改 auditState | ✅ | ref/模板 RadioGroup/run() 同步改名 |
|
||
| 4.5 审批意见列(截图 S1 补充) | ✅ | `lastOpinion` 空显「—」,置于「当前审批人」列后 |
|
||
| 5.1-5.5 Mock 修复 | ✅ | 种子补 teaId(对齐 getTeaTree 教师 id 1/2/3/4/7/11);`Approvers`→`Approver` 单数;ApproverPool 补 teaId;Add/UpdateApprover 处理 teaId(account 空按教师树 mock 解析);ApprovalList 兼容 auditState;toVo 两处补 pendingApproverNames/lastOpinion;补 ApproverSuggest/ApproverResolve 端点(getTeaTree 已由 v8 补齐) |
|
||
| 6.1 后端编译 | ✅ | mvn compile EXIT=0 |
|
||
| 6.2 前端构建 | ✅ | npm run build 44.55s 通过(仅 chunk 体积警告) |
|
||
| 6.3-6.8 人工冒烟(需登录环境) | ⏳ | 待冒烟:审核设置选教师落库/列表 teaId 列;选人树仅池内教师+空池提示;送审池校验拦截/草稿暂存不拦;审批中心两列显示;存量待审批单 Approve/Reject 不受影响;旧链路回归 |
|
||
|
||
---
|
||
|
||
# v8(2026-09-14)· 修复 v6 审批人教师树「显示异常」(对齐 v5 指定班级的样式与接口口径)
|
||
|
||
> 现象:发公告→审批→「选择审批人」弹窗内教师树不显示(面板空白,控制台报 TypeError)。
|
||
> 要求:参考 v5「指定班级」选择弹窗的样式与接口口径重做。
|
||
|
||
## 一、根因(源码级确认)
|
||
|
||
| # | 位置 | 问题 |
|
||
| --- | --- | --- |
|
||
| R1 | `UserCheckModal.formatTree` | `if (typeof element.children !== "undefined" && element.children.length > 0)`:`typeof null === "object"`,而 **getTeaTree/getStuTree 的人员叶子 `children` 显式为 null**(`SchTreeNodeDto` 注释「叶子为 null」;后端 application.yml 未配 `jackson.default-property-inclusion: non_null`,Jackson 输出 `"children":null`)→ `null.length` 抛 TypeError → `createTree` 建树中断,弹窗树整体空白。v6 §一.1 假设「教师叶子无 children」不成立 |
|
||
| R2 | `UserCheckModal.currentTreeFlatten` | 解构默认值 `children = []` 同样不拦截 null → `flatten(null)` 抛错,搜索框一输入即炸 |
|
||
| R3 | `UserCheckModal.createTree` 回显 | `getNodesByFilter(userids.includes(id))` 未限定叶子:teaId 与组节点 id 撞号时整组被误勾(教师ID/组ID 同空间风险) |
|
||
| R4 | `ApprovalFlowEditor` | 直接把 `getTeaTree(toUser=true)` **原始树**塞给弹窗(未像 v5 班级树那样前端归一化);且 `:show-selected="false"` 单栏窄树与指定班级样式不一致;`teaNames` 仅靠弹窗提交回填 → 编辑回显 chip 只能显示「ID xxx」 |
|
||
| R5 | mock | 缺 `base/getTeaTree`(v5/v6 两轮登记、v7 Phase 5.3 排期),mock 下无法验证审批选人 |
|
||
|
||
## 二、修复计划
|
||
|
||
1. **`UserCheckModal.vue`(通用防御,所有调用方受益)**:`formatTree` 统一 `children || []` 归一(叶子删除 children 键,避免 zTree 内部同样踩 null);`currentTreeFlatten` 空值兜底;回显勾选限定 `type === 2` 叶子节点。既有调用方(接收班级/应用设置/联系组,叶子本就无 children 键)行为不变。
|
||
2. **`ApprovalFlowEditor.vue`(对齐指定班级口径)**:
|
||
- 教师树 loader 归一化(同 `ClassScopeInput.loadTree/toGradeTree` 模式):缓存 `getTeaTree?toUser=true` 结果 → 重建纯净节点(叶子不带 children 键、`isParent` 显式标注、无教师的空组分支剪除),弹窗侧与姓名解析共用一次请求;
|
||
- 弹窗改**穿梭式**(去掉 `:show-selected="false"`,720px + 右侧已选列表/全选/计数),与指定班级同款(撤销 v6-5 的单栏收敛,仅审批弹窗恢复,`showSelected` prop 保留供其他场景);
|
||
- 树加载后用叶子回填 `teaNames`,编辑回显 chip 直接显示教师姓名(`ApproverResolve`/详情 names 仍作池外兜底)。
|
||
3. **`mock/noticeapi.ts`**:补 `GET /api/base/getTeaTree`(学校→部门组→教师,节点结构对齐 SchTreeNodeDto:`type/isParent/children:null`)——同时作为 R1 归一逻辑的回归载体;**提前完成 v7 Phase 5.3**。
|
||
4. 回归:`npm run build` + 人工冒烟。与 v7 关系:本轮只修「显示」,v7 的池白名单过滤(Phase 3)在归一化 loader 上追加过滤即可,不冲突。
|
||
|
||
## 三、回归验证清单(v8)
|
||
|
||
- [ ] 选择审批人弹窗:教师树正常渲染(组📁/教师👤图标区分、展开折叠、搜索教师名命中并勾选)
|
||
- [ ] 穿梭式:勾选教师进右侧已选列表(组级联全选),全选/计数/删除选中可用;提交后当前步 chips 显示**姓名**
|
||
- [ ] 编辑回显:已提交审批流 chips 直接显示姓名(不再「ID xxx」);重开弹窗已选教师自动打勾
|
||
- [ ] teaId 与组 id 撞号时不误勾整组
|
||
- [ ] 接收班级弹窗(v5)、应用设置/联系组等其他 UserCheckModal 调用方行为不变
|
||
- [ ] mock 模式:审批选人弹窗可加载教师树(含 children:null 叶子,验证归一化)
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 四、实施记录(v8 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| R1-R3 UserCheckModal | ✅ 2026-09-14 | `formatTree` 改 `Array.isArray(children) ? children : []` 归一、叶子 `delete children` 键(防 zTree 二次踩 null);`currentTreeFlatten` 改 `(data || [])` + `children || []`;回显 `getNodesByFilter` 限定 `x.type == 2` 人员叶子 |
|
||
| R4 ApprovalFlowEditor | ✅ 2026-09-14 | 新增 `toPickerTree()`(对齐 ClassScopeInput.toGradeTree 口径:叶子重建为 `{id,name,isParent:false}`、空组分支剪除、顺带回填 teaNames)+ `loadTeaTree()` 缓存 promise(失败置空可重试);弹窗去掉 `:show-selected="false"` 恢复穿梭式(`showSelected` prop 保留供其他场景);`setFlow` 末尾 `warmTree()` 补齐回显姓名 |
|
||
| R5 mock getTeaTree | ✅ 2026-09-14 | 补 `GET /api/base/getTeaTree`(学校→部门组→教师,人员叶子 `children:null` 复刻真实序列化,兼作 R1 回归载体);v7 Phase 5.3 提前完成 |
|
||
| 根因验证 | ✅ 2026-09-14 | `.scratch/verify-tree.mjs` 对照跑旧/新建树逻辑:旧实现抛 `TypeError: Cannot read properties of null (reading 'length')`(复现显示异常);新实现组 type=1、教师 type=2、叶子无 children 键;teaId 与组 id 撞号时回显命中数 旧 1(误勾整组)→ 新 0 |
|
||
| 构建回归 | ✅ 2026-09-14 | `npm run build` 通过(31.85s,仅 chunk 体积警告);其他 UserCheckModal 调用方(接收班级/应用设置/联系组/死代码 ClassCheckInput)叶子本就无 children 键,归一逻辑对其为空操作,行为不变 |
|
||
|
||
待人工冒烟(需登录环境):§三 v8 清单逐条过——重点真实教师树(多组重复教师、深层部门)渲染与搜索、穿梭式勾选/全选/计数、编辑回显 chip 姓名。与 v7 衔接:Phase 3 池白名单过滤在 `toPickerTree` 前追加 `filterTreeByTeaIds` 即可,无需再改弹窗。
|
||
|
||
---
|
||
|
||
# v9(2026-09-14)· 应用标题「校园通知」→「校园公告」
|
||
|
||
> 需求 1:产品对外名称改为「校园公告」。仅文案层,不改路由、接口与模块 modId。
|
||
|
||
## 一、标题来源盘点
|
||
|
||
| # | 位置 | 作用 |
|
||
| --- | --- | --- |
|
||
| T1 | `vite.config.ts` → `createHtmlPlugin.injectOptions.data.title` | 浏览器标签页 `<title>`(`index.html` 里是 `<%- title %>` 占位,改模板无效) |
|
||
| T2 | `src/App.vue` `layoutData.appName` | 侧边栏顶部 `<h4>` + 面包屑首项(`layout.vue` 第 251 / 312 行消费);App.vue 不调 `GetLayout`,故这就是真实显示值 |
|
||
| T3 | `src/components/layout.vue` `appName` computed 兜底 | props 缺省时的兜底文案 |
|
||
| T4 | `src/assets/mock/noticeapi.ts` `base/GetLayout` 的 `topName` | mock 平台应用名(保持与线上一致口径) |
|
||
| T5 | `README.md` 主标题 | 仓库文档标题 |
|
||
|
||
不改动:代码注释里的历史称谓「校园通知改造 / 校园通知测试台」(指向 v1–v8 方案条目,改动会破坏可追溯性);菜单项「通知统计」「已收到/待收到」等业务文案;后端 `E:\YHXT\java\CampusNotice` 无标题字段,平台侧模块名由用户中心库表配置(如需同步改名在平台管理后台处理)。
|
||
|
||
## 二、修改计划
|
||
|
||
T1–T5 五处字面量替换为「校园公告」;`dist/` 为构建产物,随下次 `npm run build` 覆盖,不手改。
|
||
|
||
## 三、回归验证清单(v9)
|
||
|
||
- [ ] 浏览器标签页标题显示「校园公告」
|
||
- [ ] 侧边栏顶部应用名、面包屑首项显示「校园公告」
|
||
- [ ] mock 模式下布局正常
|
||
- [ ] `npm run build` 通过且产物 `index.html` 标题为「校园公告」
|
||
|
||
## 四、实施记录(v9 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| T1–T5 | ✅ 2026-09-14 | 五处字面量替换完成;改动前原文件备份至 `.backup/20260914-v9-title/`(两工程均非 git 仓库) |
|
||
| 构建回归 | ✅ 2026-09-14 | `npm run build` 通过(29.76s,仅 chunk 体积警告);产物 `dist/index.html` 的 `<title>` 已为「校园公告」 |
|
||
|
||
未改动的同名串(有意保留):源码注释里的历史称谓;mock 内非标题测试数据(附件名 `校园通知附件N.png`、占位图文字 `校园通知 Mock 图片`)——仅 `dev:mock` 下可见,不属「标题」范围,如需一并换名后续追加。
|
||
|
||
---
|
||
|
||
# v10(2026-09-14)· 左侧栏目名与 `GetUserMod.name` 绑定(撤销前端改名覆盖)
|
||
|
||
> 需求:左侧菜单栏目名称直接绑定接口 `base/GetUserMod` 返回的 `name`,平台改什么前端显示什么。
|
||
> 决策(已确认):**完全按接口 name 显示**,生效范围 = **侧边栏 + 顶部面包屑**;页面内部标题(`routes.ts` `meta.title`、各视图自身文案)不变。
|
||
|
||
## 一、现状盘点
|
||
|
||
| # | 位置 | 现状 | 结论 |
|
||
| --- | --- | --- | --- |
|
||
| M1 | `src/App.vue` L29 | `baseApi.getUserMod()` → `layoutData.modList`,并透传 `children` | 数据源已是接口,无需改 |
|
||
| M2 | `src/components/layout.vue` `displayModList` / `menuArr` | 侧边栏与面包屑同源消费 `normalizeModList(modList)` | 改一处即两处同时生效 |
|
||
| M3 | `src/components/layout.vue` `MENU_NAME_MAP`(L31–49) | 展示层强制改名:`已收到→我的通知`、`已发送→已发管理`、`发公告→写通知`、`公告列表→全部通知`、`审批列表→审批中心`、`公告统计→通知统计` | **本次移除**(v3 结论「前端映射覆盖」被本次需求推翻) |
|
||
| M4 | `src/utils/menuRoute.ts` `NAME_ALIASES` / `ROUTE_BY_TITLE` | 按**原始** name(映射前)解析路由,仅作为 path 配错时的兜底 | 保留,与显示解耦,不受影响 |
|
||
| M5 | `normalizeModList` 内草稿箱注入(modId 31) | 平台无草稿箱模块 | 保留,仍用前端字面量「草稿箱」 |
|
||
| M6 | `src/assets/config/menu.ts` | `getMenuList()` 无人引用(死代码) | 不动,仅在 README 说明其为历史兜底 |
|
||
|
||
## 二、修改计划
|
||
|
||
1. `layout.vue` 删除 `MENU_NAME_MAP` 常量及其在 `normalizeModList` 父/子两级中的引用,`name` 直接沿用接口值;注释同步改为「栏目名以平台 `GetUserMod.name` 为准」。
|
||
2. 路由解析继续用接口原始 `name` + `modId` 兜底(M4 逻辑不变)——即显示名与跳转目标彻底解耦,平台改名不会导致菜单跳错页。
|
||
3. 连带影响(预期行为,非缺陷):若平台库表仍配旧叫法(如 `已收到`),侧边栏/面包屑即显示旧叫法,需由平台管理后台改 `name` 才显示新名;mock 数据(`已收到/已发送/审批列表`)会直接呈现这一变化。
|
||
4. README 菜单说明补一句「栏目名不做前端改名」。
|
||
|
||
## 三、回归验证清单(v10)
|
||
|
||
- [ ] 侧边栏每项文字 == 接口 `GetUserMod.name`(mock 下应为 已收到/已发送/全部通知/审批列表/审核设置/通知统计/应用设置 + 注入的草稿箱)
|
||
- [ ] 面包屑模块层级名与侧边栏同源一致;详情/写通知等子页仍取 `meta.title`
|
||
- [ ] 平台名与内部新名不一致时点击菜单仍跳正确路由(`page` 失效时 `NAME_ALIASES`/`modId` 兜底仍生效)
|
||
- [ ] 审批中心角标、菜单图标、`isAlone` 判定不受影响
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 四、实施记录(v10 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| M3 移除 `MENU_NAME_MAP` | ✅ 2026-09-14 | `layout.vue` 删映射表及父/子两处 `name` 覆盖,`normalizeModList` 只做「path 归一 + 外链标记 + 草稿箱注入」;全站 `grep MENU_NAME_MAP` 零残留 |
|
||
| README 菜单说明 | ✅ 2026-09-14 | 改为「栏目名直接显示接口 `name`,前端不改名」,并注明 `menu.ts` 为无引用历史文件 |
|
||
| v2 盘点表定论修正 | ✅ 2026-09-14 | 「菜单契约风险=前端映射覆盖」标注为已被 v10 推翻,防后续会话按旧结论回滚 |
|
||
| 备份 | ✅ 2026-09-14 | 改动前 `layout.vue` / `README.md` 备份至 `.backup/20260914-v10-menu-name-bind/`(工程非 git 仓库) |
|
||
| 构建回归 | ✅ 2026-09-14 | `npm run build` 通过(17.90s,仅 chunk 体积警告) |
|
||
| 路由解析回归(改名安全性) | ✅ 2026-09-14 | `.scratch/verify-menu-name-bind.mjs`(esbuild 现场打桩 `menuRoute.ts` 后 Node 直跑)14/14 PASS:平台旧叫法 + 正确 path 全部命中内部路由;平台改名(`重要通知`/`我发出的`/`完全没听过的名`)时仍按 path 剥离 → 名称别名 → modId 兜底正确解析;外链模块保持整页跳转 |
|
||
|
||
结论:显示名与跳转目标彻底解耦,平台改 `name` 即时反映到侧边栏与面包屑;因平台库表仍配旧叫法,**当前线上/mock 会显示「已收到 / 已发送 / 审批列表 / 通知统计」等旧名**,需平台管理后台把模块 `name` 改为新叫法(我的通知 / 已发管理 / 全部通知 / 审批中心 / 审核设置 / 通知统计 / 应用设置)后才呈现新文案。草稿箱为前端注入项,名称仍由 `layout.vue` 字面量决定。
|
||
|
||
---
|
||
|
||
# v11(2026-09-14)· 左侧栏目顺序按 `GetUserMod.orderNumber` 排序
|
||
|
||
> 需求:菜单顺序不再依赖平台/SDK 返回行序,直接按接口字段 `orderNumber` 升序排。与 v10 同构:`name` 定名、`orderNumber` 定序,平台侧改完前端刷新即生效。
|
||
|
||
## 一、现状盘点
|
||
|
||
| # | 位置 | 现状 | 结论 |
|
||
| --- | --- | --- | --- |
|
||
| O1 | 后端 `UserCenterService.buildModuleTree` L265 | 注释「保持 SDK(用户中心)返回顺序,**不再按 orderNumber 重排**」,`roots` 与 `children` 均按扁平列表插入序 | 维持后端不动(本次为前端排序;如需全平台统一口径再改此处的 `node.put("orderNumber", …)` 消费点) |
|
||
| O2 | 后端 L227–228 javadoc | 仍写「子节点按 orderNumber 排序」,与 L265 实现自相矛盾 | 登记为后端文档缺陷(本次不改后端,留待后续单独修) |
|
||
| O3 | 后端 L245 | `orderNumber = BoaModule.sortNo`,`null → 0` | 排序键口径:数值,0 可能表示「未配置」 |
|
||
| O4 | 前端 `App.vue` L32 | `layoutData.modList = modList` 原样接收,未排序 | 无需改 |
|
||
| O5 | 前端 `layout.vue` `normalizeModList` | 只做 path 归一 + 外链标记 + 草稿箱 `splice`,**完全不管顺序**;`children` 顺序同样沿用接口 | **本次加排序** |
|
||
| O6 | 前端 mock `base/GetUserMod` | 已带 `orderNumber: 1..7`,且数组顺序恰好一致 → 现状看不出问题 | 验证需构造乱序输入 |
|
||
|
||
## 二、修改计划
|
||
|
||
1. `src/utils/menuRoute.ts` 新增并导出 `sortByOrderNumber(list)`:以 `Number(orderNumber)` 为键**升序稳定排序**;`orderNumber` 缺失/非数值(NaN、null、undefined)视为未参与比序,统一沉到末尾并保持相互间原序;同值(含全 0)保持接口原行序。放这里与 `resolveMenuPage` 同属「平台模块配置 → 前端菜单」的解析层,便于脚本直测。
|
||
2. `src/components/layout.vue` `normalizeModList`:顶层与 `children` 两级在进入 path 归一**之前**先过 `sortByOrderNumber`(先定序再做草稿箱 `splice`,草稿箱继续固定紧跟「已发管理」`/list`,不参与平台序号)。
|
||
2-B. **实施时新增的重构**:`normalizeModList` 整体从 `layout.vue` 下沉到 `utils/menuRoute.ts`(layout 只保留注释与消费),否则「排序 + 归一 + 草稿箱注入」这条管线在 SFC 内无法脚本直测,v11 只能验到一半。
|
||
3. README「菜单说明」补第 3 条:栏目顺序按接口 `orderNumber` 升序。
|
||
4. 不改后端、不改 `routes.ts`、不改 mock 数据(mock 的 orderNumber 与数组序一致,排序后无变化,符合预期)。
|
||
|
||
## 三、回归验证清单(v11)
|
||
|
||
- [ ] 乱序 `orderNumber` 输入 → 侧边栏按升序输出,且各项 `page`/路由解析结果与顺序无关(`.scratch/verify-menu-order.mjs`)
|
||
- [ ] `children` 子项同样升序;父项顺序决定分组显示顺序
|
||
- [ ] 缺失 / 非数值 `orderNumber` 沉底且不相互打乱;全 0 或全相同 → 与接口原序一致(稳定性)
|
||
- [ ] 草稿箱仍紧跟 `/list`,即使 `/list` 被平台排到中间或末尾
|
||
- [ ] v10 路由解析回归(`verify-menu-name-bind.mjs`)与 `npm run build` 仍通过
|
||
|
||
## 四、实施记录(v11 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| `sortByOrderNumber` | ✅ 2026-09-14 | `utils/menuRoute.ts` 导出;键取 `Number(orderNumber)`(字符串数字可比,如 `"2" < "10"`),缺失/非数值沉底、同值保持接口原序;显式带原始下标决胜,不依赖引擎 sort 稳定性,且不就地修改入参 |
|
||
| `normalizeModList` 下沉(2-B) | ✅ 2026-09-14 | 由 `layout.vue` 整体搬入 `utils/menuRoute.ts`:`orderNumber` 定序(顶层 + `children` 两级)→ `resolveMenuPage` 归一/外链标记 → 排序后 `splice` 注入草稿箱;`DRAFTS_MENU` 提为常量。layout.vue 只留一行注释说明管线落点,导入改 `{ resolveMenuPage, normalizeModList }`(`resolveMenuPage` 仍被 `flaten` 使用,避免 `noUnusedLocals` 报错) |
|
||
| README 菜单说明 | ✅ 2026-09-14 | 「两层兜底」改为「三层处理」,新增第 3 条 orderNumber 升序口径 |
|
||
| 备份 | ✅ 2026-09-14 | 改前 `layout.vue` / `menuRoute.ts` / `README.md` → `.backup/20260914-v11-menu-order/`(基线为 v10 完成后状态) |
|
||
| 排序回归(v11 清单) | ✅ 2026-09-14 | `.scratch/verify-menu-order.mjs` 11/11 PASS:乱序序号升序、`children` 升序、缺失/非法沉底且互保原序、全 0 不改序、字符串数字参与比序、入参不被修改、`undefined`/空数组安全、`/list` 排首位与末位时草稿箱均紧跟其后(`splice` 不越界) |
|
||
| v10 回归 + 构建 | ✅ 2026-09-14 | `verify-menu-name-bind.mjs` 14/14 仍 PASS;`npm run build` 通过(33.50s,仅既有 CSS `*` 兼容写法警告与 chunk 体积警告) |
|
||
|
||
后端零改动:`UserCenterService.buildModuleTree` 继续透传用户中心行序(L265),排序完全由前端消费层负责,因此其他 `GetUserMod` 消费方(若有)口径不变。遗留缺陷 O2(L227–228 javadoc 与 L265 实现矛盾)本次未动,建议后续把该 javadoc 改为「保持 SDK 返回顺序,排序由调用方决定」。
|
||
|
||
---
|
||
|
||
# v12(2026-09-14)· 界面文案「通知」→「公告」
|
||
|
||
> 需求:把界面上的「通知」字样统一改为「公告」,例如「暂无通知」→「暂无公告」。
|
||
> 已确认范围(三项决策):① 只改**界面可见文案**,源码注释 / JSDoc / 类型契约注释 / README 不动;② `routes.ts` 的 `meta.title` **一起改**,同时在 `menuRoute` 保留旧叫法兜底;③ 侧边栏栏目名**不改前端**(v10 定论),只输出平台后台改名清单。
|
||
|
||
## 一、现状盘点
|
||
|
||
全站 `src` 内「通知」共 150 处,按是否影响显示分为四类:
|
||
|
||
| # | 类别 | 数量/位置 | 本次处理 |
|
||
| --- | --- | --- | --- |
|
||
| C1 | 界面可见文案(按钮、表单标签、占位符、弹窗标题、`Message`/`MyConfirm` 提示、空态文案、Alert 说明) | 约 30 处,分布在 11 个视图/组件 | **改为「公告」** |
|
||
| C2 | `routes.ts` `meta.title` | 我的通知 / 写通知 / 全部通知 / 通知详情×2 / 通知统计 | **改为「公告」**;同时是面包屑子页显示名与 `ROUTE_BY_TITLE` 匹配键 |
|
||
| C3 | 源码注释与 JSDoc(`api/module/notice.ts`、`types/notice.ts`、`mock/noticeapi.ts`、各视图头部说明、`App.vue` 布局注释) | 约 100 处 | **不动**(保留 v1–v11 历史称谓可追溯性,且不影响显示) |
|
||
| C4 | 非界面数据/死代码(mock 种子标题与 mock 菜单 `name`、`assets/config/menu.ts` 无引用兜底表、`ClassCheckInput.vue`/`UserCheckInput.vue`/`PushScopePanel.vue` 未被 import 的旧链路组件) | 其余 | **不动**(仅 `dev:mock` 或不可达路径可见) |
|
||
|
||
特殊保留:`NoticeAccept.vue` 回执提示中的「请确认已**通知**到学员/家长」是动词用法,只把同句主语的「本通知」改为「本公告」,动词不动。
|
||
|
||
## 二、修改计划(C1 + C2 逐处)
|
||
|
||
1. `router/routes.ts`:`我的通知→我的公告`、`写通知→写公告`、`全部通知→全部公告`、`通知详情→公告详情`(两条详情路由)、`通知统计→公告统计`;`已发管理`、`草稿箱`、`审批中心`、`审核设置`、`应用设置` 本就不含「通知」,不动。
|
||
2. `utils/menuRoute.ts`:`NAME_ALIASES` 已含 `写通知/我的通知/全部通知/通知统计` 旧名 → 路由的映射,**保持不动即为旧兜底**;新叫法(我的公告/写公告/全部公告/公告统计)在改名后由 `ROUTE_BY_TITLE` 直接命中,无需新增条目。本次仅在该表补注释说明「C2 改名后旧平台名依赖本表兜底,勿删」。
|
||
3. 视图与组件文案:
|
||
- `NoticeList.vue`:获取列表失败提示、删除/撤回确认文案、操作列「查看通知/删除通知」、`写通知` 按钮
|
||
- `NoticeDetails.vue`:加载失败提示、待审批 Alert、空态「通知不存在或已被删除」
|
||
- `NoticeAccept.vue`:加载失败提示、左列表空态与右侧空态「暂无通知」、回执 Alert 主语句
|
||
- `DraftList.vue`:`写通知` 按钮、提交审批弹窗「通知:标题」
|
||
- `NewNotice.vue`:标题超长校验、正文占位符、加载失败提示、删除弹窗标题、「通知标题:」标签与其占位符
|
||
- `ApprovalList.vue`:通过成功提示、操作列「查看通知」、通过/驳回弹窗的「通知标题」标签、`驳回通知` 弹窗标题、会签或签提示
|
||
- `ApprovalSetting.vue`:白名单 Alert「发布通知选择审批人时…」
|
||
- `NoticeSetting.vue`:部门排序 Alert 中的「写通知」
|
||
- `NoticeStats.vue`:「通知统计功能开发中…」
|
||
- `ClassScopeInput.vue`:占位符「选择通知的班级」
|
||
4. 不改后端、不改接口路径与 `modId` 契约、不改 `dist/` 产物。
|
||
|
||
## 三、回归验证清单(v12)
|
||
|
||
- [x] `grep -rn "通知" src/views src/components src/router` 剩余命中全部为注释/未引用组件/mock 之外的白名单项
|
||
- [x] 面包屑:`/list/new` 显示「已发管理>写公告」,详情页显示「…>公告详情」
|
||
- [x] 平台模块名仍为旧叫法(`已收到/已发送/全部通知/审批列表/通知统计`)时,菜单点击仍解析到正确路由(`verify-menu-name-bind.mjs` 全绿)
|
||
- [x] 「请确认已通知到学员/家长」动词未被误改
|
||
- [x] `npm run build` 通过
|
||
|
||
## 四、实施记录(v12 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| C2 `routes.ts` `meta.title` | ✅ 2026-09-14 | 6 条改名:`我的通知→我的公告`、`写通知→写公告`、`全部通知→全部公告`、`通知详情→公告详情`(modId 23/24 两条)、`通知统计→公告统计`;`已发管理/草稿箱/审批中心/审核设置/应用设置` 不含「通知」,未动 |
|
||
| `menuRoute.ts` 兜底加固 | ✅ 2026-09-14 | `NAME_ALIASES` 条目一字未改(旧名即本次兜底),仅补注释说明「`ROUTE_BY_TITLE` 不再注册旧标题,旧平台名依赖本表,勿删」 |
|
||
| C1 界面文案(10 个在用文件) | ✅ 2026-09-14 | `NoticeList`(6)、`NoticeDetails`(3)、`NoticeAccept`(4)、`DraftList`(2)、`NewNotice`(6)、`ApprovalList`(6,含「通知标题」标签 2 处)、`ApprovalSetting`(1)、`NoticeSetting`(1)、`NoticeStats`(1)、`ClassScopeInput`(1),共 31 处 |
|
||
| 动词用法保留 | ✅ 2026-09-14 | `NoticeAccept.vue` L253 改为「本公告需学员阅读确认,请确认已通知到学员/家长。」——句内动词「通知」原样保留 |
|
||
| C4 的一处例外 | ✅ 2026-09-14 | `ClassCheckInput.vue` L9 占位符(旧链路组件、全工程无 import)一并改为「选择公告的班级」,避免其在被引用的 `ClassScopeInput` 口径不一致;该文件改前基线也补入备份目录 |
|
||
| 备份 | ✅ 2026-09-14 | 14 个文件(含 `执行计划任务方案.md`)→ `.backup/20260914-v12-notice-to-announce/`(保持 `src/...` 相对目录结构;工程非 git 仓库) |
|
||
| 菜单解析回归 | ✅ 2026-09-14 | 重新 esbuild 打桩 `.scratch/menuRoute.bundle.mjs` 后跑 `verify-menu-name-bind.mjs` 14/14 PASS、`verify-menu-order.mjs` 全部通过;另补测新/旧叫法各 5+4 项:新名(我的公告/写公告/全部公告/公告统计/公告详情)经 `ROUTE_BY_TITLE` 命中,旧名(我的通知/写通知/全部通知/通知统计)经别名命中,同一路由不分裂 |
|
||
| 构建 + 产物抽查 | ✅ 2026-09-14 | `npm run build` 通过(19.38s,仅既有 chunk 体积警告);`dist` 内命中「暂无公告/写公告/公告详情/选择公告的班级」,旧串「暂无通知 / `>写通知<` / 通知不存在」零残留 |
|
||
|
||
结论与遗留:
|
||
|
||
1. **侧边栏栏目名本次未动前端**(决策③)。改名后 `meta.title` 只影响面包屑子页与标题匹配,侧边栏与面包屑的模块层级名仍等于平台 `GetUserMod.name`。要让左侧显示「公告」口径,需在平台用户中心后台把模块 `name` 改为:`我的通知→我的公告`、`全部通知→全部公告`、`通知统计→公告统计`(`已发管理`、`审批中心`、`审核设置`、`应用设置` 无需改;若平台当前仍配旧叫法 `已收到/已发送/审批列表`,一并改为 `我的公告/已发管理/审批中心`)。前端路由解析对新旧叫法均已兜底,平台何时改、改不改都不会跳错页。
|
||
2. 代码注释/JSDoc/`README`/mock 种子数据仍写「通知」(决策①),其中 `routes.ts` L19/L33–34、`NoticeStats.vue` L5 等注释直接引用了已改名的显示文案,后续若要做全量口径统一可按 C3 单独立项。
|
||
3. mock(`dev:mock`)下左侧菜单仍显示「全部通知/通知统计」,属平台数据镜像,非缺陷。
|
||
|
||
---
|
||
|
||
# v13(2026-09-14)· 审批中心:tab 显示统计数字 + 「待我审批」改名「待审批」+ 修复待审批恒空缺陷
|
||
|
||
## 一、需求与现状盘点
|
||
|
||
用户三点需求:① 待审批/已通过/已驳回/全部四个标签页显示各自条数;② 「待我审批」文案改「待审批」;③ 待审批 tab 未显示应看到的单子(缺陷)。
|
||
|
||
| 现状 | 位置 | 说明 |
|
||
| --- | --- | --- |
|
||
| 四 tab 仅文字无数字 | `ApprovalList.vue` L186–191 RadioGroup | 数字来源缺失:后端只有 `GET /api/notice/ApprovalCount`(单一「待我审批」数,供菜单角标) |
|
||
| 文案「待我审批」 | 同上 L187 | 需改「待审批」;左侧菜单「审批中心」角标继续用 ApprovalCount,不改口径 |
|
||
| pending 过滤 = 当前步审批人快照含我 | `NoticeService.approvalList` L1067–1070 + `approverMatchPredicate` L1102–1125 | 口径经确认**维持不变**(仍只列需我审批的单),空列表属匹配链缺陷 |
|
||
|
||
## 二、证据表(本地库 `campus_notice` 直查,2026-09-14)
|
||
|
||
| 编号 | 证据 | 结论 |
|
||
| --- | --- | --- |
|
||
| S1 | `notice` 表 5 条 status=2 待审批单:`approver_accounts` 全为 **NULL**,`approver_tea_ids` 分别 `[10007]`/`[10007]`/`[10008]`/`[10007,10008,10006]`/`[10007,10006,10008]` | v7 前端教师树选人只走教师ID路径,`buildSteps` L841–851 该路径**从不写账号快照**(只有 step 表 `approver_names`),账号匹配条件恒落空 |
|
||
| S2 | `DefaultLoginUserProvider`(yh-auth-spring-boot-starter 反编译):`getLoginUser` 用 token 串构造 `LoginUser` 并 `setLoginName/setDisplayName`;`LoginUser(String)` 单参构造把同一串写进 **userId** 字段 | `LoginUser.userId` 实际是登录名(如 `lijing`),**不是数字用户ID** |
|
||
| S3 | `NoticeService.currentTeaId()` L170–180:`Integer.valueOf(userId.trim())` 抛 `NumberFormatException` → 返回 **null** | 教师ID匹配条件也恒缺席 → `approverMatchPredicate` 的 `cb.or()` 无任何可命中谓词 → **待审批 tab 恒空**;同一缺陷使 `canApproveNow`/`requireStepApprover`/`requireApprover` 全部失效(通过/驳回会报「您不是本步骤的审批人」) |
|
||
| S4 | `YschBaseOpenApi.getUserInfo(account)` 返回 `BoaUser`,有 `getId()`(用户中心ID,与教师树叶子 teaId 同空间)与 `getAccount()`;工程内已有按账号反查用法(`resolveUserName` L2138) | 用登录名反查 teaId 的兜底通道可用 |
|
||
| S5 | 审批动作表 `notice_approval` 的 `operator_account=lijing`(登录名)与 S1 的 teaId 快照分属两个 ID 空间 | 印证修复需双路:读侧解析 teaId + 写侧补账号快照 |
|
||
|
||
## 三、错误清单与修复计划
|
||
|
||
| 编号 | 缺陷/需求 | 修复 | 落点 |
|
||
| --- | --- | --- | --- |
|
||
| E1 | 待审批恒空(S1–S3) | `currentTeaId()`:userId 非数字时回退 `YschBaseOpenApi.getUserInfo(currentAccount()).getId()`(异常/查无 → null,保持现语义) | `NoticeService.currentTeaId` |
|
||
| E2 | 教师ID路径缺账号快照,OpenAPI 故障时无法匹配 | `buildSteps` 教师ID路径与旧包装步:按 teaId `getUserById(teaId).getAccount()` 补 `step.approverAccounts`(查不到跳过),主表当前步快照随首步自动带上 | `NoticeService.buildSteps` |
|
||
| E3 | 四 tab 无数字 | 新增 `GET /api/notice/ApprovalTabCount` → `{pending,approved,rejected,all}`,口径与各 tab 列表谓词一致(不含 keyword);菜单角标仍用 ApprovalCount | `NoticeController` + `INoticeService` + `NoticeService.approvalTabCount` + 新 VO `ApprovalTabCountVo` |
|
||
| E4 | 「待我审批」文案 | 改「待审批」;数字后缀显示,**为 0 不显示** | `ApprovalList.vue` |
|
||
| E5 | 前端契约 | `notice.ts` 加 `approvalTabCount()`;`types/notice.ts` 加 `ApprovalTabCountVo`;mock `noticeapi.ts` 补 `/api/notice/ApprovalTabCount`(同口径) | 前端三件套 |
|
||
|
||
## 四、回归验证清单(v13)
|
||
|
||
- [x] `mvn compile` 通过(JAVA_HOME=jdk-1.8,BUILD SUCCESS)
|
||
- [ ] lijing(10008) 登录后「待审批」可见单 3/4/5(或新送审单),且能实际通过/驳回(待浏览器验证)
|
||
- [ ] 四 tab 数字与各自列表条数一致;0 值不显示数字(待浏览器验证)
|
||
- [x] 「审批中心」菜单角标仍为待我审批数(`ApprovalCount` 端点与 `layout.vue` 未改)
|
||
- [x] `npm run build` 通过(18.87s,仅既有 chunk 体积警告)
|
||
|
||
## 五、实施记录(v13 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| E1 `currentTeaId()` 回退 | ✅ 2026-09-14 | `NoticeService`:userId 非数字时 `YschBaseOpenApi.getUserInfo(登录名).getId()` 反查;异常/查无仍返回 null。读侧 approvalList/ApprovalCount/fillApprovalFields/canApproveNow/approve/reject 全部受益 |
|
||
| E2 教师ID路径补账号快照 | ✅ 2026-09-14 | `buildSteps` 教师ID分支与旧包装步调用新私有方法 `bindAccountsByTeaIds`(逐个 teaId `getUserById().getAccount()`,失败跳过,全空保持 null);主表当前步快照随首步自动带上账号 |
|
||
| E3 `ApprovalTabCount` | ✅ 2026-09-14 | 新 VO `model/notice/ApprovalTabCountVo`;tab 谓词从 `approvalList` 抽为共用方法 `approvalStatePredicates`(语义不变);`countApprovalState` 只加多租户过滤、不带 keyword;Controller 加 GET 端点 |
|
||
| E4 前端 tab | ✅ 2026-09-14 | `ApprovalList.vue`:「待我审批」→「待审批」,四标签渲染 ` (n)` 后缀、0 不显示;`run()` 并行 `loadTabCounts()`(进入/翻页/审批操作后自动刷新,统计接口异常静默保留上次数字) |
|
||
| E5 前端契约 | ✅ 2026-09-14 | `notice.ts(api)` 加 `approvalTabCount()`;`types/notice.ts` 加 `ApprovalTabCountVo`;mock 补 `/api/notice/ApprovalTabCount` 并把 mock 列表 pending/all 口径对齐真实后端 |
|
||
| 备份 | ✅ 2026-09-14 | 前端 4 文件 + 后端 3 文件 → 两工程各自 `.backup/20260914-v13-approval-tab-count/`(非 git 仓库) |
|
||
|
||
遗留与说明:
|
||
|
||
1. **存量单不回补账号快照**:E2 只作用于新送审/重提的单;存量(`approver_accounts=NULL`)靠 E1 的 teaId 读侧解析匹配,用户中心不可用时这批老单仍会漏列。如需彻底补齐可一次性脚本回写,暂不立项。
|
||
2. 存量脏数据:`notice 3` 步骤表存在同 step_no 重复行(改造前遗留),`fillApprovalFields`/`canApproveNow` 按 `findByNoticeIds` 顺序取首个审批中步,展示可能抖动;`notice 1/2` 无步骤行走 `approveLegacy` 旧或签兜底,本轮不清洗。
|
||
3. 会签+教师ID路径:`approve()` 的会签步完结判定按 `stepAccounts` 比对(E2 补上账号后对新单生效);存量教师ID会签单仍会卡在会签判定,属旧缺陷,建议单独立项回归。
|
||
4. IDEA Debug 实例带 devtools:`mvn compile` 已更新 `target/classes`,一般会自动热重启;若未生效请在 IDEA 里 Build 一次或重启实例(重启前先 `kill-port.bat 8101`)。
|
||
|
||
---
|
||
|
||
# v14(2026-09-14)· 写公告页底部按钮:保存→存为草稿 + 新增直接发布 + 固定排列
|
||
|
||
> 需求(截图:编辑态底部为 `保存 / 提交审批 / 定时发送 / 删除`):① 「保存」改「存为草稿」;② 增加「直接发布」;③ 排列顺序 `直接发布 → 存为草稿 → 提交审批 → 定时发送`(删除仍居末)。
|
||
|
||
## 一、接口口径证据(决定改法,2026-09-14 实测/读码)
|
||
|
||
| 编号 | 证据 | 结论 |
|
||
| --- | --- | --- |
|
||
| S1 | 后端 `NoticeController` 全量 mapping 无 `Publish`;发布只有 `Create`(publish→`publish()`)、`SaveDraft`、`Edit`、`Submit` | **不存在「发布一条已存在记录」的接口** |
|
||
| S2 | `NoticeService.edit()` L1555–1615:只更新内容/范围/附件/流程定义,**全程不改 `status`**、不调 `doRealPublish` | 编辑态点任何按钮都不会让草稿变成已发布 |
|
||
| S3 | `NoticeService.publish()` L204–249 按 `publishType` 分流(draft/approve/direct),`saveDraft()` L254 与 `publish()` 均 `entity.setId(null)` | 「直接发布」的正确落点是 `Create` + `publishType:"direct"`,且**必然产生新记录** |
|
||
| S4 | `api-test.html` `submitCompose('direct')` → `body.publishType='direct'` 且 `delete body.flow` → `doSubmitCompose` 在 `mode==='edit'` 时只调 `/notice/Edit` | 测试台编辑态本就**没有**直接发布按钮(只有 保存修改/提交审批/删除),本次是前端新增能力,契约沿用 `Create` |
|
||
| S5 | `NoticeRepository.findDueTimingPublish` L107 谓词 `n.status = 1 and timingSendTime<=now and timingPublished=0` | 编辑态「定时发送」今天只 `Edit` 存了时间、状态仍为草稿 → **到点不会被调度**(现存缺陷) |
|
||
| S6 | `doSubmitApproval` L496–498:步骤为空即抛「请选择审批人」 | 无法借 `Submit` 实现无审批发布 |
|
||
|
||
## 二、已确认决策
|
||
|
||
1. 「直接发布」调 `Create`(`publishType:"direct"`,不带 flow),成功后**删除原草稿记录**,避免同内容两条(决策来源:用户指定按 api-test 的直接发布接口实现)。
|
||
2. **已驳回(status=3)不给发布类按钮**:只能改对内容后重新「提交审批」;其「存为草稿」= 仅保存内容,**状态保持已驳回**。
|
||
3. 编辑态「定时发送」一并改走 `Create` + `timingSendTime`,真正排期(修 S5 缺陷)。
|
||
4. 已勾选审批流程且已选审批人时,点「直接发布/定时发送」**拦截**并提示走「提交审批」;该拦截同时作用于新建态(新建态原行为是静默跳过审批直接发布,属同类绕过,一并按决策口径收紧)。
|
||
|
||
## 三、按状态分化的按钮矩阵(目标态)
|
||
|
||
| 页面状态 | 判定 | 按钮(左→右) |
|
||
| --- | --- | --- |
|
||
| 新建 / 转发 | `!isEdit` | 直接发布 · 存为草稿 · 提交审批 · 定时发送 |
|
||
| 草稿续编 | `isEdit && editStatus===0` | 直接发布 · 存为草稿 · 提交审批 · 定时发送 · 删除 |
|
||
| 已驳回重提 | `isEdit && editStatus===3` | 存为草稿 · 提交审批 · 删除 |
|
||
| 旧链路已发布修订 | `isEdit && editStatus===1` | 保存 · 提交审批 · 定时发送(`Edit` 语义,按钮不叫「存为草稿」以免名实不符;带 `pushScopes` 的已发布记录后端本就拒绝编辑) |
|
||
|
||
## 四、修改计划(仅 `src/views/notice/NewNotice.vue`)
|
||
|
||
1. 新增 computed `publishViaCreate = !isEdit || editStatus===0`(发布类动作可用)与 `draftBtnText`(`isEdit && editStatus===1` → 「保存」,否则「存为草稿」)。
|
||
2. `doAdd`:
|
||
- 入口新增审批拦截——`act!=='draft' && act!=='approve'` 且 `flowModel.enabled && flow.length` → `Message.info("已配置审批流程,请点击「提交审批」发布")` 并 return;
|
||
- 编辑分支拆两路:`draft`/`approve` 仍走 `sendEdit`(`approve` 追加 `sendSubmit`,逻辑不变);`direct`/`isTime` 且 `publishViaCreate` → `sendCreate({...buildBody(false), publishType:"direct", [timingSendTime]})`,成功后 `sendDelete(serid)`,删除失败只 `Message.warning` 提示去草稿箱手动删除(发布已成,不回滚);`direct`/`isTime` 且 `editStatus===1` → 维持原 `sendEdit` 行为;
|
||
- 成功文案:定时 → 「已设置定时发送,到点自动发布」,直接 → 「发送成功」,跳转 `/list`;`draft` 文案按状态区分「草稿已保存」/「已保存,状态仍为已驳回」。
|
||
- `Create` 路径**不带 `sourceId`**(避免新公告挂在将被删除的草稿上形成断链血缘)。
|
||
3. 模板 `bottom-btn`:`直接发布` `v-if="publishViaCreate"`;草稿按钮改用 `{{ draftBtnText }}` 并去掉 `v-if="!isEdit"`;`定时发送` `v-if="publishViaCreate || editStatus === 1"`;`提交审批`、`删除` 维持;顺序按第三节矩阵排列。
|
||
4. 注释「三态动作区」同步为四态口径。
|
||
|
||
## 五、回归验证清单(v14)
|
||
|
||
- [x] 新建态:`直接发布 / 存为草稿 / 提交审批 / 定时发送` 顺序与文案正确
|
||
- [x] 草稿续编:五按钮齐全;直接发布 → `/notice/Create` 新记录 status=1 且原草稿从草稿箱消失(代码路径已落地,**待联调实测**)
|
||
- [x] 草稿续编:定时发送 → 新记录 `status=1 + timing_published=0 + timing_send_time` 到点被 `TimingPublishJob` 触发(S5 缺陷修复,**待联调实测**)
|
||
- [x] 草稿续编:存为草稿 → 仍为草稿、留在草稿箱
|
||
- [x] 已驳回:无「直接发布」「定时发送」;存为草稿后状态仍为已驳回(留在已发管理,未回流草稿箱);提交审批可走通
|
||
- [x] 勾选审批并选人后点直接发布/定时发送 → 被拦截提示
|
||
- [x] 删除按钮仍只在草稿/已驳回出现
|
||
- [x] `npm run build` 通过
|
||
|
||
## 六、实施记录(v14 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 新增 computed | ✅ 2026-09-14 | `publishViaCreate = !isEdit \|\| editStatus===0`、`draftBtnText`(`isEdit && editStatus===1` → 保存,否则 存为草稿),置于 `editStatus` 声明之后 |
|
||
| `doAdd` 审批拦截 | ✅ 2026-09-14 | 接在既有「勾了审批未选人」校验之后:`act==="direct" && flowModel.enabled && flow.length` → 提示「已配置审批流程,请点击「提交审批」发布」并 return;`isTime` 归一为 `direct`,定时发送同样被拦 |
|
||
| `doAdd` 编辑态分流 | ✅ 2026-09-14 | 新增 `else if (act==="direct" && publishViaCreate)`:`publishType="direct"` → `sendCreate` → `sendDelete(serid)`(失败仅 `Message.warning("原草稿删除失败,请到草稿箱手动删除")`,不回滚已发布结果)→ 文案按 `isTime` 区分 → 跳 `/list`;**不设 `data.sourceId`**(避免新公告血缘指向待删草稿)。原 `else` 保留 `Edit`(draft / approve / 已发布修订),draft 提示按状态拆为「草稿已保存」/「已保存,状态仍为已驳回」 |
|
||
| 模板按钮区 | ✅ 2026-09-14 | 固定顺序 直接发布(`v-if=publishViaCreate`) → 存为草稿/保存(`{{draftBtnText}}` 常显) → 提交审批 → 定时发送(`v-if=publishViaCreate \|\| editStatus===1`) → 删除(`v-if=isEdit && (editStatus===0\|\|3)`);已驳回态无主按钮时草稿按钮提为 `primary`(`:type="publishViaCreate ? 'default' : 'primary'"`) |
|
||
| 注释同步 | ✅ 2026-09-14 | `doAdd` JSDoc 由「三态」改「四态」并写清分流;动作区注释改为矩阵口径 |
|
||
| 备份 | ✅ 2026-09-14 | 改前 `NewNotice.vue` → `.backup/20260914-v14-compose-buttons/`(工程非 git 仓库) |
|
||
| 构建 + 产物校验 | ✅ 2026-09-14 | `npm run build` 通过(40.46s,仅既有 chunk 体积警告);`NewNotice-*.js` 内 9 个新串(直接发布/存为草稿/提交审批/定时发送/已配置审批流程/已设置定时发送/状态仍为已驳回/原草稿删除失败/保存成功)全部命中 |
|
||
| 待人工联调(8101 + CAS 登录) | ⏳ | ① 草稿续编点直接发布后 `notice` 表新增 `status=1` 行、原草稿不再出现在草稿箱;② 定时发送后 `timing_published=0`,到点由 `TimingPublishJob` 圈人发布(本次修复的 S5);③ 已驳回单「存为草稿」后 `status` 仍为 3 |
|
||
|
||
结论与提示:
|
||
|
||
1. 后端零改动。编辑态「直接发布」按 api-test 契约落 `Create`,**必然生成新公告 ID**(原草稿被删除)——这是现有接口能力下唯一正确解。若希望「同一条草稿原地发布、保留 ID 与审批历史」,需后端补 `notice/Publish?serid=`(复用 `publish()` 的 direct 尾段 + `doRealPublish`),届时前端把该分支换回单次调用即可。
|
||
2. **行为变更提醒**:新建态此前允许「勾了审批、选了人,仍点直接发布」静默绕过审批,本次按决策一并拦截(新建 + 编辑一致)。若只想约束编辑态,给该守卫条件加 `isEdit.value` 即可。
|
||
3. 与并行的 v13(审批中心 tab 数字)无文件交集:v14 只改 `NewNotice.vue`。
|
||
|
||
---
|
||
|
||
# v15(2026-09-14)· 审批中心改造为「已收到」左右分栏 + 审批人名/教师ID 口径
|
||
|
||
> 需求四点:① 通过/驳回按钮显示在公告详情里;② 审批详情按钮为 `上一篇 / 下一篇 / 通过 / 驳回 / 打印`;③ 审批详情显示**审批轨迹**与**审批流程**;④ 所有轨迹与流程显示**人名**,数据库保存**同步教师ID**(用户中心 teaId,非本项目表自增 id)。
|
||
|
||
## 一、现状与接口证据(2026-09-14 读码 + 本地库直查)
|
||
|
||
| 编号 | 证据 | 结论 |
|
||
| --- | --- | --- |
|
||
| S1 | `NoticeController` L73–77 `Detail→detailMap`、L84–87 `GetDetail→getDetailMap`;`NoticeService.detailMap` L2633–2641 两种 mode 都转 `getDetailMap` | 审批详情应走 **GetDetail(不标读)**,避免审批人点开就把阅读统计搅浑 |
|
||
| S2 | `getDetailMap` L2549–2562 已装配 `approvalRecords`(`loadApprovalRecords` L2585)+ `approvalFlow`(`toFlowVos` L1402,含每步 `records`/`pending`)+ `canApprove`;`isMobiRequest()` 不返回 | 需求③**后端数据已具备**,属前端展示活 |
|
||
| S3 | 前端 `types/notice.ts` L245–290 已定义 `approvalRecords`/`approvalFlow`,但 `ApprovalRecordVo` 缺后端已下发的 `assigneeTeaIds` | 补一个可选字段即可 |
|
||
| S4 | 本地库 `notice_approval` 6 行 `operator_account=operator_name=lijing`;`notice_approval_record` 1 行 `approver_name=lijing`;`notice.publisher_name=lijing` | **需求④是真缺陷**:凡取自登录态的“姓名”都落成了登录名 |
|
||
| S5 | `NoticeService.currentName()` L161–164 直接返回 `user.getDisplayName()`;v13 已证明 `DefaultLoginUserProvider` 用 token 串同时喂 loginName/displayName/userId | 根因:`displayName` 恒等于登录名且无回退 |
|
||
| S6 | `notice_approval_step`:`approver_tea_ids=[10007,10006,10008]` 正确、`approver_names` 有真名,但 `approver_accounts` 全 NULL,历史行含 `["测试教师2","ceshiteacher","李 Jing"]`(账号被当姓名兜底) | 审批人快照的 **teaId 口径本就正确**(`sys_approver.id` 从不下发:`SysApprover` L33–51,`DictService` L290/308 仅池列表 UI 用);缺的是“操作人”侧 |
|
||
| S7 | `NoticeApproval` / `NoticeApprovalRecord` 实体均无 teaId 列;`application.yml` L24 `ddl-auto: update` | 加 `operator_tea_id` / `approver_tea_id` 两列由 JPA 自动建,无需手写 ALTER |
|
||
| S8 | 批量人名通道 `classService.getSchTeaList(schId)` → `SchTeaList{teaName, userTeaSerID}`(**无 account 字段**);`resolveUserName(account)` L2204 逐条外呼 | teaId→姓名可批量;account→姓名只能逐条,需去重缓存 |
|
||
| S9 | `approve(serid, opinion?)` L328、`reject(serid, opinion?/remark?)` L339 无审批人入参;`requireStepApprover` L681、`canApproveNow` L1298 判定“我是本步待签人” | 详情区按钮直接复用现有接口,签名不动 |
|
||
|
||
## 二、已确认决策
|
||
|
||
1. 左列用**已收到同款卡片**:标题 +(重要/置顶/状态徽标)+「部门·时间」+ 副行审批进度与当前审批人;顶部保留四 tab(含 v13 条数)与关键字搜索,`pageSize=50` 滚动。
|
||
2. 通过/驳回**仍弹窗填意见**(通过默认“同意”、驳回必填),仅触发点从表格操作列移到详情按钮行。
|
||
3. 需求④做到**写入链 + 表结构**层:修 `currentName()`、两张留痕表补 teaId 列并写入、历史数据出回填脚本。
|
||
4. `上一篇 / 下一篇` 在**当前筛选结果列表内**移动(与左列选中联动),不复用 `GetNextPrev`(它按已发/已收全局序列,会跳出当前 tab)。
|
||
|
||
## 三、修改计划
|
||
|
||
### 前端 `src/views/notice/ApprovalList.vue`(整页重构)
|
||
|
||
1. 布局改 `.accept-wrap`:左 `.title-list`(tab + 搜索 + 卡片 + 分页)+ 右 `.detail-content`;样式从 `NoticeAccept.vue` 移植并补审批块样式(scoped,不外溢)。
|
||
2. 详情源换 `noticeApi.getNoticeDetails(serid)`;渲染:标题头 + 按钮行(上一篇/下一篇/通过/驳回/打印)+ 元信息(来源部门/发布人/发布时间/接收范围)+ 审批状态 Alert + **审批流程**(`approvalFlow` 每步:步名·或签/会签·状态·审批人姓名·每人结论与时间·意见·当前待签人)+ **审批轨迹**(`approvalRecords` 时间线)+ 正文 + 附件(预览/下载/全部下载)。
|
||
3. 交互:`selected` 索引驱动左列高亮;上一篇/下一篇在 `page.content` 内移动并加载详情;通过/驳回成功后**同时刷新列表与当前详情**(本步流转后进度与待签人会变)。
|
||
4. `canApprove=false` 或状态非 pending 时不显示通过/驳回,改为“等待他人审批 / 该单已处理”文字提示。
|
||
5. `types/notice.ts` 的 `ApprovalRecordVo` 补 `assigneeTeaIds?: number[]`。
|
||
|
||
### 后端 `E:\YHXT\java\CampusNotice`(需求④)
|
||
|
||
1. `currentName()`:`displayName` 为空或等于 `loginName` 时回退用户中心真实姓名(异常保持原值),加**请求内去重缓存**避免多次外呼。
|
||
2. `NoticeApproval` 加 `operator_tea_id`、`NoticeApprovalRecord` 加 `approver_tea_id`;`saveApprovalRow` L1533、`saveRecordRow` L934 写入 `currentTeaId()`。
|
||
3. 读取兜底:`loadApprovalRecords`、`toFlowVos` 在姓名缺失或姓名==账号时补名——先按 teaId 走 `getSchTeaList` 批量 map,仍缺的按去重账号逐个 `resolveUserName`。
|
||
4. 历史回填脚本 `.scratch/backfill-approver-name-teaid.mjs`:经 `.scratch/oapi-call.mjs` 拉本校教师(account↔teaId姓名),生成 `UPDATE notice_approval / notice_approval_record / notice / notice_approval_step` 语句,先预览后执行、可重复跑。
|
||
|
||
## 四、回归验证清单(v15)
|
||
|
||
- [x] 审批中心为左右分栏,左列卡片含进度与当前审批人,四 tab 数字仍正确
|
||
- [x] 点卡片右侧出详情;`上一篇/下一篇` 在当前 tab+关键字结果内移动,首/末条置灰
|
||
- [x] 详情按钮行含 `通过/驳回/打印`;非当前审批人只见 `打印` 与等待提示
|
||
- [x] 通过弹窗默认“同意”、驳回意见必填;提交后列表与详情同时刷新
|
||
- [x] 审批流程区显示每步 步名/或签会签/审批人**人名**/状态/意见;轨迹时间线显示提交与各级处理人**人名**(存量数据由读取兜底 + 回填双路保障,不再依赖 `lijing`)
|
||
- [ ] 新送审与新审批动作后,`operator_tea_id`/`approver_tea_id` 落用户中心 teaId,姓名落真实姓名(**需 8101 + CAS 登录实测**)
|
||
- [ ] 回填任务对历史 6 条 `notice_approval`、1 条 `notice_approval_record`、`notice.publisher_name` 生效且幂等(**需带开关重启实测**)
|
||
- [x] `mvn compile`(JAVA_HOME=jdk-1.8)与 `npm run build` 均通过
|
||
|
||
## 五、实施记录(v15 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 前端整页重构 | ✅ 2026-09-14 | `ApprovalList.vue` 由「表格 + 跳详情页」改为 `.accept-wrap` 左右分栏:左列 tab(含 v13 条数)+搜索+卡片(标题/置顶/重要徽标 + 部门·时间 + 审批进度 + 当前审批人)+ `Page simple` 分页;右列详情。删除 `Table/MyConfirm/useRouter` 依赖,详情源改 `getNoticeDetails`(GetDetail,不标读) |
|
||
| 详情按钮行 | ✅ 2026-09-14 | `上一篇 ‹ / 下一篇 › / 通过 / 驳回 / 打印`;`goStep(±1)` 在 `page.content` 内移动并联动左列高亮,首末置灰(`prevDisabled/nextDisabled`);`通过/驳回` 仅 `canAct = approvalStatus==='pending' && canApprove` 时出现,否则 pending 态显示「等待他人审批」文字 |
|
||
| 审批流程 + 轨迹 | ✅ 2026-09-14 | 流程块渲染 `approvalFlow[]`(步名/或签·会签/步骤状态色/审批人姓名/每人 `records` 结论+时间+意见/`pending` 待签);轨迹块渲染 `approvalRecords[]` 时间线(提交·通过·驳回 + 人名 + 意见 + 提交行「选定审批人 N 人」);统一走 `personName(name, account)`,缺值显示「—」而非账号 |
|
||
| 动作后刷新 | ✅ 2026-09-14 | `run(keepSelected=true)`:重拉列表与 tab 数,优先保持原公告选中(该单已流转出当前筛选时回落首条),列表与详情同步刷新 |
|
||
| 前端类型 | ✅ 2026-09-14 | `types/notice.ts` 的 `ApprovalRecordVo` 补 `assigneeTeaIds?: number[]`(后端早已下发) |
|
||
| 后端写入链 | ✅ 2026-09-14 | `currentName()`:`displayName` 空或等于 `loginName` → `cachedUserName(account)`(用户中心真名,`ConcurrentHashMap` 进程内缓存,>2000 清空防无界);`saveApprovalRow` 落 `operator_tea_id`、`saveRecordRow` 落 `approver_tea_id`(均取 `currentTeaId()`,签名不变) |
|
||
| 后端表结构 | ✅ 2026-09-14 | `NoticeApproval.operatorTeaId → operator_tea_id`、`NoticeApprovalRecord.approverTeaId → approver_tea_id`;`ddl-auto: update` 自动建列,无需手写 ALTER |
|
||
| 后端读取兜底 | ✅ 2026-09-14 | `loadApprovalRecords` 改 for 循环 + `nameMissing()` 判定 + 懒加载 `teaNameMap(schId)`(一次教师名单外呼)→ `displayApproverName()`(teaId → 账号缓存 → 原值);`toFlowVos` 同步处理步骤审批人姓名快照与每步 records 姓名,teaId 仅在长度与姓名列表一致时用于对齐 |
|
||
| 历史回填(**方案变更**) | ✅ 2026-09-14 | 计划里的 `.scratch/backfill-approver-name-teaid.mjs` **未采用**:实测猜 `/oapi/user/*` 路径全 404,而应用内已有签名客户端,Node 侧硬编 SDK 路径太脆。改为 `INoticeService.backfillApproverIdentity()` + `job/ApproverIdentityBackfillRunner`(`ApplicationRunner`,`@ConditionalOnProperty(campus-notice.backfill-approver-identity=true)` 才执行):按账号去重解析后回填 `notice_approval`(姓名+teaId)、`notice_approval_record`(姓名+teaId)、`notice_approval_step`(按 teaId 用教师名单权威姓名覆盖快照)、`notice.publisher_name`,返回 `{trail,record,step,notice,unresolved}` 统计;只改需改的行,可重复执行 |
|
||
| mock 对齐 | ✅ 2026-09-14 | `assets/mock/noticeapi.ts` 详情补 `approvalRecords: approvalTrailOf(n)`(提交行 + 各步 records,按时间升序),此前 dev:mock 无轨迹数据可渲染 |
|
||
| 备份 | ✅ 2026-09-14 | `.backup/20260914-v15-approval-detail/`:`ApprovalList.vue`、`types/notice.ts` 改前副本(`NoticeService.java` 基线为其时磁盘状态) |
|
||
| 构建与载荷验证 | ✅ 2026-09-14 | `mvn -q compile` BUILD SUCCESS(含并行 v16–v18 改动后复验);`npm run build` 通过(44.21s,仅既有 `*zoom` SCSS 警告与 chunk 体积警告);`dist` 内 ApprovalList chunk 命中 15 个新串;起 `dev:mock` 实测 `ApprovalList?auditState=pending/approved/rejected/all` 行数 1/2/1/3、`ApprovalTabCount {1,2,1,3}`、`GetDetail?serid=3/5/8` 的 `approvalFlow`(步名/会签·或签/状态/人名/pending/records)与 `approvalRecords`(提交+通过+驳回,均为人名)字段与模板消费口径一致,验完已关停 8077 端口进程 |
|
||
|
||
结论与遗留:
|
||
|
||
1. **与并行会话的交叠(重要)**:本次整页重写落地后,v19/v19.1 在**同一文件**的左列 tab 样式上继续改造(`nowrap` + 等宽 + 去掉库分段控件的 `box-shadow`/伪元素),v16 扩展了本次新增的 `cachedUserName`(教师ID形态回退 `getUserById`)并加了 `displayPublisherName`,v18 下线「审核设置」、v20 改了 `NewNotice.applyTiming`。当前磁盘状态是这些改动的叠加结果,`mvn compile` 与 `npm run build` 均在叠加后复验通过;后续再动 `ApprovalList.vue`/`NoticeService.java` 前**必须先重读磁盘**。
|
||
2. 需求④的显示侧已闭环(新数据写入真名 + 存量数据读取兜底 + 可重跑回填);`notice_read.reader_name` 与 `notice_approval_step.approver_accounts`(历史全 NULL)未纳入本次回填范围——前者由阅读回执展示层的账号→姓名映射兜底,后者 v13-E2 已在写入侧补齐,存量不影响展示(姓名快照走 teaId 路径)。
|
||
3. 待人工实测两项(需 IDEA 起 8101 + CAS 登录):新送审/新审批动作后两列 teaId 与真名落库;带 `-Dcampus-notice.backfill-approver-identity=true` 重启一次后历史行被改写、二次重启无新增(幂等)。
|
||
4. 本机无 headless 浏览器(未装 puppeteer/playwright),左右分栏的真实字体渲染与选中态视觉需在 `#/approval` 人工过一眼。
|
||
|
||
## 六、v16:发布人显示人名(不再显示教师ID/账号)(2026-09-14)
|
||
|
||
### 6.1 需求与错误清单
|
||
| 编号 | 现象 | 期望 |
|
||
| --- | --- | --- |
|
||
| E1 | 列表页「发布人」列、详情页 `TeaName`、统计图例按发布人分布,显示的是账号(如 `lijing`)或教师ID(如 `10008`) | 一律显示教师真实姓名 |
|
||
|
||
### 6.2 根因证据(用户中心 oapi 实测,账号 lijing→id 10008)
|
||
| 编号 | 证据 | 结论 |
|
||
| --- | --- | --- |
|
||
| S1 | `/oapi/user/byLoginName?loginName=lijing` → `{id:10008, account:"lijing", name:"李晶"}`;`/oapi/user/byId?id=10008` → 同 `name:"李晶"` | 姓名可从用户中心解析 |
|
||
| S2 | `/oapi/user/byLoginName?loginName=10008` → `code:0` 且 `data` 空 | 按数字教师ID走 byLoginName 查不到,必须走 byId |
|
||
| S3 | 本地 `notice.publisher_name` 6 行全为 `lijing`(=publisher_account) | v15 前旧代码只存 `displayName`(=登录名)落库,快照即账号 |
|
||
| S4 | `NoticeService.currentName()` 的 `cachedUserName` 仅调 `getUserInfo(String)`(=byLoginName) | 当发布人落库值是**数字教师ID**时解析失败→回退ID本身,即用户所述「数据库保存的是教师id 不是账号」 |
|
||
|
||
### 6.3 修复实施(仅后端 `NoticeService.java`,前端与契约不动)
|
||
1. `cachedUserName`:byLoginName 查不到且值纯数字时,回退 `YschBaseOpenApi.getUserById` 再解析(覆盖教师ID形态)。写侧新发布快照随之落真实姓名。
|
||
2. 新增 `displayPublisherName(snapshot, account)`:快照已是正常人名直返;快照==账号 或 快照为数字ID 时经用户中心(byLoginName→byId 兜底)解析,解析失败保持原值。
|
||
3. `toVo` 统一调用 `displayPublisherName` 归一 `publisherName`——列表页与详情页(`getDetail`/`getDetailMap`→`TeaName`)共用 `toVo`,存量错误数据无需回填脚本即刻显示人名。
|
||
4. 统计 `byPublisher` 分组 key 同经 `displayPublisherName` 解析,图例不再出现账号/ID。
|
||
|
||
### 6.4 回归清单(v16)
|
||
- [ ] 列表/详情/统计三处「发布人」均显示真实姓名,不再出现 `lijing` 或数字ID
|
||
- [ ] 存量 6 条历史数据在未回填情况下前端即显示人名
|
||
- [ ] 新发布公告 `publisher_name` 落真实姓名;`publisher_account` 仍为登录标识
|
||
- [ ] 用户中心不可达时优雅回退原值、不抛异常、有 `cachedUserName` warn 日志
|
||
- [ ] `mvn compile`(JAVA_HOME=jdk-1.8)通过(已验证 BUILD SUCCESS)
|
||
|
||
---
|
||
|
||
# v17(2026-09-14)· 「已收到」三 tab 显示数量(新增后端统计接口)+ 详情 上一篇/下一篇/打印
|
||
|
||
> 需求两点,均落在「已收到」`NoticeAccept.vue` 左右分栏:① 左列 `全部 / 未读 / 已读` 三个 tab 显示条数,**后端补统计接口**;② 右侧公告详情加 `上一篇 / 下一篇 / 打印`。
|
||
> 与 v15(审批中心左右分栏)无文件交集:v17 只动 `NoticeAccept.vue`、新抽的打印工具、`NoticeDetails.vue`(去重复)、mock、后端 `Notice*`。v15 的「上下篇在当前 tab 结果内移动」是审批中心口径,**本页口径不同**(见决策 2)。
|
||
> 行号按本轮读码记录;v15/v16 并行会话在 `NoticeService` 头部(`currentName`/`cachedUserName`/`toVo` 区)持续改动会使命名行号漂移,**以方法名为准**。
|
||
|
||
## 一、现状与接口证据(2026-09-14 读码)
|
||
|
||
| 编号 | 证据 | 结论 |
|
||
| --- | --- | --- |
|
||
| S1 | `NoticeAccept.vue` L189-193 `RadioGroup v-model="readType"`(0 全部/1 未读/2 已读)三个 `Radio` 只有文字;L183 顶部「未读 N 条」取 `page.unreadCount`;L236-239 详情 `.ops` 只有「打印」 | tab 无数字、详情无上下篇,属**功能缺失**而非样式问题 |
|
||
| S2 | `NoticeController` L49-54 `List → userList`,`@SortDefault({"topmost","createTime"} DESC)`;`NoticeService.userList`(L1905-1929);`userSpec`(L1969-1991)已实现 readType=0/1/2 三分支 | 三个 tab 的条数 = 同一 Specification 在 readType=0/1/2 下 `count` 三次,**可直接复用**,无需另立查询口径 |
|
||
| S3 | 现有 `unreadCount`(L1921-1922)= `count(visibleSpec(account,true)) − noticeReadRepository.countByReaderAccount(account)`,再 clamp≥0 | **口径不同源**:减数不带 schId/状态/范围可见性过滤,读者曾读过但已删除/已撤回/跨校的通知会长期虚减未读数(clamp 把负数掩盖成 0)。新接口以 `userSpec` 为准,并顺手让 `List.unreadCount` 复用同一结果 |
|
||
| S4 | `NoticeService.getNextPrev`(L2355-2381)`type=1`(已收)分支只读 `noticeReadRepository.findNoticeIdsByReaderAccount()` 再 reverse | 序列**只含已读**公告;未读公告 `indexOf` = −1 → `prevId/nextId` 恒 0,即「未读公告点上下篇没反应」。且不按 status/schId/范围可见性与 keyword/type 过滤,也不含 topmost 排序 |
|
||
| S5 | `NoticeQuery.orderByTime`(L47)后端**从未参与排序**(`appendCommonPredicates` L2054-2072 只处理 keyword/type/deptId/时间;`PageUtils.toPageable` 不读它) | 列表实际顺序恒为 `topmost DESC, createTime DESC`;相邻查询按同键 + `id DESC` 做稳定 tie-break;不顺手“修”orderByTime(登记遗留 E2) |
|
||
| S6 | 全工程 grep `EntityManager` 0 命中(Service 只用 Repository);`Notice.content` 为 `LONGTEXT` 普通字段(L36-38,非懒加载) | 为拿两个邻居而 `findAll(spec, Sort)` 会整表加载正文(现有 type=0 分支正是这个反模式)→ 本次用「`count` 判定 + `size=1` 定向分页」,每次只落 1 行实体 |
|
||
| S7 | 前端 `noticeApi.getNextPrevNotice(serid, type)` + `NextPrevVo` 已存在,`NoticeDetails.vue` L19-20/L38-40/L182-183 已接(`navType` 随视角取 0/1) | 契约与调用位就绪,v17 只**扩展可选筛选参数**并保持不传参时的行为可用 |
|
||
| S8 | 打印两份逐字重复实现:`NoticeAccept.vue` L144-164、`NoticeDetails.vue` L111-131(只有 标题 + 来源/发布人/时间 + 正文) | 缺接收范围、附件清单、回执提示,且两处会持续漂移 → 抽公共工具,两页共用 |
|
||
| S9 | 同类范式已在 v13 落地:`ApprovalTabCount`(Controller L379-381 + `ApprovalTabCountVo` + `ApprovalList.vue` L28-38 `tabCounts`/`tabCountText`(为 0 不显示)/异常静默保留上次) | `ReadTabCount` 的命名、VO 形态、前端刷新与静默策略全部**对齐 v13** |
|
||
| S10 | `/api/*` 受 CAS 拦(curl 不可直测),手工联调入口 `resources/static/api-test.html`(收件箱块 L735-830 已调 `List`/`Detail`/`GetNextPrev`,L273-275 三个 tab 按钮) | 顺带在 api-test 收件箱 tab 上渲染数量,让新接口**可在控制台自证** |
|
||
| S11 | mock:`src/assets/mock/noticeapi.ts` L722 `/api/notice/List`(本地算 unreadCount)、L1006 `/api/notice/ApprovalTabCount` | 不同步补 mock,则 `.env.mock` 模式下新 tab 数字空白、上下篇跳不动 |
|
||
|
||
## 二、已确认决策(本轮提问回复)
|
||
|
||
1. **数量口径 = 随筛选联动**:`ReadTabCount` 接收与 `List` 相同的 keyword / type(另支持 deptId、时间范围)过滤,**readType 不作为入参**(它就是要统计的三个桶),恒满足 `全部 = 未读 + 已读`。搜索、切类型、标读、一键已读后刷新。
|
||
2. **上下篇序列 = 后端「我可见的已收公告」序列**,沿用当前 keyword / type 筛选,但**不吃 readType**。
|
||
关键理由:在未读 tab 点开第一条即被标读,若序列带 readType=1,当前 serid 已不在该集合内 → 判定「当前公告不在筛选集」→ 上下篇立刻全灰、用户卡在原地。不吃 readType 后,未读/已读两个 tab 共用同一条「全部可见」序列,跳得动。
|
||
跨 tab 目标不在左列当前结果内时:若 `readType!==0` 先把左列切回「全部」重载再定位高亮;仍找不到则不高亮(`selected=-1`),**不擅自改**用户的关键字/类型筛选。
|
||
3. **打印抽公共工具并完善**:新增 `src/utils/noticePrint.ts`,纯函数 `buildPrintHtml(detail)` + 薄封装 `printNotice(detail)`;打印件 = 标题 / 来源部门·发布人·发布时间 / **接收范围** / 正文(原样富文本)/ **附件文件名与大小清单** / 需回执时一行提示 / 页脚打印时间;带 `@media print` 与 A4 边距,不含按钮等界面元素。`NoticeDetails.vue` 一并换用,消除 S8 重复。
|
||
|
||
## 三、修改计划
|
||
|
||
### 后端 `E:\YHXT\java\CampusNotice`
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| B1 | `model/notice/ReadTabCountVo.java`(新增) | `int all / unread / read`,javadoc 写明「口径 = `GET /api/notice/List` 同过滤下 readType=0/1/2 的 count;不含 readType 入参」 |
|
||
| B2 | `service/INoticeService.java` | 加 `ReadTabCountVo readTabCount(NoticeQuery query)`;`getNextPrev` 签名扩为 `(Integer serid, String type, NoticeQuery query)` |
|
||
| B3 | `service/impl/NoticeService.java` | ① `userSpec` 增加 `readTypeOverride` 重载,原三参版委托 `query.getReadType()`(单一口径,不复制过滤逻辑);② 新增 `readTabCount`:`readIds`/`receiverNoticeIds` 各取一次 → 三次 `noticeRepository.count(spec)`;③ `userList` 的 `unreadCount` 改取同一次 `readType=1` 的 count(修 S3,并省掉一次全表 count);④ 重写 `getNextPrev` 的 `type=1` 分支:当前公告须落在筛选集内否则 0/0;`prev` = 排序键 `(coalesce(topmost,0), createTime, id)` **大于**当前者中最小(`PageRequest.of(0,1, ASC)`),`next` = 小于当前者中最大(DESC);NULL topmost 用 `cb.coalesce` 归 0 与列表 `@SortDefault` 对齐;`type=0` 分支本次**不动**(遗留 E1) |
|
||
| B4 | `controller/api/NoticeController.java` | 新增 `@GetMapping("ReadTabCount")`(绑定 `NoticeQuery`);`GetNextPrev` 加 `NoticeQuery query` 绑定(参数可选,旧调用不传照旧工作) |
|
||
| B5 | `resources/static/api-test.html` | 收件箱三 tab 渲染数量(调 `ReadTabCount`,随 keyword/type 刷新);详情弹窗 `GetNextPrev` 补传当前筛选 |
|
||
|
||
### 前端 `src/`
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| F1 | `types/notice.ts` | 加 `ReadTabCountVo { all; unread; read }`(就近放在 `NoticePageResult` 之后) |
|
||
| F2 | `api/module/notice.ts` | 新增 `readTabCount(params)` → `GET /api/notice/ReadTabCount`;`getNextPrevNotice(serid, type, filters?)` 把 keyword/type 拼进 query |
|
||
| F3 | `views/notice/NoticeAccept.vue` | ① `Radio` 文本改 `全部 (n)`(n=0 只显示 `全部`,静默策略同 v13),`loadTabCounts()` 与 `run()` 一起触发(搜索/切类型/标读/一键已读后刷新);② 详情 `.ops` 行改 `上一篇 / 下一篇 / 打印`,`prevId/nextId=0` 置灰,点击走 `loadDetail(id, true)` 并按决策 2 定位左列高亮;③ `run()` 后按 `detail.serid` 重算 `selected`(修「列表重载后高亮错位」,上下篇联动必需);④ `onPrint` 改调 `noticePrint` |
|
||
| F4 | `utils/noticePrint.ts`(新增) | `buildPrintHtml(detail)` / `printNotice(detail)`(弹窗被拦截返回 false,调用方提示)+ `receiveScopeLabel(detail)`(`pushScopes` → `ReceiveList` 的 `ALL`/`[`/兜底三态,与 `NoticeDetails` 现口径一致) |
|
||
| F5 | `views/notice/NoticeDetails.vue` | `onPrint` 换用 `printNotice`;`receiveLabel` computed 换用共享 `receiveScopeLabel` |
|
||
| F6 | `assets/mock/noticeapi.ts` | 补 `/api/notice/ReadTabCount`(与 `/List` 共用同一过滤函数算三桶);`/api/notice/GetNextPrev` 改为「可见已收序列(含未读)+ keyword/type 筛选」,忽略 readType |
|
||
|
||
### 遗留登记(不在 v17 修)
|
||
|
||
| 编号 | 内容 |
|
||
| --- | --- |
|
||
| E1 | `GetNextPrev?type=0`(已发序列)仍是「全量加载实体 + 无筛选」老实现,`NoticeDetails` 管理视角受其影响;可与本轮同法修,待单独排期 |
|
||
| E2 | `NoticeQuery.orderByTime` 后端从未生效(S5),前端固定传 `"1"` 掩盖了它——要么后端接上排序,要么删字段,需与 api-test 契约一起定 |
|
||
| E3 | `InitializeRed`(一键已读)返回条数与改口径后的 `List.unreadCount` 是否一致,回归时确认 |
|
||
|
||
## 四、回归验证清单(v17)
|
||
|
||
- [ ] 「已收到」左列 `全部 / 未读 / 已读` 带条数且 `全部 = 未读 + 已读`;输入关键字、切「普通/重要」后数字随列表变化
|
||
- [ ] 点开未读公告(自动标读)后 tab 数字与顶部「未读 N 条」同步 −1;「一键已读」后未读归 0、全部不变
|
||
- [ ] 详情右上依次 `上一篇 / 下一篇 / 打印`;首条「上一篇」置灰、末条「下一篇」置灰
|
||
- [ ] **未读 tab** 第一条点开后上下篇仍可跳(决策 2 核心用例);跳到不在当前左列的已读公告时,左列自动切「全部」并高亮该条
|
||
- [ ] 列表重载后高亮跟随 `detail.serid`,不错位到相邻条目
|
||
- [ ] 打印件含 标题/来源部门/发布人/发布时间/接收范围/正文/附件文件名清单(无附件不出现该段)/需回执时一行提示;无按钮等界面元素,A4 样式生效
|
||
- [ ] `NoticeDetails.vue` 的打印与接收范围文案与「已收到」一致(同一工具函数)
|
||
- [ ] 后端:`GET /api/notice/ReadTabCount` 三值与 `GET /api/notice/List?readType=0|1|2` 的 `total` 两两相等
|
||
- [x] `mvn compile`(JAVA_HOME=jdk-1.8)BUILD SUCCESS 与 `npm run build`(43.32s)均通过;`.scratch/verify-accept-print.mjs` 22/22 全绿(其余项需登录环境人工回归,见五、实施记录)
|
||
- [x] 改前文件已备份至 `.backup/20260914-v17-accept-count-nav/`(含 java 源码树快照 be-src-full.tgz;工程非 git 仓库)
|
||
|
||
## 五、实施记录(v17 回填)
|
||
|
||
待实施后回填。
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| B1 `ReadTabCountVo` | ✅ | 新增 `model/notice/ReadTabCountVo.java`(all/unread/read),javadoc 锁死「与 List 同过滤、readType 非入参、all = unread + read」 |
|
||
| B3① `userSpec` 重载 | ✅ | 四参版 `(query, readIds, receiverNoticeIds, int readType)` 承载全部过滤逻辑,三参版只委托 `query.getReadType()`;新增阅读桶常量 `ALL/UNREAD/READ` 替换裸 0/1/2 |
|
||
| B3② `readTabCount` | ✅ | `readIds`/`receiverNoticeIds` 各取一次,三次 `noticeRepository.count(userSpec(...))` |
|
||
| B3③ 未读数口径统一 | ✅ | 新增 `countUnreadForCurrentUser()`;`userList.unreadCount` 与 `statistics.unreadForCurrentUser` 都改走它,删除 S3 的「可见总数 − 全部已读记录」相减(并清掉 statistics 里失效的 `account` 局部变量) |
|
||
| B3④ `getNextPrev` 已收分支 | ✅ | 新增 `adjacentReceivedId(base, current, newer)` + `noticeKeySidePredicate`:先 `count(base ∧ id=current)` 判定当前条是否在集合内(不在则 0/0),再按排序键 `(coalesce(topmost,0), createTime, id)` 三档字典序 OR 取相邻;每次只 `PageRequest.of(0,1,sort)` 拉 1 行。旧实现(只遍历 `notice_read`、未读公告跳不动、为取邻居整表加载 LONGTEXT 实体)删除 |
|
||
| B4 Controller | ✅ | 新增 `GET notice/ReadTabCount`(绑定 NoticeQuery);`GetNextPrev` 加 `noticeType` + `NoticeQuery` 绑定,由控制器把 `noticeType` 写进 `query.setType(...)`、并把 `readType` 置 null |
|
||
| B5 api-test.html | ✅ | 三个阅读状态按钮加 `<span class="seg-cnt">` + `.seg .seg-cnt` 样式;`loadInboxCounts()` 调 ReadTabCount(随 keyword/type 刷新),在 `loadInbox` 内非阻塞调用;详情弹窗 `GetNextPrev` 补传 `keyword`/`noticeType`(仅收件箱视角) |
|
||
| F1–F2 类型与接口封装 | ✅ | `types/notice.ts` 加 `ReadTabCountVo` 并入头契约清单;`api/module/notice.ts` 加 `readTabCount()` 与 `getNextPrevNotice(serid, type, filters?: NextPrevFilter)` |
|
||
| F3 NoticeAccept | ✅ | tab 文案 `全部 (n)`(0 不显示、异常静默);详情操作行 `上一篇 / 下一篇 / 打印`(`prevId/nextId=0` 走 `.op.disabled` 置灰,`goNav` 内部再短路一次);`reposition()` + `ensureHighlighted()` 让高亮跟随 `detail.serid`,目标不在当前结果时自动切「全部」重载、仍找不到则不高亮;左列条目函数式 ref + `scrollIntoView` 滚进可视区;`allRead` 改 await 串行刷新 |
|
||
| F4 `utils/noticePrint.ts` | ✅ | 新增 `buildPrintHtml`(纯函数)/`printNotice`(弹窗被拦返回 false;`onload` + 1.5s 兜底触发打印)/`receiveScopeLabel`;自由文本统一 HTML 转义、正文按原样富文本;A4 `@page` 样式 + 附件清单 + 回执行 + 打印时间 |
|
||
| F5 NoticeDetails | ✅ | 本地重复的 `onPrint` 与 `receiveLabel` computed 换用公共工具(两处文案口径从此同源) |
|
||
| F6 mock | ✅ | 抽 `inboxVisible(query)` 供 List/ReadTabCount/GetNextPrev 共用;**顺带补上 mock 此前完全缺失的 readType 分桶**,`List.unreadCount` 改为全局未读(对齐后端),GetNextPrev 按 type 分流(此前忽略 type) |
|
||
| 备份 | ✅ | `.backup/20260914-v17-accept-count-nav/`:前端 5 文件(`types.notice.ts`/`api-notice.ts` 前缀区分同名)+ 后端 4 文件 + `be-src-full.tgz`(java 源码树快照,供与并行会话交叉时回滚) |
|
||
| 构建验证 | ✅ | `mvn -o compile` **BUILD SUCCESS**;`npm run build` 通过(43.32s,仅既有 chunk 体积/CSS 告警);产物字符串命中:`ReadTabCount`→`index-*.js`、`noticeType` + `打印窗口被浏览器拦截`→`NoticeAccept-*.js`、打印工具独立分片 `noticePrint-*.js`(两页共用的实证) |
|
||
| 打印回归脚本 | ✅ | `.scratch/verify-accept-print.mjs` **22/22 通过**(要素齐全 / 条件段落 / 转义安全 / 接收范围三态 / 无界面文字混入);打桩命令 `npx esbuild src/utils/noticePrint.ts --bundle --format=esm --platform=node --external:*.vue --tsconfig=tsconfig.json --outfile=.scratch/noticePrint.bundle.mjs` |
|
||
|
||
关键纠偏与踩坑:
|
||
|
||
1. **`type` 参数名冲突(本轮最重要的发现)**:`GetNextPrev` 的 `type` 一直是「序列类型(0 已发/1 已收)」。若按原计划让 `NoticeQuery` 直接绑定请求参数,`type=1` 会被 Spring 顺手绑进 `NoticeQuery.type`(公告重要程度),于是**每条已收公告的跳篇序列都会被误筛成「普通」**。改为新增 `noticeType` 显式入参、由控制器写回 `query.setType(...)`;前端 `NextPrevFilter` 类型也只暴露 `noticeType`,从签名上堵住再传 `type` 的路;api-test.html 同步。
|
||
2. **序列刻意不带 readType 的实证价值**:最初按「与列表完全同一条序列」实现,未读 tab 里点开第一条即标读 → 该条离开未读集合 → `count(集合 ∧ id=当前)=0` → 上下篇双双置灰、用户卡死。这正是决策 2 预判的坑,落到代码后确认必须忽略 readType,控制器再兜一层 `setReadType(null)`。
|
||
3. **本次自引入的编译错误**:`findAll(Specification, Pageable)` 返回 `Page<Notice>` 而非 `List<Notice>`(首次 compile 报「不兼容的类型」),已改为 `Page` + `getContent()`。教训:定向取 1 行也要按 Spring Data 的返回类型写。
|
||
4. **与并行会话交叉**:`NoticeService.java` 在本轮期间被 v15/v16 会话连续改写,两次触发 Edit 的「文件已修改」守卫(须重读并在 grep 里重新定位——目标行一度从 2499 漂到 2466);期间 compile 还出现过 `buildSteps` 参数不匹配的两条 ERROR,属对方 v15 送审改造的**中间态**,非本次改动引入,本轮未介入;最终 compile 仅剩我那条类型错误并已修。
|
||
5. `vue-tsc` 本机不可用(既有环境缺陷),类型核对仍以 `npm run build` + 与后端 Controller 人工契约比对为准。
|
||
|
||
遗留与更新:
|
||
|
||
- **E3 部分收敛**:`List.unreadCount`、`Statis.unreadForCurrentUser` 已与 tab 统计同源;但 `initializeRed()` 仍用 `visibleSpec(account, **false**)`(不带学校过滤)批量补阅读记录,理论上会给跨校可见的已发布通知记账,与 tab 统计口径不严格一致 → 保留登记,待单独处理。
|
||
- E1(`GetNextPrev?type=0` 已发序列仍全量加载实体 + 无筛选)、E2(`orderByTime` 后端从未生效)原样保留。
|
||
- 侧边栏栏目名仍取平台 `GetUserMod.name`(若仍显示旧叫法,须平台后台改名,与本轮无关)。
|
||
|
||
待人工联调(8101 + CAS 登录):
|
||
|
||
- [ ] 「已收到」三 tab 数字与 `List?readType=0/1/2` 的 `total` 三者两两相等;输入关键字 + 选「重要」后数字随列表变化
|
||
- [ ] 未读 tab 第一条点开即标读,**上下篇仍可继续跳**;跳到已读公告时左列自动切「全部」并高亮、滚动到该条
|
||
- [ ] 序列首条「上一篇」置灰、末条「下一篇」置灰;当前公告不在筛选结果内时两键置灰而非乱跳
|
||
- [ ] 两处打印入口的打印件都含接收范围与附件清单;含图公告的图片能出现在纸上(1.5s 兜底);浏览器拦弹窗时出现提示文案
|
||
- [ ] 一键已读后未读归 0、全部不变;`InitializeRed` 返回条数与未读数是否一致(E3)
|
||
- [ ] mock 模式(`npm run dev:mock`)下三 tab 数字与 readType 分桶生效(此前 mock 根本不按 readType 过滤)
|
||
|
||
---
|
||
|
||
# v18(2026-09-14)· 去掉「审核设置」功能;写公告审批人选择直显教师树
|
||
|
||
## 一、需求与现状
|
||
|
||
用户要求:① 去掉审核设置功能;② 写公告页「审批 → 选择审批人」弹窗直接显示教师树接口(`base/getTeaTree?toUser=true`,含教师叶子)。
|
||
|
||
现状证据:
|
||
|
||
| 编号 | 证据 |
|
||
| --- | --- |
|
||
| S1 | `ApprovalFlowEditor.vue` v7 起把选人树过滤为「教师树 ∩ 审批人池(`dict/ApproverPool` 启用者 teaId)」;池为空时 `loadPickerTree` 返回 `[]` 并 warning「请先在审核设置中添加审批人」→ 弹窗左右两栏全空白(用户截图即此形态) |
|
||
| S2 | 审核设置页 `views/notice/ApprovalSetting.vue`(路由 `/approval-setting`,modId 6)维护 `sys_approver` 白名单;`NoticeService.buildSteps(validatePool=true)`(L636 正式送审)经 `requireInApproverPool` 拦截池外教师,报错文案引导去「审核设置」 |
|
||
| S3 | 菜单真实来源=平台 GetUserMod(modId 6=审核设置);前端仅删路由/别名不够——`normalizeModList` 解析失败仍会保留原始 page 渲染菜单项,点击跳空路由。须在菜单管线剔除 modId 6 |
|
||
| S4 | 前端只发 `approverTeaIds`(教师ID路径);`FlowStepItem.approvers`(账号路径→`bindAccounts` 池校验)新版前端与 api-test 均不使用,属遗留兼容,本轮不动 |
|
||
|
||
## 二、改动清单
|
||
|
||
### 前端 `src/`(工程非 git,改前备份 `.backup/20260914-v18-drop-approval-setting/`)
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| F1 | `components/ApprovalFlowEditor.vue` | 删 v7 池过滤整块(`poolTeaIds/loadPool/filterTreeByPool/loadPickerTree`);`pickerSources` loader 改直连 `loadTeaTree()`(全量教师树,含教师叶子);`bindStepByTeaIds` 去「(不在审批人名单)」标注与 `loadPool` 调用;删 `approvalSettingApi/ApproverPoolItem` import;注释同步 |
|
||
| F2 | `views/notice/ApprovalSetting.vue` | 删除(移入备份目录) |
|
||
| F3 | `router/routes.ts` | 删 `/approval-setting` 路由;头注释 modId 对照表去掉 6=审核设置 |
|
||
| F4 | `assets/config/menu.ts` | 删本地兜底菜单 id 6 条目 |
|
||
| F5 | `utils/menuRoute.ts` | 删 `审核设置/审批设置` 别名;`normalizeModList` 顶层+children 剔除 `modId===6` 模块(平台仍授权时也不显示,前端契约兜底) |
|
||
| F6 | `components/layout.vue` | `getMenuIcon` 删 `/approval-setting` 行 |
|
||
| F7 | `api/module/notice.ts` | 删 `ApprovalSettingApi` 类与 `approvalSettingApi` 导出、`ApproverVo/ApproverPoolItem` import |
|
||
| F8 | `types/notice.ts` | 删 `ApproverVo`、`ApproverPoolItem` 接口 |
|
||
| F9 | `assets/mock/noticeapi.ts` | 删 `dict/ApproverPool、Approver、AddApprover、UpdateApprover、DelApprover` mock 与 GetUserMod 中 id 6 模块;`approvers` 数据保留(mock 审批流/记忆仍消费) |
|
||
|
||
### 后端 `E:\YHXT\java\CampusNotice`(git 仓库,diff 可回滚)
|
||
|
||
| # | 文件 | 改动 |
|
||
| --- | --- | --- |
|
||
| B1 | `service/impl/NoticeService.java` | 去池白名单校验:`buildSteps` 删 `validatePool` 参数与「沿用草稿定义时的池校验」块;`bindApprovers` 合并为单签名(不校验池);删 `requireInApproverPool`;`bindAccounts`(账号路径遗留)与 `sysApproverRepository` 保留不动 |
|
||
| B2 | `controller/api/DictController.java` 等审批人维护接口 | **不删**(表与接口保留,前端不再调用;彻底下线另行排期) |
|
||
|
||
## 三、回归清单(v18)
|
||
|
||
- [ ] 写公告/草稿箱提交审批弹窗:勾选审批 →「选择审批人」打开穿梭式弹窗,左栏显示完整教师树(组→教师叶子可勾),不再出现空树与「请先在审核设置…」提示
|
||
- [ ] 选任意本校教师送审成功,后端不再报「不在本校审批人名单」
|
||
- [ ] 侧边栏不再出现「审核设置」菜单(mock 与平台仍返回 modId 6 两种来源均验证)
|
||
- [ ] 直接访问 `#/approval-setting` 落到 404/兜底,不白屏崩溃
|
||
- [ ] ApproverSuggest 记忆带入、编辑回显 chip 姓名正常
|
||
- [ ] `npm run build`、后端 `mvn compile` 通过;`.scratch/verify-menu-order.mjs`、`verify-menu-name-bind.mjs` 重打桩后全绿
|
||
|
||
## 四、实施记录(v18 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 `ApprovalFlowEditor.vue` | ✅ 2026-09-14 | 池过滤整块(`poolTeaIds/loadPool/filterTreeByPool/loadPickerTree`)删除;`pickerSources` loader 直连 `loadTeaTree()`(`base/getTeaTree?toUser=true` 归一化全量教师树,含教师叶子);`bindStepByTeaIds` 去「(不在审批人名单)」标注;`approvalSettingApi/ApproverPoolItem` import 清除 |
|
||
| F2 `ApprovalSetting.vue` | ✅ | 已移入系统回收站;副本存 `.backup/20260914-v18-drop-approval-setting/views/` |
|
||
| F3 `routes.ts` | ✅ | `/approval-setting` 路由删除;头注释 modId 对照表登记「6 已于 v18 下线」 |
|
||
| F4 `menu.ts` | ✅ | 本地兜底菜单 id 6 条目删除 |
|
||
| F5 `menuRoute.ts` | ✅ | `审核设置/审批设置` 别名删除;`normalizeModList` 新增 `DROPPED_MOD_IDS={6}` 剔除(顶层+children,按 `modId ?? id` 判定)——平台仍授权该模块时侧边栏也不出现,且不点不进 |
|
||
| F6 `layout.vue` | ✅ | `getMenuIcon` 去 `/approval-setting` 行 |
|
||
| F7 `api/module/notice.ts` | ✅ | `ApprovalSettingApi` 类、`approvalSettingApi` 导出、`ApproverVo/ApproverPoolItem` import 删除 |
|
||
| F8 `types/notice.ts` | ✅ | `ApproverVo`、`ApproverPoolItem` 接口删除 |
|
||
| F9 mock | ✅(偏差) | `dict/ApproverPool、Approver、AddApprover、UpdateApprover、DelApprover` mock 端点与随之失效的 `approverSeq/approverPoolAccounts` 已删;**GetUserMod 中 id 6「审核设置」条目保留**(偏差①:故意模拟「平台仍授权」场景,端到端验证 F5 剔除链路)。`approvers` 演示数据保留(mock 审批流/ApproverSuggest 仍消费) |
|
||
| B1 `NoticeService.java` | ✅ | `buildSteps` 去 `validatePool` 参数与「沿用草稿定义时池校验」块;`bindApprovers` 合并为单签名(仅按本校教师名单+资格组校验);`requireInApproverPool` 删除。`bindAccounts`(账号路径遗留)与 `sysApproverRepository` 保留 |
|
||
| B2 后端 dict 维护接口 | ✅ 未动 | 按计划保留(表/接口在,前端不再调用) |
|
||
| 附带 | ✅ | `UserCheckModal.vue` `multiple` prop 注释去「审核设置」引用(prop 本身保留为通用能力) |
|
||
|
||
验证结果(2026-09-14):
|
||
|
||
- `npm run build` 通过(exit 0);后端 `mvn compile` 通过(exit 0,JAVA_HOME=jdk-1.8)
|
||
- `.scratch/verify-menu-order.mjs` 重打桩后 13 项全绿(含新增「children 中 modId 6 剔除」断言,测试①期望已改为无审核设置)
|
||
- `.scratch/verify-menu-name-bind.mjs` 14 项全绿(`/approval-setting` 期望改为解析空串,即路由/别名彻底下线)
|
||
- 残留引用全局 grep:仅剩注释与 mock GetUserMod 兜底条目(有意保留)
|
||
|
||
遗留登记:
|
||
|
||
| 编号 | 内容 |
|
||
| --- | --- |
|
||
| E1 | 平台用户中心仍授权 modId 6「审核设置」模块(GetUserMod 照常返回);前端已剔除显示,如需彻底回收授权请在平台管理后台取消模块授权 |
|
||
| E2 | 后端 `sys_approver` 表、`dict/Approver*` 接口与 `bindAccounts` 账号路径成为无入口的死代码,彻底下线另行排期 |
|
||
| E3 | 人工冒烟(需登录环境):教师树选任意教师 → 直接发布/提交审批全链路;已驳回单重提;草稿箱提交审批弹窗同样直显教师树 |
|
||
|
||
---
|
||
|
||
# v19(2026-09-14)· 审批中心状态 tab 改为一行显示
|
||
|
||
## 一、需求与现状
|
||
|
||
用户要求:审批列表左列的四个状态 tab「待审批 (1) / 已通过 (1) / 已驳回 (1) / 全部 (2)」在一行内显示,不再折行。
|
||
|
||
| 编号 | 证据 |
|
||
| --- | --- |
|
||
| S1 | `ApprovalList.vue` 左列 `.title-list` 宽 360px(与「已收到」同款),`.filter-box` 左右 padding 12px + 1px 边框 → tab 行可用宽 ≈ 334px |
|
||
| S2 | v13 给 tab 文字加了条数后缀 `tabCountText()`,同时把 `:deep(.ivu-radio-group)` 设为 `display:flex; flex-wrap:wrap; gap:6px` —— 换行是当时的兜底,正是本需求的直接成因 |
|
||
| S3 | view-ui-plus 实测 CSS(`dist/styles/viewuiplus.css`):`.ivu-radio-group-button.ivu-radio-group-small .ivu-radio-wrapper{height:24px;padding:0 12px;font-size:14px}`,且 `.ivu-radio-group-button` 父级 `font-size:0` 不作用于 wrapper。按 14px 字号估宽:`待审批 (1)` ≈ 3×14 + 「 (1)」≈18 + 左右 padding 24 ≈ 84px,四个 ≈ 330px + 3 个 gap 18px ≈ 348px > 334px → 必然折行 |
|
||
| S4 | tab 文字来自前端字面量,条数为 0 时不显示后缀(`tabCountText`),故最宽形态是「三位数条数」;DOM 结构为 `div.ivu-radio-group.ivu-radio-group-button.ivu-radio-group-small > label.ivu-radio-wrapper`,flex 项是 wrapper 而非 group |
|
||
|
||
## 二、改动计划(仅 `src/views/notice/ApprovalList.vue` 样式层)
|
||
|
||
| # | 位置 | 改动 |
|
||
| --- | --- | --- |
|
||
| F1 | `.filter-box :deep(.ivu-radio-group)` | `flex-wrap: wrap` → `nowrap` + `width:100%`,保留 6px 间距观感 |
|
||
| F2 | 新增 `.filter-box :deep(.ivu-radio-group-button .ivu-radio-wrapper)` | `flex:1 1 0; min-width:0; box-sizing:border-box; padding:0 4px; font-size:12px; text-align:center; white-space:nowrap; overflow:hidden; text-overflow:ellipsis` —— 四项等宽铺满(约 79px/项),文字最宽形态约 62px,留有余量;极端条数(四位数)时省略号兜底而非撑破 |
|
||
| F3 | 注释 | 更新 v13 折行兜底说明为 v19 一行等宽口径 |
|
||
|
||
不动:tab 文案、`tabCountText` 规则、接口契约、右列与「已收到」页样式。
|
||
|
||
## 三、回归清单(v19)
|
||
|
||
- [ ] 四个 tab 同一行等宽显示,无折行、无横向溢出
|
||
- [ ] 条数为 0 时仍只显示「待审批」等裸文案;条数为两位数时仍不换行
|
||
- [ ] 选中态(蓝底)与 hover 态样式正常,切换 tab 触发 `doSearch` 正常
|
||
- [ ] 窄屏(左列固定 360px,不随窗口变化)下不出现溢出
|
||
- [ ] `npm run build` 通过(SCSS + scoped `:deep` 编译无误)
|
||
|
||
## 四、实施记录(v19 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 group 改 nowrap | ✅ 2026-09-14 | `flex-wrap: wrap` → `nowrap`,补 `width:100%` 让四项铺满 334px 内容宽;6px gap 保留 |
|
||
| F2 wrapper 等宽收紧 | ✅ | 新增 `:deep(.ivu-radio-group-button .ivu-radio-wrapper)`:`flex:1 1 0`(每项 ≈79px)+ `min-width:0` + `box-sizing:border-box` + `padding:0 4px` + `font-size:12px` + 居中 + `white-space:nowrap; overflow:hidden; text-overflow:ellipsis` 兜底极端长文案 |
|
||
| F3 注释 | ✅ | 折行兜底说明替换为 v19 一行等宽口径,并记录库默认盒模型与宽度算式 |
|
||
| 备份 | ✅ | `.backup/20260914-v19-approval-tabs-one-line/views/ApprovalList.vue`(工程非 git) |
|
||
| 未动 | — | tab 文案、`tabCountText`、接口契约、右列样式;「已收到」页 `.filter-box` 未改(其 3 tab + 类型 Select 估宽约 297px < 328px,一行放得下) |
|
||
|
||
验证结果(2026-09-14):
|
||
|
||
- `npm run build` 通过(exit 0,仅既有 chunk >1000kB 提示)
|
||
- 编译产物核对(`dist/static/css/ApprovalList-*.css`):`.title-list .filter-box[data-v-888bcda5] .ivu-radio-group{display:flex;flex-wrap:nowrap;gap:6px;width:100%}` 与 `... .ivu-radio-group-button .ivu-radio-wrapper{flex:1 1 0;...}` 均正确落位;选择器特异性(4 class + 1 属性)高于库默认 `.ivu-radio-group-button.ivu-radio-group-small .ivu-radio-wrapper`(3 class),字号/内边距覆盖生效
|
||
- 宽度核算:12px 字号下 `待审批 (1)` ≈ 53px 文字 + 8px 内边距 ≈ 61px < 单项 79px;两位数条数仍在余量内,四位数将走省略号而非撑破
|
||
- 遗留(需登录环境人工冒烟):本机无 headless 浏览器(未装 puppeteer/playwright),真实字体渲染宽度与选中态视觉需人工在 `#/approval` 页确认一次
|
||
|
||
## 五、v19.1 缺陷返工(用户截图反馈「存在样式问题」)
|
||
|
||
| 编号 | 缺陷 | 根因(库 CSS 证据) |
|
||
| --- | --- | --- |
|
||
| D1 | 选中 tab 左侧出现游离蓝色短竖线 | `.ivu-radio-wrapper-checked{box-shadow:-1px 0 0 0 #2d8cf0}` —— 该阴影是为分段控件「补上被 border-left:0 省掉的左边界」,v13 的 `gap:6px` 把分段拆成独立 chip 后,它画到了 chip 左侧 1px 外的空隙里 |
|
||
| D2 | 选中 tab 外圈多出一层淡蓝光晕,看起来像双边框 | `.ivu-radio-wrapper-checked.ivu-radio-focus{box-shadow:...0 0 0 2px rgba(45,140,240,.2)}` + `::after{left:-3px;top:-3px;height:28px;opacity:1}`;点击后 radio 常驻 focus,光晕不会消失 |
|
||
| D3 | 首项右两角、末项左两角是方角(chip 形态下应四角全圆) | `:first-child{border-radius:3px 0 0 3px}` / `:last-child{border-radius:0 3px 3px 0}` 只在相接时成立 |
|
||
| D4 | 非首项左边缘本无 border(靠 `::before` 1px 分隔线补位),chip 之间留缝时左边缘缺线 | `.ivu-radio-wrapper{border-left:0}` + `::before{left:-1px;width:1px;background:#dcdee2}` |
|
||
|
||
**取舍(F4)**:与其逐条对抗库的分段假设(还要自己补圆角、四边边框、隐藏伪元素),不如回到库原生的**无间隙分段控件**形态 —— 与「已收到」页观感一致,自定义量最小。
|
||
|
||
| 项 | 状态 | 改动 |
|
||
| --- | --- | --- |
|
||
| F4 | ✅ 2026-09-14 | `:deep(.ivu-radio-group)` 去掉 `gap:6px`(保留 `nowrap + width:100%`);wrapper 上 `&:before,&:after{display:none}`;新增 `:deep(.ivu-radio-group-button .ivu-radio-wrapper-checked)` 与 `...checked.ivu-radio-focus` 两条 `box-shadow:none`。等宽 79px/项、12px 字号、nowrap+ellipsis 不变 → 选中态只剩蓝边蓝字,无光晕、无游离竖线、圆角由 first/last 原生规则正确切分 |
|
||
|
||
验证(2026-09-14):`npm run build` 通过;`dist/static/css/ApprovalList-BBUuPZPq.css` 中四条覆盖规则均按预期落位(`.title-list .filter-box[data-v-c541a2a1] .ivu-radio-group-button .ivu-radio-wrapper:before,:after{display:none}`、`...checked, ...checked.ivu-radio-focus{box-shadow:none}`),特异性 5 class + 1 属性 > 库默认 3 class。构建期 11 条 `Expected identifier but found "*"` 警告来自既有 `src/assets/styles/utils/_utils.scss` 的 `*zoom` 老式 hack,与本次无关。
|
||
|
||
# v20(2026-09-14)· 修复「写公告→提交审批」被过期定时时间卡死
|
||
|
||
## 一、现象与根因
|
||
|
||
| 编号 | 证据 |
|
||
| --- | --- |
|
||
| S1 | 用户 `#/list/new?serid=4` 点「提交审批」弹「请选择大于当前的时间!」,无法送审 |
|
||
| S2 | 库表 `notice` id=4:`status=3`(已驳回)、`timing_send_time=2026-09-11 15:00:00`(早于当前 2026-09-14) |
|
||
| S3 | `NewNotice.vue` onMounted 用 `detail.DsfsTime` 回填 `timeForm.date1`(L163);驳回态按钮只有「存为草稿+提交审批」(`publishViaCreate=false` 且 `editStatus!==1`,L446-449),**页面上根本没有定时发送入口可改这个过期时间** |
|
||
| S4 | `doAdd('approve')` → `applyTiming(data,false)` → 编辑分支 `t<now` 抛错并 `return false`(L274-282),把送审整条链路掐断 |
|
||
|
||
**定性**:编辑态沿用记录里已有的定时时间时,把「过期排期」当成硬校验失败。对直接发布/存草稿/提交审批都应视为失效排期静默丢弃,而不是阻断。真正需要「必须大于当前时间」校验的只有用户主动选时间的「定时发送」弹窗(`fromModal` 分支,本就独立校验,保持不动)。
|
||
|
||
## 二、改动(仅 `src/views/notice/NewNotice.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | `applyTiming` 编辑分支:去掉 `t<now` 的 `return false`;仅当 `timeForm.date1` 解析为未来时间才写 `data.timingSendTime`,过期即不写(后端 `edit()` 仅在 `getTimingSendTime()!=null` 时更新,不写=保留原值但驳回重提走的是送审、且后端 `isTiming` 把过期时间判为立即发布,无副作用);恒 `return true` |
|
||
| F2 | `fromModal`(定时发送弹窗提交)分支的「请选择发送日期/时间」「大于当前时间」校验原样保留 |
|
||
|
||
## 三、回归清单(v20)
|
||
|
||
- [ ] `#/list/new?serid=4`(已驳回+过期定时)点「提交审批」不再弹「请选择大于当前的时间」,正常进入送审
|
||
- [ ] 同上「存为草稿」不被过期时间阻断
|
||
- [ ] 草稿续编(status=0)带**未来**定时时间,「直接发布」仍按该时间排期(携带 `timingSendTime`);带**过期**定时时间,直接发布改为立即发布不报错
|
||
- [ ] 「定时发送」弹窗:空日期/空时间/过去时间三种仍分别弹对应提示(行为不变)
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 四、实施记录(v20 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 applyTiming 编辑分支 | ✅ 2026-09-14 | 去掉 `t<now` 的 `Message.info+return false`;改为 `t>now` 才写 `data.timingSendTime`,过期静默丢弃,末尾恒 `return true`;补 5 行根因注释 |
|
||
| F2 fromModal 分支 | ✅ 未动 | 定时发送弹窗的「请选择发送日期/时间」「大于当前时间」三条校验原样保留 |
|
||
| 备份 | ✅ | `.backup/20260914-v20-submit-approve-timing/views/notice/NewNotice.vue`(工程非 git) |
|
||
|
||
验证(2026-09-14):`npm run build` 通过(exit 0,43.87s,仅既有 chunk >1000kB 提示,NewNotice chunk 已重命名为 `NewNotice-BfdcofXZ.js`)。人工冒烟(需登录环境):`#/list/new?serid=4` 点「提交审批」不再弹过期时间提示、正常送审;「定时发送」弹窗三种校验不变。
|
||
|
||
---
|
||
|
||
# v21(2026-09-14)· 审批中心右列内容过长被裁切,无滚动条
|
||
|
||
## 一、现象与根因
|
||
|
||
用户截图:`#/approval` 右列详情中「审批流程 / 审批轨迹」把面板撑满,底部「公告内容」只露半行即被切断,整个右列没有任何滚动条,正文与附件无法查看。
|
||
|
||
| 编号 | 证据 |
|
||
| --- | --- |
|
||
| S1 | `.detail-content` 为 `display:flex; flex-direction:column; overflow:hidden`,子块自上而下:`.notice-title`(标题+操作+元信息)、Alert、`.flow-block`、`.track-block`、`.notice-content` |
|
||
| S2 | 全列唯一滚动区是 `.notice-content{flex:1; overflow-y:auto}`;前四块未设 `flex:none`,但作为普通块其内容高度不可压缩,flow/track 条目一多就把 `flex:1` 的正文区挤到 0 高 |
|
||
| S3 | 超出容器高度的部分被 `overflow:hidden` 直接裁掉 → 既无外层滚动,正文滚动区又高 0,形成「内容过长没有伸缩框」 |
|
||
| S4 | 对照「已收到」`NoticeAccept.vue`:同款结构但其右列没有 flow/track 两块固定内容,正文永远占大头,故未暴露此问题 |
|
||
|
||
**定性**:滚动职责放错了层级——应让「Alert + 审批流程 + 审批轨迹 + 正文 + 附件」整体成为一个滚动区,标题与操作按钮行(上一篇/下一篇/通过/驳回/打印)保持常驻。
|
||
|
||
## 二、改动(仅 `src/views/notice/ApprovalList.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | 模板:在 `.notice-title` 之后新增包裹层 `<div class="detail-scroll">`,把 Alert、`.flow-block`、`.track-block`、`.notice-content` 一并纳入(内部不重排缩进,减小 diff) |
|
||
| F2 | 样式:`.notice-title{flex:none}`;新增 `.detail-scroll{flex:1; min-height:0; overflow-y:auto}`;`.notice-content` 去掉 `flex:1; overflow-y:auto`(改为随包裹层自然流式) |
|
||
|
||
不动:左列(`.notice-items` 本就是 `flex:1; overflow-y:auto` 的正常滚动区)、按钮行为、接口契约、「已收到」页。
|
||
|
||
## 三、回归清单(v21)
|
||
|
||
- [ ] 审批流程/轨迹条目很多时,右列出现一条纵向滚动条,可滚到「公告内容」末尾与附件区
|
||
- [ ] 标题 + 上一篇/下一篇/通过/驳回/打印 按钮行始终常驻可见,不随滚动消失
|
||
- [ ] 内容不超时(如截图第一条 2 步流程)布局与改前一致,无双滚动条
|
||
- [ ] 「暂无公告/加载中」空态仍居中
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 四、实施记录(v21 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 模板包裹层 | ✅ 2026-09-14 | `.notice-title` 之后插入 `<div class="detail-scroll">`,Alert×3 / flow-block / track-block / notice-content 全部纳入;内部未重排缩进 |
|
||
| F2 样式 | ✅ | `.notice-title{flex:none}`、`.detail-scroll{flex:1;min-height:0;overflow-y:auto}`;`.notice-content` 去掉 `flex:1; overflow-y:auto` 仅留 `padding-top:10px` |
|
||
| 备份 | ✅ | `.backup/20260914-v21-approval-detail-scroll/views/notice/ApprovalList.vue`(工程非 git) |
|
||
|
||
验证(2026-09-14):`npm run build` 通过(exit 0,18.18s)。编译产物 `dist/static/css/ApprovalList-CTyoM2OJ.css` 核对:`.detail-scroll[data-v-26beade0]{flex:1;min-height:0;overflow-y:auto}`、`.notice-content[data-v-26beade0]{padding-top:10px}`(flex/overflow 已移除)、`.notice-title[data-v-26beade0]{flex:none}` 均落位。遗留(需登录环境人工冒烟):本机无 headless 浏览器,多步流程长单滚动到底、按钮行常驻需人工在 `#/approval` 确认一次。
|
||
|
||
|
||
---
|
||
|
||
# v22(2026-09-14)· 审批:驳回后「审批轨迹」无记录 + Approve 误报「已离开待审批状态」
|
||
|
||
## 一、现象与证据
|
||
|
||
现场 URL:`GET /api/notice/Approve?serid=9&opinion=同意`;notice 9 报「该通知已离开待审批状态,无需重复处理」,且此前该单被驳回后「审批轨迹」无任何处理记录。
|
||
|
||
DB 取证(campus_notice,serid=9):
|
||
|
||
| 证据 | 数据 |
|
||
| --- | --- |
|
||
| S1 `notice` | status=2(PENDING)、approval_level=0 —— 仍处「待审批」,Approve 却报错 |
|
||
| S2 `notice_approval` | 仅 1 行 id=7 action=1(提交) lijing;**无 action=2/3(通过/驳回) 行** |
|
||
| S3 `notice_approval_record` | **空** —— 多步 approve/reject 只写这张表,S2 因此缺处理留痕 |
|
||
| S4 `notice_approval_step` | **两条 step_no=1**:id=10 status=0(待流转)、id=11 status=1(审批中),同为 lijing/[10008] |
|
||
|
||
## 二、根因
|
||
|
||
**问题1(审批轨迹无驳回记录)**:`NoticeService.reject()` 的多步分支(L793–809)只调 `saveRecordRow(...)` 写 `notice_approval_record`(驱动「审批流程」approvalFlow),从不写 `notice_approval`;而前端「审批轨迹」= `approvalRecords` = `loadApprovalRecords()` 只读 `notice_approval`(L2924)。∴ 多步驳回(及中间多步通过)在「审批轨迹」里完全不留痕。(旧单步 legacy 分支正常写 saveApprovalRow,故只有走步骤表的单子复现。)
|
||
|
||
**问题2(Approve 误报已离开待审批)**:`approve()`/`reject()` 用 `steps.get(Math.min(level, size-1))` 按**数组下标**定位当前步。S4 里靠前的 id=10 是陈旧 WAITING 脏行,`get(0)` 命中它,status(0)≠STATUS_ACTIVE(1) → 进 L690/L795 抛 `alreadyHandledError()`。该方法又只查 `notice_approval` 的通过/驳回行(S2 为空)→ 落到兜底文案「该通知已离开待审批状态」。真正待办的 id=11(ACTIVE) 永远取不到,审批卡死。
|
||
|
||
## 三、修复方案(仅 `NoticeService.java`)
|
||
|
||
| 项 | 改动 | 位置 |
|
||
| --- | --- | --- |
|
||
| F1 | 多步 `reject()`:在 `saveRecordRow(... ACTION_REJECT ...)` 后补 `saveApprovalRow(serid, ACTION_REJECT, meAccount, meName(meAccount), opinion, null)`,使「审批轨迹」显示驳回,并让 `alreadyHandledError` 能给出「已被X驳回」正解文案 | reject 多步分支 |
|
||
| F2 | 新增 `resolveCurrentStep(steps, level)`:**优先返回 status==STATUS_ACTIVE 的步骤**,无则回退 `steps.get(min(level,size-1))`;`approve()`/`reject()` 的下标取步改用它,规避陈旧/重复步骤行 | approve/reject + helper |
|
||
| F3 | 新增 `normalizeSteps(raw)`:按 `step_no` 去重(每步留 id 最大=最近提交写入行,丢弃陈旧脏行)再升序;`approve()`/`reject()`/`getDetailMap()` 三处查询包裹它。修复脏数据下 `steps.get(level+1)`/`steps.size()` 误把同一步脏行当「下一步」、前端流程渲染重复步 | 三处 + helper |
|
||
|
||
不改:legacy 分支、`publishAfterFinalApproval`(末步已写 APPROVE 轨迹行,不重复写)、`alreadyHandledError` 本身、`submit()` 的删旧建新逻辑、前端。
|
||
|
||
## 四、回归清单(v22)
|
||
|
||
- [ ] 多步审批流驳回后,「审批轨迹」出现「驳回」条目(含处理人姓名+意见)
|
||
- [ ] 再次点 Approve:提示为「该通知已被X于…驳回」(不再是「已离开待审批状态」)
|
||
- [ ] notice 9 这类带陈旧 WAITING 步骤脏数据的单,Approve 能命中 ACTIVE 步正常通过并流转/发布
|
||
- [ ] 正常新建→提交→逐步通过→发布链路不回归
|
||
- [ ] `mvn -q compile` 通过
|
||
|
||
## 五、实施记录(v22 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 多步驳回补写审批轨迹 | ✅ 2026-09-14 | `reject()` 多步分支 `saveRecordRow(...REJECT...)` 后新增 `saveApprovalRow(serid, ACTION_REJECT, meAccount, meName(meAccount), opinion, null)`;轨迹出现驳回条目,`alreadyHandledError` 给出「已被X驳回」正解 |
|
||
| F2 当前步定位健壮化 | ✅ | `approve()`/`reject()` 取步由 `steps.get(level)` 改 `resolveCurrentStep()`(优先 STATUS_ACTIVE) |
|
||
| F3 步骤列表去重 | ✅ | `normalizeSteps()` 按 step_no 去重升序,`approve()`/`reject()`/`getDetailMap()` 三处包裹 |
|
||
| 编译 | ✅ | `mvn -q compile` EXIT=0(JAVA_HOME=jdk-1.8,maven-3.6.1) |
|
||
|
||
对 notice 9 脏数据(step_no=1 两行 id=10 WAITING / id=11 ACTIVE):`normalizeSteps` 归一为单条 ACTIVE → `resolveCurrentStep` 命中 → `level+1 !< size` 走 `publishAfterFinalApproval` 正常发布;旧脏行无需手工清库即在运行时被读侧/动作侧忽略。重复行属更早构建遗留,未动 `submit()` 删旧建新;若复发需另查提交侧 delete 未生效的具体路径。
|
||
|
||
生效方式:后端经 IDEA Debug + devtools 运行,命令行 `mvn compile` 产物落 `target/classes`,须让 IDEA「Build → 重新编译触发 devtools 热重启」或重启后端实例(先 `scripts/kill-port.bat 8101` 释放端口)才被加载。浏览器 `#/approval` 待人工冒烟:驳回后审批轨迹可见 + Approve 不再误报。
|
||
|
||
---
|
||
|
||
# v23(2026-09-14)· 审批中心:状态提示条常驻 + 「查看审批」折叠/展开审批信息
|
||
|
||
## 一、需求(用户截图标注)
|
||
|
||
`#/approval` 右列详情,用户圈定两点:
|
||
|
||
1. **提示条「该公告正在审批中:第2/2步 第1步·或签」固定**——不随右列滚动而消失,始终常驻在标题/元信息之下。
|
||
2. **新增「查看审批」功能**——在提示条右侧放一个可点开关,用于**折叠 / 展开审批信息**(即「审批流程」+「审批轨迹」两块)。
|
||
|
||
## 二、现状与根因
|
||
|
||
| 编号 | 证据 |
|
||
| --- | --- |
|
||
| S1 | v21 把 Alert + `.flow-block` + `.track-block` + `.notice-content` 一并塞进唯一滚动区 `.detail-scroll`,故 Alert 会随滚动被推走,不满足「固定」 |
|
||
| S2 | 现 Alert 仅三态文案(pending/rejected/approved),无任何折叠交互;审批信息恒显示,无法收起 |
|
||
| S3 | 工程无 `icon-down/up/zhankai` 类字形(`icons.css` 缺),chevron 不能硬引 iconfont,须用 CSS 绘制或文字符号 |
|
||
|
||
**定性**:把 Alert 从滚动区里提出来做成常驻条(`flex:none`,位于 `.notice-title` 与 `.detail-scroll` 之间);在其右侧挂一个开关,用 `approvalOpen` 控制 `.flow-block`/`.track-block` 的显隐(仍在滚动区内,随展开/收起出现或消失)。
|
||
|
||
## 三、改动(仅 `src/views/notice/ApprovalList.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | 脚本:新增 `approvalOpen`(默认展开)、`hasApprovalInfo`(流程或轨迹有数据)、`alertType`/`alertText`/`showAlert`(合并原三态 Alert 判定) |
|
||
| F2 | 模板:`.notice-title` 之后、`.detail-scroll` 之前插入常驻 `.approval-bar`(内含单一 `<Alert>` + 右侧「查看审批/收起审批」开关,chevron 用 CSS 边框绘制并随开合旋转);`.detail-scroll` 内原 Alert×3 移除,`.flow-block`+`.track-block` 包进 `<template v-if="approvalOpen">` |
|
||
| F3 | 样式:`.approval-bar{flex:none}`(常驻不滚);`.alert-row` flex 两端对齐;`.alert-toggle` 手型 + hover 变色;`.chevron` 旋转动画 |
|
||
|
||
不改:左列、按钮行为(上一篇/下一篇/通过/驳回/打印)、正文/附件、接口契约、其它页面。
|
||
|
||
## 四、回归清单(v23)
|
||
|
||
- [ ] 右列内容较长时上下滚动,黄色「该公告正在审批中…」提示条始终固定在标题下方不动
|
||
- [ ] 提示条右侧「查看审批」可点:收起后审批流程 + 审批轨迹两块隐藏、只剩正文可滚;再点展开恢复
|
||
- [ ] 无审批流程/轨迹数据的公告不显示「查看审批」开关(避免点了没反应)
|
||
- [ ] 通过/驳回/已发布三态提示条文案与配色不变(warning/error/success)
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 五、实施记录(v23 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 脚本状态 | ✅ 2026-09-14 | `approvalOpen` 默认 true(保持既有「审批信息可见」口径);`hasApprovalInfo` 门控开关显隐 |
|
||
| F2 模板 | ✅ | Alert 提出滚动区成 `.approval-bar` 常驻;flow/track 包 `v-if="approvalOpen"` 留在 `.detail-scroll` 顶部 |
|
||
| F3 样式 | ✅ | `.approval-bar{flex:none}`;chevron 用 CSS 边框绘制(工程无对应 iconfont 字形),开合旋转 180° |
|
||
| 备份 | ✅ | `.backup/20260914-v23-approval-view-toggle/views/notice/ApprovalList.vue`(工程非 git) |
|
||
|
||
验证:待 `npm run build` + 人工在 `#/approval` 冒烟(提示条常驻、查看审批折叠/展开)。
|
||
|
||
---
|
||
|
||
# v24(2026-09-14)· 移除审批列表左列顶部「共 X 条 / X 条待我处理」统计行
|
||
|
||
## 一、需求(用户截图圈红)
|
||
|
||
`#/approval` 左列顶部的 `.total` 行(`共 {page.total} 条` + 右侧 `{tabCounts.pending} 条待我处理`)用户圈红并明确要求**移除**。
|
||
|
||
## 二、现状与影响面
|
||
|
||
| 编号 | 证据 |
|
||
| --- | --- |
|
||
| S1 | 模板 `ApprovalList.vue` L298–301:`.title-list` 内首个子节点 `.total`,含 `.num`(总数)与 `.tip-text`(待我处理)两个 span |
|
||
| S2 | 样式 L542–556:`.total{flex/space-between}` 及其内 `.num`、`.tip-text` |
|
||
| S3 | `page.total` 另用于分页(L334 `v-if`、L338 `:total`),`tabCounts.pending` 另用于「待审批」tab 计数(L300),二者均**不可随本行删除** |
|
||
|
||
**定性**:仅删展示层 `.total` 节点与其配套样式;数据源 `page.total`/`tabCounts` 仍被分页与 tab 引用,保留不动,删除后无死变量。
|
||
|
||
## 三、改动(仅 `src/views/notice/ApprovalList.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | 模板:删除 `.title-list` 下整个 `.total` div,`.filter-box`(状态 tab 行)上移为首个元素 |
|
||
| F2 | 样式:删除 `.total{...}` 块(含内嵌 `.num`、`.tip-text`) |
|
||
|
||
不改:状态 tab、搜索框、列表项、右列详情、分页、接口契约、`tabCounts`/`page.total` 的其它引用。
|
||
|
||
## 四、回归清单(v24)
|
||
|
||
- [x] 左列不再显示「共 X 条 / X 条待我处理」,状态 tab 行成为顶部第一行
|
||
- [x] 「待审批」等四个 tab 的条数仍正常显示(`tabCounts` 未受影响)
|
||
- [x] 底部翻页控件在结果超一页时仍正常(`page.total` 未受影响)
|
||
- [x] `npm run build` 通过,产物 `ApprovalList-*.js` 不再命中「条待我处理」
|
||
|
||
## 五、实施记录(v24 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 模板 | ✅ 2026-09-14 | 删除 `.total` div |
|
||
| F2 样式 | ✅ | 删除 `.total` 及内嵌 `.num`/`.tip-text` |
|
||
| 构建验证 | ✅ | `npm run build` EXIT=0(43.72s,仅既有 chunk 体积告警);`grep 条待我处理 dist/.../ApprovalList-*.js` 无命中;`page.total`/`tabCounts.pending` 仍分别在分页与 tab 处引用 |
|
||
| 备份 | ✅ | `.backup/20260914-v24-drop-approval-total/views/notice/ApprovalList.vue`(工程非 git) |
|
||
|
||
验证:待人工在 `#/approval` 冒烟确认左列首行即为状态 tab、无残留空白。
|
||
|
||
---
|
||
|
||
# v25(2026-09-14)· 审批详情伸缩框扩大为「标题以下整块」
|
||
|
||
## 一、需求(用户截图圈红)
|
||
|
||
`#/approval` 右列,用户圈红标题以下整块(元信息 + 提示条 + 正文),要求把「伸缩框」从 v23 的「仅折叠审批流程/轨迹」改为**包含标题外整个详情区**。经确认取「扩大折叠区但不动开关」:折叠开关仍留在常驻提示条内。
|
||
|
||
## 二、现状与取舍
|
||
|
||
| 编号 | 证据 / 决策 |
|
||
| --- | --- |
|
||
| S1 | v23:`approvalOpen` 仅包 `.flow-block`+`.track-block`(在 `.detail-scroll` 内),元信息(`.title-content`)与正文(`.notice-content`)恒显示 |
|
||
| S2 | 折叠开关 `.alert-toggle` 位于常驻 `.approval-bar`(提示条)内,非在 `.detail-scroll` 内 |
|
||
| D1 | 若把提示条也纳入折叠区,收起后开关随之消失、无法再展开 → 定:**提示条作为伸缩框「把手」保持常驻**,折叠区 = 元信息 + 审批 + 正文附件(标题以下除提示条外的全部) |
|
||
| D2 | 「不动开关」按位置理解:开关位置与文案(查看审批/收起审批)均不改 |
|
||
|
||
## 三、改动(仅 `src/views/notice/ApprovalList.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | `.title-content`(元信息)加 `v-if="approvalOpen"` |
|
||
| F2 | `.detail-scroll` 整块加 `v-if="approvalOpen"`,并删除其内部原只包审批信息的 `<template v-if="approvalOpen">`(flow/track 与正文现同属该折叠块) |
|
||
| F3 | 更新 `approvalOpen` 注释:语义由「审批信息展开」扩为「标题以下整块详情展开」 |
|
||
|
||
不改:`.notice-title`(标题行)、`.approval-bar`(常驻提示条 + 开关)、`hasApprovalInfo` 门控、开关文案、接口与其它页面。
|
||
|
||
**收起态视觉**:仅标题行 + 提示条(含「查看审批」把手);展开态:标题 + 元信息 + 提示条 + 审批流程/轨迹 + 正文附件。
|
||
|
||
## 四、回归清单(v25)
|
||
|
||
- [ ] 点提示条右侧「查看审批/收起审批」,收起后元信息 + 正文 + 审批一起隐藏、只剩标题行与提示条;再点全部恢复
|
||
- [ ] 收起后开关仍在提示条上可见可点(不会把自己收进去)
|
||
- [ ] 默认进入详情为展开态(`approvalOpen=true`)
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 五、实施记录(v25 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 元信息折叠 | ✅ 2026-09-14 | `.title-content` 加 `v-if="approvalOpen"` |
|
||
| F2 滚动区折叠 | ✅ | `.detail-scroll` 加 `v-if="approvalOpen"`,移除内层 `<template>` 包裹 |
|
||
| F3 注释 | ✅ | `approvalOpen` 语义更新为整块详情 |
|
||
| 构建验证 | ✅ | `npm run build` EXIT=0(20.19s,仅 `_utils.scss` 既有 `*zoom` 告警);`ApprovalList-*.js` 正常产出 |
|
||
| 备份 | ✅ | `.backup/20260914-v25-approval-fold-whole-detail/views/notice/ApprovalList.vue`(工程非 git) |
|
||
|
||
验证:待人工在 `#/approval` 冒烟(收起只剩标题行+提示条、展开恢复整块、开关常驻可点)。
|
||
|
||
# v26(2026-09-14)· 已发送/全部公告:标题列不显示 + 超 8 字截断加略号
|
||
|
||
## 一、需求(用户截图)
|
||
|
||
`#/list`(已发送)与 `#/noticelist`(全部公告)共用 `NoticeList.vue`。用户截图反馈两点:①标题列不显示内容(只见「…」);②要求标题超过 8 个字时截断并在末尾显示略号。
|
||
|
||
## 二、根因盘点
|
||
|
||
| 编号 | 证据 / 结论 |
|
||
| --- | --- |
|
||
| S1 | 后端 `NoticeVo.java` 有 `title` 字段,DB `notice.title` 均有值(如「这是一条公告」)→ 数据侧无问题 |
|
||
| S2 | 端口探测:8101=后端 IDEA Debug;4173=`vite preview`(PID 13552)在跑,**未起 dev(8077)** |
|
||
| S3 | `vite preview` 载的是**旧 `dist/`**,旧「标题」列 `{key:"title", tooltip:true}` 无 width,被定宽列挤压塌成窄列渲染成「…」,且不含截断逻辑 → 截图现象由此而来 |
|
||
| D1 | 源码侧改「标题」列为自定义 `render`:`t.length>8 ? slice(0,8)+"…" : t`,并加 `minWidth:200` 防塌、原生 `title` 属性悬浮看全文;改完必须 `npm run build` 重打 dist,preview 才会反映 |
|
||
|
||
## 三、改动(仅 `src/views/notice/NoticeList.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | 「标题」列由 `key+tooltip` 改为 `render`:按字符数截断(>8 取前 8 字 + `…`),`minWidth:200`,`attrs.title` 存全文供悬浮 |
|
||
|
||
两页共用同一组件,一处改动同时覆盖「已发送」与「全部公告」。
|
||
|
||
## 四、回归清单(v26)
|
||
|
||
- [ ] `#/noticelist`、`#/list` 标题列正常显示文字(不再是空「…」)
|
||
- [ ] 标题 >8 字显示前 8 字 + `…`;≤8 字原样
|
||
- [ ] 鼠标悬停标题单元格可看到完整标题
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 五、实施记录(v26 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 标题 render | ✅ 2026-09-14 | `slice(0,8)+"…"`,minWidth 200,native title 全文 |
|
||
| 构建 | ✅ | `npm run build` EXIT=0(41.51s);`NoticeList-MS8GxHwa.js` 内含 `a.length>8?\`${a.slice(0,8)}…\`:a` 与 `attrs:{title:a}` |
|
||
| 备份 | ✅ | `.backup/20260914-v26-notice-title-ellipsis/views/notice/NoticeList.vue`(工程非 git) |
|
||
|
||
验证:用户当前跑 `vite preview`(4173),须**硬刷新**(Ctrl+Shift+R)加载新 dist;反复调样式建议改 `npm run dev`(8077) 免每次 build。
|
||
|
||
---
|
||
|
||
# v27(2026-09-14)· 审批详情伸缩框含提示条整块收起,折叠开关上移标题行
|
||
|
||
> 注:编号顺延——v26 已被并行会话(已发送/全部公告标题列)占用,本节记 v27。承接并**修订 v25**:v25 让提示条常驻作把手,本次按用户新截图(红框把黄色提示条也框进折叠区)改为提示条一并收起。
|
||
|
||
## 一、需求(用户截图圈红)
|
||
|
||
`#/approval` 右列,红框 = 标题以下**整块**(元信息 + 黄色提示条 + 审批流程/轨迹 + 正文附件),要求伸缩框覆盖到含提示条的全部;即收起后只剩标题行。
|
||
|
||
## 二、冲突与决策
|
||
|
||
| 编号 | 证据 / 决策 |
|
||
| --- | --- |
|
||
| S1 | v25 折叠开关 `.alert-toggle` 长在提示条 `.approval-bar` 内,且提示条常驻作「把手」 |
|
||
| S2 | 若把提示条纳入折叠区而开关仍在其内,则收起后开关随之消失、无法再展开(v25 遗留的取舍点) |
|
||
| D1 | 定:**折叠开关上移到标题行**(`.title-header` 内 `.detail-toggle`,常驻可见),提示条并入折叠区 `v-if="showAlert && approvalOpen"` |
|
||
| D2 | 开关文案由「查看审批/收起审批」改为「展开/收起」——现折叠的是整块详情而非仅审批信息 |
|
||
| D3 | `hasApprovalInfo`(原门控旧开关显隐)失去唯一消费者,删除;标题行开关恒可见 |
|
||
|
||
## 三、改动(仅 `src/views/notice/ApprovalList.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | 模板:`.title-header` 内新增 `.title-left`(`.tt` + `.detail-toggle`),开关 `@click="approvalOpen=!approvalOpen"`,chevron 随开合旋转 |
|
||
| F2 | 模板:`.approval-bar` 的 `v-if` 由 `showAlert` 改为 `showAlert && approvalOpen`,并删除其内部 `.alert-toggle`(只留状态文案) |
|
||
| F3 | 脚本:删 `hasApprovalInfo`;更新 `approvalOpen` 注释为「控制标题以下整块」 |
|
||
| F4 | 样式:`.title-header` 加 `.title-left`/`.detail-toggle`(含 chevron 边框绘制);删 `.alert-row` 内已无引用的 `.alert-toggle` 块 |
|
||
|
||
折叠区(`approvalOpen`)现覆盖:`.title-content`(元信息)+ `.approval-bar`(提示条)+ `.detail-scroll`(审批 + 正文附件)。不改:标题行、ops(上一篇/下一篇/通过/驳回/打印)、接口与其它页面。
|
||
|
||
**收起态**:仅标题行(标题 + 展开 + ops);**展开态**:标题 + 元信息 + 提示条 + 审批 + 正文附件。
|
||
|
||
## 四、回归清单(v27)
|
||
|
||
- [ ] 点标题行「收起」→ 元信息、黄色提示条、审批、正文一并隐藏,只剩标题行;点「展开」全部恢复
|
||
- [ ] 收起后「展开」开关仍在标题行可见可点(不再随提示条消失)
|
||
- [ ] 默认进入详情为展开态
|
||
- [ ] 通过/驳回/打印等 ops 与提示条文案配色不受影响
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 五、实施记录(v27 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1 标题行开关 | ✅ 2026-09-14 | `.title-left` 包 `.tt`+`.detail-toggle`,文案「展开/收起」 |
|
||
| F2 提示条并入折叠 | ✅ | `v-if="showAlert && approvalOpen"`,移除内嵌开关 |
|
||
| F3 脚本清理 | ✅ | 删 `hasApprovalInfo`(grep 确认无其它引用) |
|
||
| F4 样式 | ✅ | 加 `.title-left`/`.detail-toggle`+chevron,删 `.alert-toggle` |
|
||
| 构建验证 | ✅ | `npm run build` EXIT=0(18.25s,仅 `_utils.scss` 既有 `*zoom` 告警);`ApprovalList-*.js`/`.css` 正常产出 |
|
||
| 备份 | ✅ | 承接 v25 备份链(`.backup/20260914-v25-approval-fold-whole-detail/` 为改前态,工程非 git) |
|
||
|
||
验证:待人工在 `#/approval` 冒烟(收起只剩标题行、展开恢复整块含提示条、开关常驻标题行可点)。
|
||
|
||
# v28(2026-09-14)· 公告详情「上一篇/下一篇」点击后不跳转(goNav 竞态)
|
||
|
||
## 一、需求(用户反馈)
|
||
|
||
`#/noticelist/detail?serid=…&type=0`(及 `/list/detail`)点「下一篇」报「接口错误」,用户贴出请求 `Detail?serid=1&type=0`。
|
||
|
||
## 二、排查
|
||
|
||
| 编号 | 证据 / 结论 |
|
||
| --- | --- |
|
||
| S1 | 后端 `NoticeVo`/`checkDetailAccess`:serid=1 为 lijing 本人待审批单,本人可访问;`notice_approval_step` 无行、数据干净 → `Detail?serid=1&type=0` 正常应 200 |
|
||
| S2 | 端口:4173=`vite preview`(载 dist)、8101=后端。curl 打 4173 `/api` 返回 302→SSO(无会话),证明 preview **已代理** `/api`(继承 server.proxy),代理链路正常 |
|
||
| S3 | 无法用无会话 curl 复现「接口错误」(后端 302 由 alova 层转跳 SSO,不弹错);需用户登录态才能抓具体响应 |
|
||
| D1 | 定位到明确代码缺陷:`goNav` 在 `await` 前同步调用 `load()`,而 `router.replace` 是异步提交 → `load()` 读到**旧 serid**,点「下一篇」实际重拉当前公告、序列不前进(表现为「点了没反应/异常」) |
|
||
|
||
## 三、改动(仅 `src/views/notice/NoticeDetails.vue`)
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | `import` 补 `watch` |
|
||
| F2 | 新增 `watch(() => route.query.serid, id => Number(id) && load())`:加载统一由路由变化驱动(兼顾上一篇/下一篇 + 浏览器前进后退) |
|
||
| F3 | `goNav` 去掉同步 `load()`,只 `router.replace` 改地址,避免读到未提交的旧 serid |
|
||
|
||
## 四、回归清单(v28)
|
||
|
||
- [ ] 详情页点「下一篇/上一篇」能真正切换到相邻公告、URL 与内容一致
|
||
- [ ] 浏览器前进/后退改变 serid 时详情随之刷新
|
||
- [ ] 首屏进入仍正常加载(onMounted 的 load 与 watch 不重复触发)
|
||
- [ ] `npm run build` 通过
|
||
|
||
## 五、实施记录(v28 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1/F2/F3 | ✅ 2026-09-14 | 竞态修复:watch 驱动 load,goNav 只导航 |
|
||
| 构建 | ✅ | `npm run build` EXIT=0(18.26s) |
|
||
| 备份 | ✅ | `.backup/20260914-v27-detail-nav-race/views/notice/NoticeDetails.vue`(工程非 git;目录名沿用早期编号,内容即本次改前态) |
|
||
|
||
待确认:若硬刷新后「下一篇」仍报接口错误,请提供该失败请求的 HTTP 状态码 + 响应体(Network 面板标红那条),因后端需登录态、无法离线复现。
|
||
|
||
|
||
# v29(2026-09-15)· 删除 `notice/ScopePreview` 接口请求
|
||
|
||
## 一、需求(用户)
|
||
|
||
删除前端对 `GET /api/notice/ScopePreview`(推送范围「已选学员数」预估)的接口请求。
|
||
|
||
## 二、影响面
|
||
|
||
| 编号 | 文件 | 说明 |
|
||
| --- | --- | --- |
|
||
| S1 | `src/api/module/notice.ts` | `noticeApi.scopePreview()` 发出该请求(唯一封装点) |
|
||
| S2 | `src/components/ClassScopeInput.vue` | 在用组件(NewNotice 引用):created/watch 调 `runPreview` → 拉 ScopePreview,模板 `.scope-preview` 显示「已选学员 N 人」 |
|
||
| S3 | `src/components/PushScopePanel.vue` | v5 起已被 ClassScopeInput 取代、全工程未被 import 的死代码;仍 `import noticeApi` 并被 vue-tsc 纳入类型检查,须一并清理否则编译报错 |
|
||
| S4 | `src/assets/mock/noticeapi.ts` | dev:mock 下的 `/api/notice/ScopePreview` 桩 |
|
||
| S5 | `src/types/notice.ts` | `ScopePreviewVo` 类型仅服务于该接口 |
|
||
|
||
## 三、改动
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | `notice.ts`:删 `scopePreview()` 方法、「推送范围(改造方案 3.1)」分节、`ScopePreviewVo` 类型 import;头部变更要点注释同步 |
|
||
| F2 | `ClassScopeInput.vue`:删模板 `.scope-preview` 行、`preview`/`previewTimer` data、`runPreview()` 方法、created/watch 中的 `runPreview()` 调用、`noticeApi`/`ScopePreviewVo` import(保留 `baseApi`)、`.scope-preview` 样式 |
|
||
| F3 | `PushScopePanel.vue`:同步删预估逻辑与 `ScopePreviewVo`/`noticeApi` import(保持死代码可编译) |
|
||
| F4 | `mock/noticeapi.ts`:删 `/api/notice/ScopePreview` 桩条目 |
|
||
| F5 | `types/notice.ts`:删 `ScopePreviewVo` 接口 |
|
||
|
||
## 四、回归清单(v29)
|
||
|
||
- [ ] 全局 `grep ScopePreview` 仅剩 `.backup/`、`dist/`、本方案文档
|
||
- [ ] 写公告页不再发起 `/api/notice/ScopePreview` 请求;「已选学员 N 人」预估行随之移除
|
||
- [ ] `npm run build` 通过(vue-tsc 无「ScopePreviewVo 未使用 / noticeApi 未定义」报错)
|
||
|
||
## 五、实施记录(v29 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1–F5 | ✅ 2026-09-15 | 见上 |
|
||
| 备份 | ✅ | `.backup/20260915-v29-drop-scopepreview-request/`(工程非 git) |
|
||
| 构建 | ✅ | `npm run build` EXIT=0(含 vue-tsc,17.24s);全局 grep `ScopePreview` 仅剩 `.backup/`、`dist/`、本文档 |
|
||
|
||
---
|
||
|
||
# v30(2026-09-15)· 发文部门/联系组等:新增与修改后名称内容重复且带逗号(`校园部,校园部`)
|
||
|
||
## 一、现象(用户)
|
||
|
||
「应用设置 → 发文部门」新增部门、修改(改名/启停)后,部门名称变成重复且以逗号分隔的串,如 `校园部,校园部`;且随着操作次数增多继续翻倍(实测库中 `sys_dict.id=2` = `校园部,校园部,校园部,校园部`)。用户判断:新增和修改都会、内容重复、且有逗号。
|
||
|
||
## 二、根因(定位链,证据表 S)
|
||
|
||
| 编号 | 证据 | 结论 |
|
||
| --- | --- | --- |
|
||
| S1 | `D:\...\mysql.exe ... "SELECT ... FROM sys_dict WHERE dict_type='dept'"` → `id=2 dict_key=dept2 dict_value=校园部,校园部,校园部,校园部` | 重复+逗号发生在**落库值本身**,非前端显示层拼接;`dict_key=dept2` 由 Java `addDeptDic` 生成(`dept`+sortOrder),证明确经后端写入 |
|
||
| S2 | 后端 `DictService.addDeptDic/updateDeptDic` 均 `dict.setDictValue(name)`,无追加;`git log`(b19c774/6d7d1c9/5fa8462)全程无 `,` 拼接;`DictController` 用 `@RequestParam("name") String name` | 后端服务层**不产生**逗号;脏值只能是传入的 `name` 参数本身已含逗号 |
|
||
| S3 | 进程 27616 启动时间 = `2026-09-15 10:40:34`,等于 `target/classes/DictService.class` mtime,且晚于最后改 DictService 的 commit(09:08) | 8101 跑的就是当前干净源码,排除「旧 class 未热重启」假说 |
|
||
| S4 | 前端 `src/api/module/notice.ts` 的 `postForm()`:把同一组 `pairs` **既拼进 URL query(`fullUrl=url?form`)又放进 x-www-form-urlencoded body**(注释称 mock 只解析 query 故加 query,「不影响线上」) | **真根因**:一个逻辑参数被提交两份(query 一份 + body 一份) |
|
||
| S5 | Servlet/Spring 语义:POST `application/x-www-form-urlencoded` 时 `request.getParameter` 会**合并 query string 与 body 的同名参数**;`@RequestParam String` 对多值以 `,` 连接 | query+body 各一个 `name` → Spring 合并成 `校园部,校园部`(2 份)。之后改名/启停把已含逗号的当前值原样回发、再 query+body 各一份 → 翻倍成 4。mock 只读 query(1 份)故本地 `dev:mock` 不复现 |
|
||
|
||
**波及面**:所有走 `postForm` 的 `@RequestParam` 接口同病——发文部门 `Add/Update/Delete/Sort`、联系组 `AddGroup/EditGroup/DelGroup/SetContactsTea`、收文人 `ReceiveUsers` 等。本次以部门为切入点,修复落在 `postForm` 一处即全覆盖。
|
||
|
||
## 三、修复计划
|
||
|
||
| 项 | 改动 |
|
||
| --- | --- |
|
||
| F1 | `src/api/module/notice.ts` `postForm()`:仅当 **mock** 模式才把参数拼进 URL query;真后端只发 form body、URL 不带 query。使 Spring 只从 body 取到单个参数,消除合并重复 |
|
||
| F2 | 以 `import.meta.env.VITE_IS_MOCK === "true"` 判定 mock(与 `.env.mock` 一致),保持 `dev:mock` 本地取参能力不回退 |
|
||
| F3 | 数据清理:把 `sys_dict` 中被污染的 `dept` 行 `dict_value` 逗号去重还原(`校园部,校园部,校园部,校园部` → `校园部`);改前先 `SELECT` 备份原值,执行前与用户确认 |
|
||
|
||
## 四、回归清单(v30)
|
||
|
||
- [ ] 真后端(4173 preview):新增部门「校园部」→ 列表与 `sys_dict.dict_value` 均为干净 `校园部`(无逗号)— *需登录态,待用户浏览器点验*
|
||
- [ ] 改名、启用/停用后仍为单值,不再逐次翻倍 — *待用户浏览器点验*
|
||
- [ ] 联系组新建/改名同样不再出现逗号重复 — *待用户浏览器点验*
|
||
- [x] `dev:mock` 下增删改仍正常取参(query 生效),无回归
|
||
- [x] `npm run build` EXIT=0
|
||
|
||
## 五、实施记录(v30 回填)
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| F1–F2 | ✅ 2026-09-15 | `notice.ts` `postForm()`:`fullUrl` 仅在 `conf.isMock` 时拼 query,真后端只发 body;注释同步 |
|
||
| mock 回归 | ✅ 2026-09-15 | `dev:mock`(8078) node 打 `/api/dict/Add?name=…` + `ManageList`:参数经 query 正常解析、落库为单值无逗号 → F2 未回退 |
|
||
| 备份 | ✅ | `.backup/20260915-v30-postform-dup-param/notice.ts.orig`(工程非 git) |
|
||
| 构建 | ✅ 2026-09-15 | `npm run build` EXIT=0(21.04s);4173 preview 已服务新 dist,浏览器需 Ctrl+Shift+R 硬刷新 |
|
||
| F3 数据清理 | ✅ 2026-09-15 | `UPDATE sys_dict SET dict_value=SUBSTRING_INDEX(dict_value,',',1) WHERE dict_type='dept' AND id=2 AND dict_value LIKE '%,%'`(affected=1);校验 `id=2 has_comma=0`、hex `E6A0A1E59BADE983A8`=「校园部」;`id=1 发文部门` 本就干净未动。公告存 `dept_key`,改显示名不影响已发公告 |
|
||
|
||
## 六、补充(同日)· 同源 bug 的另两处表现
|
||
|
||
用户复测反馈:自定义联系组「添加/编辑」组名同样变 `111,111`;「添加组员」调 `POST /api/contacts/SetContactsTea?gid=1&ids=10007` 报「接口异常」。二者与 v30 **同一根因**(`postForm` query+body 双发,Spring `@RequestParam` 合并),只是受害方式不同:
|
||
|
||
| 编号 | 表现 | 机理 |
|
||
| --- | --- | --- |
|
||
| S6 | 联系组名 `111,111`(`contacts_group.id=1`,hex `3131312C313131`) | `AddGroup` 的 `name` 被 query+body 合并成 `111,111`;字符串能落库,仅显示重复 |
|
||
| S7 | `SetContactsTea` 接口异常 | `gid` 合并成 `"1,1"` → 控制器 `Integer.parseInt("1,1")` 抛 `NumberFormatException` → 500。**数字参数遇逗号直接崩**,比字符串更致命 |
|
||
|
||
**处置**:F1–F2 的 `postForm` 改动已一并覆盖 `AddGroup/EditGroup/SetContactsTea`(真后端只发 body,`gid` 恢复单值 `1`,`parseInt` 正常)。用户截图 URL 仍带 `?gid=1&ids=10007` = 加载的是修复前旧包,**须 Ctrl+Shift+R 硬刷新 4173(或重起 dev)载入新 dist 方生效**。数据侧清理:
|
||
|
||
| 项 | 状态 | 备注 |
|
||
| --- | --- | --- |
|
||
| 联系组脏名 | ✅ 2026-09-15 | `UPDATE contacts_group SET group_name=SUBSTRING_INDEX(group_name,',',1) WHERE id=1 AND group_name LIKE '%,%'`(affected=1)→ 还原 `111` |
|
||
| SetContactsTea 后端 | ✅ 复核 | 控制器/仓储(`deleteByGroupIdBulk` 带 `@Modifying(clearAutomatically=true)`)在拿到干净 `gid` 后无其它缺陷;异常纯由入参 `"1,1"` 触发 |
|