This commit is contained in:
2026-08-25 22:35:50 +08:00
parent cb98355715
commit 3da2d428f3
11 changed files with 883 additions and 130 deletions
@@ -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 {
@@ -49,9 +49,9 @@
v-loading="loading"
:data="rows"
border
@selection-change="handleSelectionChange"
highlight-current-row
@current-change="handleCurrentChange"
>
<el-table-column type="selection" width="52" fixed="left" align="center" />
<el-table-column type="index" label="序号" width="64" fixed="left" align="center" />
<el-table-column
v-for="column in columns"
@@ -61,12 +61,18 @@
show-overflow-tooltip
>
<template #default="{ row }">
<el-link v-if="column.link && row[column.prop]" type="primary" @click="emitAction('view', row)">{{
row[column.prop]
}}</el-link>
<el-tag v-else-if="column.status" :type="statusType(row.approvalStatus)" class="status-text">{{
displayValue(row[column.prop])
}}</el-tag>
<el-link
v-if="column.link && row[column.prop]"
type="primary"
@click="emitAction('view', row)"
>{{ row[column.prop] }}</el-link
>
<el-tag
v-else-if="column.status"
:type="statusType(row.approvalStatus)"
class="status-text"
>{{ displayValue(row[column.prop]) }}</el-tag
>
<span v-else-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</span>
<span v-else-if="column.prop === 'invoiceStatusName'">{{
invoiceName(row.invoiceStatus)
@@ -74,10 +80,13 @@
<span v-else-if="column.prop === 'paymentStatusName'">{{
paymentName(row.paymentStatus)
}}</span>
<span v-else-if="column.prop === 'kingdeeSyncStatus'">{{
kingdeeSyncName(row.kingdeeSyncStatus)
}}</span>
<span v-else>{{ displayValue(row[column.prop]) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" width="120" fixed="right" align="center">
<el-table-column label="操作" width="320" fixed="right" align="center">
<template #default="{ row }"
><div class="fs-table-panel__links">
<el-link
@@ -109,6 +118,7 @@
import { Refresh } from '@element-plus/icons-vue';
import {
invoiceStatusOptions,
kingdeeSyncStatusOptions,
paymentStatusOptions,
} from '@/option/settlement/formalSettlementSearch';
@@ -129,9 +139,9 @@ export default {
};
},
methods: {
handleSelectionChange(value) {
this.selection = value;
this.$emit('update:selection', value);
handleCurrentChange(row) {
this.selection = row ? [row] : [];
this.$emit('update:selection', this.selection);
},
emitAction(type, row) {
this.$emit('action', { type, row });
@@ -180,6 +190,9 @@ export default {
paymentName(value) {
return paymentStatusOptions.find(item => item.value === value)?.label || value || '-';
},
kingdeeSyncName(value) {
return kingdeeSyncStatusOptions.find(item => item.value === value)?.label || value || '-';
},
displayValue(value) {
return value === null || value === undefined || value === '' ? '-' : value;
},
@@ -419,7 +419,7 @@
<el-dialog
v-model="candidateDialog.visible"
title="添加应收应付明细进预结算单"
title="选择结算明细"
width="92%"
append-to-body
destroy-on-close
@@ -466,6 +466,9 @@
>
<template #default="{ row }">
<span v-if="column.money">{{ formatMoney(row[column.prop], row.currency) }}</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>
@@ -1086,8 +1089,15 @@ export default {
},
transportTypeName(value) {
if (value === undefined || value === null || value === '') return '';
const normalizedValue = String(value).trim().toLocaleLowerCase();
const option = this.transportTypeOptions.find(
item => String(item.dictKey) === String(value) || String(item.dictValue) === String(value)
item =>
String(item.dictKey ?? '')
.trim()
.toLocaleLowerCase() === normalizedValue ||
String(item.dictValue ?? '')
.trim()
.toLocaleLowerCase() === normalizedValue
);
return option?.dictValue || value;
},
@@ -102,14 +102,13 @@
}}</span></template
></el-table-column
>
<el-table-column label="调整金额(不含税)" min-width="170" align="center"
<el-table-column
v-if="!editable"
label="调整金额(不含税)"
min-width="170"
align="center"
><template #default="{ row }"
><el-input-number
v-if="editable"
v-model="row.adjustmentAmountNoTax"
:precision="2"
:controls="false"
/><span v-else>{{ formatMoney(row.adjustmentAmountNoTax) }}</span></template
><span>{{ formatMoney(row.adjustmentAmountNoTax) }}</span></template
></el-table-column
>
<el-table-column label="备注" min-width="180" align="center"
@@ -140,6 +139,60 @@
>调整后结算金额<strong>{{ formatMoney(form.adjustedSettlementAmount) }}</strong>
</div>
</section-card>
<section-card title="附件材料">
<el-table :data="attachments" border>
<el-table-column type="index" label="序号" width="64" align="center" />
<el-table-column label="文件名" min-width="220" show-overflow-tooltip>
<template #default="{ row }">
<el-link type="primary" @click="previewAttachment(row)">
{{ attachmentName(row) }}
</el-link>
</template>
</el-table-column>
<el-table-column label="附件描述" min-width="240">
<template #default="{ row }">
<el-input
v-if="editable"
v-model="row.description"
maxlength="200"
placeholder="请输入附件描述"
/>
<span v-else>{{ displayValue(row.description) }}</span>
</template>
</el-table-column>
<el-table-column label="文件大小" width="120" align="center">
<template #default="{ row }">{{ formatFileSize(row) }}</template>
</el-table-column>
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
<el-table-column prop="uploadTime" label="上传时间" width="170" align="center" />
<el-table-column label="操作" :width="editable ? 130 : 70" fixed="right" align="center">
<template #default="{ row, $index }">
<div class="settlement-adjustment-editor__links">
<el-link type="primary" @click="downloadAttachment(row)">下载</el-link>
<el-link v-if="editable" type="danger" @click="removeAttachment($index)">
删除
</el-link>
</div>
</template>
</el-table-column>
<template #empty><el-empty description="暂无附件材料" /></template>
</el-table>
<div v-show="editable" class="settlement-adjustment-editor__attachment-upload">
<vehicle-attachment-upload
ref="attachmentUploadRef"
v-model="attachments"
:readonly="!editable"
:multiple="true"
:limit="20"
:max-size="500"
:file-types="attachmentFileTypes"
:show-file-list="false"
button-text="上传附件"
@change="handleAttachmentChange"
/>
</div>
</section-card>
</div>
<template #footer
><el-button @click="visible = false">取消</el-button
@@ -199,6 +252,7 @@ import {
createSettlementAdjustmentForm,
settlementAdjustmentFormFields,
} from '@/option/settlement/settlementAdjustmentForm';
import { downloadFileByUrl } from '@/utils/util';
export default {
name: 'SettlementAdjustmentEditor',
props: { modelValue: Boolean, recordId: [String, Number], readonly: Boolean },
@@ -209,6 +263,23 @@ export default {
form: createSettlementAdjustmentForm(),
fields: settlementAdjustmentFormFields,
details: [],
attachments: [],
attachmentFileTypes: [
'pdf',
'bmp',
'jpeg',
'png',
'jpg',
'doc',
'docx',
'ppt',
'pptx',
'xlsx',
'xls',
'eml',
'msg',
'zip',
],
candidates: [],
feeRows: [],
rules: {
@@ -248,6 +319,7 @@ export default {
async initialize() {
this.form = createSettlementAdjustmentForm();
this.details = [];
this.attachments = [];
this.candidates = [];
this.feeRows = [];
if (!this.recordId) {
@@ -264,6 +336,7 @@ export default {
adjustmentAmountNoTax:
item.adjustmentAmountNoTax == null ? null : Number(item.adjustmentAmountNoTax),
}));
this.attachments = this.parseAttachments(data.attachmentsJson);
await this.loadFormalSettlements(this.form.formalSettlementNo || '');
} finally {
this.loading = false;
@@ -276,8 +349,9 @@ export default {
async handleFormalChange(id) {
const item = this.candidates.find(row => String(row.id) === String(id));
if (!item) return;
Object.assign(this.form, item, {
formalSettlementId: id,
const { id: formalSettlementId, ...formalSettlement } = item;
Object.assign(this.form, formalSettlement, {
formalSettlementId,
formalSettlementNo: item.formalSettlementNo,
originalSettlementAmount: Number(item.settlementAmount || 0),
settlementTypeName: item.settlementTypeName,
@@ -321,8 +395,9 @@ export default {
this.saving = true;
try {
await api.save({
id: this.form.id,
...(this.recordId ? { id: this.form.id } : {}),
formalSettlementId: this.form.formalSettlementId,
attachmentsJson: JSON.stringify(this.attachments || []),
remark: this.form.remark,
details: this.details.map(item => ({
formalSettlementDetailId: item.formalSettlementDetailId,
@@ -347,15 +422,77 @@ export default {
formatMoney(value) {
return Number(value || 0).toFixed(2);
},
handleAttachmentChange(files) {
const userInfo = this.$store.getters.userInfo || {};
const uploadUserName = userInfo.realName || userInfo.userName || '';
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
this.attachments = (files || []).map(file => ({
...file,
description: file.description || '',
uploadUserName: file.uploadUserName || uploadUserName,
uploadTime: file.uploadTime || uploadTime,
}));
},
parseAttachments(value) {
if (!value) return [];
if (Array.isArray(value)) return value;
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [];
} catch (error) {
return [];
}
},
attachmentName(file = {}) {
return file.originalName || file.name || file.fileName || '附件';
},
attachmentUrl(file = {}) {
return file.url || file.link || file.fileUrl || file.domain || '';
},
formatFileSize(file = {}) {
const bytes = Number(file.size || file.fileSize || file.attachSize || 0);
if (!bytes) return '-';
if (bytes < 1024) return `${bytes}B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
return `${(bytes / 1024 / 1024).toFixed(1)}MB`;
},
previewAttachment(file) {
this.$refs.attachmentUploadRef?.handlePreview(file);
},
downloadAttachment(file) {
const url = this.attachmentUrl(file);
if (!url) {
this.$message.warning('附件地址为空');
return;
}
downloadFileByUrl(url, this.attachmentName(file));
},
removeAttachment(index) {
this.attachments.splice(index, 1);
},
},
};
</script>
<style scoped lang="scss">
.settlement-adjustment-editor {
max-height: 72vh;
overflow-y: auto;
padding-right: 4px;
}
.settlement-adjustment-editor__summary {
padding: 12px 0;
text-align: right;
}
.settlement-adjustment-editor__attachment-upload {
padding-top: 12px;
}
.settlement-adjustment-editor__links {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.settlement-adjustment-editor :deep(.el-form-item) {
margin-bottom: 16px;
}