diff --git a/src/api/business/contract-manage.js b/src/api/business/contract-manage.js
index 872a5ea..b7ce897 100644
--- a/src/api/business/contract-manage.js
+++ b/src/api/business/contract-manage.js
@@ -36,6 +36,8 @@ export const approve = id =>
url: `${baseUrl}/approve`,
method: 'post',
params: { id },
+ // 审批通过后同步推送合规(附件转传可达 20s+),覆盖全局 10s 超时
+ timeout: 120000,
});
export const reject = id =>
diff --git a/src/api/business/hcs.js b/src/api/business/hcs.js
new file mode 100644
index 0000000..47012a2
--- /dev/null
+++ b/src/api/business/hcs.js
@@ -0,0 +1,96 @@
+import request from '@/axios';
+
+const baseUrl = '/blade-transport/hcs';
+
+// 合规推送涉及 HCS 远程多次调用(风险校验+附件转传+起草),可达 20s+,覆盖全局 10s 超时
+export const repush = (bizType, id) =>
+ request({
+ url: `${baseUrl}/sync/${bizType}/${id}`,
+ method: 'post',
+ timeout: 60000,
+ });
+
+// 批量推送为逐条同步推送,耗时随勾选行数线性增长,进一步放宽超时
+export const repushBatch = (bizType, ids) =>
+ request({
+ url: `${baseUrl}/sync/${bizType}/batch`,
+ method: 'post',
+ data: ids,
+ timeout: 300000,
+ });
+
+export const getTemplates = () =>
+ request({
+ url: `${baseUrl}/master-data/template`,
+ method: 'get',
+ });
+
+export const refreshMasterData = () =>
+ request({
+ url: `${baseUrl}/master-data/refresh`,
+ method: 'post',
+ timeout: 60000,
+ });
+
+export const getRisk = customerCode =>
+ request({
+ url: `${baseUrl}/risk`,
+ method: 'get',
+ params: { customerCode },
+ timeout: 30000,
+ });
+
+// 客商风险批量查询(客商列表页展示用):入参编号数组,后端逐个查 C101-1,单个失败跳过
+export const getRiskBatch = customerCodes =>
+ request({
+ url: `${baseUrl}/risk/batch`,
+ method: 'get',
+ params: { customerCodes: (customerCodes || []).join(',') },
+ timeout: 60000,
+ });
+
+// 客商风险按名称查询(客商档案表单展示用):名称反查编号后口径同编号查询
+export const getRiskByName = customerName =>
+ request({
+ url: `${baseUrl}/risk/by-name`,
+ method: 'get',
+ params: { customerName },
+ timeout: 30000,
+ });
+
+export const uploadAttachment = formData =>
+ request({
+ url: `${baseUrl}/attachment/upload`,
+ method: 'post',
+ data: formData,
+ timeout: 60000,
+ });
+
+export const downloadAttachment = (fdId, name) =>
+ request({
+ url: `${baseUrl}/attachment/download`,
+ method: 'get',
+ params: { fdId, name },
+ responseType: 'blob',
+ timeout: 120000,
+ });
+
+export const applyContractSpec = data =>
+ request({
+ url: `${baseUrl}/contract/spec-apply`,
+ method: 'post',
+ data,
+ timeout: 60000,
+ });
+
+export const confirmContractSpec = (id, result) =>
+ request({
+ url: `${baseUrl}/contract/spec/confirm`,
+ method: 'post',
+ params: { id, result },
+ });
+
+export const unwrapData = response => {
+ const data = response?.data?.data ?? response?.data;
+ return data && typeof data === 'object' && 'data' in data ? data.data : data;
+};
diff --git a/src/api/business/project-apply.js b/src/api/business/project-apply.js
index 83ac915..bf12e6a 100644
--- a/src/api/business/project-apply.js
+++ b/src/api/business/project-apply.js
@@ -43,6 +43,8 @@ export const approve = id =>
url: `${baseUrl}/approve`,
method: 'post',
params: { id },
+ // 审批通过后同步推送合规(远程多次调用),覆盖全局 10s 超时
+ timeout: 60000,
});
export const reject = id =>
diff --git a/src/api/common.js b/src/api/common.js
index 854e4d2..7e03551 100644
--- a/src/api/common.js
+++ b/src/api/common.js
@@ -1,6 +1,9 @@
import request from '@/axios';
import { ElLoading, ElMessage } from 'element-plus';
+/** 审核按钮统一超时:审批通过后可能同步推送金蝶等耗时操作,覆盖全局 10s */
+export const APPROVE_TIMEOUT = 30000;
+
/**
* 文件流返回
* @param url 接口地址
diff --git a/src/api/payment/invoiceApplication.js b/src/api/payment/invoiceApplication.js
index 717703d..af6fb87 100644
--- a/src/api/payment/invoiceApplication.js
+++ b/src/api/payment/invoiceApplication.js
@@ -1,4 +1,5 @@
import request from '@/axios';
+import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/invoice-application';
@@ -18,7 +19,8 @@ export const getReceiverInformation = settlementIds =>
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
-export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
+export const approve = data =>
+ request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
export const syncKingdee = id =>
diff --git a/src/api/payment/invoiceReceipt.js b/src/api/payment/invoiceReceipt.js
index 071eff1..8db2dfb 100644
--- a/src/api/payment/invoiceReceipt.js
+++ b/src/api/payment/invoiceReceipt.js
@@ -1,4 +1,5 @@
import request from '@/axios';
+import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/invoice-receipt';
@@ -31,7 +32,8 @@ export const getReferenceInformation = settlementIds =>
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
-export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
+export const approve = data =>
+ request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
export const pushKingdee = ids =>
diff --git a/src/api/payment/paymentApplication.js b/src/api/payment/paymentApplication.js
index 59c9aa1..e464552 100644
--- a/src/api/payment/paymentApplication.js
+++ b/src/api/payment/paymentApplication.js
@@ -1,4 +1,5 @@
import request from '@/axios';
+import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/payment-application';
export const getList = (current, size, params) =>
@@ -20,7 +21,8 @@ export const getProjectAdvanceWriteOffOptions = params =>
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
-export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
+export const approve = data =>
+ request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
// 同步金蝶涉及多次金蝶远程接口调用(A103-2 预检 + A103-1/A103-3 推送),单笔可达 20s+,覆盖全局 10s 超时
diff --git a/src/api/settlement/formalSettlement.js b/src/api/settlement/formalSettlement.js
index ea4cb14..2d980d4 100644
--- a/src/api/settlement/formalSettlement.js
+++ b/src/api/settlement/formalSettlement.js
@@ -1,4 +1,5 @@
import request from '@/axios';
+import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/formal-settlement';
@@ -27,7 +28,8 @@ export const getCandidateDetails = (current, size, params) =>
export const save = data => request({ url: `${baseUrl}/save`, method: 'post', data });
export const remove = id => request({ url: `${baseUrl}/remove`, method: 'post', params: { id } });
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
-export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
+export const approve = data =>
+ request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
export const voidBill = data => request({ url: `${baseUrl}/void`, method: 'post', data });
// 同步金蝶涉及多次金蝶远程接口调用(A102-4 预检 + A102-2 保存),可达 20s+,覆盖全局 10s 超时
diff --git a/src/api/settlement/preSettlement.js b/src/api/settlement/preSettlement.js
index e35f2a6..9aa2755 100644
--- a/src/api/settlement/preSettlement.js
+++ b/src/api/settlement/preSettlement.js
@@ -1,4 +1,5 @@
import request from '@/axios';
+import { APPROVE_TIMEOUT } from '@/api/common';
const baseUrl = '/blade-transport/pre-settlement';
@@ -42,7 +43,8 @@ export const removeDetail = (id, detailId) =>
export const submit = data => request({ url: `${baseUrl}/submit`, method: 'post', data });
-export const approve = data => request({ url: `${baseUrl}/approve`, method: 'post', data });
+export const approve = data =>
+ request({ url: `${baseUrl}/approve`, method: 'post', data, timeout: APPROVE_TIMEOUT });
export const returnBill = data => request({ url: `${baseUrl}/return`, method: 'post', data });
diff --git a/src/api/system/business-process.js b/src/api/system/business-process.js
index 5d0f502..0ea48d5 100644
--- a/src/api/system/business-process.js
+++ b/src/api/system/business-process.js
@@ -1,5 +1,8 @@
import request from '@/axios';
+/** 提交审核流(MK)统一超时:客商/项目/合同/预结算/正式结算/付款/开票/收票/汇票付款 */
+export const MK_SUBMIT_TIMEOUT = 30000;
+
/**
* 调用 MK processSubmit 提交审核流
* @param {Object} data
@@ -15,6 +18,7 @@ export const processSubmit = data => {
url: '/blade-system/businessProcess/processSubmit',
method: 'post',
data,
+ timeout: MK_SUBMIT_TIMEOUT,
});
};
@@ -28,6 +32,7 @@ export const processDelete = data => {
url: '/blade-system/businessProcess/processDelete',
method: 'post',
data,
+ timeout: MK_SUBMIT_TIMEOUT,
});
};
diff --git a/src/option/business/common.js b/src/option/business/common.js
index c142601..729199e 100644
--- a/src/option/business/common.js
+++ b/src/option/business/common.js
@@ -1,4 +1,4 @@
-export const transportTypeOptions = [
+export const transportTypeOptions = [
{ label: '公路运输', value: '公路运输' },
{ label: '铁路运输', value: '铁路运输' },
{ label: '水路运输', value: '水路运输' },
@@ -144,6 +144,17 @@ export const contractArchiveStatusOptions = [
{ label: '已归档', value: '已归档' },
];
+export const hcsSyncStatusOptions = [
+ { label: '待推送', value: 'unsynced' },
+ { label: '已推送', value: 'synced' },
+ { label: '推送失败', value: 'failed' },
+];
+
+export const yesNoOptions = [
+ { label: '是', value: true },
+ { label: '否', value: false },
+];
+
export const nodeOptions = [
{ label: '接单', value: '接单' },
{ label: '到场', value: '到场' },
diff --git a/src/option/business/contract-manage.js b/src/option/business/contract-manage.js
index e59adf3..9efc345 100644
--- a/src/option/business/contract-manage.js
+++ b/src/option/business/contract-manage.js
@@ -7,9 +7,11 @@ import {
createCrudOption,
effectiveTypeOptions,
nonNegativeRule,
+ hcsSyncStatusOptions,
selectRule,
signTypeOptions,
textRule,
+ yesNoOptions,
} from './common';
const contractSettlementModeOptions = [
@@ -606,6 +608,50 @@ export const option = createCrudOption([
addDisplay: false,
editDisplay: false,
},
+ {
+ label: '合规状态',
+ prop: 'hcsSyncStatus',
+ type: 'select',
+ search: true,
+ searchOrder: 6,
+ searchPlaceholder: '请选择',
+ slot: true,
+ dicData: hcsSyncStatusOptions,
+ minWidth: 110,
+ addDisplay: false,
+ editDisplay: false,
+ },
+ {
+ label: '金蝶合同ID',
+ prop: 'kingdeeId',
+ minWidth: 130,
+ addDisplay: false,
+ editDisplay: false,
+ },
+ {
+ label: '是否同步金蝶',
+ prop: 'kingdeeSynced',
+ type: 'select',
+ search: true,
+ searchPlaceholder: '请选择',
+ searchOrder: 7,
+ dicData: yesNoOptions,
+ hide: true,
+ addDisplay: false,
+ editDisplay: false,
+ },
+ {
+ label: '是否同步合规',
+ prop: 'hcsSynced',
+ type: 'select',
+ search: true,
+ searchPlaceholder: '请选择',
+ searchOrder: 8,
+ dicData: yesNoOptions,
+ hide: true,
+ addDisplay: false,
+ editDisplay: false,
+ },
{
label: '当前节点',
prop: 'currentNode',
diff --git a/src/option/business/project-apply.js b/src/option/business/project-apply.js
index 502032e..08c0eef 100644
--- a/src/option/business/project-apply.js
+++ b/src/option/business/project-apply.js
@@ -3,6 +3,7 @@ import {
businessModeOptions,
businessTypeOptions,
createCrudOption,
+ hcsSyncStatusOptions,
nonNegativeRule,
projectApprovalStatusOptions,
projectSourceOptions,
@@ -10,6 +11,7 @@ import {
selectRule,
settlementModeOptions,
textRule,
+ yesNoOptions,
} from './common';
const projectEffectiveTypeOptions = [
@@ -406,6 +408,57 @@ export const option = createCrudOption([
addDisplay: false,
editDisplay: false,
},
+ {
+ label: '合规状态',
+ prop: 'hcsSyncStatus',
+ type: 'select',
+ search: true,
+ searchPlaceholder: '请选择',
+ searchOrder: 1,
+ slot: true,
+ dicData: hcsSyncStatusOptions,
+ minWidth: 110,
+ addDisplay: false,
+ editDisplay: false,
+ },
+ {
+ label: '金蝶项目ID',
+ prop: 'kingdeeId',
+ minWidth: 130,
+ addDisplay: false,
+ editDisplay: false,
+ },
+ {
+ label: '金蝶项目编号',
+ prop: 'kingdeeNumber',
+ minWidth: 130,
+ addDisplay: false,
+ editDisplay: false,
+ },
+ {
+ label: '是否同步金蝶',
+ prop: 'kingdeeSynced',
+ type: 'select',
+ search: true,
+ searchPlaceholder: '请选择',
+ searchOrder: 2,
+ dicData: yesNoOptions,
+ hide: true,
+ addDisplay: false,
+ editDisplay: false,
+ },
+ {
+ label: '是否同步合规',
+ prop: 'hcsSynced',
+ type: 'select',
+ search: true,
+ searchPlaceholder: '请选择',
+ searchOrder: 3,
+ dicData: yesNoOptions,
+ hide: true,
+ addDisplay: false,
+ editDisplay: false,
+ },
{
label: '生效类型',
prop: 'effectiveType',
diff --git a/src/views/business/components/contract-attachment-section.vue b/src/views/business/components/contract-attachment-section.vue
index bd3f02f..d595931 100644
--- a/src/views/business/components/contract-attachment-section.vue
+++ b/src/views/business/components/contract-attachment-section.vue
@@ -205,7 +205,9 @@ export const defaultContractAttachmentTypeOptions = [
{ label: '双章归档文件', value: '双章归档文件' },
{ label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
];
+// 特批附件归口附件(佐证材料等)区上传:高风险/黑名单客商合同必传,后端按 fileType=特批附件 校验并挂特批面板(与后端 HcsSyncConstant.ATTACHMENT_TYPE_SPEC_EXPLANATION 对齐)
export const defaultContractSupportingAttachmentTypeOptions = [
+ { label: '特批附件', value: '特批附件' },
{ label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
];
/** @deprecated 兼容旧引用,实际选项由业务字典动态加载 */
@@ -299,6 +301,8 @@ export const resolveContractAttachmentType = (
item => String(item.label) === aliasedFileType || String(item.label) === rawFileType
);
if (matchedByLabel) return matchedByLabel.value;
+ // 行已带类型但字典未命中(如历史保存值、字典缺失)时保留原值,避免重开页面时被降级为其他文件
+ if (rawFileType) return aliasedFileType;
const fileName = normalizeAttachmentTypeText(attachmentName(row));
if (!fileName) return otherType;
const matched = list
@@ -641,7 +645,7 @@ export default {
this.contractAttachmentTypeOptions
);
} else {
- this.uploadDialogType = this.defaultAttachmentType;
+ this.uploadDialogType = this.uploadDialogType || this.defaultAttachmentType;
}
this.$message.success('上传成功');
},
diff --git a/src/views/business/components/hcs-contract-panel.vue b/src/views/business/components/hcs-contract-panel.vue
new file mode 100644
index 0000000..2a3b1f3
--- /dev/null
+++ b/src/views/business/components/hcs-contract-panel.vue
@@ -0,0 +1,271 @@
+
+
+
+
+
+
+
diff --git a/src/views/business/contract-manage-change.vue b/src/views/business/contract-manage-change.vue
index 63f5f88..e3fef3c 100644
--- a/src/views/business/contract-manage-change.vue
+++ b/src/views/business/contract-manage-change.vue
@@ -251,6 +251,11 @@
:disabled="isSettlementRuleChange"
/>
+
+
String(name || '').trim())
+ .filter(Boolean)
+ .join('、');
+ return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷。提交变更前须上传「特批附件」类型附件`;
+ },
isSettlementRuleChange() {
return this.form.changeType === '结算规则变更';
},
@@ -1045,6 +1061,10 @@ export default {
}
.contract-basic-section__date-range { display: grid; grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr); align-items: center; gap: 8px; width: 360px; max-width: 100%; }
.contract-basic-section__date-range span { color: #606266; }
+// 风险客商提示条(基本信息备注后):橙黄背景 + 红色文字,与合同新增页一致
+.contract-manage-form__risk-tip { margin: 0 0 18px; padding: 10px 16px; background: #faecd8; border-radius: 4px; }
+.contract-manage-form__risk-tip-title { color: #f56c6c; font-weight: 600; line-height: 22px; }
+.contract-manage-form__risk-tip-content { color: #f56c6c; line-height: 22px; }
.contract-basic-section__date-range :deep(.el-date-editor) { width: auto; min-width: 0; }
.contract-basic-section :deep(.el-form-item) { align-items: center; }
.contract-basic-section :deep(.el-form-item__label) { flex: 0 0 96px !important; width: 96px !important; height: auto; padding-right: 12px; white-space: normal; word-break: break-all; line-height: 18px; }
diff --git a/src/views/business/contract-manage.vue b/src/views/business/contract-manage.vue
index 158b9cb..15b9161 100644
--- a/src/views/business/contract-manage.vue
+++ b/src/views/business/contract-manage.vue
@@ -32,6 +32,14 @@
>
批量导出
+
+ 批量推送合规
+
@@ -72,6 +80,18 @@
+
+
+
+ {{ hcsSyncLabel(row.hcsSyncStatus) }}
+
+
+
+ {{ hcsSyncLabel(row.hcsSyncStatus) }}
+
+ -
+
+
{{ operation.label }}
+
+ {{ hcsRepushingId === row.id ? '推送中…' : '推送合规' }}
+
删除
@@ -180,9 +203,18 @@
{{
detailValue('remark')
}}
+
+ {{ detailRiskTipText }}
+
+
+
visible && loadContractPartyOptions()"
+ @change="queryPartyRisk('partyA')"
>
visible && loadContractPartyOptions()"
+ @change="queryPartyRisk('partyB')"
>
+
+
{
Object.keys(form).forEach(prop => {
if (Object.prototype.hasOwnProperty.call(detail, prop)) form[prop] = detail[prop];
});
+ // 特批附件属于原合同的特批佐证,复制后须对新合同重新上传,否则会绕过高风险/黑名单校验
+ const stripSpecAttachments = value => {
+ const rows = parseArray(value).filter(item => item?.fileType !== '特批附件');
+ return rows.length ? JSON.stringify(rows) : '';
+ };
return {
...form,
contractNo: '',
@@ -1181,8 +1229,8 @@ const createCopiedDetail = (detail = {}) => {
archiveStatus: '未归档',
feeGenerationMode: detail.feeGenerationMode || '',
billingEnabled: detail.billingEnabled,
- contractFileJson: detail.contractFileJson || '',
- attachmentsJson: detail.attachmentsJson || '',
+ contractFileJson: stripSpecAttachments(detail.contractFileJson),
+ attachmentsJson: stripSpecAttachments(detail.attachmentsJson),
billingPlanJson: detail.billingPlanJson || '',
paymentRatioJson: detail.paymentRatioJson || '',
settlementRuleJson: detail.settlementRuleJson || '',
@@ -1241,9 +1289,11 @@ export default {
ContractAttachmentSection,
BillingPlanEditor,
ChangeRecordDetailDialog,
+ HcsContractPanel,
ElImageViewer,
OpenFileViewer,
PdfPreview,
+ Position,
},
data() {
return {
@@ -1335,6 +1385,9 @@ export default {
contractCategoryDictOptions: [],
contractPartyAOptions: [],
contractPartyBOptions: [],
+ // 合规客商风险(C101-1 展示用):甲方/乙方选择后即时查询
+ contractPartyCodeMap: {},
+ partyRisk: {},
contractPartyLoading: false,
contractPartyRequestId: 0,
organizationOptions: [],
@@ -1373,6 +1426,8 @@ export default {
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
detailLoading: false,
detailRow: {},
+ // 合规(HCS)推送/对账状态
+ hcsRepushingId: null,
detailContractFileRows: [],
detailAttachmentRows: [],
detailBillingPlanRows: [],
@@ -1549,6 +1604,32 @@ export default {
const endDate = this.detailRow.endDate || '-';
return `${startDate} 至 ${endDate}`;
},
+ // 高风险/黑名单客商签约提示(C101-1,签约前提示;强制拦截在后端提交校验与推送链路)
+ partyRiskText() {
+ const parts = [];
+ ['partyA', 'partyB'].forEach(prop => {
+ const risk = this.partyRisk[prop];
+ if (!risk?.risky) return;
+ // riskTip 由后端按风险项拼装(【公司名称】:黑名单;涉诉;被执行人等),缺失时兜底通用提示
+ parts.push(
+ risk.riskTip ||
+ `${prop === 'partyA' ? '甲方' : '乙方'}[${this.form[prop] || ''}]为高风险/黑名单客商`
+ );
+ });
+ if (!parts.length) return '';
+ return `${parts.join(';')},按集团规定不可直接签约,提交前须上传「特批附件」类型附件,审批通过后将自动发起合同特批`;
+ },
+ // 详情风险客商提示(基本信息备注后展示):按推送留痕 hcs_risk_level/hcs_black_status 判定(甲乙取高/任一),文案与后端 riskTip 一致
+ detailRiskTipText() {
+ const riskLevel = String(this.detailRow.hcsRiskLevel || '').trim();
+ const blacklist = String(this.detailRow.hcsBlackStatus || '').toLowerCase() === 'yes';
+ if (riskLevel !== '2' && !blacklist) return '';
+ const parties = [this.detailRow.partyA, this.detailRow.partyB]
+ .map(name => String(name || '').trim())
+ .filter(Boolean)
+ .join('、');
+ return `【${parties || '相对方'}】:被列入外部黑名单;涉贪污受贿裁判相关提及方(3年以上及其他);被执行人信息;买卖合同纠纷;劳动纠纷;重大纠纷`;
+ },
detailLegalSealText() {
const value = this.detailRow.legalSealFlag;
if (value === undefined || value === null || value === '') return '-';
@@ -1670,6 +1751,92 @@ export default {
if (stage === 'terminated') return 'danger';
return 'info';
},
+ // ==================== 合规(HCS)推送/对账(照“同步金蝶”按钮模式) ====================
+ hcsSyncLabel(value) {
+ return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
+ },
+ hcsSyncTagType(value) {
+ return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
+ },
+ canHcsRepush(row) {
+ return (
+ ['approved', 'change_approved'].includes(row.approvalStatus) &&
+ row.hcsSyncStatus !== 'synced'
+ );
+ },
+ handleHcsRepush(row) {
+ if (this.hcsRepushingId) return;
+ this.$confirm('确定推送该数据到合同合规系统?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ }).then(() => {
+ // 同步推送合规耗时较长,加遮罩防重复操作(照审批通过遮罩)
+ const loading = this.$loading({
+ lock: true,
+ text: '正在推送合规,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ this.hcsRepushingId = row.id;
+ hcsRepush('contract', row.id)
+ .then(res => {
+ if (res.data?.success === false) {
+ this.$message.error(res.data?.msg || '推送合规失败');
+ return;
+ }
+ this.$message.success(res.data?.data || '推送合规成功');
+ this.onLoad(this.page, this.query);
+ })
+ .catch(() => {
+ // 失败原因由 axios 拦截器统一弹出后端 msg,此处不再重复提示
+ })
+ .finally(() => {
+ this.hcsRepushingId = null;
+ loading.close();
+ });
+ });
+ },
+ handleHcsBatchRepush() {
+ if (this.hcsRepushingId) return;
+ if (!this.selectionList.length) {
+ this.$message.warning('请选择至少一条数据');
+ return;
+ }
+ const invalidCount = this.selectionList.filter(item => !this.canHcsRepush(item)).length;
+ if (invalidCount) {
+ this.$message.warning(`选中数据中有${invalidCount}条不符合推送条件(仅审批通过且未同步的可推送),请重新选择`);
+ return;
+ }
+ this.$confirm(`确定将选中的${this.selectionList.length}条数据推送到合同合规系统?`, '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ }).then(() => {
+ // 同步推送合规耗时较长,加遮罩防重复操作(照审批通过遮罩)
+ const loading = this.$loading({
+ lock: true,
+ text: '正在批量推送合规,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ this.hcsRepushingId = 'batch';
+ hcsRepushBatch('contract', this.selectionList.map(item => item.id))
+ .then(res => {
+ if (res.data?.success === false) {
+ this.$message.error(res.data?.msg || '批量推送合规失败');
+ return;
+ }
+ this.$message.success(res.data?.data || '批量推送合规完成');
+ this.onLoad(this.page, this.query);
+ })
+ .catch(() => {
+ // 失败原因由 axios 拦截器统一弹出后端 msg,此处不再重复提示
+ })
+ .finally(() => {
+ this.hcsRepushingId = null;
+ loading.close();
+ });
+ });
+ },
canEdit(row) {
return (
this.hasPermission(`${this.config.permission}_edit`) &&
@@ -2246,8 +2413,21 @@ export default {
])
.then(responses => {
if (requestId !== this.contractPartyRequestId) return;
- const names = responses
- .flatMap(extractRecords)
+ const records = responses.flatMap(extractRecords);
+ // 名称→客商编码映射,供 C101-1 风险查询使用(§9-U6 编码体系联调确认)
+ const codeMap = {};
+ records.forEach(item => {
+ const code = item.customerCode;
+ if (!code) return;
+ [item.fullName, item.shortName, item.customerName]
+ .map(name => String(name || '').trim())
+ .filter(Boolean)
+ .forEach(name => {
+ codeMap[name] = code;
+ });
+ });
+ this.contractPartyCodeMap = codeMap;
+ const names = records
.map(item => item.fullName || item.shortName || item.customerName || item.customerCode)
.map(name => String(name || '').trim())
.filter(Boolean);
@@ -2255,11 +2435,40 @@ export default {
const options = [...new Set(names)].map(name => ({ label: name, value: name }));
this.contractPartyAOptions = options;
this.contractPartyBOptions = options;
+ // 复制/编辑回填的甲乙方不会触发下拉 change,加载选项后主动查一次 C101-1 风险
+ ['partyA', 'partyB'].forEach(prop => {
+ if (this.form[prop]) this.queryPartyRisk(prop);
+ });
})
.finally(() => {
if (requestId === this.contractPartyRequestId) this.contractPartyLoading = false;
});
},
+ // C101-1 客商风险查询(展示用,强校验在后端提交/推送链路):失败静默不阻塞填写
+ queryPartyRisk(prop) {
+ const name = this.form[prop];
+ const code = name ? this.contractPartyCodeMap[name] : null;
+ if (!code) {
+ this.partyRisk = { ...this.partyRisk, [prop]: null };
+ return;
+ }
+ getRisk(code)
+ .then(res => {
+ const risk = res?.data?.data ?? res?.data ?? {};
+ this.partyRisk = {
+ ...this.partyRisk,
+ [prop]: {
+ customerKind: risk.customerKind || '',
+ // risky 由后端统一判定:外部客商高风险(等级=2)或黑名单;内部组织客商默认无风险恒为 false
+ risky: risk.risky === true,
+ riskTip: risk.riskTip || '',
+ },
+ };
+ })
+ .catch(() => {
+ this.partyRisk = { ...this.partyRisk, [prop]: null };
+ });
+ },
loadOrganizationOptions() {
if (this.organizationLoading) return;
this.organizationLoading = true;
@@ -3046,10 +3255,26 @@ export default {
})
)
: request();
- afterMk.then(() => {
- this.$message.success(operation.successMessage || `${operation.label}成功`);
- this.onLoad(this.page, this.query);
- });
+ // 合同通过会同步等待附件转传 + 合规推送,加遮罩防重复点击
+ const loading =
+ operation.action === 'approve'
+ ? this.$loading({
+ lock: true,
+ text: '正在推送合规,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ })
+ : null;
+ afterMk
+ .then(res => {
+ // data=false 表示审批已通过但合规推送失败,msg 带具体原因(照正式结算单审批)
+ if (operation.action === 'approve' && res?.data?.data === false) {
+ this.$message.warning(res.data.msg);
+ } else {
+ this.$message.success(operation.successMessage || `${operation.label}成功`);
+ }
+ this.onLoad(this.page, this.query);
+ })
+ .finally(() => loading && loading.close());
};
if (operation.prompt) {
this.$prompt(operation.prompt, operation.label, {
@@ -3146,6 +3371,29 @@ export default {
vertical-align: bottom;
}
+.contract-manage-form__risk-alert {
+ width: 100%;
+}
+
+// 风险客商提示条(基本信息备注后):橙黄背景 + 红色文字(设计稿 2026-10)
+.contract-manage-form__risk-tip {
+ margin: 0 0 18px;
+ padding: 10px 16px;
+ background: #faecd8;
+ border-radius: 4px;
+
+ .contract-manage-form__risk-tip-title {
+ color: #f56c6c;
+ font-weight: 600;
+ line-height: 22px;
+ }
+
+ .contract-manage-form__risk-tip-content {
+ color: #f56c6c;
+ line-height: 22px;
+ }
+}
+
.contract-manage-page {
&__expiry-search {
display: flex;
diff --git a/src/views/business/hcs-master-data.vue b/src/views/business/hcs-master-data.vue
new file mode 100644
index 0000000..0cf5872
--- /dev/null
+++ b/src/views/business/hcs-master-data.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ formatTime(row.fdCreateTime) }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/business/project-apply.vue b/src/views/business/project-apply.vue
index 75dcdfe..965f9a0 100644
--- a/src/views/business/project-apply.vue
+++ b/src/views/business/project-apply.vue
@@ -52,12 +52,37 @@
>
批量删除
+
+ 批量推送合规
+
{{ displayStatus(row, 'approvalStatus') }}
+
+
+
+ {{ hcsSyncLabel(row.hcsSyncStatus) }}
+
+
+
+ {{ hcsSyncLabel(row.hcsSyncStatus) }}
+
+ -
+
+
@@ -128,6 +153,13 @@
>
{{ operation.label }}
+
+ {{ hcsRepushingId === row.id ? '推送中…' : '推送合规' }}
+
删除
@@ -916,6 +948,8 @@
import { exportBlob } from '@/api/common';
import { defineAsyncComponent } from 'vue';
import * as api from '@/api/business/project-apply';
+import { repush as hcsRepush, repushBatch as hcsRepushBatch } from '@/api/business/hcs';
+import { Position } from '@element-plus/icons-vue';
import { getMkPublicDetail } from '@/api/mk-process';
import {
getList as getCustomerArchiveList,
@@ -1060,6 +1094,7 @@ export default {
ElImageViewer,
OpenFileViewer,
PdfPreview,
+ Position,
},
data() {
const validateAmount = (rule, value, callback) => {
@@ -1121,6 +1156,8 @@ export default {
{ label: '高风险', value: 'high', statKey: 'high' },
{ label: '中风险', value: 'medium', statKey: 'medium' },
],
+ // 合规(HCS)推送状态
+ hcsRepushingId: null,
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
@@ -1613,6 +1650,93 @@ export default {
return this.canOperation(operation, row);
});
},
+ // ==================== 合规(HCS)推送/对账(照“同步金蝶”按钮模式) ====================
+ hcsSyncLabel(value) {
+ return { unsynced: '待推送', synced: '已推送', failed: '推送失败' }[value] || value;
+ },
+ hcsSyncTagType(value) {
+ return { unsynced: 'info', synced: 'success', failed: 'danger' }[value] || 'info';
+ },
+ canHcsRepush(row) {
+ return (
+ !this.readonlyScope &&
+ ['approved', 'change_approved'].includes(row.approvalStatus) &&
+ row.hcsSyncStatus !== 'synced'
+ );
+ },
+ handleHcsRepush(row) {
+ if (this.hcsRepushingId) return;
+ this.$confirm('确定推送该数据到合同合规系统?', '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ }).then(() => {
+ // 同步推送合规耗时较长,加遮罩防重复操作(照审批通过遮罩)
+ const loading = this.$loading({
+ lock: true,
+ text: '正在推送合规,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ this.hcsRepushingId = row.id;
+ hcsRepush('project', row.id)
+ .then(res => {
+ if (res.data?.success === false) {
+ this.$message.error(res.data?.msg || '推送合规失败');
+ return;
+ }
+ this.$message.success(res.data?.data || '推送合规成功');
+ this.onLoad(this.page, this.query);
+ })
+ .catch(() => {
+ // 失败原因由 axios 拦截器统一弹出后端 msg,此处不再重复提示
+ })
+ .finally(() => {
+ this.hcsRepushingId = null;
+ loading.close();
+ });
+ });
+ },
+ handleHcsBatchRepush() {
+ if (this.hcsRepushingId) return;
+ if (!this.selectionList.length) {
+ this.$message.warning('请选择至少一条数据');
+ return;
+ }
+ const invalidCount = this.selectionList.filter(item => !this.canHcsRepush(item)).length;
+ if (invalidCount) {
+ this.$message.warning(`选中数据中有${invalidCount}条不符合推送条件(仅审批通过且未同步的可推送),请重新选择`);
+ return;
+ }
+ this.$confirm(`确定将选中的${this.selectionList.length}条数据推送到合同合规系统?`, '提示', {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning',
+ }).then(() => {
+ // 同步推送合规耗时较长,加遮罩防重复操作(照审批通过遮罩)
+ const loading = this.$loading({
+ lock: true,
+ text: '正在批量推送合规,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ this.hcsRepushingId = 'batch';
+ hcsRepushBatch('project', this.selectionList.map(item => item.id))
+ .then(res => {
+ if (res.data?.success === false) {
+ this.$message.error(res.data?.msg || '批量推送合规失败');
+ return;
+ }
+ this.$message.success(res.data?.data || '批量推送合规完成');
+ this.onLoad(this.page, this.query);
+ })
+ .catch(() => {
+ // 失败原因由 axios 拦截器统一弹出后端 msg,此处不再重复提示
+ })
+ .finally(() => {
+ this.hcsRepushingId = null;
+ loading.close();
+ });
+ });
+ },
canOperation(operation, row) {
const permission = operation.permission || `${this.config.permission}_${operation.action}`;
if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) return false;
@@ -1754,14 +1878,31 @@ export default {
mkEnabled: this.mkEnabled,
}).then(() => request())
: request();
- afterMk.then(res => {
- if (res.data?.success === false || res.data?.data === false) {
- this.$message.error(res.data?.msg || `${operation.label}失败,请联系管理员`);
- return;
- }
- this.$message.success(`${operation.label}成功`);
- this.onLoad(this.page, this.query);
- });
+ // 项目通过会同步等待合规推送,加遮罩防重复点击
+ const loading =
+ operation.action === 'approve'
+ ? this.$loading({
+ lock: true,
+ text: '正在推送合规,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ })
+ : null;
+ afterMk
+ .then(res => {
+ // data=false 表示审批已通过但合规推送失败,msg 带具体原因(照正式结算单审批)
+ if (operation.action === 'approve' && res.data?.data === false) {
+ this.$message.warning(res.data?.msg);
+ this.onLoad(this.page, this.query);
+ return;
+ }
+ if (res.data?.success === false) {
+ this.$message.error(res.data?.msg || `${operation.label}失败,请联系管理员`);
+ return;
+ }
+ this.$message.success(`${operation.label}成功`);
+ this.onLoad(this.page, this.query);
+ })
+ .finally(() => loading && loading.close());
};
if (operation.prompt) {
this.$prompt(operation.prompt, operation.label, {
diff --git a/src/views/payment/invoice-application.vue b/src/views/payment/invoice-application.vue
index 969c41a..a447f77 100644
--- a/src/views/payment/invoice-application.vue
+++ b/src/views/payment/invoice-application.vue
@@ -270,6 +270,7 @@ export default {
rows: [],
selection: [],
loading: false,
+ approveLoading: false,
batchSyncing: false,
page: { current: 1, size: 10, total: 0 },
mkEnabled: false,
@@ -408,15 +409,29 @@ export default {
});
},
async handleApprove(row) {
- await this.$confirm('确认审批通过该开票申请?', '提示', { type: 'warning' });
- const res = await api.approve({ id: row.id });
- // data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
- if (res.data.data === false) {
- this.$message.warning(res.data.msg);
- } else {
- this.$message.success(res.data.msg || '审批通过');
+ // 防止重复点击:请求进行中忽略后续点击,避免重复审批和重复提示
+ if (this.approveLoading) return;
+ this.approveLoading = true;
+ let loading = null;
+ try {
+ await this.$confirm('确认审批通过该开票申请?', '提示', { type: 'warning' });
+ loading = this.$loading({
+ lock: true,
+ text: '审批处理中,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ const res = await api.approve({ id: row.id });
+ // data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
+ if (res.data.data === false) {
+ this.$message.warning(res.data.msg);
+ } else {
+ this.$message.success(res.data.msg || '审批通过');
+ }
+ this.loadTable();
+ } finally {
+ loading?.close();
+ this.approveLoading = false;
}
- this.loadTable();
},
async handleReturn(row) {
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
diff --git a/src/views/payment/invoice-receipt.vue b/src/views/payment/invoice-receipt.vue
index 8710449..aed1115 100644
--- a/src/views/payment/invoice-receipt.vue
+++ b/src/views/payment/invoice-receipt.vue
@@ -255,6 +255,7 @@ export default {
rows: [],
selection: [],
loading: false,
+ approveLoading: false,
pushLoading: false,
page: { current: 1, size: 10, total: 0 },
mkEnabled: false,
@@ -365,15 +366,29 @@ export default {
});
},
async handleApprove(row) {
- await this.$confirm('确认审批通过该收票登记?', '提示', { type: 'warning' });
- const res = await api.approve({ id: row.id });
- // data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
- if (res.data.data === false) {
- this.$message.warning(res.data.msg);
- } else {
- this.$message.success(res.data.msg || '审批通过');
+ // 防止重复点击:请求进行中忽略后续点击,避免重复审批和重复提示
+ if (this.approveLoading) return;
+ this.approveLoading = true;
+ let loading = null;
+ try {
+ await this.$confirm('确认审批通过该收票登记?', '提示', { type: 'warning' });
+ loading = this.$loading({
+ lock: true,
+ text: '审批处理中,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ const res = await api.approve({ id: row.id });
+ // data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
+ if (res.data.data === false) {
+ this.$message.warning(res.data.msg);
+ } else {
+ this.$message.success(res.data.msg || '审批通过');
+ }
+ this.loadTable();
+ } finally {
+ loading?.close();
+ this.approveLoading = false;
}
- this.loadTable();
},
async handleReturn(row) {
const { value } = await this.$prompt('请输入驳回原因', '审批驳回');
diff --git a/src/views/payment/payment-application-form.vue b/src/views/payment/payment-application-form.vue
index 598d1e8..3e91449 100644
--- a/src/views/payment/payment-application-form.vue
+++ b/src/views/payment/payment-application-form.vue
@@ -219,6 +219,11 @@
show-word-limit />
+
+
+
风险客商提示:
+
{{ payeeRiskText }}
+
item.accountName || item.accountHolderName || item.bankAccount)
.map(item => ({
@@ -3312,6 +3351,15 @@ export default {
this.clearMessages();
const saved = await this.saveDraft({ showSuccess: false, closePage: false });
if (!saved) return;
+ // 高风险/黑名单收款方必须上传特批附件(与后端提交强校验同口径)
+ if (
+ this.missingAttachmentMaterials.some(
+ item => item.key === ATTACHMENT_MATERIALS.specialApproval.key
+ )
+ ) {
+ this.$message.warning('收款方为高风险/黑名单客商,必须上传「特批附件」类型附件后再提交审批');
+ return;
+ }
const previousApprovalStatus = this.form.approvalStatus || '';
await api.submit({ id: this.form.id });
await submitMkApprovalFlow({
@@ -3344,6 +3392,26 @@ export default {
.payment-form-page__form {
padding-bottom: 72px;
}
+
+// 风险客商提示条(基本信息备注后,同合同新增页):橙黄背景 + 红色文字
+.payment-form-page__risk-tip {
+ margin: 0 0 18px;
+ padding: 10px 16px;
+ background: #faecd8;
+ border-radius: 4px;
+
+ .payment-form-page__risk-tip-title {
+ color: #f56c6c;
+ font-weight: 600;
+ line-height: 22px;
+ }
+
+ .payment-form-page__risk-tip-content {
+ color: #f56c6c;
+ line-height: 22px;
+ }
+}
+
.payment-form-page__settlement-nos {
display: flex;
align-items: center;
diff --git a/src/views/payment/payment-application.vue b/src/views/payment/payment-application.vue
index baf454c..45b8fe6 100644
--- a/src/views/payment/payment-application.vue
+++ b/src/views/payment/payment-application.vue
@@ -331,6 +331,11 @@ export default {
// 防止重复点击:请求进行中忽略后续点击
if (this.approveLoading) return;
this.approveLoading = true;
+ const loading = this.$loading({
+ lock: true,
+ text: '审批处理中,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
try {
const res = await api.approve({ id: row.id });
// data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
@@ -341,6 +346,7 @@ export default {
}
this.refreshChange();
} finally {
+ loading.close();
this.approveLoading = false;
}
},
diff --git a/src/views/settlement/formal-settlement.vue b/src/views/settlement/formal-settlement.vue
index 742cdb7..3c7926c 100644
--- a/src/views/settlement/formal-settlement.vue
+++ b/src/views/settlement/formal-settlement.vue
@@ -140,6 +140,7 @@ export default {
rows: [],
selection: [],
loading: false,
+ approveLoading: false,
syncLoading: false,
page: { currentPage: 1, pageSize: 10, pageSizes: [10, 20, 50, 100], total: 0 },
editor: { visible: false, id: null, readonly: false },
@@ -371,19 +372,33 @@ export default {
});
},
async handleApprove(row) {
- await this.$confirm(
- `审核通过后${this.billLabel}生效,金额只能通过结算调整单处理,确认通过?`,
- '提示',
- { type: 'warning' }
- );
- const res = await api.approve({ id: row.id });
- // data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
- if (res.data.data === false) {
- this.$message.warning(res.data.msg);
- } else {
- this.$message.success(res.data.msg || '审批通过');
+ // 防止重复点击:请求进行中忽略后续点击,避免重复审批和重复提示
+ if (this.approveLoading) return;
+ this.approveLoading = true;
+ let loading = null;
+ try {
+ await this.$confirm(
+ `审核通过后${this.billLabel}生效,金额只能通过结算调整单处理,确认通过?`,
+ '提示',
+ { type: 'warning' }
+ );
+ loading = this.$loading({
+ lock: true,
+ text: '审批处理中,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ const res = await api.approve({ id: row.id });
+ // data=false 表示审批已通过但自动同步金蝶失败,msg 带具体原因
+ if (res.data.data === false) {
+ this.$message.warning(res.data.msg);
+ } else {
+ this.$message.success(res.data.msg || '审批通过');
+ }
+ this.onLoad(this.page);
+ } finally {
+ loading?.close();
+ this.approveLoading = false;
}
- this.onLoad(this.page);
},
async handleReturn(row) {
const { value } = await this.$prompt('请输入驳回原因', '审批驳回', {
diff --git a/src/views/settlement/pre-settlement.vue b/src/views/settlement/pre-settlement.vue
index dda0871..8648db4 100644
--- a/src/views/settlement/pre-settlement.vue
+++ b/src/views/settlement/pre-settlement.vue
@@ -334,6 +334,7 @@ export default {
ArrowDown,
ArrowUp,
loading: false,
+ approveLoading: false,
query: emptyQuery(),
searchFields: preSettlementSearchFields,
searchExpanded: false,
@@ -691,10 +692,24 @@ export default {
});
},
async handleApprove(row) {
- await this.$confirm('确认审批通过该预付结算单?', '提示', { type: 'warning' });
- await approve({ id: row.id });
- this.$message.success('审批通过');
- this.loadTable();
+ // 防止重复点击:请求进行中忽略后续点击,避免重复审批和重复提示
+ if (this.approveLoading) return;
+ this.approveLoading = true;
+ let loading = null;
+ try {
+ await this.$confirm('确认审批通过该预付结算单?', '提示', { type: 'warning' });
+ loading = this.$loading({
+ lock: true,
+ text: '审批处理中,请稍候…',
+ background: 'rgba(255, 255, 255, 0.6)',
+ });
+ await approve({ id: row.id });
+ this.$message.success('审批通过');
+ this.loadTable();
+ } finally {
+ loading?.close();
+ this.approveLoading = false;
+ }
},
async handleReturn(row) {
const { value } = await this.$prompt('请输入驳回原因', '审批驳回', {
diff --git a/src/views/vehicle/customer-archive.vue b/src/views/vehicle/customer-archive.vue
index 68a4d1d..c08062d 100644
--- a/src/views/vehicle/customer-archive.vue
+++ b/src/views/vehicle/customer-archive.vue
@@ -47,6 +47,29 @@
{{ row.customerCode }}
+
+
+
+ {{ customerRiskMap[row.customerCode].riskTip || '该客商为高风险/黑名单客商' }}
+
+
+ {{ customerRiskLabel(customerRiskMap[row.customerCode]) }}
+
+
+
+ {{ customerRiskLabel(customerRiskMap[row.customerCode]) }}
+
+ -
+
+
+
+ {{ archiveNameRiskTip || '该客商为高风险/黑名单客商' }}
+
+ 高风险/黑名单客商
+
@@ -1556,6 +1591,7 @@ import {
queryKingdeeCustomer,
syncKingdee,
} from '@/api/vehicle/customer-archive';
+import { getRiskBatch, getRiskByName } from '@/api/business/hcs';
import {
getList as getCreditScoreQuantificationList,
getDetail as getCreditScoreQuantificationDetail,
@@ -1654,6 +1690,9 @@ export default {
deptSearchInitialized: false,
loading: true,
data: [],
+ customerRiskMap: {},
+ archiveNameRisky: false,
+ archiveNameRiskTip: '',
page: {
pageSize: 10,
pageSizes: [10, 20, 50, 100],
@@ -1911,6 +1950,12 @@ export default {
{ label: '内部组织', value: 'internal' },
],
},
+ {
+ label: '客商风险',
+ prop: 'customerRisk',
+ slot: true,
+ minWidth: 100,
+ },
{
label: '客户性质',
prop: 'customerNature',
@@ -2469,6 +2514,7 @@ export default {
form.kingdeeStatus = 'exist';
// 工商信息以金蝶为准,直接覆盖(后续字段锁定)
if (customer.name) form.fullName = customer.name;
+ if (customer.name) this.checkArchiveNameRisk();
if (customer.artificialperson) form.legalPerson = customer.artificialperson;
if (customer.bizpartnerAddress) form.registeredDetailAddress = customer.bizpartnerAddress;
if (customer.regcapital) {
@@ -3924,6 +3970,7 @@ export default {
const scope = this.getBusinessLicenseOcrValue(result, ['经营范围']);
if (code) this.archiveForm.unifiedCreditCode = code.replace(/\s/g, '').toUpperCase();
if (name) this.archiveForm.fullName = name;
+ if (name) this.checkArchiveNameRisk();
if (legalPerson) this.archiveForm.legalPerson = legalPerson;
if (address) {
this.archiveForm.registeredAddress = address;
@@ -4345,6 +4392,7 @@ export default {
this.selectedQualificationFiles = [];
if (this.isPublicViewPage) this.ensurePublicViewOptions(this.archiveForm);
this.applyFixedOwnerDept();
+ this.checkArchiveNameRisk();
this.archiveBox = true;
this.loadChangeRecords();
if (this.reviewMode) this.openReviewScore();
@@ -4363,6 +4411,7 @@ export default {
this.ocrQualificationUploadFiles = [];
this.selectedQualificationFiles = [];
this.applyFixedOwnerDept();
+ this.checkArchiveNameRisk();
this.archiveBox = true;
},
resetArchive() {
@@ -5478,12 +5527,52 @@ export default {
const pageData = res.data.data;
this.page.total = pageData.total;
this.data = pageData.records;
+ this.loadCustomerRisk(this.data);
this.selectionClear();
})
.finally(() => {
this.loading = false;
});
},
+ // 客商风险标签文案:后端 riskLevelName 转义(黑名单/高风险/中风险/低风险),缺失时兜底
+ customerRiskLabel(risk) {
+ if (!risk) return '';
+ return risk.riskLevelName || (risk.risky ? '风险客商' : '正常');
+ },
+ // 客商名称风险校验(C101-1,仅展示不作为安全边界):名称失焦/回显时查询,
+ // risky 在名称框下展示红色提示,悬浮见风险项明细;查询失败静默不阻断填写,提交链路由后端强校验兜底
+ checkArchiveNameRisk() {
+ this.archiveNameRisky = false;
+ this.archiveNameRiskTip = '';
+ const customerName = String(this.archiveForm.fullName || '').trim();
+ if (!customerName) return;
+ getRiskByName(customerName)
+ .then(res => {
+ const risk = (res.data && res.data.data) || {};
+ this.archiveNameRisky = risk.risky === true;
+ this.archiveNameRiskTip = risk.riskTip || '';
+ })
+ .catch(() => {
+ // 忽略风险查询异常,表单正常填写
+ });
+ },
+ // 客商列表风险批量查询(C101-1,仅展示不作为安全边界):失败静默,行内显示"-",提交链路由后端强校验兜底
+ loadCustomerRisk(rows) {
+ this.customerRiskMap = {};
+ const customerCodes = [...new Set(rows.map(row => row.customerCode).filter(Boolean))];
+ if (!customerCodes.length) return;
+ getRiskBatch(customerCodes)
+ .then(res => {
+ const riskMap = {};
+ ((res.data && res.data.data) || []).forEach(risk => {
+ if (risk && risk.customerCode) riskMap[risk.customerCode] = risk;
+ });
+ this.customerRiskMap = riskMap;
+ })
+ .catch(() => {
+ // 忽略风险查询异常,列表正常展示
+ });
+ },
handleExport() {
this.$confirm('是否导出客商档案数据?', '提示', {
confirmButtonText: '确定',
@@ -5579,6 +5668,20 @@ export default {
color: var(--el-color-danger);
}
+// 风险客商悬浮提示:popper 挂在 body 下,需用 :global 约束宽度
+:global(.customer-risk-popper) {
+ max-width: 360px;
+ line-height: 1.6;
+}
+
+// 客商名称框下方的高风险/黑名单提示(对齐表单错误文案形态)
+.archive-name-risk {
+ color: var(--el-color-danger);
+ font-size: 12px;
+ line-height: 1.5;
+ cursor: default;
+}
+
.fund-use-risk-none {
color: #c0c4cc;
}