补充招投标供应商企业详情查看

This commit is contained in:
wkc
2026-04-23 16:12:56 +08:00
parent 0b2571b962
commit c7f4982451
2 changed files with 163 additions and 12 deletions

View File

@@ -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` 在实施前已存在未提交改动,为避免混入同文件既有变更,本次仅完成实现、验证与文档沉淀。

View File

@@ -506,6 +506,16 @@
<el-table-column label="联系人" prop="contactPerson" min-width="120" :show-overflow-tooltip="true" />
<el-table-column label="联系电话" prop="contactPhone" min-width="150" :show-overflow-tooltip="true" />
<el-table-column label="银行账户" prop="supplierBankAccount" min-width="180" :show-overflow-tooltip="true" />
<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-table>
<el-divider content-position="left">重要日期</el-divider>
@@ -552,6 +562,41 @@
</div>
</el-dialog>
<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="企业类型">{{ enterpriseDetailData.enterpriseType || "-" }}</el-descriptions-item>
<el-descriptions-item label="企业性质">{{ enterpriseDetailData.enterpriseNature || "-" }}</el-descriptions-item>
<el-descriptions-item label="行业分类">{{ enterpriseDetailData.industryClass || "-" }}</el-descriptions-item>
<el-descriptions-item label="所属行业">{{ enterpriseDetailData.industryName || "-" }}</el-descriptions-item>
<el-descriptions-item label="成立日期">{{ parseTime(enterpriseDetailData.establishDate, "{y}-{m}-{d}") || "-" }}</el-descriptions-item>
<el-descriptions-item label="注册地址">{{ enterpriseDetailData.registerAddress || "-" }}</el-descriptions-item>
<el-descriptions-item label="法定代表人">{{ enterpriseDetailData.legalRepresentative || "-" }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件类型">{{ enterpriseDetailData.legalCertType || "-" }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件号码">{{ enterpriseDetailData.legalCertNo || "-" }}</el-descriptions-item>
<el-descriptions-item label="经营状态">{{ formatEnterpriseStatus(enterpriseDetailData.status) }}</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-item label="创建时间">{{ parseTime(enterpriseDetailData.createTime) || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东1">{{ enterpriseDetailData.shareholder1 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东2">{{ enterpriseDetailData.shareholder2 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东3">{{ enterpriseDetailData.shareholder3 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东4">{{ enterpriseDetailData.shareholder4 || "-" }}</el-descriptions-item>
<el-descriptions-item label="股东5">{{ enterpriseDetailData.shareholder5 || "-" }}</el-descriptions-item>
</el-descriptions>
<div slot="footer" class="dialog-footer">
<el-button @click="enterpriseDetailOpen = false">关闭</el-button>
</div>
</el-dialog>
<el-dialog
:title="upload.title"
:visible.sync="upload.open"
@@ -608,6 +653,12 @@
/>
<el-table :data="failureList" v-loading="failureLoading">
<el-table-column label="失败Sheet" prop="sheetName" align="center" width="140" />
<el-table-column label="失败行号" prop="sheetRowNum" align="center" width="120">
<template slot-scope="scope">
<span>{{ scope.row.sheetRowNum ? `${scope.row.sheetRowNum}` : "-" }}</span>
</template>
</el-table-column>
<el-table-column label="采购事项ID" prop="purchaseId" align="center" />
<el-table-column label="项目名称" prop="projectName" align="center" :show-overflow-tooltip="true" />
<el-table-column label="标的物名称" prop="subjectName" align="center" :show-overflow-tooltip="true" />
@@ -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());
},