fix bug
This commit is contained in:
@@ -136,6 +136,9 @@
|
||||
<el-input-number
|
||||
v-else-if="editable && column.prop === 'adjustAmount'"
|
||||
v-model="row.adjustAmount"
|
||||
:class="{
|
||||
'formal-editor__negative-amount': Number(row.adjustAmount || 0) < 0,
|
||||
}"
|
||||
:precision="2"
|
||||
:step="0.01"
|
||||
:controls="false"
|
||||
@@ -147,7 +150,13 @@
|
||||
maxlength="50"
|
||||
show-word-limit
|
||||
/>
|
||||
<span v-else-if="isSummaryMoney(column.prop)">
|
||||
<span
|
||||
v-else-if="isSummaryMoney(column.prop)"
|
||||
:class="{
|
||||
'formal-editor__negative-amount':
|
||||
column.prop === 'adjustAmount' && Number(row[column.prop] || 0) < 0,
|
||||
}"
|
||||
>
|
||||
{{ formatMoney(row[column.prop]) }}
|
||||
</span>
|
||||
<span v-else-if="column.prop === 'feeType'">{{ feeCategoryName(row.feeType) }}</span>
|
||||
@@ -214,6 +223,9 @@
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else-if="column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[column.prop]) }}
|
||||
</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -232,15 +244,79 @@
|
||||
</el-table>
|
||||
</section-card>
|
||||
|
||||
<section-card title="附件">
|
||||
<vehicle-attachment-upload
|
||||
v-model="attachments"
|
||||
:readonly="!editable"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="500"
|
||||
:file-types="attachmentFileTypes"
|
||||
/>
|
||||
<section-card>
|
||||
<template #title>
|
||||
<span>附件</span>
|
||||
<span v-if="missingAttachmentTypeText" class="formal-editor__attachment-missing">
|
||||
{{ missingAttachmentTypeText }}
|
||||
</span>
|
||||
</template>
|
||||
<el-table :data="attachments" border>
|
||||
<el-table-column type="index" label="序号" width="64" align="center" />
|
||||
<el-table-column label="类型" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select
|
||||
v-if="editable"
|
||||
v-model="row.type"
|
||||
filterable
|
||||
placeholder="请选择类型"
|
||||
@change="sortAttachments"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in attachmentTypeOptions"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-else>{{ attachmentTypeName(row.type) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件名" min-width="220" align="left" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
<el-link
|
||||
:disabled="!getAttachmentUrl(row)"
|
||||
type="primary"
|
||||
@click="previewAttachment(row)"
|
||||
>
|
||||
{{ row.originalName || row.name || '-' }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadUserName" label="上传人" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ displayValue(row.uploadUserName) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="uploadTime" label="上传时间" min-width="170" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ displayValue(row.uploadTime) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="size" label="文件大小" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
{{ displayValue(row.size) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="editable" label="操作" width="90" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-link type="danger" @click="removeAttachment($index)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div v-if="editable" class="formal-editor__attachment-upload">
|
||||
<vehicle-attachment-upload
|
||||
v-model="attachmentUploadFiles"
|
||||
:multiple="true"
|
||||
:limit="20"
|
||||
:max-size="500"
|
||||
:file-types="attachmentFileTypes"
|
||||
button-text="上传附件"
|
||||
:show-file-list="false"
|
||||
:show-uploading="true"
|
||||
@success="handleAttachmentUploadSuccess"
|
||||
/>
|
||||
</div>
|
||||
</section-card>
|
||||
|
||||
<section-card v-if="readonly && payments.length" title="付款信息">
|
||||
@@ -313,10 +389,13 @@
|
||||
v-bind="column"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }"
|
||||
><span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span
|
||||
><span v-else>{{ displayValue(row[column.prop]) }}</span></template
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="column.money">{{ formatMoney(row[column.prop]) }}</span>
|
||||
<span v-else-if="column.prop === 'transportType'">
|
||||
{{ transportTypeName(row[column.prop]) }}
|
||||
</span>
|
||||
<span v-else>{{ displayValue(row[column.prop]) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="formal-editor__pagination">
|
||||
@@ -493,6 +572,8 @@ import {
|
||||
sourceColumns,
|
||||
summaryColumns,
|
||||
} from '@/option/settlement/formalSettlementTable';
|
||||
import { getDictionary } from '@/api/system/dictbiz';
|
||||
import { mapGetters } from 'vuex';
|
||||
import * as XLSX from 'xlsx';
|
||||
|
||||
export default {
|
||||
@@ -518,6 +599,8 @@ export default {
|
||||
summaryFees: [],
|
||||
payments: [],
|
||||
attachments: [],
|
||||
attachmentUploadFiles: [],
|
||||
attachmentTypeOptions: [],
|
||||
attachmentFileTypes: [
|
||||
'pdf',
|
||||
'bmp',
|
||||
@@ -539,6 +622,7 @@ export default {
|
||||
allContracts: [],
|
||||
projects: [],
|
||||
feeOptions: [],
|
||||
transportTypeOptions: [],
|
||||
fields: formalSettlementFormFields,
|
||||
sourceTableColumns: sourceColumns,
|
||||
summaryTableColumns: summaryColumns,
|
||||
@@ -592,6 +676,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['userInfo']),
|
||||
visible: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
@@ -635,6 +720,10 @@ export default {
|
||||
})
|
||||
);
|
||||
},
|
||||
missingAttachmentTypeText() {
|
||||
const missing = this.getMissingAttachmentTypes();
|
||||
return missing.length ? `未上传:${missing.join('、')}` : '';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
@@ -663,14 +752,19 @@ export default {
|
||||
this.summaryFees = [];
|
||||
this.payments = [];
|
||||
this.attachments = [];
|
||||
this.attachmentUploadFiles = [];
|
||||
this.detailCollapsed = false;
|
||||
this.resetDetailQuery(false);
|
||||
await Promise.all([this.loadAllContracts(), this.loadFeeOptions()]);
|
||||
await Promise.all([
|
||||
this.loadAllContracts(),
|
||||
this.loadFeeOptions(),
|
||||
this.loadAttachmentTypeOptions(),
|
||||
this.loadTransportTypeOptions(),
|
||||
]);
|
||||
if (!this.recordId) {
|
||||
this.form.exchangeRateDate = this.$dayjs().format('YYYY-MM-DD');
|
||||
const response = await getNextNo();
|
||||
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
||||
if (this.initialData) await this.applyInitialData();
|
||||
await this.refreshFormalSettlementNo();
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
@@ -690,6 +784,7 @@ export default {
|
||||
this.summaryFees = data.summaryFees || [];
|
||||
this.payments = data.payments || [];
|
||||
this.attachments = this.parseAttachments(data.attachmentsJson);
|
||||
this.sortAttachments();
|
||||
this.contracts = this.allContracts.filter(
|
||||
item => String(item.projectId) === String(this.form.projectId)
|
||||
);
|
||||
@@ -697,6 +792,14 @@ export default {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async refreshFormalSettlementNo() {
|
||||
if (this.form.id || !this.form.settlementType) {
|
||||
if (!this.form.id) this.form.formalSettlementNo = '';
|
||||
return;
|
||||
}
|
||||
const response = await getNextNo(this.form.settlementType);
|
||||
this.form.formalSettlementNo = this.unwrapData(response) || '';
|
||||
},
|
||||
async applyInitialData() {
|
||||
const sourcePreSettlements = Array.isArray(this.initialData?.sourcePreSettlements)
|
||||
? this.initialData.sourcePreSettlements
|
||||
@@ -739,7 +842,7 @@ export default {
|
||||
settlementType,
|
||||
});
|
||||
}
|
||||
this.handleContractChange(contractId);
|
||||
this.handleContractChange(contractId, false);
|
||||
Object.assign(this.form, {
|
||||
contractId,
|
||||
contractNo: first.contractNo || this.form.contractNo,
|
||||
@@ -781,7 +884,7 @@ export default {
|
||||
settlementType,
|
||||
});
|
||||
}
|
||||
this.handleContractChange(contractId);
|
||||
this.handleContractChange(contractId, false);
|
||||
this.sources = rows.map(row => ({
|
||||
...row,
|
||||
preSettlementId: row.preSettlementId || row.id,
|
||||
@@ -869,7 +972,7 @@ export default {
|
||||
this.form.sourceDetailIds = [];
|
||||
this.loadContracts();
|
||||
},
|
||||
handleContractChange(id) {
|
||||
handleContractChange(id, refreshSettlementNo = true) {
|
||||
const contract = this.contracts.find(item => String(item.id) === String(id));
|
||||
if (!contract) return;
|
||||
const formalSettlementId = this.form.id;
|
||||
@@ -889,6 +992,7 @@ export default {
|
||||
this.summaryFees = [];
|
||||
this.form.sourcePreSettlementIds = [];
|
||||
this.form.sourceDetailIds = [];
|
||||
if (refreshSettlementNo) this.refreshFormalSettlementNo();
|
||||
},
|
||||
openCandidateDialog() {
|
||||
this.candidate.visible = true;
|
||||
@@ -933,6 +1037,7 @@ export default {
|
||||
contractId: this.sources[0].contractId,
|
||||
formalSettlementNo: this.form.formalSettlementNo,
|
||||
});
|
||||
this.refreshFormalSettlementNo();
|
||||
}
|
||||
this.form.settlementAmount = this.sources.reduce(
|
||||
(sum, item) => sum + Number(item.settlementAmount || 0),
|
||||
@@ -1187,6 +1292,7 @@ export default {
|
||||
if (invalidManualFee) {
|
||||
return this.$message.warning('请完善手工费用的费用类型和费用项');
|
||||
}
|
||||
if (!this.validateAttachments()) return;
|
||||
this.saving = true;
|
||||
try {
|
||||
await save({
|
||||
@@ -1213,7 +1319,7 @@ export default {
|
||||
remark: row.remark,
|
||||
manualFlag: row.manualFlag || 0,
|
||||
})),
|
||||
attachmentsJson: JSON.stringify(this.attachments || []),
|
||||
attachmentsJson: this.stringifyAttachments(this.attachments),
|
||||
remark: this.form.remark,
|
||||
});
|
||||
this.$message.success('保存成功');
|
||||
@@ -1233,6 +1339,142 @@ export default {
|
||||
formatMoney(value) {
|
||||
return `${Number(value || 0).toFixed(2)} RMB`;
|
||||
},
|
||||
async loadTransportTypeOptions() {
|
||||
const { data } = await getDictionary({ code: 'transport_type' });
|
||||
this.transportTypeOptions = data?.data || [];
|
||||
},
|
||||
transportTypeName(value) {
|
||||
if (value === undefined || value === null || value === '') return '';
|
||||
const normalizedValue = String(value).trim().toLocaleLowerCase();
|
||||
const option = this.transportTypeOptions.find(
|
||||
item =>
|
||||
String(item.dictKey ?? '')
|
||||
.trim()
|
||||
.toLocaleLowerCase() === normalizedValue ||
|
||||
String(item.dictValue ?? '')
|
||||
.trim()
|
||||
.toLocaleLowerCase() === normalizedValue
|
||||
);
|
||||
return option?.dictValue || value;
|
||||
},
|
||||
async loadAttachmentTypeOptions() {
|
||||
const response = await getDictionary({ code: 'settle_attachment_types' });
|
||||
const data = response?.data?.data || [];
|
||||
this.attachmentTypeOptions = data.map(item => ({
|
||||
label: item.dictValue,
|
||||
value: item.dictValue,
|
||||
}));
|
||||
this.sortAttachments();
|
||||
},
|
||||
resolveAttachmentType(fileName) {
|
||||
const normalizedName = String(fileName || '').toLocaleLowerCase();
|
||||
const matchedType = [...this.attachmentTypeOptions]
|
||||
.filter(item => item?.label || item?.value)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
String(b.label || b.value || '').length - String(a.label || a.value || '').length
|
||||
)
|
||||
.find(item => {
|
||||
const typeText = String(item.label || item.value || '').toLocaleLowerCase();
|
||||
return typeText && normalizedName.includes(typeText);
|
||||
});
|
||||
if (matchedType?.value) return matchedType.value;
|
||||
const otherType = this.attachmentTypeOptions.find(item =>
|
||||
String(item.label || item.value || '').includes('其他')
|
||||
);
|
||||
if (otherType?.value) return otherType.value;
|
||||
this.attachmentTypeOptions.push({ label: '其他附件', value: '其他附件' });
|
||||
return '其他附件';
|
||||
},
|
||||
getAttachmentTypeOrder(type) {
|
||||
const normalizedType = String(type || '').trim();
|
||||
const index = this.attachmentTypeOptions.findIndex(
|
||||
item =>
|
||||
String(item.value || '').trim() === normalizedType ||
|
||||
String(item.label || '').trim() === normalizedType
|
||||
);
|
||||
return index === -1 ? Number.MAX_SAFE_INTEGER : index;
|
||||
},
|
||||
sortAttachments() {
|
||||
this.attachments = (this.attachments || [])
|
||||
.map((file, index) => ({ file, index }))
|
||||
.sort((a, b) => {
|
||||
const orderDifference =
|
||||
this.getAttachmentTypeOrder(a.file.type) - this.getAttachmentTypeOrder(b.file.type);
|
||||
return orderDifference || a.index - b.index;
|
||||
})
|
||||
.map(item => item.file);
|
||||
},
|
||||
getMissingAttachmentTypes(files = this.attachments) {
|
||||
const uploadedTypes = new Set(
|
||||
(files || [])
|
||||
.filter(item => this.getAttachmentUrl(item))
|
||||
.map(item => String(item.type || '').trim())
|
||||
.filter(Boolean)
|
||||
);
|
||||
return this.attachmentTypeOptions
|
||||
.filter(item => String(item.label || item.value || '').trim() !== '其他附件')
|
||||
.filter(item => !uploadedTypes.has(String(item.value || item.label || '').trim()))
|
||||
.map(item => item.label || item.value);
|
||||
},
|
||||
validateAttachments(files = this.attachments) {
|
||||
const missing = this.getMissingAttachmentTypes(files);
|
||||
if (!missing.length) return true;
|
||||
this.$message.warning(`请先上传附件:${missing.join('、')}`);
|
||||
return false;
|
||||
},
|
||||
handleAttachmentUploadSuccess(file) {
|
||||
const originalName = file.originalName || file.name || '附件';
|
||||
this.attachments.push({
|
||||
type: this.resolveAttachmentType(originalName),
|
||||
originalName,
|
||||
name: originalName,
|
||||
uploadUserName: this.userInfo.realName || this.userInfo.userName || '',
|
||||
uploadTime: this.$dayjs().format('YYYY-MM-DD HH:mm:ss'),
|
||||
size: this.formatAttachmentSize(file.size),
|
||||
url: file.url || file.link || '',
|
||||
});
|
||||
this.sortAttachments();
|
||||
},
|
||||
removeAttachment(index) {
|
||||
this.attachments.splice(index, 1);
|
||||
},
|
||||
getAttachmentUrl(file = {}) {
|
||||
return file.url || file.link || file.src || file.domain || '';
|
||||
},
|
||||
previewAttachment(file) {
|
||||
const url = this.getAttachmentUrl(file);
|
||||
if (!url) return this.$message.warning('附件地址为空,无法预览');
|
||||
window.open(url, '_blank');
|
||||
},
|
||||
attachmentTypeName(type) {
|
||||
const option = this.attachmentTypeOptions.find(
|
||||
item => String(item.value) === String(type) || String(item.label) === String(type)
|
||||
);
|
||||
return this.displayValue(option?.label || type);
|
||||
},
|
||||
formatAttachmentSize(size) {
|
||||
if (!size) return '';
|
||||
const byteSize = Number(size);
|
||||
if (!Number.isFinite(byteSize)) return String(size);
|
||||
const mb = byteSize / 1024 / 1024;
|
||||
if (mb >= 1) return `${mb.toFixed(2)} MB`;
|
||||
return `${(byteSize / 1024).toFixed(2)} KB`;
|
||||
},
|
||||
stringifyAttachments(list) {
|
||||
const files = (list || [])
|
||||
.filter(item => this.getAttachmentUrl(item))
|
||||
.map(item => ({
|
||||
type: String(item.type || '').trim(),
|
||||
originalName: String(item.originalName || item.name || '').trim(),
|
||||
name: String(item.originalName || item.name || '').trim(),
|
||||
uploadUserName: String(item.uploadUserName || '').trim(),
|
||||
uploadTime: String(item.uploadTime || '').trim(),
|
||||
size: String(item.size || '').trim(),
|
||||
url: String(this.getAttachmentUrl(item)).trim(),
|
||||
}));
|
||||
return files.length ? JSON.stringify(files) : '';
|
||||
},
|
||||
parseFeeItems(value) {
|
||||
if (!value) return {};
|
||||
if (typeof value === 'object') return value;
|
||||
@@ -1244,12 +1486,38 @@ export default {
|
||||
},
|
||||
parseAttachments(value) {
|
||||
if (!value) return [];
|
||||
if (Array.isArray(value)) return value;
|
||||
let attachments = value;
|
||||
if (!Array.isArray(value)) {
|
||||
try {
|
||||
attachments = JSON.parse(value);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
if (!Array.isArray(attachments)) return [];
|
||||
return attachments.map(item => {
|
||||
const url = this.getAttachmentUrl(item);
|
||||
const originalName =
|
||||
item.originalName || item.name || this.getAttachmentFileName(url) || '附件';
|
||||
return {
|
||||
...item,
|
||||
type: item.type || this.resolveAttachmentType(originalName),
|
||||
originalName,
|
||||
name: originalName,
|
||||
uploadUserName: item.uploadUserName || item.createUserName || item.uploadUser || '',
|
||||
uploadTime: item.uploadTime || item.createTime || '',
|
||||
size: this.formatAttachmentSize(item.size || item.attachSize),
|
||||
url,
|
||||
};
|
||||
});
|
||||
},
|
||||
getAttachmentFileName(url) {
|
||||
if (!url) return '';
|
||||
const path = String(url).split('?')[0];
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
return decodeURIComponent(path.substring(path.lastIndexOf('/') + 1));
|
||||
} catch {
|
||||
return [];
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -1305,6 +1573,21 @@ export default {
|
||||
.formal-editor__adjust-reason {
|
||||
margin-top: 16px;
|
||||
}
|
||||
.formal-editor__attachment-missing {
|
||||
margin-left: 12px;
|
||||
color: var(--el-color-danger);
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
}
|
||||
.formal-editor__attachment-upload {
|
||||
margin-top: 12px;
|
||||
}
|
||||
.formal-editor__negative-amount {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
.formal-editor :deep(.formal-editor__negative-amount .el-input__inner) {
|
||||
color: var(--el-color-danger);
|
||||
}
|
||||
/* 选择预结算单 / 选择结算明细 弹窗搜索区白底 */
|
||||
.formal-editor__candidate-search,
|
||||
.formal-editor__detail-search {
|
||||
|
||||
Reference in New Issue
Block a user