365 lines
9.7 KiB
Vue
365 lines
9.7 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="true"
|
|
:on-success="handleSuccess"
|
|
:on-error="handleError"
|
|
: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="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 website from '@/config/website';
|
|
import { getToken } from '@/utils/auth';
|
|
|
|
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',
|
|
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: 0,
|
|
},
|
|
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,
|
|
},
|
|
},
|
|
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 {
|
|
[website.tokenHeader]: getToken(),
|
|
};
|
|
},
|
|
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() {
|
|
if (this.tip) return this.tip;
|
|
const typeText = this.acceptText || '所有类型';
|
|
const sizeText = this.maxSize > 0 ? `,单文件不超过 ${this.maxSize}MB` : '';
|
|
return `支持上传 ${typeText}${sizeText}`;
|
|
},
|
|
},
|
|
watch: {
|
|
modelValue: {
|
|
handler(value) {
|
|
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);
|
|
return {
|
|
...item,
|
|
uid: item.uid || `${url || item.name || 'file'}-${index}`,
|
|
name: item.name || this.getFileName(url) || '附件',
|
|
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('上传成功');
|
|
},
|
|
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 name = data.name || data.originalName || file.name || this.getFileName(url) || '附件';
|
|
const extension = this.getExtension({ name, url });
|
|
return {
|
|
...data,
|
|
uid: file.uid || data.uid,
|
|
name,
|
|
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%;
|
|
|
|
:deep(.el-upload-list__item-name) {
|
|
cursor: pointer;
|
|
}
|
|
|
|
&__empty {
|
|
color: #909399;
|
|
}
|
|
}
|
|
|
|
:global(.vehicle-attachment-upload__viewer-dialog .el-dialog__body) {
|
|
padding: 12px 16px 16px;
|
|
}
|
|
</style>
|