Files
wsm 237a5810cf !145 docs(xispeak): 归档前端页面 QA 审查报告(用户视角)
* docs(xispeak): 归档前端页面 QA 审查报告(用户视角)
* docs(fixcontact): 归档前端页面 QA 审查报告(用户视角)
* docs(inspectTask): 归档前端页面 QA 审查报告(工程师+用户视角)
* docs(bqtakepulse): 归档前端页面 QA 审查报告(工程师+用户视角)
* docs(bgpartymatter): 归档前端页面 QA 审查报告(工程师+用户视角)
2026-08-11 07:27:58 +00:00

87 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 前端页面工程师视角审查报告 — 党委会/所务会事项(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`(用户视角报告,可并列对比)
- 说明:全程无头浏览器实测(Playwrightdev 端口 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` console19 条: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.11trace 行号与源码逐行吻合) |
| 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 全量 bodyparty/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-TokenlocalStorage 无 tokentoken 在内存/请求头注入)。
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),不入库。