feat: 立项与合同HCS前端接入——列表合规状态列/推送合规/合规对账弹窗、合同详情合规信息面板与特批发起、甲乙方客商风险提示、合规主数据页
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
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 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,
|
||||
});
|
||||
|
||||
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 reconProject = projectCode =>
|
||||
request({
|
||||
url: `${baseUrl}/recon/project`,
|
||||
method: 'get',
|
||||
params: { projectCode },
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
export const reconContract = contractNo =>
|
||||
request({
|
||||
url: `${baseUrl}/recon/contract`,
|
||||
method: 'get',
|
||||
params: { contractNo },
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
export const reconContractDetail = params =>
|
||||
request({
|
||||
url: `${baseUrl}/recon/contract/detail`,
|
||||
method: 'get',
|
||||
params,
|
||||
timeout: 30000,
|
||||
});
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
export const transportTypeOptions = [
|
||||
export const transportTypeOptions = [
|
||||
{ label: '公路运输', value: '公路运输' },
|
||||
{ label: '铁路运输', value: '铁路运输' },
|
||||
{ label: '水路运输', value: '水路运输' },
|
||||
@@ -142,6 +142,12 @@ export const contractArchiveStatusOptions = [
|
||||
{ label: '已归档', value: '已归档' },
|
||||
];
|
||||
|
||||
export const hcsSyncStatusOptions = [
|
||||
{ label: '待推送', value: 'unsynced' },
|
||||
{ label: '已推送', value: 'synced' },
|
||||
{ label: '推送失败', value: 'failed' },
|
||||
];
|
||||
|
||||
export const nodeOptions = [
|
||||
{ label: '接单', value: '接单' },
|
||||
{ label: '到场', value: '到场' },
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
contractStageOptions,
|
||||
createCrudOption,
|
||||
effectiveTypeOptions,
|
||||
hcsSyncStatusOptions,
|
||||
selectRule,
|
||||
signTypeOptions,
|
||||
textRule,
|
||||
@@ -589,6 +590,19 @@ 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: '当前节点',
|
||||
prop: 'currentNode',
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
businessModeOptions,
|
||||
businessTypeOptions,
|
||||
createCrudOption,
|
||||
hcsSyncStatusOptions,
|
||||
nonNegativeRule,
|
||||
projectApprovalStatusOptions,
|
||||
projectSourceOptions,
|
||||
@@ -405,6 +406,19 @@ 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: '生效类型',
|
||||
prop: 'effectiveType',
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
<template>
|
||||
<section class="contract-manage-form__section contract-manage-form__section--panel hcs-contract-panel">
|
||||
<div class="contract-manage-form__attachment-head">
|
||||
<div class="dialog-section-title">合规信息</div>
|
||||
<div>
|
||||
<el-button
|
||||
v-if="canApplySpec"
|
||||
type="warning"
|
||||
plain
|
||||
:loading="specApplying"
|
||||
@click="openSpecDialog"
|
||||
>
|
||||
发起特批
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="isSpecProcessing"
|
||||
type="primary"
|
||||
plain
|
||||
@click="confirmSpecResult"
|
||||
>
|
||||
确认特批结果
|
||||
</el-button>
|
||||
<el-button type="primary" plain @click="reconVisible = true">合规对账</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-descriptions :column="3" class="contract-manage-detail__descriptions">
|
||||
<el-descriptions-item label="合规同步状态">
|
||||
<el-tooltip
|
||||
v-if="hcsSyncMsg"
|
||||
placement="top"
|
||||
:content="hcsSyncMsg"
|
||||
>
|
||||
<el-tag :type="syncTagType" class="status-text">{{ syncLabel }}</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else :type="syncTagType" class="status-text">{{ syncLabel }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="合规合同ID">{{
|
||||
contract.hcsFdId || '-'
|
||||
}}</el-descriptions-item>
|
||||
<el-descriptions-item label="客商风险">
|
||||
<template v-if="contract.hcsRiskLevel || contract.hcsBlackStatus">
|
||||
<el-tag
|
||||
v-if="isBlacklist"
|
||||
type="danger"
|
||||
class="status-text"
|
||||
>黑名单</el-tag
|
||||
>
|
||||
<span v-if="contract.hcsRiskLevel" class="hcs-contract-panel__risk">{{
|
||||
`风险等级:${contract.hcsRiskLevel}`
|
||||
}}</span>
|
||||
<span v-if="!contract.hcsRiskLevel && !isBlacklist">-</span>
|
||||
</template>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="特批状态">
|
||||
<el-tag v-if="specLabel" :type="specTagType" class="status-text">{{ specLabel }}</el-tag>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="特批说明" :span="2">{{
|
||||
contract.hcsSpecMsg || '-'
|
||||
}}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-dialog
|
||||
v-model="specDialogVisible"
|
||||
title="发起合同特批"
|
||||
width="640px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="hcs-contract-panel__spec-dialog"
|
||||
@closed="resetSpecDialog"
|
||||
>
|
||||
<el-alert
|
||||
title="高风险客商合同需经集团特批后方可签约推送;请上传特批申请材料及内部审批流程附件。"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
class="hcs-contract-panel__spec-alert"
|
||||
/>
|
||||
<el-upload
|
||||
drag
|
||||
multiple
|
||||
:show-file-list="false"
|
||||
:http-request="uploadMaterial"
|
||||
:on-error="() => uploadFailed()"
|
||||
class="hcs-contract-panel__spec-upload"
|
||||
>
|
||||
<el-icon class="el-icon--upload"><upload-filled /></el-icon>
|
||||
<div class="el-upload__text">将特批材料拖到此处,或<em>点击上传到合同系统</em></div>
|
||||
</el-upload>
|
||||
<el-table :data="materials" border class="hcs-contract-panel__spec-table">
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="name" label="文件名" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="fdId" label="合规附件ID" min-width="240" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="90" align="center" fixed="right">
|
||||
<template #default="{ $index }">
|
||||
<el-link type="danger" @click="materials.splice($index, 1)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<template #footer>
|
||||
<el-button type="primary" :loading="specApplying" @click="submitSpec">确认发起</el-button>
|
||||
<el-button @click="specDialogVisible = false">取消</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<hcs-recon-dialog
|
||||
v-model="reconVisible"
|
||||
title="合同合规对账"
|
||||
:loader="reconLoader"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { UploadFilled } from '@element-plus/icons-vue';
|
||||
import {
|
||||
applyContractSpec,
|
||||
confirmContractSpec,
|
||||
reconContract,
|
||||
uploadAttachment,
|
||||
} from '@/api/business/hcs';
|
||||
import HcsReconDialog from './hcs-recon-dialog.vue';
|
||||
|
||||
const SYNC_STATUS_OPTIONS = [
|
||||
{ label: '待推送', value: 'unsynced', type: 'info' },
|
||||
{ label: '已推送', value: 'synced', type: 'success' },
|
||||
{ label: '推送失败', value: 'failed', type: 'danger' },
|
||||
];
|
||||
|
||||
const SPEC_STATUS_OPTIONS = [
|
||||
{ label: '待特批', value: 'need', type: 'warning' },
|
||||
{ label: '特批中', value: 'processing', type: 'warning' },
|
||||
{ label: '特批通过', value: 'approved', type: 'success' },
|
||||
{ label: '特批驳回', value: 'rejected', type: 'danger' },
|
||||
];
|
||||
|
||||
export default {
|
||||
name: 'HcsContractPanel',
|
||||
components: { UploadFilled, HcsReconDialog },
|
||||
props: {
|
||||
// 合同详情行(须含 id/hcsSyncStatus/hcsFdId/hcsRiskLevel/hcsBlackStatus/hcsSpecStatus 等)
|
||||
contract: { type: Object, required: true },
|
||||
},
|
||||
emits: ['refresh'],
|
||||
data() {
|
||||
return {
|
||||
specDialogVisible: false,
|
||||
specApplying: false,
|
||||
materials: [],
|
||||
reconVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
syncLabel() {
|
||||
return SYNC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSyncStatus)?.label || '-';
|
||||
},
|
||||
syncTagType() {
|
||||
return SYNC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSyncStatus)?.type || 'info';
|
||||
},
|
||||
hcsSyncMsg() {
|
||||
return this.contract.hcsSyncMsg || '';
|
||||
},
|
||||
isBlacklist() {
|
||||
return String(this.contract.hcsBlackStatus || '').toLowerCase() === 'yes';
|
||||
},
|
||||
specLabel() {
|
||||
return SPEC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSpecStatus)?.label || '';
|
||||
},
|
||||
specTagType() {
|
||||
return SPEC_STATUS_OPTIONS.find(item => item.value === this.contract.hcsSpecStatus)?.type || 'info';
|
||||
},
|
||||
isSpecProcessing() {
|
||||
return this.contract.hcsSpecStatus === 'processing';
|
||||
},
|
||||
canApplySpec() {
|
||||
return ['need', 'rejected'].includes(this.contract.hcsSpecStatus);
|
||||
},
|
||||
reconLoader() {
|
||||
return () => reconContract(this.contract.contractNo);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openSpecDialog() {
|
||||
this.specDialogVisible = true;
|
||||
},
|
||||
resetSpecDialog() {
|
||||
this.materials = [];
|
||||
},
|
||||
// 自定义上传:直传合同合规系统(C103-2),成功记录合规附件ID
|
||||
uploadMaterial(options) {
|
||||
const file = options.file;
|
||||
if (file.size / 1024 / 1024 > 50) {
|
||||
this.$message.warning('单文件大小不能超过 50MB');
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('fdFile', file);
|
||||
uploadAttachment(formData)
|
||||
.then(response => {
|
||||
const fdId = response?.data?.data || response?.data;
|
||||
if (!fdId || typeof fdId !== 'string') {
|
||||
this.$message.error(response?.data?.msg || '上传合同系统失败');
|
||||
return;
|
||||
}
|
||||
this.materials.push({ fdId, name: file.name });
|
||||
this.$message.success(`已上传合同系统:${file.name}`);
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '上传合同系统失败');
|
||||
});
|
||||
},
|
||||
uploadFailed() {
|
||||
this.$message.error('上传合同系统失败');
|
||||
},
|
||||
submitSpec() {
|
||||
if (!this.materials.length) {
|
||||
this.$message.warning('请先上传特批申请材料');
|
||||
return;
|
||||
}
|
||||
this.specApplying = true;
|
||||
applyContractSpec({
|
||||
contractId: this.contract.id,
|
||||
attachments: this.materials.map(item => ({ fdId: item.fdId, name: item.name })),
|
||||
})
|
||||
.then(response => {
|
||||
if (response?.data?.success === false) {
|
||||
this.$message.error(response?.data?.msg || '特批发起失败');
|
||||
return;
|
||||
}
|
||||
this.$message.success(response?.data?.data || '特批发起成功,等待集团审批');
|
||||
this.specDialogVisible = false;
|
||||
this.$emit('refresh');
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '特批发起失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.specApplying = false;
|
||||
});
|
||||
},
|
||||
confirmSpecResult() {
|
||||
this.$prompt('请输入集团特批结果(approved=通过 / rejected=驳回)', '确认特批结果', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPattern: /^(approved|rejected)$/,
|
||||
inputErrorMessage: '请输入 approved 或 rejected',
|
||||
}).then(({ value }) => {
|
||||
confirmContractSpec(this.contract.id, value).then(() => {
|
||||
this.$message.success('特批结果已确认');
|
||||
this.$emit('refresh');
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hcs-contract-panel__risk {
|
||||
margin-left: 8px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.hcs-contract-panel__spec-alert {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.hcs-contract-panel__spec-upload {
|
||||
margin-bottom: 12px;
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-upload-dragger) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.hcs-contract-panel__spec-table {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
:title="title"
|
||||
width="720px"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
class="hcs-recon-dialog"
|
||||
@update:model-value="value => $emit('update:modelValue', value)"
|
||||
>
|
||||
<div v-loading="loading" class="hcs-recon-dialog__body">
|
||||
<el-alert
|
||||
v-if="error"
|
||||
:title="error"
|
||||
type="error"
|
||||
:closable="false"
|
||||
class="hcs-recon-dialog__alert"
|
||||
/>
|
||||
<template v-else-if="!loading">
|
||||
<div class="hcs-recon-dialog__download">
|
||||
<el-input
|
||||
v-model="downloadFdId"
|
||||
placeholder="合规附件ID(C103-5 下载)"
|
||||
clearable
|
||||
style="width: 320px"
|
||||
/>
|
||||
<el-input
|
||||
v-model="downloadName"
|
||||
placeholder="保存文件名(可选)"
|
||||
clearable
|
||||
style="width: 200px"
|
||||
/>
|
||||
<el-button type="primary" plain :disabled="!downloadFdId" @click="download">
|
||||
下载附件
|
||||
</el-button>
|
||||
</div>
|
||||
<pre class="hcs-recon-dialog__json">{{ jsonText }}</pre>
|
||||
</template>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button @click="$emit('update:modelValue', false)">关闭</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { downloadAttachment } from '@/api/business/hcs';
|
||||
|
||||
export default {
|
||||
name: 'HcsReconDialog',
|
||||
props: {
|
||||
modelValue: Boolean,
|
||||
title: { type: String, default: '合规对账' },
|
||||
// 对账加载函数:返回 Promise,resolve 结果对象
|
||||
loader: { type: Function, default: null },
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
error: '',
|
||||
result: null,
|
||||
downloadFdId: '',
|
||||
downloadName: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
jsonText() {
|
||||
if (this.result === null || this.result === undefined) return '';
|
||||
try {
|
||||
return JSON.stringify(this.result, null, 2);
|
||||
} catch (error) {
|
||||
return String(this.result);
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue(visible) {
|
||||
if (visible) this.load();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
if (!this.loader) return;
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
this.result = null;
|
||||
this.loader()
|
||||
.then(response => {
|
||||
const data = response?.data?.data ?? response?.data;
|
||||
this.result = data && typeof data === 'object' && 'data' in data ? data.data : data;
|
||||
})
|
||||
.catch(error => {
|
||||
this.error = error?.response?.data?.msg || error?.message || '合规对账查询失败';
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
download() {
|
||||
downloadAttachment(this.downloadFdId, this.downloadName || undefined)
|
||||
.then(response => {
|
||||
const blob = new Blob([response.data]);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = this.downloadName || `hcs-attachment-${this.downloadFdId}`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
this.$message.success('下载成功');
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('附件下载失败,请确认合规附件ID');
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hcs-recon-dialog__body {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.hcs-recon-dialog__alert {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.hcs-recon-dialog__download {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.hcs-recon-dialog__json {
|
||||
max-height: 420px;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
white-space: pre-wrap;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -72,6 +72,13 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
|
||||
<template #hcsSyncStatus="{ row }">
|
||||
<el-tag v-if="row.hcsSyncStatus" :type="hcsSyncTagType(row.hcsSyncStatus)" class="status-text">
|
||||
{{ hcsSyncLabel(row.hcsSyncStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #menu="{ row }">
|
||||
<el-link
|
||||
v-if="hasPermission(`${config.permission}_view`)"
|
||||
@@ -92,10 +99,20 @@
|
||||
>
|
||||
{{ operation.label }}
|
||||
</el-link>
|
||||
<el-link v-if="canHcsRepush(row)" type="warning" @click="handleHcsRepush(row)">
|
||||
{{ hcsRepushingId === row.id ? '推送中…' : '推送合规' }}
|
||||
</el-link>
|
||||
<el-link v-if="canHcsRecon(row)" type="info" @click="openHcsRecon(row)">合规对账</el-link>
|
||||
<el-link v-if="canDelete(row)" type="danger" @click="removeRow(row)">删除</el-link>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<hcs-recon-dialog
|
||||
v-model="hcsReconVisible"
|
||||
title="合同合规对账"
|
||||
:loader="hcsReconLoader"
|
||||
/>
|
||||
|
||||
<empty-pagination
|
||||
:page="page"
|
||||
@size-change="sizeChange"
|
||||
@@ -177,6 +194,12 @@
|
||||
</el-descriptions>
|
||||
</section>
|
||||
|
||||
<hcs-contract-panel
|
||||
v-if="!isPublicViewPage"
|
||||
:contract="detailRow"
|
||||
@refresh="initDetailPage"
|
||||
/>
|
||||
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
description
|
||||
@@ -436,6 +459,7 @@
|
||||
clearable
|
||||
:loading="contractPartyLoading"
|
||||
@visible-change="visible => visible && loadContractPartyOptions()"
|
||||
@change="queryPartyRisk('partyA')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractPartyAOptions"
|
||||
@@ -453,6 +477,7 @@
|
||||
clearable
|
||||
:loading="contractPartyLoading"
|
||||
@visible-change="visible => visible && loadContractPartyOptions()"
|
||||
@change="queryPartyRisk('partyB')"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in contractPartyBOptions"
|
||||
@@ -462,6 +487,14 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="partyRiskText" label=" " label-width="24">
|
||||
<el-alert
|
||||
:title="partyRiskText"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
class="contract-manage-form__risk-alert"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="签订日期" prop="signDate">
|
||||
<el-date-picker
|
||||
v-model="form.signDate"
|
||||
@@ -1010,6 +1043,7 @@ import NProgress from 'nprogress';
|
||||
import 'nprogress/nprogress.css';
|
||||
import { exportBlob } from '@/api/common';
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import { repush as hcsRepush, reconContract, getRisk } from '@/api/business/hcs';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import { getList as getProjectList } from '@/api/business/project-apply';
|
||||
import { getList as getCustomerArchiveList } from '@/api/vehicle/customer-archive';
|
||||
@@ -1027,6 +1061,8 @@ import ContractAttachmentSection, {
|
||||
attachmentUrl as sharedAttachmentUrl,
|
||||
normalizeContractFileRows,
|
||||
} from './components/contract-attachment-section.vue';
|
||||
import HcsReconDialog from './components/hcs-recon-dialog.vue';
|
||||
import HcsContractPanel from './components/hcs-contract-panel.vue';
|
||||
import PdfPreview from '@/components/pdf-preview/main.vue';
|
||||
import { ElImageViewer, ElCascader } from 'element-plus';
|
||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||
@@ -1183,6 +1219,8 @@ export default {
|
||||
ContractAttachmentSection,
|
||||
BillingPlanEditor,
|
||||
ChangeRecordDetailDialog,
|
||||
HcsReconDialog,
|
||||
HcsContractPanel,
|
||||
ElImageViewer,
|
||||
OpenFileViewer,
|
||||
PdfPreview,
|
||||
@@ -1246,6 +1284,9 @@ export default {
|
||||
contractCategoryDictOptions: [],
|
||||
contractPartyAOptions: [],
|
||||
contractPartyBOptions: [],
|
||||
// 合规客商风险(C101-1 展示用):甲方/乙方选择后即时查询
|
||||
contractPartyCodeMap: {},
|
||||
partyRisk: {},
|
||||
contractPartyLoading: false,
|
||||
contractPartyRequestId: 0,
|
||||
organizationOptions: [],
|
||||
@@ -1284,6 +1325,10 @@ export default {
|
||||
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
||||
detailLoading: false,
|
||||
detailRow: {},
|
||||
// 合规(HCS)推送/对账状态
|
||||
hcsRepushingId: null,
|
||||
hcsReconVisible: false,
|
||||
hcsReconLoader: null,
|
||||
detailContractFileRows: [],
|
||||
detailAttachmentRows: [],
|
||||
detailBillingPlanRows: [],
|
||||
@@ -1459,6 +1504,21 @@ 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) return;
|
||||
const name = this.form[prop] || '';
|
||||
const label = prop === 'partyA' ? '甲方' : '乙方';
|
||||
if (risk.blacklist) parts.push(`${label}[${name}]为黑名单客商`);
|
||||
else if (['高', '高风险', 'high'].includes(String(risk.riskLevel).trim()))
|
||||
parts.push(`${label}[${name}]为高风险客商`);
|
||||
});
|
||||
if (!parts.length) return '';
|
||||
return `${parts.join(';')},按集团规定不可直接签约,审批通过后需在合同详情发起特批`;
|
||||
},
|
||||
detailLegalSealText() {
|
||||
const value = this.detailRow.legalSealFlag;
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
@@ -1573,6 +1633,55 @@ 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'
|
||||
);
|
||||
},
|
||||
canHcsRecon(row) {
|
||||
return ['approved', 'change_approved'].includes(row.approvalStatus);
|
||||
},
|
||||
handleHcsRepush(row) {
|
||||
if (this.hcsRepushingId) return;
|
||||
this.$confirm('确定推送该数据到合同合规系统?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
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(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '推送合规失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.hcsRepushingId = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
openHcsRecon(row) {
|
||||
if (!row.contractNo) {
|
||||
this.$message.warning('该合同无合同编号,无法对账');
|
||||
return;
|
||||
}
|
||||
this.hcsReconLoader = () => reconContract(row.contractNo);
|
||||
this.hcsReconVisible = true;
|
||||
},
|
||||
canEdit(row) {
|
||||
return (
|
||||
this.hasPermission(`${this.config.permission}_edit`) &&
|
||||
@@ -2113,8 +2222,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);
|
||||
@@ -2127,6 +2249,29 @@ export default {
|
||||
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]: {
|
||||
riskLevel: risk.fdRiskLevel || '',
|
||||
blacklist: String(risk.fdBlackStatus || '').toLowerCase() === 'yes',
|
||||
},
|
||||
};
|
||||
})
|
||||
.catch(() => {
|
||||
this.partyRisk = { ...this.partyRisk, [prop]: null };
|
||||
});
|
||||
},
|
||||
loadOrganizationOptions() {
|
||||
if (this.organizationLoading) return;
|
||||
this.organizationLoading = true;
|
||||
@@ -2961,6 +3106,10 @@ export default {
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.contract-manage-form__risk-alert {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.contract-manage-page {
|
||||
&__expiry-search {
|
||||
display: flex;
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<basic-container class="hcs-master-data-page">
|
||||
<div class="hcs-master-data-page__head">
|
||||
<div class="dialog-section-title">合规主数据(HCS)</div>
|
||||
<el-button type="primary" :loading="refreshing" @click="handleRefresh">刷新缓存</el-button>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="合同模板" name="template" />
|
||||
</el-tabs>
|
||||
|
||||
<el-table v-loading="loading" :data="templates" border>
|
||||
<el-table-column type="index" label="序号" width="70" align="center" />
|
||||
<el-table-column prop="fdId" label="模板ID(fdTemplateId)" min-width="320" show-overflow-tooltip />
|
||||
<el-table-column prop="fdName" label="模板名称" min-width="220" show-overflow-tooltip />
|
||||
<el-table-column prop="fdStatus" label="状态" width="100" align="center" />
|
||||
<el-table-column prop="fdContractCharacter" label="合同性质" min-width="140" align="center" />
|
||||
<el-table-column label="创建时间" min-width="170" align="center">
|
||||
<template #default="{ row }">{{ formatTime(row.fdCreateTime) }}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-alert
|
||||
class="hcs-master-data-page__tip"
|
||||
title="合同类别与合规模板的对应关系由 Nacos 配置 thirdParty.hcs.contract.template-mapping 维护;此列表用于核对映射的模板ID 是否存在。"
|
||||
type="info"
|
||||
:closable="false"
|
||||
/>
|
||||
</basic-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getTemplates, refreshMasterData } from '@/api/business/hcs';
|
||||
|
||||
export default {
|
||||
name: 'HcsMasterData',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'template',
|
||||
loading: false,
|
||||
refreshing: false,
|
||||
templates: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.loadTemplates();
|
||||
},
|
||||
methods: {
|
||||
loadTemplates() {
|
||||
this.loading = true;
|
||||
getTemplates()
|
||||
.then(response => {
|
||||
const data = response?.data?.data ?? response?.data;
|
||||
this.templates = Array.isArray(data) ? data : [];
|
||||
})
|
||||
.catch(() => {
|
||||
this.$message.error('合规模板列表加载失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
handleRefresh() {
|
||||
this.refreshing = true;
|
||||
refreshMasterData()
|
||||
.then(response => {
|
||||
this.$message.success(response?.data?.data || '合规主数据缓存已刷新');
|
||||
this.loadTemplates();
|
||||
})
|
||||
.catch(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '刷新失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.refreshing = false;
|
||||
});
|
||||
},
|
||||
formatTime(value) {
|
||||
if (!value) return '-';
|
||||
return this.$dayjs(Number(value)).format('YYYY-MM-DD HH:mm:ss');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hcs-master-data-page__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.dialog-section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hcs-master-data-page__tip {
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -58,6 +58,17 @@
|
||||
{{ displayStatus(row, 'approvalStatus') }}
|
||||
</template>
|
||||
|
||||
<template #hcsSyncStatus="{ row }">
|
||||
<el-tag
|
||||
v-if="row.hcsSyncStatus"
|
||||
:type="hcsSyncTagType(row.hcsSyncStatus)"
|
||||
class="status-text"
|
||||
>
|
||||
{{ hcsSyncLabel(row.hcsSyncStatus) }}
|
||||
</el-tag>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
|
||||
<template #header>
|
||||
<div class="project-apply-page__risk-search">
|
||||
<span class="project-apply-page__risk-label">
|
||||
@@ -128,10 +139,26 @@
|
||||
>
|
||||
{{ operation.label }}
|
||||
</el-link>
|
||||
<el-link
|
||||
v-if="canHcsRepush(row)"
|
||||
type="warning"
|
||||
@click="handleHcsRepush(row)"
|
||||
>
|
||||
{{ hcsRepushingId === row.id ? '推送中…' : '推送合规' }}
|
||||
</el-link>
|
||||
<el-link v-if="canHcsRecon(row)" type="info" @click="openHcsRecon(row)">
|
||||
合规对账
|
||||
</el-link>
|
||||
<el-link type="danger" v-if="canDelete(row)" @click="rowDel(row)"> 删除 </el-link>
|
||||
</template>
|
||||
</avue-crud>
|
||||
|
||||
<hcs-recon-dialog
|
||||
v-model="hcsReconVisible"
|
||||
title="项目立项合规对账"
|
||||
:loader="hcsReconLoader"
|
||||
/>
|
||||
|
||||
<empty-pagination
|
||||
v-if="!isProjectFormPage"
|
||||
:page="page"
|
||||
@@ -902,6 +929,7 @@
|
||||
import { exportBlob } from '@/api/common';
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import * as api from '@/api/business/project-apply';
|
||||
import { repush as hcsRepush, reconProject } from '@/api/business/hcs';
|
||||
import { getMkPublicDetail } from '@/api/mk-process';
|
||||
import {
|
||||
getList as getCustomerArchiveList,
|
||||
@@ -1042,6 +1070,7 @@ export default {
|
||||
name: 'ProjectApply',
|
||||
components: {
|
||||
CustomerArchive: defineAsyncComponent(() => import('@/views/vehicle/customer-archive.vue')),
|
||||
HcsReconDialog: defineAsyncComponent(() => import('./components/hcs-recon-dialog.vue')),
|
||||
ChangeRecordDetailDialog,
|
||||
ElImageViewer,
|
||||
OpenFileViewer,
|
||||
@@ -1107,6 +1136,10 @@ export default {
|
||||
{ label: '高风险', value: 'high', statKey: 'high' },
|
||||
{ label: '中风险', value: 'medium', statKey: 'medium' },
|
||||
],
|
||||
// 合规(HCS)推送/对账状态
|
||||
hcsRepushingId: null,
|
||||
hcsReconVisible: false,
|
||||
hcsReconLoader: null,
|
||||
page: {
|
||||
pageSize: 10,
|
||||
pageSizes: [10, 20, 50, 100],
|
||||
@@ -1578,6 +1611,56 @@ 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'
|
||||
);
|
||||
},
|
||||
canHcsRecon(row) {
|
||||
return ['approved', 'change_approved'].includes(row.approvalStatus);
|
||||
},
|
||||
handleHcsRepush(row) {
|
||||
if (this.hcsRepushingId) return;
|
||||
this.$confirm('确定推送该数据到合同合规系统?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}).then(() => {
|
||||
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(error => {
|
||||
this.$message.error(error?.response?.data?.msg || '推送合规失败');
|
||||
})
|
||||
.finally(() => {
|
||||
this.hcsRepushingId = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
openHcsRecon(row) {
|
||||
if (!row.projectCode) {
|
||||
this.$message.warning('该项目无项目编号,无法对账');
|
||||
return;
|
||||
}
|
||||
this.hcsReconLoader = () => reconProject(row.projectCode);
|
||||
this.hcsReconVisible = true;
|
||||
},
|
||||
canOperation(operation, row) {
|
||||
const permission = operation.permission || `${this.config.permission}_${operation.action}`;
|
||||
if (!this.hasPermission(permission) || this.readonlyScope || row.readonly) return false;
|
||||
|
||||
Reference in New Issue
Block a user