Merge pull request '项目+合同对接合同系统' (#8) from feature/hcs-api-p0-web into master

Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
2026-10-27 14:47:14 +08:00
26 changed files with 1304 additions and 55 deletions
@@ -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('上传成功');
},
@@ -0,0 +1,271 @@
<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>
</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>
</section>
</template>
<script>
import { UploadFilled } from '@element-plus/icons-vue';
import {
applyContractSpec,
confirmContractSpec,
uploadAttachment,
} from '@/api/business/hcs';
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 },
props: {
// 合同详情行(须含 id/hcsSyncStatus/hcsFdId/hcsRiskLevel/hcsBlackStatus/hcsSpecStatus 等)
contract: { type: Object, required: true },
},
emits: ['refresh'],
data() {
return {
specDialogVisible: false,
specApplying: false,
materials: [],
};
},
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);
},
},
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>