<template>
|
<el-upload
|
v-model:file-list="innerFileList"
|
:action="action"
|
:name="name"
|
:multiple="multiple"
|
ref="fileUploadRef"
|
:auto-upload="autoUpload"
|
:headers="headers"
|
:limit="limit"
|
:disabled="disabled"
|
:before-upload="handleBeforeUpload"
|
:on-exceed="handleExceed"
|
:on-error="handleUploadError"
|
:on-success="handleUploadSuccess"
|
:on-remove="handleRemove"
|
:on-preview="handlePreview"
|
:show-file-list="showFileList"
|
>
|
<el-button v-if="!disabled" type="primary">{{ buttonText }}</el-button>
|
<template #file="{ file }">
|
<div style="display:flex; align-items:center; gap: 10px; width: 100%;">
|
<span style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
{{ file.name }}
|
</span>
|
<div style="display:flex; align-items:center; gap: 6px;">
|
<el-button link type="success" :icon="Download" @click="handleDownload(file)" />
|
<el-button link type="primary" :icon="View" @click="handlePreview(file)" />
|
<el-button link type="danger" :icon="Delete" @click="triggerRemoveFile(file)" v-if="onView" />
|
</div>
|
</div>
|
</template>
|
<template #tip>
|
<div class="el-upload__tip">
|
{{ tipText }}
|
</div>
|
</template>
|
</el-upload>
|
</template>
|
|
<script setup>
|
import { computed, ref } from "vue";
|
import { Delete, Download, View } from "@element-plus/icons-vue";
|
|
const props = defineProps({
|
name: {
|
type: String,
|
default: "file",
|
},
|
fileList: {
|
type: Array,
|
default: () => [],
|
},
|
action: {
|
type: String,
|
required: true,
|
},
|
headers: {
|
type: Object,
|
default: () => ({}),
|
},
|
multiple: {
|
type: Boolean,
|
default: true,
|
},
|
limit: {
|
type: Number,
|
default: undefined,
|
},
|
replaceOnExceed: {
|
type: Boolean,
|
default: false,
|
},
|
autoUpload: {
|
type: Boolean,
|
default: true,
|
},
|
showFileList: {
|
type: Boolean,
|
default: true,
|
},
|
disabled: {
|
type: Boolean,
|
default: false,
|
},
|
buttonText: {
|
type: String,
|
default: "上传",
|
},
|
tipText: {
|
type: String,
|
default: "支持文档和图片格式",
|
},
|
beforeUpload: {
|
type: Function,
|
default: null,
|
},
|
onError: {
|
type: Function,
|
default: null,
|
},
|
onSuccess: {
|
type: Function,
|
default: null,
|
},
|
onRemove: {
|
type: Function,
|
default: null,
|
},
|
onPreview: {
|
type: Function,
|
default: null,
|
},
|
onDownload: {
|
type: Function,
|
default: null,
|
},
|
onView: {
|
type: Boolean,
|
default: true,
|
},
|
});
|
|
const emit = defineEmits([
|
"update:fileList",
|
"error",
|
"success",
|
"remove",
|
"preview",
|
"download",
|
]);
|
|
const fileUploadRef = ref(null);
|
|
const innerFileList = computed({
|
get: () => props.fileList || [],
|
set: (val) => emit("update:fileList", val),
|
});
|
|
const resolveUrl = (url) => {
|
const u = String(url || "");
|
if (!u) return "";
|
if (/^(https?:)?\/\//i.test(u)) return u;
|
if (/^(blob:|data:)/i.test(u)) return u;
|
const baseUrl = import.meta.env.VITE_APP_BASE_API || "";
|
if (!baseUrl) return u;
|
if (u.startsWith("/")) return baseUrl + u;
|
return baseUrl + "/" + u;
|
};
|
|
const getFileUrl = (file) => {
|
const respData = file?.response?.data;
|
const rawUrl =
|
file?.url ||
|
(Array.isArray(respData) ? respData?.[0]?.fileUrl : undefined) ||
|
respData?.fileUrl ||
|
respData?.tempPath ||
|
respData?.url ||
|
"";
|
return resolveUrl(rawUrl);
|
};
|
|
const triggerRemoveFile = (file) => {
|
fileUploadRef.value?.handleRemove?.(file);
|
};
|
|
const handleBeforeUpload = (...args) => {
|
if (props.beforeUpload) {
|
return props.beforeUpload(...args);
|
}
|
return true;
|
};
|
|
const handleUploadError = (...args) => {
|
props.onError?.(...args);
|
emit("error", ...args);
|
};
|
|
const handleExceed = (files) => {
|
if (!props.replaceOnExceed) return;
|
if (props.limit !== 1) return;
|
const file = files?.[0];
|
if (!file) return;
|
fileUploadRef.value?.clearFiles?.();
|
innerFileList.value = [];
|
fileUploadRef.value?.handleStart?.(file);
|
if (props.autoUpload) {
|
fileUploadRef.value?.submit?.();
|
}
|
};
|
|
const handleUploadSuccess = (...args) => {
|
const [response, uploadFile, uploadFiles] = args;
|
if (uploadFile && !uploadFile.url) {
|
const rawUrl = response?.data?.tempPath || response?.data?.url || response?.url || "";
|
const resolvedUrl = resolveUrl(rawUrl);
|
if (resolvedUrl) {
|
uploadFile.url = resolvedUrl;
|
}
|
}
|
if (props.limit === 1 && Array.isArray(uploadFiles) && uploadFiles.length) {
|
innerFileList.value = [uploadFiles[uploadFiles.length - 1]];
|
}
|
props.onSuccess?.(...args);
|
emit("success", ...args);
|
};
|
|
const handleRemove = (...args) => {
|
props.onRemove?.(...args);
|
emit("remove", ...args);
|
};
|
|
const handlePreview = (file, ...rest) => {
|
if (props.onPreview) {
|
props.onPreview(file, ...rest);
|
emit("preview", file, ...rest);
|
return;
|
}
|
const url = getFileUrl(file);
|
if (url) {
|
window.open(url, "_blank");
|
}
|
emit("preview", file, ...rest);
|
};
|
|
const handleDownload = (file) => {
|
if (props.onDownload) {
|
props.onDownload(file);
|
emit("download", file);
|
return;
|
}
|
const url = getFileUrl(file);
|
if (!url) return;
|
const link = document.createElement("a");
|
link.href = url;
|
link.download = file?.name || "download";
|
link.target = "_blank";
|
document.body.appendChild(link);
|
link.click();
|
document.body.removeChild(link);
|
emit("download", file);
|
};
|
</script>
|