diff --git a/docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-implementation.md b/docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-implementation.md
new file mode 100644
index 00000000..e65683ad
--- /dev/null
+++ b/docs/reports/implementation/2026-04-23-bidding-supplier-enterprise-detail-implementation.md
@@ -0,0 +1,38 @@
+# 招投标详情弹窗供应商企业信息查看实施记录
+
+## 本次修改
+- 在招投标信息维护详情弹窗的供应商明细中新增“详情”按钮,固定显示且未新增实体库权限显隐控制。
+- 复用实体库详情接口,按 `supplierUscc` 查询企业信息,并以二级弹窗展示全部字段。
+- 缺少统一信用代码、查无数据、接口 500/普通异常时,统一提示“暂无企业信息”。
+
+## 影响范围
+- `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue`
+- `ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js`
+
+## 验证方式
+- Node 源码断言测试
+ - `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/unit/purchase-transaction-enterprise-detail-ui.test.js`
+- 前端生产构建
+ - `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && cd ruoyi-ui && npm run build:prod`
+- Playwright 真实页面验证
+ - 页面地址:`http://localhost:8080/maintain/purchaseTransaction`
+ - 覆盖场景:
+ - 供应商 `supplierUscc` 命中实体库时可打开企业详情弹窗
+ - 企业详情字段顺序、日期格式、枚举中文标签与实体库详情页口径一致
+ - `supplierUscc` 为空时提示“暂无企业信息”
+ - 查无数据时提示“暂无企业信息”
+ - 接口 500 时提示“暂无企业信息”
+ - 命中后关闭企业详情弹窗,再查看未命中供应商时不残留上一条详情数据
+
+## 真实页面验证结论
+- 使用真实业务页面完成验证,供应商明细“详情”按钮在详情弹窗中固定显示。
+- 命中实体库样本时,二级弹窗成功展示统一社会信用代码、企业名称、企业类型、企业性质、行业分类、所属行业、法定代表人、风险等级、企业来源、数据来源、股东信息等字段。
+- 查无数据、缺少统一信用代码、接口异常三类异常分支均统一显示“暂无企业信息”,未出现残留旧详情数据的问题。
+
+## 测试进程清理
+- 已关闭本次启动的前端 `npm run dev -- --port 8080` 进程。
+- 后端 `62318` 端口服务在验证前已存在,本次未重新启动后端进程。
+- 已关闭 Playwright 浏览器会话,并清理残留 daemon 进程。
+
+## 备注
+- 计划中的中间提交步骤未执行:`ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue` 在实施前已存在未提交改动,为避免混入同文件既有变更,本次仅完成实现、验证与文档沉淀。
diff --git a/ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue b/ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue
index 6c4b5434..b531f086 100644
--- a/ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue
+++ b/ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue
@@ -506,6 +506,16 @@
+
+
+ 详情
+
+
重要日期
@@ -552,6 +562,41 @@
+
+
+ {{ enterpriseDetailData.socialCreditCode || "-" }}
+ {{ enterpriseDetailData.enterpriseName || "-" }}
+ {{ enterpriseDetailData.enterpriseType || "-" }}
+ {{ enterpriseDetailData.enterpriseNature || "-" }}
+ {{ enterpriseDetailData.industryClass || "-" }}
+ {{ enterpriseDetailData.industryName || "-" }}
+ {{ parseTime(enterpriseDetailData.establishDate, "{y}-{m}-{d}") || "-" }}
+ {{ enterpriseDetailData.registerAddress || "-" }}
+ {{ enterpriseDetailData.legalRepresentative || "-" }}
+ {{ enterpriseDetailData.legalCertType || "-" }}
+ {{ enterpriseDetailData.legalCertNo || "-" }}
+ {{ formatEnterpriseStatus(enterpriseDetailData.status) }}
+ {{ formatEnterpriseRiskLevel(enterpriseDetailData.riskLevel) }}
+ {{ formatEnterpriseSource(enterpriseDetailData.entSource) }}
+ {{ formatEnterpriseDataSource(enterpriseDetailData.dataSource) }}
+ {{ parseTime(enterpriseDetailData.createTime) || "-" }}
+ {{ enterpriseDetailData.shareholder1 || "-" }}
+ {{ enterpriseDetailData.shareholder2 || "-" }}
+ {{ enterpriseDetailData.shareholder3 || "-" }}
+ {{ enterpriseDetailData.shareholder4 || "-" }}
+ {{ enterpriseDetailData.shareholder5 || "-" }}
+
+
+
+
+
+
+
+ {{ scope.row.sheetRowNum ? `第${scope.row.sheetRowNum}行` : "-" }}
+
+
@@ -640,6 +691,12 @@ import {
listTransaction,
updateTransaction
} from "@/api/ccdiPurchaseTransaction";
+import { getEnterpriseBaseInfo } from "@/api/ccdiEnterpriseBaseInfo";
+import {
+ getDataSourceOptions,
+ getEnterpriseRiskLevelOptions,
+ getEnterpriseSourceOptions
+} from "@/api/ccdiEnum";
import { getToken } from "@/utils/auth";
import ImportResultDialog from "@/components/ImportResultDialog.vue";
@@ -699,10 +756,16 @@ export default {
title: "",
open: false,
detailOpen: false,
+ enterpriseDetailOpen: false,
+ enterpriseDetailLoading: false,
+ enterpriseDetailData: {},
transactionDetail: { supplierList: [] },
isAdd: false,
dateRange: [],
winnerSupplierIndex: null,
+ enterpriseRiskLevelOptions: [],
+ enterpriseSourceOptions: [],
+ enterpriseDataSourceOptions: [],
queryParams: {
pageNum: 1,
pageSize: 10,
@@ -813,6 +876,7 @@ export default {
created() {
this.getList();
this.restoreImportState();
+ this.loadEnterpriseDetailOptions();
},
beforeDestroy() {
if (this.importPollingTimer) {
@@ -821,6 +885,26 @@ export default {
}
},
methods: {
+ loadEnterpriseDetailOptions() {
+ return Promise.all([
+ getEnterpriseRiskLevelOptions(),
+ getEnterpriseSourceOptions(),
+ getDataSourceOptions()
+ ]).then(([riskRes, sourceRes, dataSourceRes]) => {
+ this.enterpriseRiskLevelOptions = riskRes.data || [];
+ this.enterpriseSourceOptions = sourceRes.data || [];
+ this.enterpriseDataSourceOptions = dataSourceRes.data || [];
+ }).catch(() => {
+ this.enterpriseRiskLevelOptions = [];
+ this.enterpriseSourceOptions = [];
+ this.enterpriseDataSourceOptions = [];
+ });
+ },
+ getEnterpriseOptionLabel(options, value) {
+ if (!value) return "-";
+ const matched = (options || []).find(item => item.value === value);
+ return matched ? matched.label : value;
+ },
getList() {
this.loading = true;
const params = this.addDateRangeFlat(this.queryParams, this.dateRange, "applyDateStart", "applyDateEnd");
@@ -859,23 +943,25 @@ export default {
maximumFractionDigits: 2
});
},
+ 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 || "-";
+ },
getSupplierFieldRules(field) {
const ruleMap = {
supplierName: [
- { required: true, message: "供应商名称不能为空", trigger: "blur" },
- { max: 200, message: "供应商名称长度不能超过200个字符", trigger: "blur" }
+ { required: true, message: "供应商名称不能为空", trigger: "blur" }
],
supplierUscc: [
- { pattern: /^$|^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/, message: "请输入正确的18位统一信用代码", trigger: "blur" }
- ],
- contactPerson: [
- { max: 50, message: "联系人长度不能超过50个字符", trigger: "blur" }
- ],
- contactPhone: [
- { pattern: /^$|^1[3-9]\d{9}$|^0\d{2,3}-?\d{7,8}$/, message: "请输入正确的联系电话(手机号或座机号)", trigger: "blur" }
- ],
- supplierBankAccount: [
- { max: 50, message: "银行账户长度不能超过50个字符", trigger: "blur" }
+ { required: true, message: "统一信用代码不能为空", trigger: "blur" }
],
};
return ruleMap[field] || [];
@@ -889,6 +975,11 @@ export default {
this.winnerSupplierIndex = null;
this.resetForm("form");
},
+ resetEnterpriseDetail() {
+ this.enterpriseDetailOpen = false;
+ this.enterpriseDetailLoading = false;
+ this.enterpriseDetailData = {};
+ },
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
@@ -928,6 +1019,28 @@ export default {
this.detailOpen = true;
});
},
+ async handleSupplierEnterpriseDetail(row) {
+ const socialCreditCode = row && row.supplierUscc ? row.supplierUscc.trim() : "";
+ if (!socialCreditCode) {
+ this.$message.warning("暂无企业信息");
+ return;
+ }
+ this.enterpriseDetailLoading = true;
+ this.enterpriseDetailData = {};
+ 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;
+ }
+ },
handleAddSupplier() {
this.form.supplierList.push(createSupplierRow());
},