feat: 接入新华社工商信息同步

This commit is contained in:
wkc
2026-07-29 17:56:27 +08:00
parent 2bcba71259
commit 5d004a66e8
72 changed files with 2610 additions and 59 deletions

View File

@@ -148,6 +148,17 @@
>查看导入失败记录</el-button>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-refresh"
size="mini"
:loading="profileBackfillLoading"
@click="handleProfileBackfill"
v-hasPermi="['ccdi:enterpriseBaseInfo:backfill']"
>工商历史补全</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@@ -407,6 +418,12 @@
<el-descriptions-item label="成立日期">{{ parseTime(detailData.establishDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
<el-descriptions-item label="注册地址">{{ detailData.registerAddress || '-' }}</el-descriptions-item>
<el-descriptions-item label="法定代表人">{{ detailData.legalRepresentative || '-' }}</el-descriptions-item>
<el-descriptions-item label="注册资本">{{ formatCapital(detailData.registeredCapital, detailData.registeredCapitalUnit) }}</el-descriptions-item>
<el-descriptions-item label="注册日期">{{ parseTime(detailData.registerDate, '{y}-{m}-{d}') || '-' }}</el-descriptions-item>
<el-descriptions-item label="区域">{{ detailData.regionName || '-' }}</el-descriptions-item>
<el-descriptions-item label="区域编码">{{ detailData.regionCode || '-' }}</el-descriptions-item>
<el-descriptions-item label="从业人数">{{ detailData.employeeCount == null ? '-' : detailData.employeeCount }}</el-descriptions-item>
<el-descriptions-item label="工商同步时间">{{ parseTime(detailData.enterpriseSyncTime) || '-' }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件类型">{{ detailData.legalCertType || '-' }}</el-descriptions-item>
<el-descriptions-item label="法定代表人证件号码">{{ detailData.legalCertNo || '-' }}</el-descriptions-item>
<el-descriptions-item label="经营状态">{{ formatStatus(detailData.status) }}</el-descriptions-item>
@@ -420,7 +437,24 @@
<el-descriptions-item label="股东4">{{ detailData.shareholder4 || '-' }}</el-descriptions-item>
<el-descriptions-item label="股东5">{{ detailData.shareholder5 || '-' }}</el-descriptions-item>
</el-descriptions>
<div class="shareholder-title">完整股东信息</div>
<el-table :data="detailData.shareholders || []" border size="small">
<el-table-column label="序号" prop="shareholderSeq" width="70" align="center" />
<el-table-column label="股东名称" prop="shareholderName" min-width="220" />
<el-table-column label="持股比例(%)" prop="stockPercent" width="130" align="right" />
<el-table-column label="认缴金额" min-width="150" align="right">
<template slot-scope="scope">{{ formatCapital(scope.row.subscribedCapital, scope.row.capitalUnit) }}</template>
</el-table-column>
</el-table>
<div slot="footer" class="dialog-footer">
<el-button
v-if="detailData.canRefreshEnterpriseProfile"
type="primary"
icon="el-icon-refresh"
:loading="profileRefreshing"
@click="handleProfileRefresh"
v-hasPermi="['ccdi:enterpriseBaseInfo:refresh']"
>重新查询</el-button>
<el-button @click="detailOpen = false"> </el-button>
</div>
</el-dialog>
@@ -506,9 +540,12 @@ import {
addEnterpriseBaseInfo,
delEnterpriseBaseInfo,
getEnterpriseBaseInfo,
getEnterpriseProfileBackfillStatus,
getImportFailures,
getImportStatus,
listEnterpriseBaseInfo,
refreshEnterpriseProfile,
startEnterpriseProfileBackfill,
updateEnterpriseBaseInfo
} from "@/api/ccdiEnterpriseBaseInfo";
import {
@@ -546,6 +583,9 @@ export default {
detailOpen: false,
isAdd: true,
detailData: {},
profileRefreshing: false,
profileBackfillLoading: false,
profileBackfillTimer: null,
corpTypeOptions: [],
corpNatureOptions: [],
certTypeOptions: [],
@@ -626,6 +666,10 @@ export default {
clearInterval(this.pollingTimer);
this.pollingTimer = null;
}
if (this.profileBackfillTimer) {
clearInterval(this.profileBackfillTimer);
this.profileBackfillTimer = null;
}
},
methods: {
loadOptions() {
@@ -709,6 +753,51 @@ export default {
this.detailOpen = true;
});
},
handleProfileRefresh() {
if (!this.detailData.socialCreditCode) return;
this.profileRefreshing = true;
refreshEnterpriseProfile(this.detailData.socialCreditCode).then(() => {
this.$modal.msgSuccess("工商信息已更新");
return getEnterpriseBaseInfo(this.detailData.socialCreditCode);
}).then(response => {
this.detailData = response.data || {};
this.getList();
}).finally(() => {
this.profileRefreshing = false;
});
},
handleProfileBackfill() {
this.$modal.confirm("确认启动实体库工商信息历史补全?").then(() => {
this.profileBackfillLoading = true;
return startEnterpriseProfileBackfill();
}).then(response => {
this.pollProfileBackfill(response.data);
}).catch(() => {
this.profileBackfillLoading = false;
});
},
pollProfileBackfill(taskId) {
const query = () => getEnterpriseProfileBackfillStatus(taskId).then(response => {
const status = response.data || {};
if (status.status === "COMPLETED") {
clearInterval(this.profileBackfillTimer);
this.profileBackfillTimer = null;
this.profileBackfillLoading = false;
this.$notify({ title: "工商历史补全完成", message: `成功 ${status.successCount || 0},跳过 ${status.skippedCount || 0},失败 ${status.failureCount || 0}`, type: status.failureCount ? "warning" : "success" });
this.getList();
}
}).catch(() => {
clearInterval(this.profileBackfillTimer);
this.profileBackfillTimer = null;
this.profileBackfillLoading = false;
});
query();
this.profileBackfillTimer = setInterval(query, 2000);
},
formatCapital(amount, unit) {
if (amount === null || amount === undefined || amount === "") return "-";
return `${amount}${unit || ""}`;
},
handleUpdate(row) {
this.reset();
this.isAdd = false;
@@ -1009,4 +1098,12 @@ export default {
.el-upload__tip {
line-height: 1.6;
}
.shareholder-title {
margin: 18px 0 10px;
color: #303133;
font-size: 15px;
font-weight: 600;
letter-spacing: 0;
}
</style>

View File

@@ -172,6 +172,14 @@
>
导出流水
</el-button>
<el-button
size="small"
plain
icon="el-icon-refresh"
:loading="counterpartyBackfillLoading"
@click="handleCounterpartyBackfill"
v-hasPermi="['ccdi:project:counterpartyEnterprise:backfill']"
>工商历史补全</el-button>
</div>
<el-alert
v-if="resultMode === 'statement' && prefillTip"
@@ -237,7 +245,13 @@
<template slot-scope="scope">
<div class="multi-line-cell">
<div class="primary-text">
{{ formatField(scope.row.customerAccountName) }}
<el-button
v-if="scope.row.customerAccountName"
type="text"
class="counterparty-link"
@click="openCounterpartyDetail(scope.row.customerAccountName)"
>{{ scope.row.customerAccountName }}</el-button>
<span v-else>-</span>
</div>
<div class="secondary-text">
{{ formatField(scope.row.customerAccountNo) }}
@@ -496,6 +510,60 @@
<el-button type="primary" @click="closeDetailDialog">确定</el-button>
</div>
</el-dialog>
<el-dialog title="对手方工商信息" :visible.sync="counterpartyVisible" width="920px" append-to-body>
<div v-loading="counterpartyLoading">
<el-alert
v-if="counterpartyData.status === 'NOT_FOUND'"
title="当前项目尚无该对手方工商数据"
type="info"
:closable="false"
show-icon
class="counterparty-alert"
/>
<el-alert
v-else-if="counterpartyData.status === 'EXPIRED'"
title="工商缓存已过期,可重新查询"
type="warning"
:closable="false"
show-icon
class="counterparty-alert"
/>
<el-descriptions :column="2" border size="small">
<el-descriptions-item label="流水对手方">{{ formatField(counterpartyData.counterpartyName) }}</el-descriptions-item>
<el-descriptions-item label="企业名称">{{ formatField(counterpartyData.enterpriseName) }}</el-descriptions-item>
<el-descriptions-item label="统一社会信用代码">{{ formatField(counterpartyData.socialCreditCode) }}</el-descriptions-item>
<el-descriptions-item label="法定代表人">{{ formatField(counterpartyData.legalRepresentative) }}</el-descriptions-item>
<el-descriptions-item label="注册资本">{{ formatCapital(counterpartyData.registeredCapital, counterpartyData.registeredCapitalUnit) }}</el-descriptions-item>
<el-descriptions-item label="注册日期">{{ formatDateOnly(counterpartyData.registerDate) }}</el-descriptions-item>
<el-descriptions-item label="成立日期">{{ formatDateOnly(counterpartyData.establishDate) }}</el-descriptions-item>
<el-descriptions-item label="机构类型">{{ formatField(counterpartyData.organizationTypeName) }}</el-descriptions-item>
<el-descriptions-item label="所属行业">{{ formatField(counterpartyData.industryName) }}</el-descriptions-item>
<el-descriptions-item label="区域">{{ formatField(counterpartyData.regionName) }}</el-descriptions-item>
<el-descriptions-item label="从业人数">{{ counterpartyData.employeeCount == null ? '-' : counterpartyData.employeeCount }}</el-descriptions-item>
<el-descriptions-item label="同步时间">{{ formatDate(counterpartyData.syncTime) }}</el-descriptions-item>
<el-descriptions-item label="注册地址" :span="2">{{ formatField(counterpartyData.registerAddress) }}</el-descriptions-item>
</el-descriptions>
<div class="counterparty-section-title">完整股东信息</div>
<el-table :data="counterpartyData.shareholders || []" border size="small">
<el-table-column label="序号" prop="sequenceNo" width="70" align="center" />
<el-table-column label="股东名称" prop="shareholderName" min-width="220" />
<el-table-column label="持股比例(%)" prop="stockPercent" width="130" align="right" />
<el-table-column label="认缴金额" min-width="150" align="right"><template slot-scope="scope">{{ formatCapital(scope.row.subscribedCapital, scope.row.capitalUnit) }}</template></el-table-column>
</el-table>
</div>
<div slot="footer">
<el-button
v-if="counterpartyData.canRefresh"
type="primary"
icon="el-icon-refresh"
:loading="counterpartyRefreshing"
@click="refreshCounterparty"
v-hasPermi="['ccdi:project:counterpartyEnterprise:refresh']"
>重新查询</el-button>
<el-button @click="counterpartyVisible=false">关闭</el-button>
</div>
</el-dialog>
</div>
</template>
@@ -508,6 +576,12 @@ import {
getBankStatementDetail,
} from "@/api/ccdiProjectBankStatement";
import { buildFlowEvidenceFingerprint, buildFlowEvidenceSnapshot } from "@/utils/ccdiEvidence";
import {
getCounterpartyEnterpriseBackfillStatus,
getCounterpartyEnterpriseDetail,
refreshCounterpartyEnterprise,
startCounterpartyEnterpriseBackfill,
} from "@/api/ccdiProjectCounterpartyEnterprise";
const TAB_MAP = {
all: "all",
@@ -592,6 +666,8 @@ const createEmptyDetailData = () => ({
hitTags: [],
});
const createEmptyCounterpartyData = () => ({ status: "NOT_FOUND", canRefresh: false, shareholders: [] });
export default {
name: "DetailQuery",
props: {
@@ -629,6 +705,12 @@ export default {
listError: "",
prefillTip: "",
detailData: createEmptyDetailData(),
counterpartyVisible: false,
counterpartyLoading: false,
counterpartyRefreshing: false,
counterpartyBackfillLoading: false,
counterpartyBackfillTimer: null,
counterpartyData: createEmptyCounterpartyData(),
queryParams: createDefaultQueryParams(this.projectId),
optionData: createEmptyOptionData(),
};
@@ -642,6 +724,9 @@ export default {
return this.resultMode === "counterparty" ? this.counterpartyTotal : this.total;
},
},
beforeDestroy() {
if (this.counterpartyBackfillTimer) clearInterval(this.counterpartyBackfillTimer);
},
watch: {
dateRange(value) {
this.queryParams.transactionStartTime = value && value[0] ? value[0] : "";
@@ -838,6 +923,53 @@ export default {
this.detailLoading = false;
this.detailData = createEmptyDetailData();
},
async openCounterpartyDetail(counterpartyName) {
if (!this.projectId || !counterpartyName) return;
this.counterpartyVisible = true;
this.counterpartyLoading = true;
try {
const response = await getCounterpartyEnterpriseDetail(this.projectId, counterpartyName);
this.counterpartyData = { ...createEmptyCounterpartyData(), ...(response.data || {}) };
} catch (error) {
this.counterpartyVisible = false;
this.$message.error("加载对手方工商信息失败");
} finally {
this.counterpartyLoading = false;
}
},
async refreshCounterparty() {
this.counterpartyRefreshing = true;
try {
const response = await refreshCounterpartyEnterprise({ projectId: this.projectId, counterpartyName: this.counterpartyData.counterpartyName });
this.counterpartyData = { ...createEmptyCounterpartyData(), ...(response.data || {}) };
this.$message.success("对手方工商信息已更新");
} finally {
this.counterpartyRefreshing = false;
}
},
handleCounterpartyBackfill() {
this.$confirm("确认启动全部项目对手方工商信息历史补全?", "提示", { type: "warning" }).then(() => {
this.counterpartyBackfillLoading = true;
return startCounterpartyEnterpriseBackfill();
}).then(response => this.pollCounterpartyBackfill(response.data)).catch(() => { this.counterpartyBackfillLoading = false; });
},
pollCounterpartyBackfill(taskId) {
const query = () => getCounterpartyEnterpriseBackfillStatus(taskId).then(response => {
const status = response.data || {};
if (status.status === "COMPLETED") {
clearInterval(this.counterpartyBackfillTimer);
this.counterpartyBackfillTimer = null;
this.counterpartyBackfillLoading = false;
this.$notify({ title: "对手方工商补全完成", message: `成功 ${status.successCount || 0},失败 ${status.failureCount || 0}`, type: status.failureCount ? "warning" : "success" });
}
}).catch(() => {
clearInterval(this.counterpartyBackfillTimer);
this.counterpartyBackfillTimer = null;
this.counterpartyBackfillLoading = false;
});
query();
this.counterpartyBackfillTimer = setInterval(query, 2000);
},
handleAddEvidence() {
const detail = this.detailData || {};
const sourceRecordId = buildFlowEvidenceFingerprint(detail);
@@ -881,6 +1013,13 @@ export default {
formatDate(value) {
return value ? parseTime(value, "{y}-{m}-{d} {h}:{i}:{s}") : "-";
},
formatDateOnly(value) {
return value ? parseTime(value, "{y}-{m}-{d}") : "-";
},
formatCapital(amount, unit) {
if (amount === null || amount === undefined || amount === "") return "-";
return `${amount}${unit || ""}`;
},
formatAmount(value) {
if (value === null || value === undefined || value === "") {
return "-";
@@ -1010,6 +1149,26 @@ export default {
}
}
.counterparty-link {
max-width: 100%;
padding: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.counterparty-alert {
margin-bottom: 12px;
}
.counterparty-section-title {
margin: 18px 0 10px;
color: #303133;
font-size: 15px;
font-weight: 600;
letter-spacing: 0;
}
.shell-sidebar,
.shell-main {
border: 1px solid #ebeef5;