完善合同附件上传回填、类型匹配与列表附件弹窗

修复上传成功后表格不回显,附件类型可切换,未匹配文件名默认其他文件;列表附件上传支持合同文件/其它附件。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-23 14:49:09 +08:00
parent 6212d68a77
commit e2e2eb9a2f
4 changed files with 454 additions and 207 deletions
@@ -212,7 +212,14 @@ export default {
.filter(Boolean);
},
acceptText() {
return this.acceptedList.join(',');
return this.acceptedList
.map(item => {
const value = String(item).trim();
if (!value || value.includes('/') || value.startsWith('.')) return value;
return `.${value}`;
})
.filter(Boolean)
.join(',');
},
tipText() {
return this.tip || `${UNIFIED_ATTACHMENT_TIP_PREFIX}${this.maxSize}M`;
@@ -304,12 +311,22 @@ export default {
});
},
handleSuccess(response, file, files) {
if (!response || response.success === false || (response.code && response.code !== 200)) {
const code = response?.code;
const codeInvalid = code !== undefined && code !== null && code !== '' && Number(code) !== 200;
if (!response || response.success === false || codeInvalid) {
this.$message.error((response && response.msg) || '上传失败');
return;
}
this.emitUploadFiles(this.multiple ? files : [file]);
this.$emit('success', this.normalizeUploadFile(file));
if (file && !file.response) {
file.response = response;
}
const normalized = this.normalizeUploadFile(file, response);
if (!normalized.url) {
this.$message.error('上传成功但未返回文件地址');
return;
}
this.$emit('success', normalized);
this.emitUploadFiles(this.multiple ? files : [file], normalized);
this.$message.success('上传成功');
},
handleChange(file, files) {
@@ -323,35 +340,89 @@ export default {
this.emitUploadFiles(files);
return true;
},
emitUploadFiles(files) {
const list = files
.filter(file => file.status === 'success' || this.getFileUrl(file))
.map(this.normalizeUploadFile)
emitUploadFiles(files, preferredFile) {
const list = (files || [])
.filter(file => {
if (file?.status === 'fail') return false;
return (
file?.status === 'success' ||
this.getFileUrl(file) ||
file?.response?.data ||
file?.response?.link ||
file?.response
);
})
.map(file => this.normalizeUploadFile(file))
.filter(item => item.url);
this.$emit('update:modelValue', list);
this.$emit('change', list);
if (preferredFile?.url) {
const exists = list.some(
item =>
(preferredFile.uid && item.uid && String(item.uid) === String(preferredFile.uid)) ||
item.url === preferredFile.url
);
if (!exists) list.push(preferredFile);
}
const current = this.parseValue(this.modelValue).map(item => ({
...item,
url: this.getFileUrl(item),
}));
const merged = [...current];
list.forEach(file => {
const index = merged.findIndex(
item =>
(file.uid && item.uid && String(item.uid) === String(file.uid)) ||
(file.url && this.getFileUrl(item) === file.url)
);
if (index >= 0) merged.splice(index, 1, { ...merged[index], ...file });
else merged.push(file);
});
const result = merged.filter(item => this.getFileUrl(item));
this.$emit('update:modelValue', result);
this.$emit('change', result);
},
normalizeUploadFile(file) {
const data = (file.response && file.response.data) || file.data || file;
const url = data.link || data.url || data.domain || file.url || '';
normalizeUploadFile(file, responseOverride) {
const response = responseOverride || file?.response;
let data = {};
if (response && typeof response === 'object') {
if (response.data && typeof response.data === 'object') {
data = response.data;
} else if (response.link || response.url || response.domain) {
data = response;
}
}
if (!data.link && !data.url && !data.domain) {
data = file?.data && typeof file.data === 'object' ? file.data : file || {};
}
const url =
data.link ||
data.url ||
data.domain ||
data.fileLink ||
data.fileUrl ||
file?.url ||
file?.link ||
'';
const originalName =
data.originalName ||
file.originalName ||
file.name ||
file?.originalName ||
file?.name ||
data.name ||
this.getFileName(url) ||
'附件';
const extension = this.getExtension({ name: originalName, url });
return {
...data,
uid: file.uid || data.uid,
uid: file?.uid || data.uid,
originalName,
name: originalName,
url,
link: data.link || url,
size: data.size || data.attachSize || file.size || '',
size: data.size || data.attachSize || file?.size || '',
extension,
mimeType: data.mimeType || data.contentType || file.raw?.type || MIME_MAP[extension] || '',
mimeType: data.mimeType || data.contentType || file?.raw?.type || MIME_MAP[extension] || '',
};
},
handlePreview(file) {
@@ -1,94 +1,102 @@
<template>
<section
class="contract-manage-form__section contract-manage-form__section--panel contract-manage-form__section--attachment"
>
<div class="contract-manage-form__attachment-head">
<div class="dialog-section-title">{{ title }}</div>
<el-button type="primary" :disabled="!rows.length" @click="batchDownload">批量下载</el-button>
</div>
<el-table
:data="rows"
border
class="contract-manage-form__attachment-table"
@selection-change="selected = $event"
<div class="contract-attachment-section">
<section
v-if="!dialogOnly"
class="contract-manage-form__section contract-manage-form__section--panel contract-manage-form__section--attachment"
>
<el-table-column v-if="!readonly" type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column v-if="attachmentType" label="附件类型" min-width="160" align="center">
<template #default="{ row }">
<span v-if="readonly || isRowLocked(row)">{{ row.fileType || '-' }}</span>
<el-select
v-else
:model-value="row.fileType"
placeholder="请选择"
:teleported="false"
:fit-input-width="true"
@update:model-value="value => updateRowFileType(row, value)"
>
<el-option
v-for="item in contractAttachmentTypeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column
label="文件名"
min-width="240"
align="left"
header-align="left"
show-overflow-tooltip
<div class="contract-manage-form__attachment-head">
<div class="dialog-section-title">{{ title }}</div>
<el-button type="primary" :disabled="!rows.length" @click="batchDownload"
>批量下载</el-button
>
</div>
<el-table
:data="rows"
border
class="contract-manage-form__attachment-table"
@selection-change="selected = $event"
>
<template #default="{ row }">
<el-link type="primary" @click="preview?.(row, rows)">{{ attachmentName(row) }}</el-link>
</template>
</el-table-column>
<el-table-column v-if="description" label="附件描述" min-width="220">
<template #default="{ row }">
<span v-if="readonly || isRowLocked(row)">{{ row.description || '-' }}</span>
<el-input
v-else
:model-value="row.description"
maxlength="200"
@update:model-value="value => updateRowDescription(row, value)"
/>
</template>
</el-table-column>
<el-table-column label="文件大小" width="120" align="center">
<template #default="{ row }">{{ formatSize(row.size) }}</template>
</el-table-column>
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
<el-table-column prop="uploadTime" label="上传时间" width="180" align="center" sortable />
<el-table-column v-if="!readonly" label="操作" width="100" align="center" fixed="right">
<template #default="{ row, $index }">
<span v-if="isRowLocked(row)">-</span>
<el-link v-else type="danger" @click="remove($index)">删除</el-link>
</template>
</el-table-column>
</el-table>
<el-table-column v-if="!readonly" type="selection" width="55" align="center" />
<el-table-column type="index" label="序号" width="70" align="center" />
<el-table-column v-if="attachmentType" label="附件类型" min-width="160" align="center">
<template #default="{ row, $index }">
<span v-if="readonly || isRowLocked(row)">{{ row.fileType || '-' }}</span>
<el-select
v-else
:model-value="row.fileType"
placeholder="请选择"
style="width: 100%"
teleported
clearable
@update:model-value="value => updateRowFileType($index, value)"
>
<el-option
v-for="item in contractAttachmentTypeOptions"
:key="`${item.value}-${item.label}`"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column
label="文件名"
min-width="240"
align="left"
header-align="left"
show-overflow-tooltip
>
<template #default="{ row }">
<el-link type="primary" @click="preview?.(row, rows)">{{
attachmentName(row)
}}</el-link>
</template>
</el-table-column>
<el-table-column v-if="description" label="附件描述" min-width="220">
<template #default="{ row, $index }">
<span v-if="readonly || isRowLocked(row)">{{ row.description || '-' }}</span>
<el-input
v-else
:model-value="row.description"
maxlength="200"
@update:model-value="value => updateRowDescription($index, value)"
/>
</template>
</el-table-column>
<el-table-column label="文件大小" width="120" align="center">
<template #default="{ row }">{{ formatSize(row.size) }}</template>
</el-table-column>
<el-table-column prop="uploadUserName" label="上传人" width="140" align="center" />
<el-table-column prop="uploadTime" label="上传时间" width="180" align="center" sortable />
<el-table-column v-if="!readonly" label="操作" width="100" align="center" fixed="right">
<template #default="{ row, $index }">
<span v-if="isRowLocked(row)">-</span>
<el-link v-else type="danger" @click="remove($index)">删除</el-link>
</template>
</el-table-column>
</el-table>
<div v-if="!readonly" class="contract-manage-form__attachment-upload">
<template v-if="useUploadDialog">
<el-button type="primary" plain @click="openUploadDialog">上传附件</el-button>
<span class="contract-manage-form__attachment-tip">
支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过50M
</span>
</template>
<vehicle-attachment-upload
v-else
:model-value="rows"
:file-types="attachmentFileTypes"
:max-size="50"
show-tip
tip="支持pdfbmpjpegpngjpgdocdocxpptpptxxlsxxlsemlmsgzip的文件格式单个文件不超过50M"
:show-file-list="false"
button-text="上传附件"
@update:model-value="handleChange"
@change="handleChange"
/>
</div>
<div v-if="!readonly" class="contract-manage-form__attachment-upload">
<template v-if="useUploadDialog">
<el-button type="primary" plain @click="openUploadDialog">上传附件</el-button>
<span class="contract-manage-form__attachment-tip">
支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过50M
</span>
</template>
<vehicle-attachment-upload
v-else
:model-value="rows"
:file-types="attachmentFileTypes"
:max-size="50"
show-tip
tip="支持pdfbmpjpegpngjpgdocdocxpptpptxxlsxxlsemlmsgzip的文件格式单个文件不超过50M"
:show-file-list="false"
button-text="上传附件"
@change="handleChange"
@success="handleUploadSuccess"
/>
</div>
</section>
<el-dialog
v-model="uploadDialogVisible"
@@ -103,7 +111,12 @@
<div class="contract-attachment-upload-dialog__body">
<div class="contract-attachment-upload-dialog__field">
<span class="contract-attachment-upload-dialog__label">附件位置</span>
<el-select v-model="uploadDialogLocation" placeholder="请选择附件位置" style="width: 100%">
<el-select
v-model="uploadDialogLocation"
placeholder="请选择附件位置"
style="width: 100%"
@change="syncUploadDialogTypeByLocation"
>
<el-option
v-for="item in resolvedLocationOptions"
:key="item"
@@ -149,7 +162,7 @@
</div>
</template>
</el-dialog>
</section>
</div>
</template>
<script>
@@ -159,11 +172,11 @@ import { getUploadHeaders } from '@/utils/upload';
import { downloadFileByUrl } from '@/utils/util';
import { getDictionary as getBizDictionary } from '@/api/system/dictbiz';
const CONTRACT_ATTACHMENT_TYPE_OTHER = '其文件';
const CONTRACT_ATTACHMENT_TYPE_OTHER = '其文件';
export const defaultContractAttachmentTypeOptions = [
{ label: '合同文件', value: '合同文件' },
{ label: '双章归档文件', value: '双章归档文件' },
{ label: '其文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
{ label: '其文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER },
];
/** @deprecated 兼容旧引用,实际选项由业务字典 contract_attachment_type 动态加载 */
export const contractAttachmentTypeOptions = defaultContractAttachmentTypeOptions;
@@ -206,21 +219,32 @@ const normalizeAttachmentTypeText = value => {
.replace(/[\s_\-—–·•()()\[\]【】{}《》<>“”"'、,,。.]/g, '');
};
const isOtherAttachmentTypeOption = item => {
const text = `${item?.label || ''}${item?.value || ''}`;
return (
String(item?.value) === CONTRACT_ATTACHMENT_TYPE_OTHER ||
String(item?.value) === '其它文件' ||
text.includes('其他文件') ||
text.includes('其它文件') ||
text.includes('其他') ||
text.includes('其它')
);
};
const attachmentTypeKeywords = fileType => {
if (fileType === '双章归档文件') return ['双章归档文件', '双章归档', '双章'];
if (fileType === '合同文件') return ['合同文件', '合同'];
return [fileType];
const text = String(fileType || '');
if (text.includes('双章')) return ['双章归档文件', '双章归档', '双章'];
if (text.includes('合同') && !text.includes('其他') && !text.includes('其它')) {
return ['合同文件', '合同'];
}
if (isOtherAttachmentTypeOption({ label: text, value: text })) return [];
return text ? [text] : [];
};
const resolveOtherAttachmentType = (options = []) => {
const list = options.length ? options : defaultContractAttachmentTypeOptions;
const matched = list.find(
item =>
String(item.value) === CONTRACT_ATTACHMENT_TYPE_OTHER ||
String(item.label).includes('其它') ||
String(item.label).includes('其他')
);
return matched?.value || list[list.length - 1]?.value || CONTRACT_ATTACHMENT_TYPE_OTHER;
const matched = list.find(isOtherAttachmentTypeOption);
return matched?.value || CONTRACT_ATTACHMENT_TYPE_OTHER;
};
export const resolveContractAttachmentType = (
@@ -228,22 +252,27 @@ export const resolveContractAttachmentType = (
options = defaultContractAttachmentTypeOptions
) => {
const list = options.length ? options : defaultContractAttachmentTypeOptions;
const otherType = resolveOtherAttachmentType(list);
const values = list.map(item => item.value);
if (values.includes(row.fileType)) return row.fileType;
const matchedByLabel = list.find(item => String(item.label) === String(row.fileType));
if (matchedByLabel) return matchedByLabel.value;
const fileName = normalizeAttachmentTypeText(attachmentName(row));
const otherType = resolveOtherAttachmentType(list);
if (!fileName) return otherType;
const matched = list
.filter(item => item.value !== otherType)
.filter(item => !isOtherAttachmentTypeOption(item))
.flatMap(item =>
attachmentTypeKeywords(item.value).map(keyword => ({
value: item.value,
keyword: normalizeAttachmentTypeText(keyword),
}))
attachmentTypeKeywords(item.value)
.concat(attachmentTypeKeywords(item.label))
.map(keyword => ({
value: item.value,
keyword: normalizeAttachmentTypeText(keyword),
}))
)
.filter(item => item.keyword)
.sort((a, b) => b.keyword.length - a.keyword.length)
.find(item => fileName.includes(item.keyword));
// 文件名未命中任何附件类型时,默认「其他文件」
return matched?.value || otherType;
};
@@ -277,11 +306,12 @@ export default {
lockApproved: Boolean,
markApprovedOnUpload: Boolean,
useUploadDialog: Boolean,
dialogOnly: Boolean,
attachmentLocation: { type: String, default: '合同文件' },
attachmentLocationOptions: { type: Array, default: null },
preview: { type: Function, default: null },
},
emits: ['update:rows', 'upload-to-location'],
emits: ['update:rows', 'upload-to-location', 'upload-confirmed'],
data() {
return {
selected: [],
@@ -323,12 +353,28 @@ export default {
.then(res => {
const options = mapDictOptions(res);
if (options.length) {
this.contractAttachmentTypeOptions = options;
// 字典缺少「其他文件」时补上,保证未匹配文件名可默认选中
this.contractAttachmentTypeOptions = options.some(isOtherAttachmentTypeOption)
? options
: [...options, { label: '其他文件', value: CONTRACT_ATTACHMENT_TYPE_OTHER }];
if (
!options.some(item => String(item.value) === String(this.uploadDialogType))
!this.contractAttachmentTypeOptions.some(
item => String(item.value) === String(this.uploadDialogType)
)
) {
this.uploadDialogType = this.defaultAttachmentType;
}
// 字典加载后,把已有行的中文类型名对齐到 dictKey,保证下拉可选中
if (this.attachmentType && (this.rows || []).length) {
const aligned = (this.rows || []).map(row => ({
...row,
fileType: resolveContractAttachmentType(row, this.contractAttachmentTypeOptions),
}));
const changed = aligned.some(
(row, index) => row.fileType !== this.rows[index]?.fileType
);
if (changed) this.update(aligned);
}
}
})
.catch(() => {
@@ -339,24 +385,76 @@ export default {
return this.lockApproved && isAttachmentApproved(row);
},
update(rows) {
this.$emit('update:rows', rows);
this.$emit('update:rows', Array.isArray(rows) ? rows.map(item => ({ ...item })) : []);
},
updateRowFileType(row, value) {
if (this.isRowLocked(row)) return;
row.fileType = value;
this.update([...this.rows]);
enrichUploadedRow(row, existing) {
const uploadUserName = this.$store.getters.userInfo?.realName || '';
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
const next = {
...existing,
...row,
description: row.description || existing?.description || '',
uploadUserName: existing?.uploadUserName || row.uploadUserName || uploadUserName,
uploadTime: existing?.uploadTime || row.uploadTime || uploadTime,
approved: existing?.approved || row.approved || false,
};
if (this.attachmentType) {
next.fileType = existing?.fileType
? existing.fileType
: resolveContractAttachmentType(
{ ...next, fileType: '' },
this.contractAttachmentTypeOptions
);
}
if (this.markApprovedOnUpload && !existing) next.approved = true;
return next;
},
updateRowDescription(row, value) {
if (this.isRowLocked(row)) return;
row.description = value;
this.update([...this.rows]);
findExistingRow(row, rows = this.rows) {
const fileUrl = attachmentUrl(row);
return (rows || []).find(item => {
const sameUid = row.uid && item.uid && String(row.uid) === String(item.uid);
const sameUrl = fileUrl && attachmentUrl(item) === fileUrl;
return sameUid || sameUrl;
});
},
updateRowFileType(index, value) {
const rows = [...(this.rows || [])];
const row = rows[index];
if (!row || this.isRowLocked(row)) return;
rows[index] = { ...row, fileType: value };
this.update(rows);
},
updateRowDescription(index, value) {
const rows = [...(this.rows || [])];
const row = rows[index];
if (!row || this.isRowLocked(row)) return;
rows[index] = { ...row, description: value };
this.update(rows);
},
resolveTypeByLocation(location) {
const options = this.contractAttachmentTypeOptions || [];
const locationText = String(location || '');
if (locationText.includes('其它') || locationText.includes('其他')) {
return this.defaultAttachmentType;
}
const matched = options.find(
item =>
String(item.value) === locationText ||
String(item.label) === locationText ||
String(item.value).includes('合同') ||
String(item.label).includes('合同')
);
return matched?.value || options[0]?.value || this.defaultAttachmentType;
},
syncUploadDialogTypeByLocation() {
this.uploadDialogType = this.resolveTypeByLocation(this.uploadDialogLocation);
},
openUploadDialog() {
const options = this.resolvedLocationOptions;
this.uploadDialogLocation = options.includes(this.attachmentLocation)
? this.attachmentLocation
: options[0] || this.attachmentLocation;
this.uploadDialogType = this.defaultAttachmentType;
this.syncUploadDialogTypeByLocation();
this.uploadDialogFiles = [];
this.uploadDialogFileList = [];
this.uploadDialogVisible = true;
@@ -365,7 +463,7 @@ export default {
this.uploadDialogFiles = [];
this.uploadDialogFileList = [];
this.uploadDialogLocation = this.attachmentLocation;
this.uploadDialogType = this.defaultAttachmentType;
this.syncUploadDialogTypeByLocation();
},
beforeUpload(file) {
const extension = String(file.name || '')
@@ -408,13 +506,18 @@ export default {
...this.uploadDialogFiles.filter(item => String(item.uid) !== String(file.uid)),
normalized,
];
this.uploadDialogType = resolveContractAttachmentType(
{
...normalized,
fileType: '',
},
this.contractAttachmentTypeOptions
);
const locationText = String(this.uploadDialogLocation || '');
if (!(locationText.includes('其它') || locationText.includes('其他'))) {
this.uploadDialogType = resolveContractAttachmentType(
{
...normalized,
fileType: '',
},
this.contractAttachmentTypeOptions
);
} else {
this.uploadDialogType = this.defaultAttachmentType;
}
this.$message.success('上传成功');
},
handleDialogError() {
@@ -451,50 +554,39 @@ export default {
uploadTime: row.uploadTime || uploadTime,
...(this.markApprovedOnUpload ? { approved: true } : {}),
}));
if (this.uploadDialogLocation === this.attachmentLocation) {
this.update([...(this.rows || []), ...appended]);
} else {
this.$emit('upload-to-location', {
location: this.uploadDialogLocation,
rows: appended,
});
}
// 统一交给父组件按附件位置写入对应列表,避免同位置双写或跨位置不显示
this.$emit('upload-to-location', {
location: this.uploadDialogLocation,
rows: appended,
});
this.uploadDialogVisible = false;
this.$emit('upload-confirmed', {
location: this.uploadDialogLocation,
rows: appended,
});
},
handleChange(rows) {
const uploadUserName = this.$store.getters.userInfo?.realName || '';
const uploadTime = this.$dayjs().format('YYYY-MM-DD HH:mm:ss');
handleChange(list) {
if (!Array.isArray(list)) return;
// 上传组件偶发回传空列表时,禁止清空已有表格数据
if (!list.length) return;
const existingRows = this.rows || [];
this.update(
(rows || []).map(row => {
const fileUrl = attachmentUrl(row);
const existing = existingRows.find(item => {
const sameUid = row.uid && item.uid && String(row.uid) === String(item.uid);
const sameUrl = fileUrl && attachmentUrl(item) === fileUrl;
return sameUid || sameUrl;
});
if (existing && this.isRowLocked(existing)) return { ...existing };
const next = {
...existing,
...row,
description: row.description || existing?.description || '',
uploadUserName: existing?.uploadUserName || row.uploadUserName || uploadUserName,
uploadTime: existing?.uploadTime || row.uploadTime || uploadTime,
approved: existing?.approved || row.approved || false,
};
if (this.attachmentType) {
next.fileType = resolveContractAttachmentType(
{
...next,
fileType: existing?.fileType || row.fileType,
},
this.contractAttachmentTypeOptions
);
}
if (this.markApprovedOnUpload && !existing) next.approved = true;
return next;
})
);
const nextRows = list.map(row => {
const existing = this.findExistingRow(row, existingRows);
if (existing && this.isRowLocked(existing)) return { ...existing };
return this.enrichUploadedRow(row, existing);
});
existingRows.forEach(item => {
if (!this.isRowLocked(item)) return;
if (this.findExistingRow(item, nextRows)) return;
nextRows.unshift({ ...item });
});
this.update(nextRows);
},
handleUploadSuccess(file) {
const url = attachmentUrl(file);
if (!file || !url) return;
if (this.findExistingRow(file)) return;
this.update([...(this.rows || []), this.enrichUploadedRow(file)]);
},
remove(index) {
const row = this.rows[index];
@@ -502,7 +594,7 @@ export default {
this.$message.warning('已审核通过的附件不允许删除');
return;
}
const rows = [...this.rows];
const rows = [...(this.rows || [])];
rows.splice(index, 1);
this.update(rows);
},
@@ -515,7 +607,7 @@ export default {
downloadFileByUrl(url, attachmentName(row));
},
batchDownload() {
(this.selected.length ? this.selected : this.rows).forEach(this.download);
(this.selected.length ? this.selected : this.rows || []).forEach(this.download);
},
formatSize(size) {
const value = Number(size);
@@ -532,7 +624,7 @@ export default {
.contract-manage-form__section {
margin: 12px 0 0;
padding: 14px 16px 16px;
overflow: hidden;
overflow: visible;
background: #fff;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
@@ -555,6 +647,15 @@ export default {
}
}
.contract-manage-form__attachment-table {
:deep(.el-table__body-wrapper),
:deep(.el-table__header-wrapper),
:deep(.el-table__cell),
:deep(.cell) {
overflow: visible;
}
}
.contract-manage-form__attachment-head {
display: flex;
align-items: center;
@@ -189,7 +189,6 @@
title="合同文件"
description
attachment-type
use-upload-dialog
:attachment-location-options="changeAttachmentLocationOptions"
:rows="contractFileRows"
:preview="previewAttachment"
@@ -258,7 +257,6 @@
title="其它附件"
description
attachment-type
use-upload-dialog
attachment-location="其它附件"
:attachment-location-options="changeAttachmentLocationOptions"
:rows="attachments"
@@ -285,7 +283,6 @@
title="变更材料"
description
attachment-type
use-upload-dialog
attachment-location="变更材料"
:attachment-location-options="changeAttachmentLocationOptions"
:rows="changeMaterials"
+92 -14
View File
@@ -592,7 +592,6 @@
title="合同文件"
description
attachment-type
use-upload-dialog
:attachment-location-options="contractAttachmentLocationOptions"
:lock-approved="isAttachmentUploadMode || hasApprovedContractFiles"
:mark-approved-on-upload="isAttachmentUploadMode"
@@ -813,7 +812,6 @@
title="其它附件"
description
attachment-type
use-upload-dialog
attachment-location="其它附件"
:attachment-location-options="contractAttachmentLocationOptions"
:readonly="isAttachmentUploadMode"
@@ -925,6 +923,21 @@
@save="saveBillingPlan"
/>
<contract-attachment-section
ref="listAttachmentUploader"
dialog-only
title="合同文件"
description
attachment-type
use-upload-dialog
mark-approved-on-upload
:attachment-location-options="['合同文件', '其它附件']"
:rows="listUploadContractFileRows"
@update:rows="listUploadContractFileRows = $event"
@upload-to-location="handleListAttachmentUploadToLocation"
@upload-confirmed="handleListAttachmentUploadConfirmed"
/>
<el-dialog
v-model="attachmentDocumentPreviewVisible"
:title="attachmentPreviewFile.name || '附件预览'"
@@ -1236,6 +1249,9 @@ export default {
organizationLoading: false,
contractFileRows: [],
attachmentRows: [],
listUploadContractFileRows: [],
listUploadAttachmentRows: [],
attachmentUploadFromList: false,
attachmentImagePreviewVisible: false,
attachmentImagePreviewUrls: [],
attachmentImagePreviewIndex: 0,
@@ -1899,20 +1915,86 @@ export default {
return;
}
this.submitAction = 'attachment';
this.api
const contractFileRows = this.attachmentUploadFromList
? this.listUploadContractFileRows
: this.contractFileRows;
const attachmentRows = this.attachmentUploadFromList
? this.listUploadAttachmentRows
: this.attachmentRows;
return this.api
.updateAttachments({
id: this.form.id,
contractFileJson: JSON.stringify(this.contractFileRows),
attachmentsJson: JSON.stringify(this.attachmentRows),
contractFileJson: JSON.stringify(contractFileRows),
attachmentsJson: JSON.stringify(attachmentRows),
})
.then(() => {
this.$message.success('附件保存成功');
if (this.attachmentUploadFromList) {
this.attachmentUploadFromList = false;
this.listUploadContractFileRows = [];
this.listUploadAttachmentRows = [];
this.onLoad(this.page, this.query);
return;
}
this.closeForm();
})
.finally(() => {
this.submitAction = '';
});
},
openAttachmentUploadDialog(row = {}) {
if (!row?.id) {
this.$message.warning('合同不存在,无法上传附件');
return;
}
const loading = this.$loading({
lock: true,
text: '加载中',
background: 'rgba(255, 255, 255, 0.6)',
});
this.attachmentUploadFromList = true;
this.listUploadContractFileRows = [];
this.listUploadAttachmentRows = [];
this.form = { ...defaultForm(), id: row.id };
this.api
.getDetail(row.id)
.then(res => {
const detail = res.data?.data || {};
this.form = {
...defaultForm(),
...detail,
id: detail.id || row.id,
};
this.listUploadContractFileRows = normalizeContractFileRows(
parseArray(detail.contractFileJson)
);
this.listUploadAttachmentRows = parseArray(detail.attachmentsJson);
this.$nextTick(() => {
this.$refs.listAttachmentUploader?.openUploadDialog?.();
});
})
.catch(() => {
this.attachmentUploadFromList = false;
this.$message.error('合同详情加载失败');
})
.finally(() => {
loading.close();
});
},
handleListAttachmentUploadConfirmed() {
if (!this.attachmentUploadFromList) return;
this.submitAttachmentUpload();
},
handleListAttachmentUploadToLocation({ location, rows = [] }) {
if (!rows.length) return;
if (location === '合同文件') {
this.listUploadContractFileRows = [...(this.listUploadContractFileRows || []), ...rows];
return;
}
if (location === '其它附件') {
this.listUploadAttachmentRows = [...(this.listUploadAttachmentRows || []), ...rows];
}
},
loadProjectOptions() {
if (this.projectLoading) return;
this.projectLoading = true;
@@ -2749,15 +2831,7 @@ export default {
return;
}
if (operation.action === 'attachmentUpload') {
this.$router.push({
path: '/business/contract-manage/form',
query: {
mode: 'edit',
id: row.id,
name: '附件上传',
attachmentUpload: '1',
},
});
this.openAttachmentUploadDialog(row);
return;
}
const run = value => {
@@ -2978,6 +3052,10 @@ export default {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
}
&--attachment {
overflow: visible;
}
> .dialog-section-title,
:deep(.dialog-section-title) {
margin-bottom: 20px;