✨ 新增合同变更菜单列表与MK审批对接

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-27 18:34:11 +08:00
co-authored by Cursor
parent 2b51619728
commit 450505715a
9 changed files with 905 additions and 66 deletions
+140 -62
View File
@@ -1,12 +1,20 @@
<template>
<basic-container class="contract-change-page">
<div class="archive-page-form__title">{{ pageTitle }}</div>
<el-form ref="formRef" :model="form" :rules="rules" label-position="right" label-width="auto" class="contract-change-form">
<el-form
ref="formRef"
:model="form"
:rules="rules"
:disabled="pageReadonly"
label-position="right"
label-width="auto"
class="contract-change-form"
>
<section class="change-section contract-basic-section">
<div class="dialog-section-title">基本信息</div>
<div class="contract-basic-section__grid">
<el-form-item label="变更类型" class="contract-basic-section__change-type">
<el-radio-group v-model="form.changeType">
<el-radio-group v-model="form.changeType" :disabled="pageReadonly">
<el-radio label="合同信息变更" />
<el-radio label="结算规则变更" />
<el-radio label="终止合同" />
@@ -280,7 +288,7 @@
<div class="section-head">
<div class="dialog-section-title">计费信息</div>
<el-button
v-if="feeGenerationMode !== 'manual'"
v-if="!pageReadonly && feeGenerationMode !== 'manual'"
type="primary"
plain
@click="addPlan"
@@ -301,6 +309,7 @@
row.defaultPlan ? '是' : '否'
}}</template></el-table-column
><el-table-column prop="remark" label="备注" min-width="260" /><el-table-column
v-if="!pageReadonly"
label="操作"
width="180"
><template #default="{ row, $index }"
@@ -416,7 +425,7 @@
:value="item.value"
/></el-select></template
></el-table-column>
<el-table-column label="操作" width="100"
<el-table-column v-if="!pageReadonly" label="操作" width="100"
><template #default="{ $index }"
><el-link v-if="$index === 0" type="primary" @click="addCustomPeriodRow">添加</el-link
><el-link v-else type="danger" @click="removeCustomPeriodRow($index)"
@@ -425,7 +434,7 @@
></el-table-column
>
<template #empty
><el-link type="primary" @click="addCustomPeriodRow">添加</el-link></template
><el-link v-if="!pageReadonly" type="primary" @click="addCustomPeriodRow">添加</el-link></template
>
</el-table>
</section>
@@ -461,7 +470,7 @@
:disabled="isSettlementRuleChange"
/></template
></el-table-column>
<el-table-column v-if="!isSettlementRuleChange" label="操作" width="100"
<el-table-column v-if="!pageReadonly && !isSettlementRuleChange" label="操作" width="100"
><template #default="{ $index }"
><el-link type="danger" @click="paymentRatioRows.splice($index, 1)"
>删除</el-link
@@ -470,7 +479,7 @@
>
</el-table>
<el-button
v-if="!isSettlementRuleChange"
v-if="!pageReadonly && !isSettlementRuleChange"
type="primary"
plain
@click="addPaymentRatioRow"
@@ -525,8 +534,9 @@
@update:rows="changeMaterials = $event"
@upload-to-location="handleAttachmentUploadToLocation"
/>
<div class="page-footer">
<el-button @click="$router.back()">取消</el-button>
<div v-if="!pageReadonly" class="page-footer">
<el-button @click="goBack">取消</el-button>
<el-button v-if="isContractChangeModule" @click="saveDraft">保存草稿</el-button>
<el-button type="primary" @click="submit">提交</el-button>
</div>
</el-form>
@@ -539,7 +549,9 @@
</template>
<script>
import * as api from '@/api/business/contract-manage';
import * as contractApi from '@/api/business/contract-manage';
import * as changeApi from '@/api/business/contract-change';
import { getMkPublicDetail } from '@/api/mk-process';
import { loadMkEnabled, submitMkApprovalFlow } from '@/utils/mk-approval';
import BillingPlanEditor from './components/billing-plan-editor.vue';
import ContractAttachmentSection, {
@@ -577,6 +589,8 @@ const defaultContractFormatOptions = [
{ label: '电子合同', value: '电子合同' },
{ label: '纸质合同', value: '纸质合同' },
];
const contractChangeListRoute = '/business/contract-change';
const contractChangeListRefreshKey = 'contract-change-list-refresh';
const normalizeOptionalPositiveInteger = value => {
if (value === undefined || value === null || value === '') return null;
const number = Number(value);
@@ -713,8 +727,21 @@ export default {
},
computed: {
pageTitle() {
if (this.pageReadonly) return this.$route.query.name || '查看合同变更';
return this.$route.query.name || '合同变更';
},
isContractChangeModule() {
return (
this.$route.path === '/business/contract-change/form' ||
this.$route.path === '/business/contract-change/public-view'
);
},
isPublicViewPage() {
return this.$route.path === '/business/contract-change/public-view';
},
pageReadonly() {
return this.isPublicViewPage || this.$route.query.mode === 'view';
},
// 高风险/黑名单客商提示:按合同推送合规留痕 hcs_risk_level/hcs_black_status 判定(与合同详情口径一致)
riskTipText() {
const riskLevel = String(this.form.hcsRiskLevel || '').trim();
@@ -833,13 +860,25 @@ export default {
async load() {
const id = this.$route.query.id;
if (!id) return;
const res = await api.getDetail(id);
const data = res.data?.data || res.data || {};
let data = {};
if (this.isPublicViewPage) {
const res = await getMkPublicDetail('contract-change', id);
data = res.data?.data || res.data || {};
} else if (this.isContractChangeModule) {
const res = await changeApi.getDetail(id);
data = res.data?.data || res.data || {};
} else {
const res = await contractApi.getDetail(id);
data = res.data?.data || res.data || {};
}
const isChangeRecord = this.isContractChangeModule || this.isPublicViewPage;
this.form = {
...data,
changeContent: '',
changeReason: '',
changeAttachmentsJson: '',
// 独立变更单保留 contractId;旧路由以合同 id 作为主键
contractId: data.contractId || (isChangeRecord ? data.contractId : data.id),
changeContent: isChangeRecord ? data.changeContent || '' : '',
changeReason: isChangeRecord ? data.changeReason || '' : '',
changeAttachmentsJson: data.changeAttachmentsJson || '',
copyCount: normalizeOptionalPositiveInteger(data.copyCount),
invoiceCycle: normalizeOptionalPositiveInteger(data.invoiceCycle),
paymentDays: normalizeOptionalPositiveInteger(data.paymentDays),
@@ -847,14 +886,14 @@ export default {
templateFlag: data.templateFlag ?? 0,
electronicSealFlag: data.electronicSealFlag ?? 0,
archiveStatus: data.archiveStatus || '未归档',
changeType: '合同信息变更',
changeType: data.changeType || '合同信息变更',
};
this.changeMaterials = [];
this.changeMaterials = this.parse(data.changeAttachmentsJson);
this.period = data.startDate && data.endDate ? [data.startDate, data.endDate] : [];
this.plans = this.parse(data.billingPlanJson);
this.attachments = this.mergeChangeAttachments(
this.parse(data.attachmentsJson),
this.parse(data.changeAttachmentsJson)
isChangeRecord ? [] : this.parse(data.changeAttachmentsJson)
);
this.contractFileRows = normalizeContractFileRows(this.parse(data.contractFileJson));
const rules = this.parseObject(data.settlementRuleJson);
@@ -896,6 +935,70 @@ export default {
data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system');
this.paymentRatioRows = this.parse(data.paymentRatioJson);
},
goBack() {
if (this.isContractChangeModule) {
this.$router.push(contractChangeListRoute);
return;
}
this.$router.back();
},
buildChangePayload() {
if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
else this.formalSettlementConfig = { ...this.settlementRule };
const settlementRule = {
preSettlementConfig: this.preSettlementConfig,
formalSettlementConfig: this.formalSettlementConfig,
};
if (this.isSettlementRuleChange) {
return {
id: this.form.id,
contractId: this.form.contractId,
changeType: this.form.changeType,
changeContent: this.form.changeContent,
changeReason: this.form.changeReason,
feeGenerationMode: this.feeGenerationMode,
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
billingPlanJson: JSON.stringify(this.plans),
settlementRuleJson: JSON.stringify(settlementRule),
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
};
}
return {
...this.form,
contractId: this.form.contractId,
changeType: this.form.changeType || '合同信息变更',
settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB',
copyCount: normalizeOptionalPositiveInteger(this.form.copyCount),
invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle),
paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays),
contractAmount: normalizeOptionalAmount(this.form.contractAmount),
startDate: this.period[0],
endDate: this.period[1],
feeGenerationMode: this.feeGenerationMode,
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
billingPlanJson: JSON.stringify(this.plans),
settlementRuleJson: JSON.stringify(settlementRule),
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
paymentRatioJson: JSON.stringify(this.paymentRatioRows),
contractFileJson: JSON.stringify(this.contractFileRows),
attachmentsJson: JSON.stringify(this.attachments),
changeContent: this.form.changeContent,
changeReason: this.form.changeReason,
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
};
},
async saveDraft() {
if (this.form.changeType === '终止合同') {
this.$message.warning('终止合同无需保存草稿,请直接提交');
return;
}
const payload = this.buildChangePayload();
await changeApi.saveDraft(payload);
this.$message.success('草稿已保存');
},
parse(value) {
try {
const result = JSON.parse(value || '[]');
@@ -989,9 +1092,10 @@ export default {
validateSettlementRule(rule, label) { if (Number(rule.autoGenerate) !== 1) return true; if (!rule.billStartDate || !rule.settlementType) { this.$message.warning(`${label}:请完整填写账单起始日和结算类型`); return false; } if (rule.settlementType === '月结' && !rule.billCycleType) { this.$message.warning(`${label}:请选择结算周期`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '固定截单日' && !rule.billCutoffDay) { this.$message.warning(`${label}:请选择账单截单日`); return false; } if (rule.settlementType === '月结' && rule.billCycleType === '自定义多周期') return this.validateCustomPeriods(rule.customPeriods, label); if (rule.settlementType === '固定天数周期结算' && !rule.cycleDays) { this.$message.warning(`${label}:请选择周期天数`); return false; } return true; },
async submit() {
if (this.form.changeType === '终止合同') {
await api.terminate(this.form.id, this.form.changeReason);
const contractId = this.form.contractId || this.form.id;
await contractApi.terminate(contractId, this.form.changeReason);
this.$message.success('合同已终止');
this.$router.back();
this.goBack();
return;
}
await this.$refs.formRef.validate();
@@ -1020,48 +1124,22 @@ export default {
) {
return;
}
const settlementRule = {
preSettlementConfig: this.preSettlementConfig,
formalSettlementConfig: this.formalSettlementConfig,
};
const payload = this.isSettlementRuleChange
? {
id: this.form.id,
changeType: this.form.changeType,
changeContent: this.form.changeContent,
changeReason: this.form.changeReason,
feeGenerationMode: this.feeGenerationMode,
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
billingPlanJson: JSON.stringify(this.plans),
settlementRuleJson: JSON.stringify(settlementRule),
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
}
: {
...this.form,
changeType: this.form.changeType || '合同信息变更',
settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB',
copyCount: normalizeOptionalPositiveInteger(this.form.copyCount),
invoiceCycle: normalizeOptionalPositiveInteger(this.form.invoiceCycle),
paymentDays: normalizeOptionalPositiveInteger(this.form.paymentDays),
contractAmount: normalizeOptionalAmount(this.form.contractAmount),
startDate: this.period[0],
endDate: this.period[1],
feeGenerationMode: this.feeGenerationMode,
billingEnabled: this.feeGenerationMode === 'system' ? 1 : 0,
billingPlanJson: JSON.stringify(this.plans),
settlementRuleJson: JSON.stringify(settlementRule),
preSettlementConfigJson: JSON.stringify(this.preSettlementConfig),
formalSettlementConfigJson: JSON.stringify(this.formalSettlementConfig),
paymentRatioJson: JSON.stringify(this.paymentRatioRows),
contractFileJson: JSON.stringify(this.contractFileRows),
attachmentsJson: JSON.stringify(this.attachments),
changeContent: this.form.changeContent,
changeReason: this.form.changeReason,
changeAttachmentsJson: JSON.stringify(this.changeMaterials),
};
await api.submitChange(payload);
const payload = this.buildChangePayload();
if (this.isContractChangeModule) {
await changeApi.submit(payload);
await submitMkApprovalFlow({
bizType: 'contract-change',
bizId: this.form.id,
subjectName: this.form.contractName || '',
approvalStatus: this.form.approvalStatus || '',
mkEnabled: await loadMkEnabled().catch(() => false),
});
sessionStorage.setItem(contractChangeListRefreshKey, '1');
this.$message.success('变更已提交');
this.$router.push(contractChangeListRoute);
return;
}
await contractApi.submitChange(payload);
await submitMkApprovalFlow({
bizType: 'contract-manage',
bizId: this.form.id,