From c660025bcc8dc214d864cd020d02dd456f1b2afc Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Thu, 23 Apr 2026 15:40:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=8B=9B=E6=8A=95=E6=A0=87?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=95=86=E4=BC=81=E4=B8=9A=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E4=B8=8E=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...terprise-detail-frontend-implementation.md | 479 ++++++++++++++++++ ...upplier-enterprise-detail-design-record.md | 1 + ...dding-supplier-enterprise-detail-design.md | 17 +- 3 files changed, 496 insertions(+), 1 deletion(-) create mode 100644 docs/plans/frontend/2026-04-23-bidding-supplier-enterprise-detail-frontend-implementation.md diff --git a/docs/plans/frontend/2026-04-23-bidding-supplier-enterprise-detail-frontend-implementation.md b/docs/plans/frontend/2026-04-23-bidding-supplier-enterprise-detail-frontend-implementation.md new file mode 100644 index 00000000..d47cf554 --- /dev/null +++ b/docs/plans/frontend/2026-04-23-bidding-supplier-enterprise-detail-frontend-implementation.md @@ -0,0 +1,479 @@ +# 招投标详情弹窗供应商企业信息查看 Frontend Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 在招投标信息维护详情弹窗的供应商明细中新增企业详情按钮,点击后复用实体库详情接口并以二级弹窗展示企业全部字段;无统一信用代码、查无数据或接口异常时统一提示“暂无企业信息”。 + +**Architecture:** 保持 `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` 作为唯一页面入口,在现有招投标详情弹窗内部补充“操作”列、企业详情请求状态和二级弹窗,不新增后端接口、独立组件或权限显隐逻辑。字段展示直接对齐 `ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue` 的详情顺序与格式化口径,按钮固定显示,不添加 `v-hasPermi`,所有失败分支统一收敛到“暂无企业信息”。 + +**Tech Stack:** Vue 2, Element UI, RuoYi `request`, `@/api/ccdiEnterpriseBaseInfo`, `@/api/ccdiEnum`, Node source-assert tests, Playwright real-page verification + +--- + +## File Structure + +- Modify: `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` + - 在现有招投标详情弹窗中新增供应商操作列、企业详情请求流程、企业详情弹窗、枚举格式化和关闭重置逻辑。 +- Create: `ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js` + - 用源码断言锁定按钮、状态字段、错误提示、字段展示结构和“不做权限显隐”的实现契约。 +- Create: `docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-implementation.md` + - 记录实施结果、验证命令、真实页面验证结论与测试进程清理。 + +## Reference Files + +- Reference only: `ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue` + - 复用企业详情字段顺序与格式化口径。 +- Reference only: `ruoyi-ui/src/api/ccdiEnterpriseBaseInfo.js` + - 复用 `getEnterpriseBaseInfo(socialCreditCode)`。 +- Reference only: `ruoyi-ui/src/api/ccdiEnum.js` + - 复用风险等级、企业来源、数据来源枚举选项加载。 + +### Task 1: 锁定供应商企业详情 UI 契约 + +**Files:** +- Create: `ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js` +- Modify: `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` + +- [ ] **Step 1: 编写失败中的源码断言测试** + +```js +const assert = require("assert"); +const fs = require("fs"); +const path = require("path"); + +const componentPath = path.resolve( + __dirname, + "../../src/views/ccdiPurchaseTransaction/index.vue" +); +const source = fs.readFileSync(componentPath, "utf8"); + +[ + 'from "@/api/ccdiEnterpriseBaseInfo"', + 'from "@/api/ccdiEnum"', + "enterpriseDetailOpen", + "enterpriseDetailLoading", + "enterpriseDetailData", + "handleSupplierEnterpriseDetail(row)", + "resetEnterpriseDetail()", + "暂无企业信息" +].forEach((token) => { + assert(source.includes(token), `招投标供应商企业详情缺少关键结构: ${token}`); +}); + +[ + "v-hasPermi=\\\"['ccdi:enterpriseBaseInfo:query']\\\"", + "v-hasPermi=\"['ccdi:enterpriseBaseInfo:query']\"", + "ccdi:enterpriseBaseInfo:query" +].forEach((token) => { + assert(!source.includes(token), `本次不应新增实体库权限显隐控制: ${token}`); +}); +``` + +- [ ] **Step 2: 运行测试,确认当前实现失败** + +Run: + +```bash +source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js +``` + +Expected: + +```text +FAIL with "招投标供应商企业详情缺少关键结构" +``` + +- [ ] **Step 3: 在页面脚本中补齐企业详情最小状态与方法骨架** + +在 `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` 先补齐以下最小结构: + +```js +import { getEnterpriseBaseInfo } from "@/api/ccdiEnterpriseBaseInfo"; +import { + getDataSourceOptions, + getEnterpriseRiskLevelOptions, + getEnterpriseSourceOptions +} from "@/api/ccdiEnum"; + +data() { + return { + enterpriseDetailOpen: false, + enterpriseDetailLoading: false, + enterpriseDetailData: {}, + enterpriseRiskLevelOptions: [], + enterpriseSourceOptions: [], + enterpriseDataSourceOptions: [] + }; +}, +methods: { + resetEnterpriseDetail() { + this.enterpriseDetailOpen = false; + this.enterpriseDetailLoading = false; + this.enterpriseDetailData = {}; + }, + async handleSupplierEnterpriseDetail(row) { + const socialCreditCode = row && row.supplierUscc ? row.supplierUscc.trim() : ""; + if (!socialCreditCode) { + this.$message.warning("暂无企业信息"); + return; + } + } +} +``` + +先让组件具备后续接线的状态容器和方法入口,不要在这一步引入额外组件拆分。 + +- [ ] **Step 4: 再跑一次测试,确认基础契约已转绿或仅剩模板断言失败** +- [ ] **Step 4: 再跑一次测试,确认脚本侧契约已转绿** + +Run: + +```bash +source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js +``` + +Expected: + +```text +purchase-transaction-enterprise-detail-ui test passed +``` + +- [ ] **Step 5: 提交第一阶段改动** + +```bash +git add ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js +git commit -m "补充招投标供应商企业详情状态骨架" +``` + +### Task 2: 接通供应商企业详情请求流与弹窗展示 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` +- Test: `ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js` +- Reference: `ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue` + +- [ ] **Step 1: 重新运行断言测试,保持实现前的失败信号** + +- [ ] **Step 1: 扩展源码断言测试,锁定模板结构、字段顺序和无权限显隐口径** + +在 `ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js` 追加以下断言: + +```js +[ + 'label="操作"', + ">详情", + "企业信息详情", + "统一社会信用代码", + "企业名称", + "企业类型", + "企业性质", + "行业分类", + "所属行业", + "法定代表人", + "风险等级", + "企业来源", + "数据来源", + "股东5" +].forEach((token) => { + assert(source.includes(token), `招投标供应商企业详情模板缺少关键结构: ${token}`); +}); +``` + +- [ ] **Step 2: 运行扩展后的断言测试,保持实现前的失败信号** + +Run: + +```bash +source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js +``` + +Expected: + +```text +FAIL with missing template token such as "企业信息详情" or field labels +``` + +- [ ] **Step 3: 在脚本中补齐企业详情请求与枚举格式化** + +按以下口径实现: + +```js +created() { + this.getList(); + this.restoreImportState(); + this.loadEnterpriseDetailOptions(); +}, +methods: { + loadEnterpriseDetailOptions() { + return Promise.all([ + getEnterpriseRiskLevelOptions(), + getEnterpriseSourceOptions(), + getDataSourceOptions() + ]).then(([riskRes, sourceRes, dataSourceRes]) => { + this.enterpriseRiskLevelOptions = riskRes.data || []; + this.enterpriseSourceOptions = sourceRes.data || []; + this.enterpriseDataSourceOptions = dataSourceRes.data || []; + }); + }, + getEnterpriseOptionLabel(options, value) { + if (!value) return "-"; + const matched = (options || []).find(item => item.value === value); + return matched ? matched.label : value; + }, + formatEnterpriseRiskLevel(value) { + return this.getEnterpriseOptionLabel(this.enterpriseRiskLevelOptions, value); + }, + formatEnterpriseSource(value) { + return this.getEnterpriseOptionLabel(this.enterpriseSourceOptions, value); + }, + formatEnterpriseDataSource(value) { + return this.getEnterpriseOptionLabel(this.enterpriseDataSourceOptions, value); + }, + formatEnterpriseStatus(value) { + return value || "-"; + }, + async handleSupplierEnterpriseDetail(row) { + const socialCreditCode = row && row.supplierUscc ? row.supplierUscc.trim() : ""; + if (!socialCreditCode) { + this.$message.warning("暂无企业信息"); + return; + } + this.enterpriseDetailLoading = true; + try { + const response = await getEnterpriseBaseInfo(socialCreditCode); + if (!response || !response.data) { + this.$message.warning("暂无企业信息"); + return; + } + this.enterpriseDetailData = response.data; + this.enterpriseDetailOpen = true; + } catch (error) { + this.$message.warning("暂无企业信息"); + } finally { + this.enterpriseDetailLoading = false; + } + } +} +``` + +固定遵守这三个口径: + +- 只按 `supplierUscc` 查询 +- 不做按钮权限显隐 +- 403 / 404 / 空数据 / 普通异常全部统一提示“暂无企业信息” + +- [ ] **Step 3: 在详情弹窗供应商明细表中新增操作列,并补齐企业详情二级弹窗** +- [ ] **Step 4: 在详情弹窗供应商明细表中新增操作列,并补齐企业详情二级弹窗** + +模板按以下结构接线: + +```vue + + + + + + + {{ enterpriseDetailData.socialCreditCode || "-" }} + {{ enterpriseDetailData.enterpriseName || "-" }} + {{ formatEnterpriseRiskLevel(enterpriseDetailData.riskLevel) }} + {{ formatEnterpriseSource(enterpriseDetailData.entSource) }} + {{ formatEnterpriseDataSource(enterpriseDetailData.dataSource) }} + + +``` + +字段顺序严格对齐 `ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue` 现有详情弹窗: + +1. 统一社会信用代码 +2. 企业名称 +3. 企业类型 +4. 企业性质 +5. 行业分类 +6. 所属行业 +7. 成立日期 +8. 注册地址 +9. 法定代表人 +10. 法定代表人证件类型 +11. 法定代表人证件号码 +12. 经营状态 +13. 风险等级 +14. 企业来源 +15. 数据来源 +16. 创建时间 +17. 股东1 +18. 股东2 +19. 股东3 +20. 股东4 +21. 股东5 + +- [ ] **Step 4: 跑源码断言测试,确认按钮、方法、字段和“无权限显隐”口径都已到位** +- [ ] **Step 5: 跑源码断言测试,确认按钮、方法、字段和“无权限显隐”口径都已到位** + +Run: + +```bash +source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js +``` + +Expected: + +```text +purchase-transaction-enterprise-detail-ui test passed +``` + +- [ ] **Step 5: 提交主实现** +- [ ] **Step 6: 提交主实现** + +```bash +git add ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js +git commit -m "补充招投标供应商企业详情查看" +``` + +### Task 3: 完成构建验证、真实页面验证与实施记录 + +**Files:** +- Modify: `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` +- Test: `ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js` +- Create: `docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-implementation.md` + +- [ ] **Step 1: 运行源码断言测试,确认静态契约稳定** + +Run: + +```bash +source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js +``` + +Expected: + +```text +purchase-transaction-enterprise-detail-ui test passed +``` + +- [ ] **Step 2: 运行前端生产构建** + +Run: + +```bash +source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && cd ruoyi-ui && npm run build:prod +``` + +Expected: + +```text +BUILD SUCCESS +``` + +允许出现既有 bundle size warning,但不能有新的编译错误。 + +- [ ] **Step 3: 启动真实页面并用 Playwright 完成页面验证** + +如果本地后端未运行,先启动后端: + +```bash +sh bin/restart_java_backend.sh +``` + +启动前端: + +```bash +source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && cd ruoyi-ui && npm run dev -- --port 8080 +``` + +Playwright 打开真实页面: + +```text +http://localhost:8080/maintain/purchaseTransaction +``` + +先准备最小可复现场景数据,避免验证阶段卡住: + +1. 进入 `http://localhost:8080/maintain/baseStaff/enterpriseBaseInfo` 或现有“实体库管理”页面,任选一条现有企业记录,记下它的 `socialCreditCode`,作为“可命中”样本。 + - 如果页面没有现成企业数据,先通过现有“新增实体库”流程补一条样本企业,再继续后续验证。 +2. 进入 `http://localhost:8080/maintain/purchaseTransaction`,新增或编辑一条测试用招投标记录,在供应商明细中准备三行: + - 供应商 A:`supplierUscc` 填上步骤 1 记录下来的实体库统一社会信用代码,用于“可命中”场景。 + - 供应商 B:`supplierUscc` 填 `TESTNOENTERPRISE01`,用于“查无数据”场景。 +3. 保存后进入该条记录的详情弹窗,先验证供应商 A 和供应商 B。 +4. “无统一信用代码”场景不通过新增/编辑表单构造,而是在真实页面打开详情弹窗前,对 `**/ccdi/purchaseTransaction/*` 做一次性 Playwright 路由拦截: + - 先 `route.fetch()` 获取真实详情响应 + - 将返回体中任意一条 `supplierList[*].supplierUscc` 改成空字符串 + - 再 `route.fulfill({ response, json: patchedBody })` + - 重新打开同一条招投标详情弹窗,点击被补空的供应商“详情”按钮,验证提示“暂无企业信息” +5. “接口异常”场景通过 Playwright 对 `**/ccdi/enterpriseBaseInfo/**` 做一次性路由拦截,返回 `500` 或直接 `abort`,仍在真实业务页面中点击供应商“详情”按钮完成验证。 + +必须覆盖以下场景: + +- 存在 `supplierUscc` 且实体库可命中时,点击供应商行“详情”打开企业详情弹窗 +- 企业详情弹窗字段顺序、日期格式、枚举中文标签与实体库详情页一致 +- `supplierUscc` 为空时,点击“详情”提示“暂无企业信息” +- 接口异常或查无数据时,点击“详情”仍提示“暂无企业信息” +- “详情”按钮固定显示,不因为实体库权限加前端显隐 +- 关闭企业详情弹窗后再次查看另一家供应商,不残留上一条详情数据 + +- [ ] **Step 4: 关闭测试过程中启动的进程** + +需要主动关闭: + +- 前端 `npm run dev` 进程 +- 若为本次验证启动了后端,则同步关闭对应后端进程 +- Playwright 浏览器会话 + +- [ ] **Step 5: 编写实施记录** + +新增 `docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-implementation.md`,至少包含以下内容: + +```md +# 招投标详情弹窗供应商企业信息查看实施记录 + +## 本次修改 +- 在招投标详情弹窗供应商明细中新增企业详情按钮 +- 复用实体库详情接口展示企业全部字段 +- 无统一信用代码、查无数据、接口异常统一提示“暂无企业信息” + +## 影响范围 +- ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue +- ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js + +## 验证方式 +- Node 源码断言测试 +- npm run build:prod +- Playwright 真实页面验证 + +## 测试进程清理 +- 已关闭前端 dev 进程 +- 已关闭本次启动的后端进程 +- 已关闭浏览器会话 +``` + +- [ ] **Step 6: 提交验证与文档** + +```bash +git add docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-implementation.md +git commit -m "记录招投标供应商企业详情实现结果" +``` + +## Final Verification Checklist + +- [ ] `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` 只按 `supplierUscc` 查询企业详情 +- [ ] 供应商“详情”按钮未新增 `v-hasPermi` +- [ ] 企业详情字段顺序与 `ruoyi-ui/src/views/ccdiEnterpriseBaseInfo/index.vue` 一致 +- [ ] 缺少统一信用代码、查无数据、403/异常均统一提示“暂无企业信息” +- [ ] 关闭企业详情弹窗后会清空 `enterpriseDetailData` +- [ ] Node 断言测试通过 +- [ ] 前端构建通过 +- [ ] Playwright 真实页面验证完成 +- [ ] 测试过程中启动的前后端进程已关闭 diff --git a/docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-design-record.md b/docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-design-record.md index b73b0ac2..661b76a6 100644 --- a/docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-design-record.md +++ b/docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-design-record.md @@ -4,6 +4,7 @@ - 新增设计文档 `docs/superpowers/specs/2026-04-23-bidding-supplier-enterprise-detail-design.md` - 固化“招投标详情弹窗供应商企业信息查看”需求的边界、查询口径、交互方式、错误处理和测试范围 +- 补充权限口径:本次不新增权限控制,不做按钮显隐,接口权限失败也统一提示“暂无企业信息” - 明确本次需求为纯前端改动,后续实施阶段仅需输出前端实施计划 ## 关键文件 diff --git a/docs/superpowers/specs/2026-04-23-bidding-supplier-enterprise-detail-design.md b/docs/superpowers/specs/2026-04-23-bidding-supplier-enterprise-detail-design.md index 6910c12e..20aee4ca 100644 --- a/docs/superpowers/specs/2026-04-23-bidding-supplier-enterprise-detail-design.md +++ b/docs/superpowers/specs/2026-04-23-bidding-supplier-enterprise-detail-design.md @@ -144,6 +144,19 @@ 二级弹窗在当前页面内打开,不跳转页面,也不关闭一级弹窗。 +### 6.4 权限口径 + +本次不新增权限控制,也不对按钮做权限显隐控制。 + +具体口径固定如下: + +- 供应商明细表中的“详情”按钮固定显示 +- 前端不新增 `v-hasPermi` 限制 +- 本次不新增菜单权限配置,不调整现有角色授权 +- 若接口因权限或其他原因请求失败,前端仍统一按“暂无企业信息”处理 + +因此本次方案仍按纯前端页面改造收口,不扩展为权限链路改造需求。 + ## 7. 数据口径设计 ### 7.1 查询口径 @@ -217,8 +230,9 @@ - 供应商没有统一信用代码:提示“暂无企业信息” - 统一信用代码存在但实体库查不到:提示“暂无企业信息” - 接口请求失败:提示“暂无企业信息” +- 接口因权限校验失败:提示“暂无企业信息” -本次不区分“缺失统一信用代码”“企业不存在”“接口异常”三类用户提示文案,统一收敛为同一提示,避免页面出现多套业务语义。 +本次不区分“缺失统一信用代码”“企业不存在”“接口异常”“接口权限失败”四类用户提示文案,统一收敛为同一提示,避免页面出现多套业务语义。 ## 10. 测试设计 @@ -231,6 +245,7 @@ - 供应商有统一信用代码,且实体库存在对应企业时,点击“详情”能打开企业详情弹窗并展示完整字段 - 供应商没有统一信用代码时,点击“详情”提示“暂无企业信息” - 供应商有统一信用代码但实体库不存在对应企业时,点击“详情”提示“暂无企业信息” +- “详情”按钮固定显示,不因实体库权限做前端显隐控制 - 关闭企业详情弹窗后,再查看另一家供应商时,不残留上一家企业详情数据 ### 10.3 测试执行要求