cba2ad3b3b
2、调整运单
421 lines
11 KiB
Vue
421 lines
11 KiB
Vue
<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>
|