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 || "-" }} + + + + + + + + @@ -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()); },