完善招投标供应商企业详情设计与计划

This commit is contained in:
wkc
2026-04-23 15:40:42 +08:00
parent bd51991248
commit c660025bcc
3 changed files with 496 additions and 1 deletions

View File

@@ -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="操作"',
">详情</el-button>",
"企业信息详情",
"统一社会信用代码",
"企业名称",
"企业类型",
"企业性质",
"行业分类",
"所属行业",
"法定代表人",
"风险等级",
"企业来源",
"数据来源",
"股东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
<el-table-column label="操作" width="100" align="center" fixed="right">
<template slot-scope="scope">
<el-button
type="text"
size="mini"
:loading="enterpriseDetailLoading"
@click="handleSupplierEnterpriseDetail(scope.row)"
>详情</el-button>
</template>
</el-table-column>
<el-dialog
title="企业信息详情"
:visible.sync="enterpriseDetailOpen"
width="1000px"
append-to-body
@close="resetEnterpriseDetail"
>
<el-descriptions :column="2" border v-loading="enterpriseDetailLoading">
<el-descriptions-item label="统一社会信用代码">{{ enterpriseDetailData.socialCreditCode || "-" }}</el-descriptions-item>
<el-descriptions-item label="企业名称">{{ enterpriseDetailData.enterpriseName || "-" }}</el-descriptions-item>
<el-descriptions-item label="风险等级">{{ formatEnterpriseRiskLevel(enterpriseDetailData.riskLevel) }}</el-descriptions-item>
<el-descriptions-item label="企业来源">{{ formatEnterpriseSource(enterpriseDetailData.entSource) }}</el-descriptions-item>
<el-descriptions-item label="数据来源">{{ formatEnterpriseDataSource(enterpriseDetailData.dataSource) }}</el-descriptions-item>
</el-descriptions>
</el-dialog>
```
字段顺序严格对齐 `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 真实页面验证完成
- [ ] 测试过程中启动的前后端进程已关闭

View File

@@ -4,6 +4,7 @@
- 新增设计文档 `docs/superpowers/specs/2026-04-23-bidding-supplier-enterprise-detail-design.md` - 新增设计文档 `docs/superpowers/specs/2026-04-23-bidding-supplier-enterprise-detail-design.md`
- 固化“招投标详情弹窗供应商企业信息查看”需求的边界、查询口径、交互方式、错误处理和测试范围 - 固化“招投标详情弹窗供应商企业信息查看”需求的边界、查询口径、交互方式、错误处理和测试范围
- 补充权限口径:本次不新增权限控制,不做按钮显隐,接口权限失败也统一提示“暂无企业信息”
- 明确本次需求为纯前端改动,后续实施阶段仅需输出前端实施计划 - 明确本次需求为纯前端改动,后续实施阶段仅需输出前端实施计划
## 关键文件 ## 关键文件

View File

@@ -144,6 +144,19 @@
二级弹窗在当前页面内打开,不跳转页面,也不关闭一级弹窗。 二级弹窗在当前页面内打开,不跳转页面,也不关闭一级弹窗。
### 6.4 权限口径
本次不新增权限控制,也不对按钮做权限显隐控制。
具体口径固定如下:
- 供应商明细表中的“详情”按钮固定显示
- 前端不新增 `v-hasPermi` 限制
- 本次不新增菜单权限配置,不调整现有角色授权
- 若接口因权限或其他原因请求失败,前端仍统一按“暂无企业信息”处理
因此本次方案仍按纯前端页面改造收口,不扩展为权限链路改造需求。
## 7. 数据口径设计 ## 7. 数据口径设计
### 7.1 查询口径 ### 7.1 查询口径
@@ -217,8 +230,9 @@
- 供应商没有统一信用代码:提示“暂无企业信息” - 供应商没有统一信用代码:提示“暂无企业信息”
- 统一信用代码存在但实体库查不到:提示“暂无企业信息” - 统一信用代码存在但实体库查不到:提示“暂无企业信息”
- 接口请求失败:提示“暂无企业信息” - 接口请求失败:提示“暂无企业信息”
- 接口因权限校验失败:提示“暂无企业信息”
本次不区分“缺失统一信用代码”“企业不存在”“接口异常”类用户提示文案,统一收敛为同一提示,避免页面出现多套业务语义。 本次不区分“缺失统一信用代码”“企业不存在”“接口异常”“接口权限失败”四类用户提示文案,统一收敛为同一提示,避免页面出现多套业务语义。
## 10. 测试设计 ## 10. 测试设计
@@ -231,6 +245,7 @@
- 供应商有统一信用代码,且实体库存在对应企业时,点击“详情”能打开企业详情弹窗并展示完整字段 - 供应商有统一信用代码,且实体库存在对应企业时,点击“详情”能打开企业详情弹窗并展示完整字段
- 供应商没有统一信用代码时,点击“详情”提示“暂无企业信息” - 供应商没有统一信用代码时,点击“详情”提示“暂无企业信息”
- 供应商有统一信用代码但实体库不存在对应企业时,点击“详情”提示“暂无企业信息” - 供应商有统一信用代码但实体库不存在对应企业时,点击“详情”提示“暂无企业信息”
- “详情”按钮固定显示,不因实体库权限做前端显隐控制
- 关闭企业详情弹窗后,再查看另一家供应商时,不残留上一家企业详情数据 - 关闭企业详情弹窗后,再查看另一家供应商时,不残留上一家企业详情数据
### 10.3 测试执行要求 ### 10.3 测试执行要求