* docs(xispeak): 归档前端页面 QA 审查报告(用户视角) * docs(fixcontact): 归档前端页面 QA 审查报告(用户视角) * docs(inspectTask): 归档前端页面 QA 审查报告(工程师+用户视角) * docs(bqtakepulse): 归档前端页面 QA 审查报告(工程师+用户视角) * docs(bgpartymatter): 归档前端页面 QA 审查报告(工程师+用户视角)
87 lines
15 KiB
Markdown
87 lines
15 KiB
Markdown
# 前端页面工程师视角审查报告 — 党委会/所务会事项(bgpartymatter)
|
||
|
||
## 审查信息
|
||
|
||
- 页面/路由:`/bg/bgpartymatter/BgPartymatterList`(党委会台账,菜单「党委会决策事项」);所务会复用 `BgOfficematterList`(`meetingType=office`,菜单「所务会决策事项」)
|
||
- 审查日期:2026-08-11
|
||
- 审查视角:前端资深工程师(浏览器实测 + 代码审查结合,覆盖 API 契约、权限、状态一致性、表单校验、性能/异常等工程师专项维度)
|
||
- 相关文件:
|
||
- `src/views/bg/bgpartymatter/BgPartymatterList.vue`
|
||
- `src/views/bg/bgpartymatter/components/BgPartymatterForm.vue`
|
||
- `src/views/bg/bgpartymatter/components/BgPartymatterModal.vue`
|
||
- `src/views/bg/bgpartymatter/BgPartymatter.data.ts`、`BgPartymatter.api.ts`
|
||
- 后端:`backend/supervison-java/.../bgpartymatter/controller/BgPartymatterController.java`、`jeecg-boot-base-core/.../base/controller/JeecgController.java`
|
||
- 测试账号:`admin`(仅 1 个账号,多角色/多部门/多密级对比场景未覆盖,见「未覆盖项」)
|
||
- 证据目录:`qa-tmp/bgpartymatter/scripts/*.json`(eng_04~eng_09、02_modal、12_adddel_final、05_flow 等)
|
||
- 配套文档:`qa-report/bgpartymatter/QA_REPORT_UX.md`(用户视角报告,可并列对比)
|
||
- 说明:全程无头浏览器实测(Playwright,dev 端口 3100,登录走 mock、业务接口走真实后端 8080),登录账号 admin/123456。
|
||
|
||
## 总体结论
|
||
|
||
- **P0 × 1 | P1 × 2 | P2 × 7**
|
||
- 一句话总结:党委会/所务会台账的搜索、详情、流程、新增、删除都能走通且状态一致,但**导出功能 100% 不可用(后端稳定 500,前端静默无任何反馈)**——这是唯一的交付阻塞项,已定位到 AutoPoi 对不可变集合执行 `Iterator.remove()` 的根因;顶部统计标签不随筛选/增删改刷新、详情弹窗字段"看似可编辑实被透明遮罩拦截"是两大 P1 体验/一致性问题。
|
||
|
||
## P0 - 必须修复
|
||
|
||
| # | 维度 | 问题描述 | 证据 | 位置 |
|
||
|---|------|---------|------|------|
|
||
| 1 | 后端调用/功能 | **导出(导出 Excel)完全不可用**:点击工具栏「导出」→ 无下载、无 toast、无任何反馈;后端 `exportXls` 稳定返回 500。党委会、所务会两页同一 exportConfig 与同一后端接口,均复现。**根因(已定位)**:后端 `JeecgController.exportXls` 将 `service.list(queryWrapper)` 的查询结果直接作为数据集合传给 AutoPoi 导出;AutoPoi `ExcelExportServer.createSheet()` 第 198-202 行对数据集合迭代器执行 `its.next(); its.remove();` 分块清理,当数据集合为**不可变集合**时 `Iterator.remove()` 抛 `UnsupportedOperationException`;第 213 行 catch 用 `e.getCause()` 包裹(该异常无 cause → null),把真实原因吞掉,统一抛成 `ExcelExportException: Excel导出错误`,响应 trace 无 "Caused by"(与实测一致)。**实测链路**:0 行导出=200,任意 ≥1 行导出=500 且与字段内容无关(最小行、全字段行均失败);用与 `BgPartymatter` 全部 `@Excel` 字段一致的副本类独立复现(`C:\Users\sz04\AppData\Local\Temp\ap_repro`):可变 `ArrayList` → 成功(8704 字节),不可变集合 → 完全相同的 500。修复方向:① 前端 `exportConfig` 切换到后端已有的 EasyExcel 导出端点 `exportXlsByEasyExcel`(`BgPartymatterController.java:490-495`,同模块 `exportXlsHeaders` 模板下载已实测可用,见 P0 修复建议);② 或后端导出前把数据集合复制为可变 `List`;③ 同时前端应把导出失败透出错误提示而非静默。 | 实测:`exportXls` 响应 HTTP 500 body `ExcelExportException: Excel导出错误`(`qa-tmp/bgpartymatter/scripts/eng_04_trace.json`、eng_09 再复现 500 且 `export_toasts=[]`);所务会页同样 500(`eng_05_empty_office.json`);独立复现成功/失败对比(`ap_repro/MainRepro2.java` vs `MainReplica.java`);AutoPoi 源码 `ExcelExportServer.java:198-213` | `BgPartymatterList.vue:303-307`(exportConfig)、后端 `BgPartymatterController.java:281-287`、`JeecgController.java:62-88`(`:74` `service.list`、`:86` DATA_LIST)、AutoPoi `ExcelExportServer.java:198-213` |
|
||
|
||
## P1 - 强烈建议
|
||
|
||
| # | 维度 | 问题描述 | 证据 | 位置 |
|
||
|---|------|---------|------|------|
|
||
| 1 | 状态一致性 | **顶部统计标签(未开始/督办中/已完成)不随筛选、搜索、增删改刷新**:搜索无结果/有结果、新增一条、删除一条后,列表数据都在变(总条数 3→4→3 正确变化),但三个统计标签始终不变(全程 `未开始 3 / 督办中 0 / 已完成 0`),与列表对不上,用户无法判断哪个是当前数据。**根因**:`fetchStatusStats` 仅在 `onMounted` 调用一次;`searchQuery`、`searchReset`、`handleSuccess`、新增/删除成功回调均未再触发统计刷新。 | 实测:`eng_09_engineer_final.json` —— 统计初始/新增后重查后/删除后重查后均为 `['未开始 3',...]`,而列表总条数 3→4→3(`events.after_api_add_reload.has_new_row=true`、`after_delete.residue=0`);`eng_07_listdump.json` —— 搜索前/搜索无结果后统计不变 | `BgPartymatterList.vue:100-104`(标签渲染)、`:446-465`(fetchStatusStats)、`:463`(仅 onMounted 调用)、`:440-444`(searchQuery)、`:509-513`(handleSuccess)、`:789-796`(searchReset) |
|
||
| 2 | 用户体验/交互 | **详情弹窗字段"看似可编辑、实际点不动"**:详情模式下表单上方有一层透明遮罩 `.jeecg-form-detail-effect`(实测 1100×580 透明 div,`pointer-events:auto`)拦截全部点击,但控件本身未灰化(下拉控件 opacity=1、cursor=pointer、肉眼无只读提示);点「密级」下拉实测不弹出;「确认」按钮被 `jee-hidden` 隐藏。用户反复点击无反应,误以为系统坏了/自己没点中。**根因**:`handleDetail` 设 `disableSubmit=true` → JFormContainer `:disabled` 渲染遮罩 + Modal 隐藏确认按钮,但只读态缺视觉表达(控件未置灰、无"只读"标识)。 | 实测:`eng_09_engineer_final.json` —— `detail_overlay_count=1`、`detail_overlay_style={pointerEvents:'auto', bg:'rgba(0,0,0,0)', 1100×580}`、`detail_select_dd_open=false`(点密级下拉不弹)、`detail_confirm_visible=false`、`detail_enabled_inputs=6`;`02_modal.json` —— 详情模式 11 只读 / 6 可编辑输入并存;关闭后页面可交互、无残留遮罩 | `BgPartymatterList.vue:486-490`(handleDetail)、`BgPartymatterModal.vue:2-6`(`jee-hidden`)、`BgPartymatterForm.vue:3`(JFormContainer `:disabled`)、`:256`(disabled=formDisabled) |
|
||
|
||
## P2 - 建议
|
||
|
||
| # | 维度 | 问题描述 | 证据 | 位置 |
|
||
|---|------|---------|------|------|
|
||
| 1 | 反馈 | **新增成功无 toast**:新增提交成功、列表出现新行,但没有任何"保存成功"提示;删除却有"删除成功!",反馈不一致。**根因**:`submitForm` 用 `createMessage.success(res.message)`,后端返回的 `res.message` 为空串,`success()` 收到空串不渲染。 | 实测:`eng_09_engineer_final.json` 的 API add `{"success":true,"message":"添加成功!"}`(后端有 message 但前端未取到/为空);`12_adddel_final.json` —— add 无 toast、delete 有「删除成功!」 | `BgPartymatterForm.vue:403-411` |
|
||
| 2 | 表单校验文案 | **校验文案与字段名不一致**:主办部门字段漏选时,提示「请选择责任部门」,但字段 label 是「主办部门」,用户对不上号。 | 实测:`02_modal.json` form_item_errors(含「请选择责任部门」) | `BgPartymatterForm.vue:232-241`(validator message 写死"责任部门") |
|
||
| 3 | 表单校验体验 | **序号字段只读却报「请输入序号」**:序号输入框是只读的(自动生成),但必填校验文案是「请输入序号」,用户无法手动消除该错误(实际需回去填"议题序号"触发自动生成)。 | 实测:`02_modal.json`、`07_formmap.json` | `BgPartymatterForm.vue:23-26`(只读)+ `:228`(校验"请输入序号") |
|
||
| 4 | 布局/性能 | **表格过宽需横向拖动**:21 列总宽约 2808px,可视区约 1200px,中部字段(事项目录编码/抄告单编号/密级等)需反复横拖才能看到,看漏字段风险高。 | 实测:`.ant-table-body` scrollWidth=2808、clientWidth=1200(`eng_02_listdata.json` / 测量输出) | `BgPartymatter.data.ts:7-153`(21 列) |
|
||
| 5 | 可发现性 | **「完成状态」筛选默认藏在「展开」折叠里**:初始工具栏不显示,需点「展开」才出现,首次用户找不到状态筛选。 | 实测:toggle 文案"展开→收起"(`04_expand_filter.png`) | `BgPartymatterList.vue:39-52`(toggleSearchStatus 默认 false) |
|
||
| 6 | 空态引导 | **搜索无结果空态只有「暂无数据」**,无"换个条件/清除筛选"引导,也无一键清空入口。 | 实测:`eng_09` race_final 空表(`03b_search_empty.png`) | `BgPartymatterList.vue`(BasicTable 默认空态) |
|
||
| 7 | 控制台异常 | **列表页交互期间控制台出现 `Invalid prop: type check failed for prop "onChange". Expected Function, got Array`(`<ASelect onChange= [, ]>`),以及点击「导出」时 500 触发 `Unhandled error during execution of component event handler`**。前者疑似筛选/搜索组件把多个 handler 合并成数组传给 ASelect(组件 prop 契约不严谨);后者证明导出失败在前端是未处理异常、被静默吞掉。 | 实测:`eng_09_engineer_final.json` console(19 条:5 个 app-shell `Failed to resolve component` + 7 条 ASelect onChange + 1 条 500 resource + 1 条 unhandled export handler) | 控制台证据(`eng_09_engineer_final.json`);export 相关 `BgPartymatterList.vue:303-307` |
|
||
|
||
## 通过项(做得好的地方)
|
||
|
||
- **列表刷新正常**:新增后手动点「查询」,列表立即出现新行(总条数 3→4);删除后「查询」,行消失(4→3)。列表数据链路(list API + 查询/重置)一致、无残留。
|
||
- **竞态安全**:快速连续输入 3 个不同关键词(`QA_TEST_ENG_RACE_01 → 督办 → QA_ZZZ_NO_MATCH_RACE`,无间隔),最终表格与最后一个关键词一致(0 行)、无旧响应串台/残留渲染,未出现 out-of-order 覆盖。搜索即输即查无竞态问题。
|
||
- **详情弹窗回显完整**:详情标题「详情」,19 项字段回显;关闭后页面完全可交互,无残留遮罩/状态污染。
|
||
- **模板下载可用**:「下载模板」成功下载 `党委会-导入模板.xls`(EasyExcel `exportXlsHeaders` 路径,与走 AutoPoi 的「导出」形成对照——问题只在 AutoPoi 导出,不在模板)。
|
||
- **删除链路完整**:行内更多 → 删除 → 二次确认 → 「删除成功!」toast → 列表刷新。新增/删除后列表均刷新。
|
||
- **数据分流正确**:党委会(party)/所务会(office)数据严格隔离,不串台。
|
||
- **流程弹窗可用**:「再次督办」发起流程弹窗标题/部门预填正确;「查询督办流程」流程列表可打开(引用 `QA_REPORT_UX.md` 任务 6 实测证据)。
|
||
- **校验覆盖**:必填校验能定位到字段;序号自动生成(会议时间+议题序号联动)有效。
|
||
- **导出 0 行场景正常**:无数据时导出返回 200(进一步佐证根因只在"有数据的迭代"路径)。
|
||
|
||
## 未覆盖项 / 待实测(不列入 P0/P1/P2 判定)
|
||
|
||
| # | 项目 | 原因 / 现有线索 |
|
||
|---|------|----------------|
|
||
| 1 | 多角色/多部门/多密级权限对比(按钮级 `v-auth` 隐藏、数据权限过滤) | 仅 admin 一个账号,无法对比;`exportXls` 接口有 `@RequiresPermissions`,前端按钮有 `v-auth`,但未用第二账号实测不同角色下的可见性/越权 |
|
||
| 2 | 「模板校验」「模板导入」真实文件流程(选 Excel → 校验通过/报错提示) | headless 无法模拟系统文件选择框;对隐藏 input 塞 .txt 无任何 toast,结果不明确(`BgPartymatterList.vue:746-784`) |
|
||
| 3 | 后端 `service.list()` 返回**不可变集合**的具体包装层(是哪个拦截器/配置把 MP 的 `selectList` 结果变成不可变) | 前端侧证据已足够(0 行 200 / ≥1 行 500 / 独立复现),但精确到后端哪个类包装需后端日志/断点确认;已确认 `BgPartymatterServiceImpl` 为纯 MP `ServiceImpl` 无 `list()` 覆盖、`JeecgController.exportXls` 无包裹、运行时 AutoPoi 为 1.4.11(trace 行号与源码逐行吻合) |
|
||
| 4 | 「完成状态」筛选选中后的实际过滤行为(BPM 状态 dict 映射) | 未单独实测(筛选在「展开」里,见 P2-5);代码走 `JDictSelectTag @change="searchQuery"`,推断正常 |
|
||
|
||
## 测试过程记录
|
||
|
||
- **环境**:前端 dev `http://127.0.0.1:3100`(运行中,未动);后端 `:8080/jeecg-boot`(真实);登录 `admin/123456`(mock 登录 + 真实业务接口)。脚本见 `qa-tmp/bgpartymatter/scripts/`。
|
||
- **核心操作与产物**:
|
||
1. `eng_04_trace.py` / `eng_05_empty_office.py`:直接导出抓 500 全量 body(party/office)、0 行导出对比 → `eng_04_trace.json`、`eng_05_empty_office.json`(0 行 200 / ≥1 行 500)。
|
||
2. `eng_07_listdump.py`:搜索无结果前后统计标签对比 → `eng_07_listdump.json`(统计不变)。
|
||
3. `eng_08_token2.py`:从请求头提取 X-Access-Token(localStorage 无 token,token 在内存/请求头注入)。
|
||
4. `eng_09_engineer_final.py`:综合验证(登录 → 列表 → API 新增 → 查询刷新 → 快速搜索竞态 → 详情遮罩 → 导出 → 模板 → API 删除)→ `eng_09_engineer_final.json`。
|
||
5. 复用既有产物:`02_modal.json`(新增必填校验+详情字段态)、`12_adddel_final.json`(新增/删除 toast)、`05_flow.json`(流程弹窗+导出 500)、`07_formmap.json`(表单字段映射)。
|
||
- **关键网络请求**:
|
||
- `GET /jeecgboot/bgpartymatter/bgPartymatter/list?...` → 200(列表,正常)
|
||
- `POST /jeecgboot/bgpartymatter/bgPartymatter/add` → 200 `{"success":true}`(新增)
|
||
- `DELETE /jeecgboot/bgpartymatter/bgPartymatter/delete?id=...` → 200 `{"success":true}`(删除)
|
||
- `GET /jeecgboot/bgpartymatter/bgPartymatter/exportXls?...` → **500**(导出,P0)
|
||
- `GET /jeecgboot/bgpartymatter/bgPartymatter/exportXlsHeaders?...` → 200 下载 `党委会-导入模板.xls`(模板)
|
||
- **console 摘要**:5 条 app-shell `Failed to resolve component`(AppSearch/Notify/SettingDrawer 等,首页框架级,非本页引入);7 条 `ASelect onChange` 类型警告(P2-7);1 条 500 resource + 1 条 export 未处理异常(P0 佐证)。
|
||
- **测试数据卫生**:本次会话创建 `QA_TEST_ENG_RACE_01` ×4(含多次中断运行残留)与既有的 `QA_ENG_BISECT_*` ×5、`清空后测试`、`assacas`(office),已全部经删除 API 清理并复核:party/office 列表按 `QA_`/`QA_TEST`/`QA_ENG` 过滤均 **0 残留**。测试数据一律置于 `qa-tmp/bgpartymatter/`(gitignore),不入库。
|