调整 合同、运力、客商、项目
This commit is contained in:
@@ -1,48 +1,199 @@
|
||||
<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">
|
||||
<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 label="合同信息变更" /><el-radio label="终止合同" /></el-radio-group></el-form-item>
|
||||
<el-form-item label="合同编号"><el-input v-model="form.contractNo" disabled /></el-form-item>
|
||||
<el-form-item label="合同名称" prop="contractName"><el-input v-model="form.contractName" /></el-form-item>
|
||||
<el-form-item label="合同类型"><el-select v-model="form.contractCategory" disabled><el-option label="客户合同" value="客户合同" /><el-option label="承运商合同" value="承运商合同" /></el-select></el-form-item>
|
||||
<el-form-item label="甲方"><el-input v-model="form.partyA" disabled /></el-form-item>
|
||||
<el-form-item label="乙方"><el-input v-model="form.partyB" disabled /></el-form-item>
|
||||
<el-form-item label="所属项目"><el-input v-model="form.projectName" disabled /></el-form-item>
|
||||
<el-form-item label="变更类型" class="contract-basic-section__change-type">
|
||||
<el-radio-group v-model="form.changeType">
|
||||
<el-radio label="合同信息变更" />
|
||||
<el-radio label="终止合同" />
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="签约类型" prop="signType">
|
||||
<el-select v-model="form.signType" placeholder="请选择签约类型" clearable>
|
||||
<el-option
|
||||
v-for="item in signTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同编号">
|
||||
<el-input v-model="form.contractNo" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="合同名称" prop="contractName">
|
||||
<el-input
|
||||
v-model="form.contractName"
|
||||
maxlength="100"
|
||||
show-word-limit
|
||||
placeholder="请输入合同名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同类型">
|
||||
<el-select v-model="form.contractCategory" disabled>
|
||||
<el-option
|
||||
v-for="item in contractCategoryOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目">
|
||||
<el-input v-model="form.projectName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织">
|
||||
<el-input v-model="form.organizationName" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="甲方">
|
||||
<el-input v-model="form.partyA" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="乙方">
|
||||
<el-input v-model="form.partyB" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="签订日期">
|
||||
<el-date-picker
|
||||
v-model="form.signDate"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
disabled
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同期限">
|
||||
<div class="contract-basic-section__date-range">
|
||||
<el-date-picker v-model="period[0]" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="YYYY-MM-DD" />
|
||||
<el-date-picker
|
||||
v-model="period[0]"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
/>
|
||||
<span>至</span>
|
||||
<el-date-picker v-model="period[1]" type="date" format="YYYY-MM-DD" value-format="YYYY-MM-DD" placeholder="YYYY-MM-DD" />
|
||||
<el-date-picker
|
||||
v-model="period[1]"
|
||||
type="date"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="YYYY-MM-DD"
|
||||
/>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属组织"><el-input v-model="form.organizationName" disabled /></el-form-item>
|
||||
<el-form-item label="签订日期"><el-date-picker v-model="form.signDate" type="date" value-format="YYYY-MM-DD" disabled /></el-form-item>
|
||||
<el-form-item label="合同格式"><el-select v-model="form.contractFormat"><el-option label="电子合同" value="电子合同" /><el-option label="纸质合同" value="纸质合同" /></el-select></el-form-item>
|
||||
<el-form-item label="结算方式"><el-input v-model="form.settlementMode" /></el-form-item>
|
||||
<el-form-item label="是否需要加盖法人章"><el-select v-model="form.legalSealFlag"><el-option label="是" :value="1" /><el-option label="否" :value="0" /></el-select></el-form-item>
|
||||
<el-form-item label="一式(份)"><el-input v-model="form.copyCount" inputmode="numeric" maxlength="9" placeholder="请输入" @input="value => positiveIntegerInput('copyCount', value)" /></el-form-item>
|
||||
<el-form-item label="回款账期"><el-input v-model="form.paymentDays" inputmode="numeric" maxlength="9" placeholder="请输入" @input="value => positiveIntegerInput('paymentDays', value)" /></el-form-item>
|
||||
<el-form-item label="合同金额"><el-input-number v-model="form.contractAmount" :min="0" :precision="2" :controls="false" placeholder="请输入" /></el-form-item>
|
||||
<el-form-item label="是否范本"><el-select v-model="form.templateFlag" placeholder="请选择"><el-option label="否" :value="0" /><el-option label="是" :value="1" /></el-select></el-form-item>
|
||||
<el-form-item label="原件合同编号"><el-input v-model="form.originalContractNo" maxlength="100" placeholder="请输入" /></el-form-item>
|
||||
<el-form-item label="是否电子章"><el-select v-model="form.electronicSealFlag" placeholder="请选择"><el-option label="否" :value="0" /><el-option label="是" :value="1" /></el-select></el-form-item>
|
||||
<el-form-item label="回款账期">
|
||||
<el-input
|
||||
v-model="form.paymentDays"
|
||||
placeholder="请输入"
|
||||
@input="value => positiveIntegerInput('paymentDays', value)"
|
||||
>
|
||||
<template #suffix>天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同金额">
|
||||
<el-input-number
|
||||
v-model="form.contractAmount"
|
||||
:min="0"
|
||||
:precision="2"
|
||||
:controls="false"
|
||||
placeholder="请输入"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否范本">
|
||||
<el-select v-model="form.templateFlag" placeholder="请选择">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="原件合同编号">
|
||||
<el-input v-model="form.originalContractNo" maxlength="100" placeholder="请输入" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否电子章">
|
||||
<el-select v-model="form.electronicSealFlag" placeholder="请选择">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="开票周期">
|
||||
<el-input
|
||||
v-model="form.invoiceCycle"
|
||||
placeholder="请输入"
|
||||
@input="value => positiveIntegerInput('invoiceCycle', value)"
|
||||
>
|
||||
<template #suffix>天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算币种" prop="settlementCurrency">
|
||||
<el-select v-model="form.settlementCurrency" placeholder="请选择结算币种" clearable>
|
||||
<el-option
|
||||
v-for="item in settlementCurrencyOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="合同格式">
|
||||
<el-select v-model="form.contractFormat" placeholder="请选择合同格式" clearable>
|
||||
<el-option
|
||||
v-for="item in contractFormatOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="一式">
|
||||
<el-input
|
||||
v-model="form.copyCount"
|
||||
placeholder="请输入"
|
||||
@input="value => positiveIntegerInput('copyCount', value)"
|
||||
>
|
||||
<template #suffix>份</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要加盖法人章">
|
||||
<el-select v-model="form.legalSealFlag" placeholder="请选择">
|
||||
<el-option label="否" :value="0" />
|
||||
<el-option label="是" :value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="结算方式">
|
||||
<el-select v-model="form.settlementMode" placeholder="请选择结算方式" clearable>
|
||||
<el-option
|
||||
v-for="item in settlementModeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="经办人">
|
||||
<el-input v-model="form.handlerUserName" disabled />
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item label="备注" class="contract-basic-section__remark"><el-input v-model="form.remark" type="textarea" :rows="2" maxlength="2000" show-word-limit placeholder="请输入备注" /></el-form-item>
|
||||
<el-form-item label="备注" class="contract-basic-section__remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
maxlength="200"
|
||||
show-word-limit
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<section class="change-section">
|
||||
<div class="section-head">
|
||||
<div class="dialog-section-title">合同文件</div>
|
||||
<el-button type="primary" :disabled="!contractFileRows.length" @click="handleContractFileBatchDownload">批量下载</el-button>
|
||||
</div>
|
||||
<el-table :data="contractFileRows" border class="change-table" @selection-change="selectedContractFiles = $event"><el-table-column type="selection" width="55" /><el-table-column type="index" label="序号" width="70" /><el-table-column label="文件名" min-width="240"><template #default="{ row }"><el-link type="primary" @click="previewAttachment(row, contractFileRows)">{{ row.originalName || row.name }}</el-link></template></el-table-column><el-table-column label="文件大小" width="120"><template #default="{ row }">{{ formatFileSize(row.size) }}</template></el-table-column><el-table-column prop="uploadUserName" label="上传人" width="140" /><el-table-column prop="uploadTime" label="上传时间" width="170" /><el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="removeContractFile($index)">删除</el-link></template></el-table-column></el-table>
|
||||
<div class="attachment-upload">
|
||||
<vehicle-attachment-upload v-model="contractFileRows" :readonly="false" :file-types="attachmentFileTypes" :max-size="50" :show-file-list="false" button-text="上传附件" @change="handleContractFileChange" />
|
||||
</div>
|
||||
</section>
|
||||
<contract-attachment-section
|
||||
title="合同文件"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
:rows="contractFileRows"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="contractFileRows = $event"
|
||||
/>
|
||||
|
||||
<section class="change-section">
|
||||
<div class="section-head"><div class="dialog-section-title">计费信息</div><el-button type="primary" plain @click="addPlan">添加</el-button></div>
|
||||
@@ -101,26 +252,41 @@
|
||||
|
||||
<billing-plan-editor v-model="planDialogVisible" :value="planEditor" :index="planEditorIndex" @save="savePlan" />
|
||||
|
||||
<section class="change-section attachment-section">
|
||||
<div class="section-head"><div class="dialog-section-title">其它附件</div><el-button type="primary" plain>批量下载</el-button></div>
|
||||
<el-table :data="attachments" border class="change-table"><el-table-column type="index" label="序号" width="70" /><el-table-column label="文件名" min-width="240"><template #default="{ row }"><el-link type="primary" @click="previewAttachment(row, attachments)">{{ row.originalName || row.name }}</el-link></template></el-table-column><el-table-column prop="description" label="附件描述" min-width="240" /><el-table-column prop="size" label="文件大小" width="120" /><el-table-column prop="uploadUserName" label="上传人" width="140" /><el-table-column prop="uploadTime" label="上传时间" width="180" /><el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="attachments.splice($index, 1)">删除</el-link></template></el-table-column></el-table>
|
||||
<el-upload action="#" :auto-upload="false" multiple :show-file-list="false" @change="handleAttachment"><el-button type="primary" plain icon="el-icon-upload">上传附件</el-button></el-upload>
|
||||
</section>
|
||||
<contract-attachment-section
|
||||
title="其它附件"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="其它附件"
|
||||
:rows="attachments"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="attachments = $event"
|
||||
/>
|
||||
|
||||
<section class="change-section change-reason-section">
|
||||
<div class="dialog-section-title">变更内容</div>
|
||||
<el-form-item prop="changeContent"><el-input v-model="form.changeContent" type="textarea" :rows="3" maxlength="2000" show-word-limit placeholder="请输入变更内容" /></el-form-item>
|
||||
<div class="dialog-section-title is-required">变更原因</div>
|
||||
<el-form-item prop="changeReason"><el-input v-model="form.changeReason" type="textarea" :rows="3" maxlength="500" show-word-limit placeholder="请输入变更原因" /></el-form-item>
|
||||
<div class="dialog-section-title">变更材料</div>
|
||||
<el-table :data="changeMaterials" border class="change-table">
|
||||
<el-table-column type="index" label="序号" width="70" />
|
||||
<el-table-column label="文件名" min-width="240"><template #default="{ row }">{{ row.originalName || row.name }}</template></el-table-column>
|
||||
<el-table-column label="文件大小" width="120"><template #default="{ row }">{{ formatFileSize(row.size) }}</template></el-table-column>
|
||||
<el-table-column label="操作" width="100"><template #default="{ $index }"><el-link type="danger" @click="changeMaterials.splice($index, 1)">删除</el-link></template></el-table-column>
|
||||
</el-table>
|
||||
<div class="attachment-upload"><vehicle-attachment-upload v-model="changeMaterials" :readonly="false" :file-types="attachmentFileTypes" :max-size="50" :show-file-list="false" button-text="上传变更材料" /></div>
|
||||
<div class="dialog-section-title">变更原因</div>
|
||||
<el-form-item prop="changeReason" class="change-reason-section__input">
|
||||
<el-input
|
||||
v-model="form.changeReason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
placeholder="请输入变更原因"
|
||||
/>
|
||||
</el-form-item>
|
||||
</section>
|
||||
|
||||
<contract-attachment-section
|
||||
title="变更材料"
|
||||
description
|
||||
attachment-type
|
||||
use-upload-dialog
|
||||
attachment-location="变更材料"
|
||||
:rows="changeMaterials"
|
||||
:preview="previewAttachment"
|
||||
@update:rows="changeMaterials = $event"
|
||||
/>
|
||||
<div class="page-footer">
|
||||
<el-button @click="$router.back()">取消</el-button>
|
||||
<el-button type="primary" @click="submit">提交</el-button>
|
||||
@@ -137,6 +303,15 @@
|
||||
<script>
|
||||
import * as api from '@/api/business/contract-manage';
|
||||
import BillingPlanEditor from './components/billing-plan-editor.vue';
|
||||
import ContractAttachmentSection, {
|
||||
normalizeContractFileRows,
|
||||
} from './components/contract-attachment-section.vue';
|
||||
import { getDictionary as getSystemDictionary } from '@/api/system/dict';
|
||||
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
|
||||
import {
|
||||
contractCategoryOptions as defaultContractCategoryOptions,
|
||||
signTypeOptions as defaultSignTypeOptions,
|
||||
} from '@/option/business/common';
|
||||
import { ElImageViewer } from 'element-plus';
|
||||
import { OpenFileViewer } from '@open-file-viewer/vue';
|
||||
import { fallbackPlugin, imagePlugin, officePlugin, pdfPlugin, textPlugin } from '@open-file-viewer/core';
|
||||
@@ -150,6 +325,17 @@ const viewerPlugins = [
|
||||
textPlugin(),
|
||||
fallbackPlugin(),
|
||||
];
|
||||
const defaultSettlementModeOptions = [
|
||||
{ label: '现结', value: '现结' },
|
||||
{ label: '日结', value: '日结' },
|
||||
{ label: '周结', value: '周结' },
|
||||
{ label: '月结', value: '月结' },
|
||||
{ label: '按固定天数周期', value: '按固定天数周期' },
|
||||
];
|
||||
const defaultContractFormatOptions = [
|
||||
{ label: '电子合同', value: '电子合同' },
|
||||
{ label: '纸质合同', value: '纸质合同' },
|
||||
];
|
||||
const normalizeOptionalPositiveInteger = value => {
|
||||
if (value === undefined || value === null || value === '') return null;
|
||||
const number = Number(value);
|
||||
@@ -169,58 +355,264 @@ const normalizeCustomPeriods = (periods = []) =>
|
||||
}));
|
||||
|
||||
export default {
|
||||
components: { BillingPlanEditor, ElImageViewer, OpenFileViewer },
|
||||
data() { return { form: {}, period: [], plans: [], attachments: [], contractFiles: [], contractFileRows: [], selectedContractFiles: [], paymentRatioRows: [], settlementConfigTab: 'pre', changeMaterials: [], imagePreviewVisible: false, imagePreviewUrls: [], imagePreviewIndex: 0, documentPreviewVisible: false, previewFile: {}, viewerPlugins, viewerToolbar: { zoom: true, rotate: true, download: true, fullscreen: true }, feeGenerationMode: 'system', settlementRule: { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [] }, preSettlementConfig: {}, formalSettlementConfig: {}, settlementTypeOptions: ['月结','日结','周结','半月结','固定天数周期结算'], billCycleTypeOptions: ['固定截单日','自然月','自定义多周期'], billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({ label: `${index + 1}日`, value: index + 1 })), cycleDayOptions: [7,15,30,60].map(value => ({ label: `${value}天`, value })), planDialogVisible: false, planEditor: { planName: '', defaultPlan: false, remark: '', rules: [] }, planEditorIndex: -1, billingElements: ['按重量','按体积','按车辆','按里程','按吨·公里','固定金额(整单一口价)','按数量'], attachmentFileTypes: ['pdf','bmp','jpeg','png','jpg','doc','docx','ppt','pptx','xlsx','xls','eml','msg','zip'], rules: { contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }], changeReason: [{ required: true, message: '请输入变更原因', trigger: 'blur' }] } }; },
|
||||
computed: { showSettlementBillCycleType() { return this.settlementRule.settlementType === '月结'; }, showSettlementBillCutoffDay() { return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '固定截单日'; }, showSettlementCustomPeriods() { return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '自定义多周期'; }, showSettlementCycleDays() { return this.settlementRule.settlementType === '固定天数周期结算'; } },
|
||||
mounted() { this.load(); },
|
||||
watch: { settlementConfigTab(tab, oldTab) { if (tab === oldTab) return; if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; this.settlementRule = { ...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig) }; } },
|
||||
components: { BillingPlanEditor, ContractAttachmentSection, ElImageViewer, OpenFileViewer },
|
||||
data() {
|
||||
return {
|
||||
form: {},
|
||||
period: [],
|
||||
plans: [],
|
||||
attachments: [],
|
||||
contractFiles: [],
|
||||
contractFileRows: [],
|
||||
paymentRatioRows: [],
|
||||
settlementConfigTab: 'pre',
|
||||
changeMaterials: [],
|
||||
imagePreviewVisible: false,
|
||||
imagePreviewUrls: [],
|
||||
imagePreviewIndex: 0,
|
||||
documentPreviewVisible: false,
|
||||
previewFile: {},
|
||||
viewerPlugins,
|
||||
viewerToolbar: { zoom: true, rotate: true, download: true, fullscreen: true },
|
||||
feeGenerationMode: 'system',
|
||||
settlementRule: {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
},
|
||||
preSettlementConfig: {},
|
||||
formalSettlementConfig: {},
|
||||
settlementTypeOptions: ['月结', '日结', '周结', '半月结', '固定天数周期结算'],
|
||||
billCycleTypeOptions: ['固定截单日', '自然月', '自定义多周期'],
|
||||
billCutoffDayOptions: Array.from({ length: 31 }, (_, index) => ({
|
||||
label: `${index + 1}日`,
|
||||
value: index + 1,
|
||||
})),
|
||||
cycleDayOptions: [7, 15, 30, 60].map(value => ({ label: `${value}天`, value })),
|
||||
signTypeOptions: defaultSignTypeOptions,
|
||||
contractCategoryDictOptions: [],
|
||||
settlementCurrencyOptions: [],
|
||||
settlementModeDictOptions: [],
|
||||
contractFormatDictOptions: [],
|
||||
planDialogVisible: false,
|
||||
planEditor: { planName: '', defaultPlan: false, remark: '', rules: [] },
|
||||
planEditorIndex: -1,
|
||||
billingElements: [
|
||||
'按重量',
|
||||
'按体积',
|
||||
'按车辆',
|
||||
'按里程',
|
||||
'按吨·公里',
|
||||
'固定金额(整单一口价)',
|
||||
'按数量',
|
||||
],
|
||||
rules: {
|
||||
contractName: [{ required: true, message: '请输入合同名称', trigger: 'blur' }],
|
||||
changeReason: [{ max: 500, message: '变更原因不能超过500个字符', trigger: 'blur' }],
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.$route.query.name || '合同变更';
|
||||
},
|
||||
contractCategoryOptions() {
|
||||
return this.contractCategoryDictOptions.length
|
||||
? this.contractCategoryDictOptions
|
||||
: defaultContractCategoryOptions;
|
||||
},
|
||||
settlementModeOptions() {
|
||||
return this.settlementModeDictOptions.length
|
||||
? this.settlementModeDictOptions
|
||||
: defaultSettlementModeOptions;
|
||||
},
|
||||
contractFormatOptions() {
|
||||
return this.contractFormatDictOptions.length
|
||||
? this.contractFormatDictOptions
|
||||
: defaultContractFormatOptions;
|
||||
},
|
||||
showSettlementBillCycleType() {
|
||||
return this.settlementRule.settlementType === '月结';
|
||||
},
|
||||
showSettlementBillCutoffDay() {
|
||||
return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '固定截单日';
|
||||
},
|
||||
showSettlementCustomPeriods() {
|
||||
return this.showSettlementBillCycleType && this.settlementRule.billCycleType === '自定义多周期';
|
||||
},
|
||||
showSettlementCycleDays() {
|
||||
return this.settlementRule.settlementType === '固定天数周期结算';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.loadDictionaries();
|
||||
this.load();
|
||||
},
|
||||
watch: {
|
||||
settlementConfigTab(tab, oldTab) {
|
||||
if (tab === oldTab) return;
|
||||
if (oldTab === 'pre') this.preSettlementConfig = { ...this.settlementRule };
|
||||
else this.formalSettlementConfig = { ...this.settlementRule };
|
||||
this.settlementRule = {
|
||||
...(tab === 'pre' ? this.preSettlementConfig : this.formalSettlementConfig),
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async load() { const id = this.$route.query.id; if (!id) return; const res = await api.getDetail(id); const data = res.data?.data || res.data || {}; this.form = { ...data, changeContent: '', changeReason: '', changeAttachmentsJson: '', copyCount: normalizeOptionalPositiveInteger(data.copyCount), paymentDays: normalizeOptionalPositiveInteger(data.paymentDays), contractAmount: normalizeOptionalAmount(data.contractAmount), templateFlag: data.templateFlag ?? 0, electronicSealFlag: data.electronicSealFlag ?? 0, changeType: '合同信息变更' }; this.changeMaterials = []; 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)); this.contractFileRows = this.parse(data.contractFileJson); const rules = this.parseObject(data.settlementRuleJson); const pre = this.parseObject(data.preSettlementConfigJson); const formal = this.parseObject(data.formalSettlementConfigJson); const legacy = Object.keys(rules).some(key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)) ? rules : {}; const normalizeRule = rule => { const next = { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [], ...(rule || {}) }; if (next.settlementType === '月结' && next.billCycleType === '自定义多周期') next.customPeriods = normalizeCustomPeriods(Array.isArray(next.customPeriods) ? next.customPeriods : []); else next.customPeriods = []; return next; }; this.preSettlementConfig = normalizeRule(rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy)); this.formalSettlementConfig = normalizeRule(rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy)); this.settlementRule = { ...this.preSettlementConfig }; this.feeGenerationMode = data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system'); this.paymentRatioRows = this.parse(data.paymentRatioJson); },
|
||||
parse(value) { try { const result = JSON.parse(value || '[]'); return Array.isArray(result) ? result : []; } catch { return []; } },
|
||||
loadDictionaries() {
|
||||
Promise.all([
|
||||
getSystemDictionary({ code: 'currency_type' }),
|
||||
getBizDictionary({ code: 'settle_method' }),
|
||||
getBizDictionary({ code: 'contractFormat' }),
|
||||
getBizDictionary({ code: 'contractCategory' }),
|
||||
]).then(([currencyRes, methodRes, formatRes, categoryRes]) => {
|
||||
const records = response => {
|
||||
const data = response?.data?.data || response?.data || [];
|
||||
return Array.isArray(data) ? data : data.records || [];
|
||||
};
|
||||
this.settlementCurrencyOptions = records(currencyRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
this.settlementModeDictOptions = records(methodRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
this.contractFormatDictOptions = records(formatRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
this.contractCategoryDictOptions = records(categoryRes).map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictKey,
|
||||
}));
|
||||
});
|
||||
},
|
||||
async load() {
|
||||
const id = this.$route.query.id;
|
||||
if (!id) return;
|
||||
const res = await api.getDetail(id);
|
||||
const data = res.data?.data || res.data || {};
|
||||
this.form = {
|
||||
...data,
|
||||
changeContent: '',
|
||||
changeReason: '',
|
||||
changeAttachmentsJson: '',
|
||||
copyCount: normalizeOptionalPositiveInteger(data.copyCount),
|
||||
invoiceCycle: normalizeOptionalPositiveInteger(data.invoiceCycle),
|
||||
paymentDays: normalizeOptionalPositiveInteger(data.paymentDays),
|
||||
contractAmount: normalizeOptionalAmount(data.contractAmount),
|
||||
templateFlag: data.templateFlag ?? 0,
|
||||
electronicSealFlag: data.electronicSealFlag ?? 0,
|
||||
archiveStatus: data.archiveStatus || '未归档',
|
||||
changeType: '合同信息变更',
|
||||
};
|
||||
this.changeMaterials = [];
|
||||
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)
|
||||
);
|
||||
this.contractFileRows = normalizeContractFileRows(this.parse(data.contractFileJson));
|
||||
const rules = this.parseObject(data.settlementRuleJson);
|
||||
const pre = this.parseObject(data.preSettlementConfigJson);
|
||||
const formal = this.parseObject(data.formalSettlementConfigJson);
|
||||
const legacy = Object.keys(rules).some(
|
||||
key => !['preSettlementConfig', 'formalSettlementConfig'].includes(key)
|
||||
)
|
||||
? rules
|
||||
: {};
|
||||
const normalizeRule = rule => {
|
||||
const next = {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
...(rule || {}),
|
||||
};
|
||||
if (next.settlementType === '月结' && next.billCycleType === '自定义多周期') {
|
||||
next.customPeriods = normalizeCustomPeriods(
|
||||
Array.isArray(next.customPeriods) ? next.customPeriods : []
|
||||
);
|
||||
} else {
|
||||
next.customPeriods = [];
|
||||
}
|
||||
return next;
|
||||
};
|
||||
this.preSettlementConfig = normalizeRule(
|
||||
rules.preSettlementConfig || (Object.keys(pre).length ? pre : legacy)
|
||||
);
|
||||
this.formalSettlementConfig = normalizeRule(
|
||||
rules.formalSettlementConfig || (Object.keys(formal).length ? formal : legacy)
|
||||
);
|
||||
this.settlementRule = { ...this.preSettlementConfig };
|
||||
this.feeGenerationMode =
|
||||
data.feeGenerationMode || (Number(data.billingEnabled) === 0 ? 'manual' : 'system');
|
||||
this.paymentRatioRows = this.parse(data.paymentRatioJson);
|
||||
},
|
||||
parse(value) {
|
||||
try {
|
||||
const result = JSON.parse(value || '[]');
|
||||
return Array.isArray(result) ? result : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// 审核通过后上一次的变更材料归集到「其它附件」,按文件地址/文件名去重,避免重复展示
|
||||
mergeChangeAttachments(attachments = [], changeAttachments = []) {
|
||||
const key = item => this.attachmentUrl(item) || this.attachmentName(item);
|
||||
const exists = new Set(attachments.map(key));
|
||||
const merged = changeAttachments
|
||||
.filter(item => !exists.has(key(item)))
|
||||
.map(item => ({ ...item, size: /^\d+$/.test(String(item.size ?? '')) ? this.formatFileSize(item.size) : item.size }));
|
||||
.map(item => ({
|
||||
...item,
|
||||
size: /^\d+$/.test(String(item.size ?? '')) ? this.formatFileSize(item.size) : item.size,
|
||||
}));
|
||||
return [...attachments, ...merged];
|
||||
},
|
||||
parseObject(value) { try { return { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [], ...(JSON.parse(value || '{}') || {}) }; } catch { return { autoGenerate: '', billStartDate: '', settlementType: '', billCycleType: '', billCutoffDay: '', cycleDays: '', customPeriods: [] }; } },
|
||||
positiveIntegerInput(prop, value) { this.form[prop] = String(value ?? '').replace(/\D/g, '').replace(/^0+/, ''); },
|
||||
parseObject(value) {
|
||||
try {
|
||||
return {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
...(JSON.parse(value || '{}') || {}),
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
autoGenerate: '',
|
||||
billStartDate: '',
|
||||
settlementType: '',
|
||||
billCycleType: '',
|
||||
billCutoffDay: '',
|
||||
cycleDays: '',
|
||||
customPeriods: [],
|
||||
};
|
||||
}
|
||||
},
|
||||
positiveIntegerInput(prop, value) {
|
||||
this.form[prop] = String(value ?? '')
|
||||
.replace(/\D/g, '')
|
||||
.replace(/^0+/, '');
|
||||
},
|
||||
addPlan() { this.planEditorIndex = -1; this.planEditor = { planName: `计费方案${this.plans.length + 1}`, defaultPlan: !this.plans.length, remark: '', rules: [{}] }; this.planDialogVisible = true; },
|
||||
editPlan(row, index) { this.planEditorIndex = index; this.planEditor = JSON.parse(JSON.stringify(row)); this.planDialogVisible = true; },
|
||||
toggleDefaultPlan(value) { if (value) this.plans.forEach(item => { item.defaultPlan = false; }); },
|
||||
savePlan(value, index) { if (index < 0) this.plans.push(value); else this.plans.splice(index, 1, value); if (value.defaultPlan) this.plans.forEach((item, current) => { if (current !== (index < 0 ? this.plans.length - 1 : index)) item.defaultPlan = false; }); },
|
||||
handleSettlementTypeChange(value) { if (value !== '月结') { this.settlementRule.billCycleType = ''; this.settlementRule.billCutoffDay = ''; this.settlementRule.customPeriods = []; } else if (this.settlementRule.billCycleType === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; if (value !== '固定天数周期结算') this.settlementRule.cycleDays = ''; },
|
||||
handleCycleTypeChange(value) { if (value !== '固定截单日') this.settlementRule.billCutoffDay = ''; if (value === '自定义多周期') this.settlementRule.customPeriods = normalizeCustomPeriods(this.settlementRule.customPeriods || []); else this.settlementRule.customPeriods = []; },
|
||||
currentUploadUserName() {
|
||||
const userInfo = this.$store.getters.userInfo || {};
|
||||
return userInfo.realName || userInfo.userName || '';
|
||||
},
|
||||
handleAttachment(event) {
|
||||
const raw = event.raw;
|
||||
if (!raw) return;
|
||||
if (raw.size > 50 * 1024 * 1024) {
|
||||
this.$message.warning('单个文件大小不能超过50M');
|
||||
return;
|
||||
}
|
||||
this.attachments.push({
|
||||
name: raw.name,
|
||||
size: `${Math.ceil(raw.size / 1024)}KB`,
|
||||
uploadUserName: this.currentUploadUserName(),
|
||||
uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
});
|
||||
},
|
||||
handleContractFileChange(list) {
|
||||
const uploadUserName = this.currentUploadUserName();
|
||||
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
|
||||
this.contractFileRows = (list || []).map(item => ({
|
||||
...item,
|
||||
uploadUserName: item.uploadUserName || uploadUserName,
|
||||
uploadTime: item.uploadTime || uploadTime,
|
||||
}));
|
||||
},
|
||||
attachmentUrl(row = {}) { return row.url || row.link || row.fileUrl || row.downloadUrl || row.domain || ''; },
|
||||
attachmentName(row = {}) { return row.originalName || row.name || row.fileName || '附件'; },
|
||||
attachmentExtension(row = {}) { const source = String(this.attachmentName(row) || this.attachmentUrl(row)).split('?')[0]; const index = source.lastIndexOf('.'); return index > -1 ? source.slice(index + 1).toLowerCase() : ''; },
|
||||
@@ -228,8 +620,6 @@ export default {
|
||||
previewAttachment(row, rows = this.attachments) { const url = this.attachmentUrl(row); if (!url) { this.$message.warning('附件地址为空,无法预览'); return; } if (this.isAttachmentImage(row)) { this.imagePreviewUrls = (rows || []).filter(item => this.isAttachmentImage(item) && this.attachmentUrl(item)).map(item => this.attachmentUrl(item)); this.imagePreviewIndex = Math.max(this.imagePreviewUrls.indexOf(url), 0); this.imagePreviewVisible = true; return; } this.previewFile = { name: this.attachmentName(row), url, mimeType: row.mimeType || row.contentType || '' }; this.documentPreviewVisible = true; },
|
||||
handlePreviewUnsupported() { this.$message.warning('当前文件暂不支持在线预览'); },
|
||||
handlePreviewError() { this.$message.error('附件预览失败'); },
|
||||
removeContractFile(index) { this.contractFileRows.splice(index, 1); },
|
||||
handleContractFileBatchDownload() { (this.selectedContractFiles.length ? this.selectedContractFiles : this.contractFileRows).forEach(row => { if (row.url) window.open(row.url, '_blank'); }); },
|
||||
formatFileSize(value) { const size = Number(value || 0); return size > 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(2)}MB` : `${Math.max(1, Math.ceil(size / 1024))}KB`; },
|
||||
addPaymentRatioRow() { this.paymentRatioRows.push({ paymentTerm: `第${this.paymentRatioRows.length + 1}笔`, ratioLimit: '', remark: '' }); },
|
||||
customPeriodEndDayOptions(row) { const startDay = Number(row?.startDay); if (!Number.isFinite(startDay) || startDay < 1) return this.billCutoffDayOptions; return Array.from({ length: 31 - startDay + 1 }, (_, index) => ({ label: `${startDay + index}日`, value: startDay + index })); },
|
||||
@@ -239,7 +629,7 @@ export default {
|
||||
removeCustomPeriodRow(index) { if (index <= 0) return; const periods = [...(this.settlementRule.customPeriods || [])]; periods.splice(index, 1); this.settlementRule.customPeriods = normalizeCustomPeriods(periods); },
|
||||
validateCustomPeriods(periods = [], label) { const rows = normalizeCustomPeriods(periods); if (!rows.length) { this.$message.warning(`${label}:请至少配置一段自定义周期`); return false; } for (let index = 0; index < rows.length; index += 1) { const row = rows[index]; const startDay = Number(row.startDay); const endDay = Number(row.endDay); if (!Number.isFinite(startDay) || startDay < 1 || startDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间开始日`); return false; } if (!Number.isFinite(endDay) || endDay < 1 || endDay > 31) { this.$message.warning(`${label}:请选择第${index + 1}行运单区间结束日`); return false; } if (endDay < startDay) { this.$message.warning(`${label}:第${index + 1}行结束日不能早于开始日`); return false; } if (index > 0 && startDay !== Number(rows[index - 1].endDay) + 1) { this.$message.warning(`${label}:自定义多周期区间必须连续,不允许重叠或存在日期缺口`); return false; } } return true; },
|
||||
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() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, settlementCurrency: String(this.form.settlementCurrency || '').trim() || 'RMB', copyCount: normalizeOptionalPositiveInteger(this.form.copyCount), 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) }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||
async submit() { await this.$refs.formRef.validate(); const total = this.paymentRatioRows.reduce((sum, row) => sum + Number(row.ratioLimit || 0), 0); if (this.paymentRatioRows.length && Math.abs(total - 100) > 0.0001) { this.$message.warning('付款比例上限合计必须等于100%'); return; } if (this.settlementConfigTab === 'pre') this.preSettlementConfig = { ...this.settlementRule }; else this.formalSettlementConfig = { ...this.settlementRule }; if (!this.validateSettlementRule(this.preSettlementConfig, '预结算配置') || !this.validateSettlementRule(this.formalSettlementConfig, '正式结算配置')) return; const settlementRule = { preSettlementConfig: this.preSettlementConfig, formalSettlementConfig: this.formalSettlementConfig }; await api.submitChange({ ...this.form, 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) }); this.$message.success('变更已提交'); this.$router.back(); },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -265,7 +655,6 @@ export default {
|
||||
.contract-basic-section :deep(.el-date-editor) { width: 360px; max-width: 100%; }
|
||||
.dialog-section-title { margin-bottom: 18px; font-size: 16px; font-weight: 600; }
|
||||
.dialog-section-title::before { display: inline-block; width: 4px; height: 16px; margin-right: 8px; vertical-align: -2px; background: #409eff; content: ''; }
|
||||
.dialog-section-title.is-required::after { margin-left: 4px; color: #f56c6c; content: '*'; }
|
||||
.section-head { display: flex; align-items: center; justify-content: space-between; }
|
||||
.change-table { margin: 16px 0; }
|
||||
.ratio-tip { margin-bottom: 8px; color: #f56c6c; }
|
||||
@@ -279,6 +668,18 @@ export default {
|
||||
.settlement-switch-tip { margin: 0 32px 0 8px; color: #a8abb2; cursor: help; font-size: 14px; }
|
||||
.settlement-form { display: grid; grid-template-columns: repeat(3, minmax(240px, 1fr)); gap: 8px 28px; }
|
||||
.change-reason-section { margin: 20px 0; }
|
||||
.change-reason-section__input {
|
||||
width: 100%;
|
||||
:deep(.el-textarea__inner) {
|
||||
border-color: #f56c6c;
|
||||
box-shadow: 0 0 0 1px #f56c6c inset;
|
||||
}
|
||||
:deep(.el-textarea__inner:hover),
|
||||
:deep(.el-textarea__inner:focus) {
|
||||
border-color: #f56c6c;
|
||||
box-shadow: 0 0 0 1px #f56c6c inset;
|
||||
}
|
||||
}
|
||||
.page-footer {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
|
||||
Reference in New Issue
Block a user