修复销售合同编辑崩溃问题

This commit is contained in:
wangchengming 2025-11-21 16:00:03 +08:00
parent 7a7d550a77
commit 48c82bbcef
2 changed files with 423 additions and 250 deletions

View File

@ -43,140 +43,249 @@
<!-- 媒体信息开始 -->
<template #purchaseMediaBoList>
<el-edit-table :columns="mediaListColumns" :value="mediaTableData" show-summary
:summary-method="mediaTypeSummary" @update-items="updateItemsPurchaseMediaBoList" :key="'media-table'">
<!-- 城市选择开始 -->
<template #cityId="{ record, isEdit }">
<el-custom-cascader v-model="record.cityIds" :dataSource="cityList" :cascaderStyle="{ width: '100%' }"
@change="(v, t) => { record.cityId = v; record.cityName = t; }" />
</template>
<!-- 城市选择结束 -->
<div class="table-container">
<el-table
:data="visibleMediaData"
:key="'media-table-' + tableKey"
style="width: 100%"
height="400"
show-summary
:summary-method="mediaTypeSummary"
>
<el-table-column
v-for="column in mediaListColumns"
:key="column.dataIndex"
:prop="column.dataIndex"
:label="column.title"
:width="column.width"
>
<template #default="scope">
<template v-if="column.slot">
<!-- 城市选择 -->
<template v-if="column.dataIndex === 'cityId'">
<el-custom-cascader
v-model="scope.row.cityIds"
:dataSource="cityList"
:cascaderStyle="{ width: '100%' }"
@change="(v, t) => { scope.row.cityId = v; scope.row.cityName = t; }"
/>
</template>
<!-- 媒体类型开始 -->
<template #mediaId="{ record, isEdit }">
<el-custom-select v-model="record.mediaId" :dataSource="mediaTypeList" :remoteAdd="handleAddMediaTypeParty"
@change="(v, t) => (record.mediaName = t)" />
</template>
<!-- 媒体类型结束 -->
<!-- 媒体类型 -->
<template v-else-if="column.dataIndex === 'mediaId'">
<el-custom-select
v-model="scope.row.mediaId"
:dataSource="mediaTypeList"
:remoteAdd="handleAddMediaTypeParty"
@change="(v, t) => (scope.row.mediaName = t)"
/>
</template>
<!-- 上刊时间开始 -->
<template #upTime="{ record, isEdit }">
<el-date-picker v-model="record.upTime" value-format="yyyy-MM-dd" style="width: 100%" />
</template>
<!-- 上刊时间结束 -->
<!-- 上刊时间 -->
<template v-else-if="column.dataIndex === 'upTime'">
<el-date-picker
v-model="scope.row.upTime"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</template>
<!-- 下刊时间开始 -->
<template #downTime="{ record, isEdit }">
<el-date-picker v-model="record.downTime" value-format="yyyy-MM-dd" style="width: 100%" />
</template>
<!-- 下刊时间结束 -->
<!-- 下刊时间 -->
<template v-else-if="column.dataIndex === 'downTime'">
<el-date-picker
v-model="scope.row.downTime"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</template>
<!-- 周期开始 -->
<template #period="{ record }">
{{ delayPeriodDate(record) }}
</template>
<!-- 周期结束 -->
<!-- 周期 -->
<template v-else-if="column.dataIndex === 'period'">
{{ delayPeriodDate(scope.row) }}
</template>
<!-- 折扣开始 -->
<template #discount="{ record, isEdit }">
<div class="flexRowCenter">
<el-input v-model="record.discount" @change="handleNumberChange(record.discount, $event)"></el-input>
</div>
</template>
<!-- 折扣结束 -->
</el-edit-table>
<!-- 折扣 -->
<template v-else-if="column.dataIndex === 'discount'">
<div class="flexRowCenter">
<el-input
v-model="scope.row.discount"
@change="handleNumberChange(scope.row.discount, $event)"
></el-input>
</div>
</template>
<!-- 默认显示 -->
<template v-else>
{{ scope.row[column.dataIndex] }}
</template>
</template>
<template v-else>
{{ scope.row[column.dataIndex] }}
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button link type="danger" @click="removeMediaRow(scope.$index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-actions">
<el-button type="primary" @click="addMediaRow">新增媒体</el-button>
<el-pagination
v-if="mediaTableData.length > pageSize"
small
layout="prev, pager, next"
:total="mediaTableData.length"
:page-size="pageSize"
:current-page="mediaCurrentPage"
@current-change="handleMediaPageChange"
/>
</div>
</div>
</template>
<!-- 媒体信息结束 -->
<!-- 付款管理开始 -->
<template #purchasePaymentBoList>
<el-edit-table :columns="contranctPayListColumns" :value="paymentTableData" show-summary
:summary-method="paymentSummary" @update-items="updateItemsPurchasePaymentBoList" :key="'payment-table'">
<!-- 笔数开始 -->
<template #transactionsNumber="{ record, isEdit }">
<el-select v-model="record.transactionsNumber" style="width: 100%;">
<el-option value="第1笔" label="第1笔"></el-option>
<el-option value="第2笔" label="第2笔"></el-option>
<el-option value="第3笔" label="第3笔"></el-option>
<el-option value="第4笔" label="第4笔"></el-option>
<el-option value="第5笔" label="第5笔"></el-option>
</el-select>
</template>
<!-- 笔数结束 -->
<div class="table-container">
<el-table
:data="visiblePaymentData"
:key="'payment-table-' + tableKey"
style="width: 100%"
height="400"
show-summary
:summary-method="paymentSummary"
>
<el-table-column
v-for="column in contranctPayListColumns"
:key="column.dataIndex"
:prop="column.dataIndex"
:label="column.title"
:width="column.width"
>
<template #default="scope">
<template v-if="column.slot">
<!-- 笔数 -->
<template v-if="column.dataIndex === 'transactionsNumber'">
<el-select v-model="scope.row.transactionsNumber" style="width: 100%;">
<el-option value="第1笔" label="第1笔"></el-option>
<el-option value="第2笔" label="第2笔"></el-option>
<el-option value="第3笔" label="第3笔"></el-option>
<el-option value="第4笔" label="第4笔"></el-option>
<el-option value="第5笔" label="第5笔"></el-option>
</el-select>
</template>
<!-- 约定付款时间开始 -->
<template #payTime="{ record, isEdit }">
<el-date-picker v-model="record.payTime" value-format="yyyy-MM-dd" style="width: 100%" />
</template>
<!-- 约定付款时间结束 -->
<!-- 约定付款时间 -->
<template v-else-if="column.dataIndex === 'payTime'">
<el-date-picker
v-model="scope.row.payTime"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</template>
<!-- 实际付款时间开始 -->
<template #arrivalTime="{ record, isEdit }">
<el-date-picker v-model="record.arrivalTime" value-format="yyyy-MM-dd" style="width: 100%" />
</template>
<!-- 实际付款时间结束 -->
<!-- 实际付款时间 -->
<template v-else-if="column.dataIndex === 'arrivalTime'">
<el-date-picker
v-model="scope.row.arrivalTime"
value-format="yyyy-MM-dd"
style="width: 100%"
/>
</template>
<!-- 是否逾期开始 -->
<template #isOverdue="{ record, isEdit }">
<el-select v-model="record.isOverdue">
<el-option :value="0" label="否"></el-option>
<el-option :value="1" label="是"></el-option>
</el-select>
</template>
<!-- 是否逾期结束 -->
<!-- 是否逾期 -->
<template v-else-if="column.dataIndex === 'isOverdue'">
<el-select v-model="scope.row.isOverdue">
<el-option :value="0" label="否"></el-option>
<el-option :value="1" label="是"></el-option>
</el-select>
</template>
<!-- 逾期时间开始 -->
<template #overdueDay="{ record }">
{{ delayDate(record) }}
</template>
<!-- 逾期时间结束 -->
<!-- 逾期时间 -->
<template v-else-if="column.dataIndex === 'overdueDay'">
{{ delayDate(scope.row) }}
</template>
<!-- 附件开始 -->
<template #annex="{ record, isEdit }">
<el-file-upload :key="`annex_${record.id || record.tempId}`" listType="list"
:uploadStyle="{ width: '336px' }" />
</template>
<!-- 附件结束 -->
</el-edit-table>
<!-- 附件 -->
<template v-else-if="column.dataIndex === 'annex'">
<el-file-upload
:key="`annex_${scope.row.id || scope.row.tempId}_${tableKey}`"
listType="list"
:uploadStyle="{ width: '336px' }"
/>
</template>
<!-- 默认显示 -->
<template v-else>
{{ scope.row[column.dataIndex] }}
</template>
</template>
<template v-else>
{{ scope.row[column.dataIndex] }}
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="100">
<template #default="scope">
<el-button link type="danger" @click="removePaymentRow(scope.$index)">
删除
</el-button>
</template>
</el-table-column>
</el-table>
<div class="table-actions">
<el-button type="primary" @click="addPaymentRow">新增付款</el-button>
<el-pagination
v-if="paymentTableData.length > pageSize"
small
layout="prev, pager, next"
:total="paymentTableData.length"
:page-size="pageSize"
:current-page="paymentCurrentPage"
@current-change="handlePaymentPageChange"
/>
</div>
</div>
</template>
<!-- 付款管理结束 -->
<!-- 合同附件开始 -->
<!-- 其他附件模板保持不变 -->
<template #contractAccess>
<el-file-upload ref="contractAccess" />
<el-file-upload ref="contractAccess" :key="`contractAccess_${tableKey}`" />
</template>
<!-- 合同附件结束 -->
<!-- 检测照片开始 -->
<template #detectPicAttr>
<el-file-upload ref="detectPicAttr" />
<el-file-upload ref="detectPicAttr" :key="`detectPicAttr_${tableKey}`" />
</template>
<!-- 检测照片结束 -->
<!-- 监测报告附件开始 -->
<template #reportAttachment>
<div class="report-attachment">
<div class="item">
<div class="report-attachmenttitle">· 上刊</div>
<el-file-upload ref="upPrint" />
<el-file-upload ref="upPrint" :key="`upPrint_${tableKey}`" />
</div>
<div class="item">
<div class="report-attachmenttitle">· 下刊</div>
<el-file-upload ref="nextPrint" />
<el-file-upload ref="nextPrint" :key="`nextPrint_${tableKey}`" />
</div>
<div class="item">
<div class="report-attachmenttitle">· 换刊</div>
<el-file-upload ref="exchangePrint" />
<el-file-upload ref="exchangePrint" :key="`exchangePrint_${tableKey}`" />
</div>
</div>
</template>
<!-- 监测报告附件结束 -->
<!-- 媒体链条附件开始 -->
<template #mediaLink>
<el-file-upload ref="mediaLink" />
<el-file-upload ref="mediaLink" :key="`mediaLink_${tableKey}`" />
</template>
<!-- 媒体链条附件结束 -->
</el-group-form>
<div class="footer">
@ -190,7 +299,6 @@
import { getAction, postAction, putAction } from "@/api/manage";
import { cityDataCache } from "@/utils/cityDataCache";
import ElCustomForm from "@/components/ElForm/ElCustomForm.vue";
import ElEditTable from "@/components/ElForm/ElEditTable.vue";
import {
formGroup,
mediaListColumns,
@ -207,7 +315,6 @@ export default {
name: 'ProcurementContract',
components: {
ElCustomForm,
ElEditTable,
ElGroupForm,
ElCustomSelect,
ElFileUpload,
@ -215,58 +322,40 @@ export default {
},
data() {
return {
// ID
currentEditId: null,
formGroup,
AEform: {
formModel: {
//
//
},
rules: {},
},
mediaListColumns,
contranctPayListColumns,
mediaListColumns: this.processColumns(mediaListColumns),
contranctPayListColumns: this.processColumns(contranctPayListColumns),
mediaTypeList: [],
flag: 'detail',
cityList: [],
//
mediaTableData: Object.freeze([]),
paymentTableData: Object.freeze([]),
// - 使
mediaTableData: [],
paymentTableData: [],
//
pageSize: 50, // 50
mediaCurrentPage: 1,
paymentCurrentPage: 1,
// key
tableKey: 0,
_periodTimer: null,
_overdueTimer: null,
_annexDebounce: false,
_isDestroyed: false
_isDestroyed: false,
_loading: false
};
},
async created() {
this.getMediaType();
const rawData = await cityDataCache.getCityData();
this.cityList = Object.freeze(rawData);
if (this.$route.query.id) {
this.getDetailData()
}
},
mounted() {
if (this.$route.query.id) {
this.getDetailData()
}
},
watch: {
'$route.query.id': {
immediate: true,
handler(newId) {
if (newId !== this.currentEditId) {
this.getDetailData();
}
}
}
},
computed: {
contractMoney() {
// 使
const list = this.mediaTableData;
if (!list || list.length === 0) return 0;
@ -277,23 +366,152 @@ export default {
}
return total;
},
//
visibleMediaData() {
const start = (this.mediaCurrentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.mediaTableData.slice(start, end);
},
//
visiblePaymentData() {
const start = (this.paymentCurrentPage - 1) * this.pageSize;
const end = start + this.pageSize;
return this.paymentTableData.slice(start, end);
}
},
watch: {
'$route.query.id': {
immediate: true,
handler(newId) {
if (newId) {
this.getDetailData(newId);
} else {
this.resetFormData();
}
}
}
},
async created() {
this.getMediaType();
const rawData = await cityDataCache.getCityData();
this.cityList = rawData;
},
methods: {
// slot
processColumns(columns) {
return columns.map(column => ({
...column,
slot: !!column.scopedSlots || ['cityId', 'mediaId', 'upTime', 'downTime', 'period', 'discount',
'transactionsNumber', 'payTime', 'arrivalTime', 'isOverdue', 'overdueDay', 'annex'].includes(column.dataIndex)
}));
},
async getDetailData(id) {
if (this._loading) return;
this._loading = true;
try {
//
this.resetFormData();
const res = await getAction(`/system/purchase/${id}`);
if (!res.data) {
console.error('获取到的数据为空');
this.$message.error('获取数据失败');
return;
}
// 使
const formModel = { ...res.data };
// -
let mediaData = [];
let paymentData = [];
if (formModel.purchaseMediaVoList && Array.isArray(formModel.purchaseMediaVoList)) {
// 200
const rawData = formModel.purchaseMediaVoList.slice(0, 200);
mediaData = rawData.map(item => ({
...item,
cityIds: item.cityIds ? item.cityIds.split(',').map(id => Number(id)) : []
}));
delete formModel.purchaseMediaVoList;
}
if (formModel.conPurchasePaymentVo && Array.isArray(formModel.conPurchasePaymentVo)) {
// 200
paymentData = formModel.conPurchasePaymentVo.slice(0, 200);
delete formModel.conPurchasePaymentVo;
}
//
this.AEform.formModel = formModel;
//
this.mediaTableData = mediaData;
this.paymentTableData = paymentData;
// key
this.tableKey = Date.now();
//
this.$nextTick(() => {
if (this._isDestroyed) return;
this.setFileUploadData({
contractAccessList: res.data.contractAccessList || [],
detectPicAttrList: res.data.detectPicAttrList || [],
upPrintList: res.data.upPrintList || [],
nextPrintList: res.data.nextPrintList || [],
exchangePrintList: res.data.exchangePrintList || [],
mediaLinkList: res.data.mediaLinkList || []
});
});
} catch (error) {
console.error('获取详情失败:', error);
this.$message.error('获取数据失败');
} finally {
this._loading = false;
}
},
setFileUploadData(fileData) {
try {
const setFileList = (ref, list) => {
if (ref && ref.setFileList && Array.isArray(list) && list.length > 0) {
ref.setFileList([...list]);
}
};
setFileList(this.$refs.contractAccess, fileData.contractAccessList);
setFileList(this.$refs.detectPicAttr, fileData.detectPicAttrList);
setFileList(this.$refs.upPrint, fileData.upPrintList);
setFileList(this.$refs.nextPrint, fileData.nextPrintList);
setFileList(this.$refs.exchangePrint, fileData.exchangePrintList);
setFileList(this.$refs.mediaLink, fileData.mediaLinkList);
} catch (error) {
console.error('设置文件上传数据失败:', error);
}
},
resetFormData() {
this.AEform.formModel = {};
this.mediaTableData = [];
this.paymentTableData = [];
this.mediaCurrentPage = 1;
this.paymentCurrentPage = 1;
this.tableKey = Date.now();
// key
this.mediaTableKey = Date.now();
this.paymentTableKey = Date.now();
this.formKey = Date.now();
//
this.$nextTick(() => {
const resetFileUpload = (ref) => {
if (ref && ref.setFileList) {
ref.setFileList([]);
if (ref && typeof ref.setFileList === 'function') {
try {
ref.setFileList([]);
} catch (error) {
console.warn('重置文件上传失败:', error);
}
}
};
@ -305,70 +523,53 @@ export default {
resetFileUpload(this.$refs.mediaLink);
});
},
async getDetailData() {
try {
if (this.$route.query.id) {
this.resetFormData()
this.currentEditId = this.$route.query.id;
const res = await getAction(`/system/purchase/${this.$route.query.id}`);
// 使
const formModel = { ...res.data };
// 使 Object.freeze
if (formModel.purchaseMediaVoList) {
const mediaData = formModel.purchaseMediaVoList.map(item => ({
...item,
cityIds: item.cityIds ? item.cityIds.split(',').map(id => Number(id)) : []
}));
this.mediaTableData = Object.freeze(mediaData);
delete formModel.purchaseMediaVoList;
} else {
this.mediaTableData = Object.freeze([]);
}
if (formModel.conPurchasePaymentVo) {
this.paymentTableData = Object.freeze([...formModel.conPurchasePaymentVo]);
delete formModel.conPurchasePaymentVo;
} else {
this.paymentTableData = Object.freeze([]);
}
//
this.AEform.formModel = formModel;
// 线
this.$nextTick(() => {
if (this._isDestroyed) return;
const setFileList = (ref, list) => {
if (ref && list) {
ref.setFileList(list);
}
};
setFileList(this.$refs.contractAccess, res.data.contractAccessList);
setFileList(this.$refs.detectPicAttr, res.data.detectPicAttrList);
setFileList(this.$refs.upPrint, res.data.upPrintList);
setFileList(this.$refs.nextPrint, res.data.nextPrintList);
setFileList(this.$refs.exchangePrint, res.data.exchangePrintList);
setFileList(this.$refs.mediaLink, res.data.mediaLinkList);
});
}
} catch (error) {
console.error('获取详情失败:', error);
}
//
addMediaRow() {
this.mediaTableData.push({
tempId: Date.now(),
cityIds: [],
mediaId: '',
mediaName: '',
upTime: '',
downTime: '',
discount: 0,
mediaFee: 0,
productFee: 0
});
},
updateItemsPurchaseMediaBoList(newItems) {
// 使
this.mediaTableData = Object.freeze([...newItems]);
removeMediaRow(index) {
const actualIndex = (this.mediaCurrentPage - 1) * this.pageSize + index;
this.mediaTableData.splice(actualIndex, 1);
},
updateItemsPurchasePaymentBoList(newItems) {
this.paymentTableData = Object.freeze([...newItems]);
handleMediaPageChange(page) {
this.mediaCurrentPage = page;
},
//
addPaymentRow() {
this.paymentTableData.push({
tempId: Date.now(),
transactionsNumber: '第1笔',
payTime: '',
arrivalTime: '',
isOverdue: 0,
payMoney: 0
});
},
removePaymentRow(index) {
const actualIndex = (this.paymentCurrentPage - 1) * this.pageSize + index;
this.paymentTableData.splice(actualIndex, 1);
},
handlePaymentPageChange(page) {
this.paymentCurrentPage = page;
},
// ...
async getMediaDepartment() {
const { code, data } = await getAction(`/contract/mediaDept/listAll`);
if (code == 200) {
@ -443,36 +644,6 @@ export default {
throw new Error(msg);
},
getPeriod() {
if (this._periodTimer) clearTimeout(this._periodTimer);
this._periodTimer = setTimeout(() => {
const data = [...this.mediaTableData];
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.upTime && item.downTime) {
const diff = dayjs(item.downTime).diff(dayjs(item.upTime), 'day');
item.period = Math.ceil(diff);
}
}
this.mediaTableData = Object.freeze(data);
}, 150);
},
getOverdueDay() {
if (this._overdueTimer) clearTimeout(this._overdueTimer);
this._overdueTimer = setTimeout(() => {
const data = [...this.paymentTableData];
for (let i = 0; i < data.length; i++) {
const item = data[i];
if (item.payTime) {
const diff = dayjs(item.arrivalTime || new Date()).diff(dayjs(item.payTime), 'day');
item.overdueDay = Math.floor(diff);
}
}
this.paymentTableData = Object.freeze(data);
}, 150);
},
handleNumberChange(val) {
if (val < 0) {
this.$message.warning(`该数值不能小于系统设置的最小值0`);
@ -595,7 +766,7 @@ export default {
try {
this.annexFunc();
// - 使
//
const submitData = {
...this.AEform.formModel,
purchaseMediaBoList: this.mediaTableData.map(item => ({
@ -634,6 +805,7 @@ export default {
} catch (error) {
console.error('保存失败:', error);
this.$message.error('保存失败');
}
},
@ -646,17 +818,6 @@ export default {
this._isDestroyed = true;
if (this._periodTimer) clearTimeout(this._periodTimer);
if (this._overdueTimer) clearTimeout(this._overdueTimer);
//
this.mediaTableData = [];
this.paymentTableData = [];
this.AEform.formModel = {};
this.mediaTypeList = [];
},
beforeRouteLeave(to, _, next) {
to.meta.flag = this.flag;
next();
}
};
</script>
@ -667,6 +828,18 @@ export default {
overflow: auto;
padding-right: 2px;
.table-container {
margin-bottom: 20px;
.table-actions {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 10px;
padding: 10px 0;
}
}
.el-group-form {
::v-deep {
>.el-card {

View File

@ -69,7 +69,7 @@ export const mediaListColumns = [
{
title: "媒体位置",
dataIndex: "mediaPosition",
minWidth: 300,
width: 180,
type: "text",
align: "center",
required: true,