260 lines
6.8 KiB
Vue
260 lines
6.8 KiB
Vue
<template>
|
|
<div v-loading="isUploading" element-loading-text="上传中..." class="upload-loading-wrapper">
|
|
<el-upload ref="uploadRef" v-model:file-list="fileList" action="*" :on-preview="handlePictureCardPreview"
|
|
v-bind="options" :http-request="handleHttpRequest" :on-change="handleChange"
|
|
:class="isMaxCount ? 'max-count' : ''" style="width: 100%">
|
|
<el-icon v-show="['image', 'images'].includes(type)">
|
|
<Plus />
|
|
</el-icon>
|
|
<div v-show="['file', 'files', 'video', 'videos'].includes(type)">
|
|
<el-icon class="el-icon--upload">
|
|
<upload-filled />
|
|
</el-icon>
|
|
<div class="el-upload__text">拖拽到这里或者 <em>点击上传</em></div>
|
|
</div>
|
|
</el-upload>
|
|
<el-button v-show="showUploadButton" type="primary" @click="handleUpload">上传</el-button>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import {
|
|
computed,
|
|
ref,
|
|
createVNode,
|
|
render,
|
|
} from "vue";
|
|
import { Plus } from "@element-plus/icons-vue";
|
|
import type { UploadFile, UploadFiles, UploadUserFile } from "element-plus";
|
|
import { ElImageViewer } from "element-plus";
|
|
import utils from "lib/utils";
|
|
|
|
interface Props {
|
|
value: {
|
|
data?: FormData;
|
|
uid?: string;
|
|
url: string;
|
|
name?: string;
|
|
id?: string;
|
|
}[];
|
|
uploadFile: (file: FormData) => Promise<any>;
|
|
type?: "image" | "images" | "file" | "files" | "video" | "videos";
|
|
transport?: string;
|
|
accept?: string;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
type: "image",
|
|
transport: "",
|
|
accept: "*",
|
|
value: [] as any,
|
|
uploadFile: () => Promise.resolve({ url: "" }),
|
|
});
|
|
|
|
const showUploadButton = computed(() => {
|
|
return options.value.autoUpload === false;
|
|
});
|
|
|
|
const emit = defineEmits(["updateValue"])
|
|
|
|
const fileList = ref<UploadUserFile[]>([...props.value] as any);
|
|
const uploadRef = ref<any>(null);
|
|
const options = computed(() => {
|
|
switch (props.type) {
|
|
case "image":
|
|
return {
|
|
limit: 1,
|
|
listType: "picture-card" as const, // 明确指定为字面量类型
|
|
accept: "image/*",
|
|
};
|
|
case "images":
|
|
return {
|
|
limit: 9,
|
|
listType: "picture-card" as const, // 明确指定为字面量类型
|
|
accept: "image/*",
|
|
multiple: true,
|
|
autoUpload: false,
|
|
};
|
|
case "video":
|
|
return {
|
|
limit: 1,
|
|
listType: "text" as const, // 明确指定为字面量类型
|
|
accept: "video/*",
|
|
};
|
|
case "videos":
|
|
return {
|
|
limit: 9,
|
|
listType: "text" as const, // 明确指定为字面量类型
|
|
accept: "video/*",
|
|
multiple: true,
|
|
autoUpload: false,
|
|
};
|
|
case "file":
|
|
return {
|
|
limit: 1,
|
|
accept: props.accept,
|
|
drag: true,
|
|
listType: "text" as const, // 为文件类型添加合适的 listType
|
|
};
|
|
case "files":
|
|
return {
|
|
limit: 10,
|
|
accept: props.accept,
|
|
drag: true,
|
|
listType: "text" as const, // 为文件类型添加合适的 listType
|
|
multiple: true,
|
|
autoUpload: false,
|
|
};
|
|
default:
|
|
return {
|
|
limit: 1,
|
|
listType: "picture-card" as const,
|
|
accept: props.accept,
|
|
};
|
|
}
|
|
});
|
|
|
|
const isMaxCount = computed(() => {
|
|
return fileList.value.length >= options.value.limit;
|
|
});
|
|
|
|
const isUploading = ref(false);
|
|
|
|
|
|
interface UploadRequestOptions {
|
|
file: File & { uid?: number; raw?: File, url?: string };
|
|
filename: string;
|
|
onSuccess: (res: any) => void;
|
|
onError: (err: any) => void;
|
|
}
|
|
|
|
const handleHttpRequest = async (options: UploadRequestOptions) => {
|
|
isUploading.value = true;
|
|
const formData = new FormData();
|
|
const rawFile = options.file?.raw ?? options.file;
|
|
formData.append(options.filename || "file", rawFile, rawFile.name);
|
|
try {
|
|
const res = await props.uploadFile(formData);
|
|
const url = res?.data?.url ?? res?.url;
|
|
if (url) {
|
|
options.file.url = url;
|
|
}
|
|
options.onSuccess(res);
|
|
isUploading.value = false;
|
|
} catch (err) {
|
|
console.log('err', err)
|
|
options.onError(err as any);
|
|
isUploading.value = false;
|
|
}
|
|
};
|
|
|
|
const handleRemove = async (uploadFile: UploadFile) => {
|
|
// props.onFileChange(uploadFile);
|
|
// emit("onUpdateValue", fileList.value.filter((f: any) => f.uid !== uploadFile.uid));
|
|
};
|
|
|
|
const handleChange = (file: any, list: any) => {
|
|
const updateList = fileList.value
|
|
.filter((f: any) => f.status === 'success')
|
|
.map((f: any) => {
|
|
return {
|
|
url: f.raw?.url,
|
|
uid: f.raw?.uid,
|
|
name: f.raw?.name,
|
|
id: f.raw?.id,
|
|
}
|
|
})
|
|
emit("updateValue", updateList);
|
|
}
|
|
|
|
|
|
const handlePictureCardPreview = (file: any) => {
|
|
const imageUrl = file.url;
|
|
if (["file", "files"].includes(props.type)) {
|
|
// 下载文件
|
|
|
|
utils.downloadfile(imageUrl, imageUrl, "image/png");
|
|
return;
|
|
}
|
|
// 如果是视频,打开标签页
|
|
if (["video", "videos"].includes(props.type)) {
|
|
window.open(imageUrl, "_blank");
|
|
return;
|
|
}
|
|
if (!imageUrl) return;
|
|
|
|
// 创建一个用于挂载 el-image-viewer 的容器
|
|
const container = document.createElement("div");
|
|
const parentNode =
|
|
document.querySelector(props.transport || "body") || document.body;
|
|
parentNode.appendChild(container);
|
|
// 创建并挂载 el-image-viewer 节点
|
|
const vnode = createVNode(ElImageViewer as any, {
|
|
urlList: [imageUrl],
|
|
onClose: () => {
|
|
render(null, container);
|
|
parentNode.removeChild(container);
|
|
},
|
|
});
|
|
|
|
render(vnode, container);
|
|
};
|
|
|
|
const handleUpload = () => {
|
|
uploadRef.value.submit();
|
|
};
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.upload-loading-wrapper {
|
|
position: relative;
|
|
min-height: 100px;
|
|
}
|
|
|
|
:deep(.el-upload--picture-card) {
|
|
--el-upload-picture-card-size: 100px;
|
|
}
|
|
|
|
:deep(.el-upload-list--picture-card) {
|
|
--el-upload-list-picture-card-size: 100px;
|
|
}
|
|
|
|
:deep(.el-icon--close-tip) {
|
|
display: none !important;
|
|
}
|
|
|
|
// 设置上传中进度条的大小
|
|
:deep(.el-upload-list__item.is-uploading .el-progress) {
|
|
.el-progress-circle {
|
|
transform: scale(0.7);
|
|
}
|
|
}
|
|
|
|
.max-count {
|
|
:deep(.el-upload--picture-card) {
|
|
display: none;
|
|
}
|
|
|
|
:deep(.el-upload) {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
:deep(.el-upload.el-upload--text) {
|
|
border: 1px solid #dcdfe6;
|
|
border-radius: 4px;
|
|
width: 100%;
|
|
min-height: 100px;
|
|
|
|
&>div {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
|
|
.el-icon.el-icon--upload {
|
|
font-size: 20px;
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|