Files
tms-erp-web/src/components/vehicle-attachment-upload/main.vue
T
b2894lxlx cba2ad3b3b 1、调整凭证
2、调整运单
2026-09-08 17:50:34 +08:00

421 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="vehicle-attachment-upload">
<el-upload
:action="uploadAction"
name="file"
:headers="uploadHeaders"
:file-list="fileList"
:accept="acceptText"
:multiple="multiple"
:limit="limit"
:disabled="readonly"
:show-file-list="showFileList"
:on-success="handleSuccess"
:on-error="handleError"
:on-change="handleChange"
:on-remove="handleRemove"
:on-preview="handlePreview"
:before-upload="beforeUpload"
>
<el-button v-if="!readonly" type="primary" plain :icon="buttonIcon">
{{ buttonText }}
</el-button>
<template #tip>
<div v-if="showTip && tipText && !readonly" class="el-upload__tip">
{{ tipText }}
</div>
</template>
</el-upload>
<span v-if="showUploading && uploadingCount" class="vehicle-attachment-upload__uploading">
上传中{{ uploadingCount }}
</span>
<span v-if="readonly && fileList.length === 0" class="vehicle-attachment-upload__empty">
-
</span>
<el-dialog
v-model="previewVisible"
:title="previewFile.name || '附件预览'"
append-to-body
destroy-on-close
width="90%"
top="4vh"
class="vehicle-attachment-upload__viewer-dialog"
>
<open-file-viewer
v-if="previewVisible && previewSource"
:file="previewSource"
:file-name="previewFile.name"
:mime-type="previewFile.mimeType"
width="100%"
height="72vh"
fit="contain"
theme="auto"
locale="zh-CN"
:toolbar="viewerToolbar"
:plugins="viewerPlugins"
@unsupported="handleUnsupported"
@error="handleViewerError"
/>
</el-dialog>
</div>
</template>
<script>
import { OpenFileViewer } from '@open-file-viewer/vue';
import {
fallbackPlugin,
imagePlugin,
officePlugin,
pdfPlugin,
textPlugin,
} from '@open-file-viewer/core';
import '@open-file-viewer/core/style.css';
import pdfWorkerSrc from 'pdfjs-dist/build/pdf.worker.mjs?url';
import { baseUrl } from '@/config/env';
import { getUploadHeaders } from '@/utils/upload';
const UNIFIED_ATTACHMENT_TIP_PREFIX =
'支持pdf、bmp、jpeg、png、jpg、doc、docx、ppt、pptx、xlsx、xls、eml、msg、zip的文件格式,单个文件不超过';
const MIME_MAP = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
bmp: 'image/bmp',
webp: 'image/webp',
pdf: 'application/pdf',
doc: 'application/msword',
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
xls: 'application/vnd.ms-excel',
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
ppt: 'application/vnd.ms-powerpoint',
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
eml: 'message/rfc822',
msg: 'application/vnd.ms-outlook',
zip: 'application/zip',
txt: 'text/plain',
};
const viewerPlugins = [
imagePlugin(),
pdfPlugin({
workerSrc: pdfWorkerSrc,
useFetchData: true,
}),
officePlugin({
pdf: {
workerSrc: pdfWorkerSrc,
useFetchData: true,
},
}),
textPlugin(),
fallbackPlugin(),
];
export default {
name: 'VehicleAttachmentUpload',
components: {
OpenFileViewer,
},
props: {
modelValue: {
type: [Array, String, Object],
default: () => [],
},
action: {
type: String,
default: '',
},
accept: {
type: [String, Array],
default: '',
},
fileTypes: {
type: Array,
default: () => [],
},
multiple: {
type: Boolean,
default: true,
},
limit: {
type: Number,
default: 20,
},
maxSize: {
type: Number,
default: 500,
},
readonly: {
type: Boolean,
default: false,
},
buttonText: {
type: String,
default: '上传附件',
},
buttonIcon: {
type: String,
default: 'el-icon-upload',
},
tip: {
type: String,
default: '',
},
showTip: {
type: Boolean,
default: true,
},
showFileList: {
type: Boolean,
default: true,
},
showUploading: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'change', 'success'],
data() {
return {
fileList: [],
previewVisible: false,
previewFile: {},
previewSource: '',
viewerToolbar: {
download: true,
fullscreen: true,
print: true,
rotate: true,
zoom: true,
},
viewerPlugins,
};
},
computed: {
uploadAction() {
return this.action || `${baseUrl}/blade-resource/oss/endpoint/put-file`;
},
uploadHeaders() {
return getUploadHeaders();
},
acceptedList() {
const types = this.fileTypes.length ? this.fileTypes : this.accept;
if (Array.isArray(types)) {
return types.map(item => String(item).trim()).filter(Boolean);
}
return String(types || '')
.split(',')
.map(item => item.trim())
.filter(Boolean);
},
acceptText() {
return this.acceptedList.join(',');
},
tipText() {
return this.tip || `${UNIFIED_ATTACHMENT_TIP_PREFIX}${this.maxSize}M`;
},
uploadingCount() {
return this.fileList.filter(file => ['ready', 'uploading'].includes(file.status)).length;
},
},
watch: {
modelValue: {
handler(value) {
// Keep pending uploads in Element Plus' internal list until every selected file settles.
if (this.fileList.some(file => ['ready', 'uploading'].includes(file.status))) return;
this.fileList = this.toUploadList(value);
},
immediate: true,
deep: true,
},
},
methods: {
toUploadList(value) {
const list = this.parseValue(value);
return list.map((item, index) => {
const url = this.getFileUrl(item);
const originalName = item.originalName || item.name || this.getFileName(url) || '附件';
return {
...item,
uid: item.uid || `${url || originalName || 'file'}-${index}`,
originalName,
name: originalName,
url,
status: 'success',
};
});
},
parseValue(value) {
if (!value) return [];
if (Array.isArray(value)) return value.filter(Boolean);
if (typeof value === 'object') return [value];
try {
const list = JSON.parse(value);
return Array.isArray(list) ? list : [];
} catch (error) {
return String(value)
.split(',')
.map(item => item.trim())
.filter(Boolean)
.map(item => ({
name: this.getFileName(item),
url: item,
}));
}
},
getFileUrl(file) {
return file.url || file.link || file.src || file.domain || '';
},
getFileName(url) {
if (!url) return '';
const path = String(url).split('?')[0];
return decodeURIComponent(path.substring(path.lastIndexOf('/') + 1));
},
getExtension(file) {
const name = String(file.name || this.getFileUrl(file) || '').split('?')[0];
const index = name.lastIndexOf('.');
return index >= 0 ? name.substring(index + 1).toLowerCase() : '';
},
beforeUpload(file) {
if (!this.validFileType(file)) {
this.$message.warning('文件类型不符合上传要求');
return false;
}
if (this.maxSize > 0 && file.size / 1024 / 1024 > this.maxSize) {
this.$message.warning(`单文件大小不能超过 ${this.maxSize}MB`);
return false;
}
return true;
},
validFileType(file) {
if (!this.acceptedList.length) return true;
const fileName = String(file.name || '').toLowerCase();
const mimeType = String(file.type || '').toLowerCase();
return this.acceptedList.some(type => {
const value = String(type).toLowerCase();
if (value === '*/*') return true;
if (value.startsWith('.')) return fileName.endsWith(value);
if (value.endsWith('/*')) return mimeType.startsWith(value.slice(0, -1));
if (value.includes('/')) return mimeType === value;
return fileName.endsWith(`.${value}`);
});
},
handleSuccess(response, file, files) {
if (!response || response.success === false || (response.code && response.code !== 200)) {
this.$message.error((response && response.msg) || '上传失败');
return;
}
this.emitUploadFiles(this.multiple ? files : [file]);
this.$emit('success', this.normalizeUploadFile(file));
this.$message.success('上传成功');
},
handleChange(file, files) {
this.fileList = files || [];
},
handleError() {
this.$message.error('上传失败');
},
handleRemove(file, files) {
if (this.readonly) return false;
this.emitUploadFiles(files);
return true;
},
emitUploadFiles(files) {
const list = files
.filter(file => file.status === 'success' || this.getFileUrl(file))
.map(this.normalizeUploadFile)
.filter(item => item.url);
this.$emit('update:modelValue', list);
this.$emit('change', list);
},
normalizeUploadFile(file) {
const data = (file.response && file.response.data) || file.data || file;
const url = data.link || data.url || data.domain || file.url || '';
const originalName =
data.originalName ||
file.originalName ||
file.name ||
data.name ||
this.getFileName(url) ||
'附件';
const extension = this.getExtension({ name: originalName, url });
return {
...data,
uid: file.uid || data.uid,
originalName,
name: originalName,
url,
link: data.link || url,
size: data.size || data.attachSize || file.size || '',
extension,
mimeType: data.mimeType || data.contentType || file.raw?.type || MIME_MAP[extension] || '',
};
},
handlePreview(file) {
const previewFile = this.normalizeUploadFile(file);
if (!previewFile.url) {
this.$message.warning('附件地址为空,无法预览');
return;
}
this.previewFile = previewFile;
this.previewSource = previewFile.url;
this.previewVisible = true;
},
handleUnsupported() {
this.$message.warning('当前文件暂不支持在线预览');
},
handleViewerError() {
this.$message.error('附件预览失败');
},
},
};
</script>
<style lang="scss" scoped>
.vehicle-attachment-upload {
width: 100%;
div{
display: flex;
flex-direction: row;
align-items: center;
}
:deep(.el-upload) {
display: flex;
align-items: center;
flex-wrap: nowrap;
}
:deep(.el-upload__tip) {
display: inline-block;
margin: 0 0 0 30px;
color: #909399;
font-size: 13px;
line-height: 1.4;
white-space: normal;
text-align: left;
}
:deep(.el-upload-list__item-name) {
cursor: pointer;
}
&__empty {
color: #909399;
}
&__uploading {
margin-left: 8px;
color: #409eff;
font-size: 13px;
}
}
:global(.vehicle-attachment-upload__viewer-dialog .el-dialog__body) {
padding: 12px 16px 16px;
}
</style>