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 测试执行要求