378 lines
7.9 KiB
Vue
378 lines
7.9 KiB
Vue
<!-- 图片上传组件 -->
|
||
<template>
|
||
<!-- 实际的上传组件(隐藏) -->
|
||
<div style="display: none">
|
||
<el-upload
|
||
ref="uploadRef"
|
||
:file-list="fileList"
|
||
:http-request="handleUpload"
|
||
:before-upload="handleBeforeUpload"
|
||
:on-success="handleSuccess"
|
||
:on-error="handleError"
|
||
:data="props.additionalParams"
|
||
:name="props.fileParamName"
|
||
:accept="props.accept"
|
||
:limit="props.limit"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 自定义的显示区域 -->
|
||
<div class="custom-upload-list">
|
||
<!-- 已上传的图片列表 -->
|
||
<div v-for="(path, index) in valFileList" :key="index" class="custom-upload-item">
|
||
<img class="upload-thumbnail" :src="path" alt="" />
|
||
<div class="upload-actions">
|
||
<span class="action-item preview" @click="previewImg(path)">
|
||
<el-icon><zoom-in /></el-icon>
|
||
</span>
|
||
<span v-if="props.showDelBtn" class="action-item delete" @click="handleRemove(path)">
|
||
<el-icon><Delete /></el-icon>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 上传按钮 -->
|
||
<div
|
||
v-if="valFileList.length < props.limit && props.showUploadBtn"
|
||
class="custom-upload-trigger"
|
||
@click="triggerUpload"
|
||
>
|
||
<el-icon><Plus /></el-icon>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图片预览组件 -->
|
||
<el-image-viewer
|
||
v-if="viewVisible"
|
||
:zoom-rate="1.2"
|
||
:initialIndex="initialIndex"
|
||
:url-list="previewImgUrls"
|
||
@close="closePreview"
|
||
/>
|
||
</template>
|
||
<script setup lang="ts">
|
||
import { UploadRawFile, UploadUserFile, UploadRequestOptions } from "element-plus";
|
||
import FileAPI, { FileInfo } from "@/api/file";
|
||
|
||
const emit = defineEmits(["update:modelValue"]);
|
||
|
||
const props = defineProps({
|
||
/**
|
||
* 附加的参数
|
||
*/
|
||
additionalParams: {
|
||
type: Object,
|
||
default: () => {
|
||
return {};
|
||
},
|
||
},
|
||
/**
|
||
* 上传文件的参数名
|
||
*/
|
||
fileParamName: {
|
||
type: String,
|
||
default: "file",
|
||
},
|
||
/**
|
||
* 文件上传数量限制
|
||
*/
|
||
limit: {
|
||
type: Number,
|
||
default: 1,
|
||
},
|
||
/**
|
||
* 是否显示删除按钮
|
||
*/
|
||
showDelBtn: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
/**
|
||
* 是否显示上传按钮
|
||
*/
|
||
showUploadBtn: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
/**
|
||
* 单张图片最大大小,单位MB
|
||
*/
|
||
maxSize: {
|
||
type: Number,
|
||
default: 10,
|
||
},
|
||
/**
|
||
* 上传文件类型
|
||
*/
|
||
accept: {
|
||
type: String,
|
||
default: "image/*",
|
||
},
|
||
/**
|
||
* 支持的文件类型,默认支持所有图片格式
|
||
* eg:['png','jpg','jpeg','gif']
|
||
*/
|
||
supportFileType: {
|
||
type: Array<string>,
|
||
default: () => [],
|
||
},
|
||
|
||
/**
|
||
* 自定义样式
|
||
*/
|
||
style: {
|
||
type: Object,
|
||
default: () => {
|
||
return {
|
||
width: "130px",
|
||
height: "130px",
|
||
};
|
||
},
|
||
},
|
||
|
||
/**
|
||
* 是否多图上传
|
||
*/
|
||
multiple: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
});
|
||
|
||
// 定义v-model
|
||
const modelValue = defineModel("modelValue", {
|
||
type: [String, Array] as PropType<string | string[]>,
|
||
required: true,
|
||
default: () => "",
|
||
});
|
||
|
||
const viewVisible = ref(false);
|
||
const initialIndex = ref(0);
|
||
|
||
const fileList = ref<UploadUserFile[]>([]); // 默认上传文件
|
||
const previewImgUrls = ref<string[]>([]);
|
||
|
||
// 添加一个ref来引用el-upload组件
|
||
const uploadRef = ref();
|
||
|
||
watch(
|
||
() => modelValue.value,
|
||
(newValue) => {
|
||
if (!props.multiple) {
|
||
return;
|
||
} else {
|
||
if (Array.isArray(newValue)) {
|
||
fileList.value = newValue.map((filePath) => {
|
||
return { url: filePath } as UploadUserFile;
|
||
});
|
||
}
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
|
||
/**
|
||
* 删除图片
|
||
*/
|
||
function handleRemove(path: string) {
|
||
if (path) {
|
||
FileAPI.deleteByPath(path).then(() => {
|
||
if (Array.isArray(modelValue.value)) {
|
||
modelValue.value = modelValue.value.filter((x) => x !== path);
|
||
} else {
|
||
modelValue.value = "";
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 限制用户上传文件的格式和大小
|
||
*/
|
||
function handleBeforeUpload(file: UploadRawFile) {
|
||
// 限制文件大小
|
||
if (file.size > props.maxSize * 1024 * 1024) {
|
||
ElMessage.warning("上传图片不能大于" + props.maxSize + "M");
|
||
return false;
|
||
}
|
||
// 判断文件类型
|
||
// 获取文件后缀名
|
||
const fileExt = file.name.split(".").pop();
|
||
if (!fileExt) {
|
||
ElMessage.warning("上传图片格式错误,支持的文件类型:" + props.supportFileType.join(","));
|
||
return false;
|
||
}
|
||
// 给文件后缀名转换为小写
|
||
const lowerCaseFileExt = fileExt.toLowerCase();
|
||
// 判断文件后缀名是否在支持的文件类型中
|
||
if (props.supportFileType.length > 0) {
|
||
let isSupport = false;
|
||
props.supportFileType.forEach((type) => {
|
||
if (type.toLowerCase() === lowerCaseFileExt) {
|
||
isSupport = true;
|
||
}
|
||
});
|
||
if (!isSupport) {
|
||
ElMessage.warning("上传图片格式错误,支持的文件类型:" + props.supportFileType.join(","));
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/*
|
||
* 上传文件
|
||
*/
|
||
function handleUpload(options: UploadRequestOptions) {
|
||
return new Promise((resolve, reject) => {
|
||
const { file, onSuccess, onError } = options;
|
||
|
||
const formData = new FormData();
|
||
formData.append(props.fileParamName, file);
|
||
|
||
// 处理附加参数
|
||
Object.keys(props.additionalParams).forEach((key) => {
|
||
formData.append(key, props.additionalParams[key]);
|
||
});
|
||
|
||
FileAPI.upload(formData)
|
||
.then((data) => {
|
||
onSuccess(data);
|
||
resolve(data);
|
||
})
|
||
.catch((error) => {
|
||
onError(error);
|
||
reject(error);
|
||
});
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 上传成功回调
|
||
*
|
||
* @param fileInfo 上传成功后的文件信息
|
||
*/
|
||
const handleSuccess = (fileInfo: FileInfo) => {
|
||
ElMessage.success("上传成功");
|
||
valFileList.value.push(fileInfo.url);
|
||
if (!props.multiple) {
|
||
emit("update:modelValue", fileInfo.url);
|
||
emit("change", fileInfo.url);
|
||
} else {
|
||
emit("update:modelValue", valFileList.value);
|
||
emit("change", valFileList.value);
|
||
}
|
||
return;
|
||
};
|
||
|
||
/**
|
||
* 上传失败回调
|
||
*
|
||
* @param error 上传失败的错误信息
|
||
*/
|
||
const handleError = (error: any) => {
|
||
ElMessage.error("上传失败");
|
||
};
|
||
|
||
/**
|
||
* 预览图片
|
||
*/
|
||
const previewImg = (path: string) => {
|
||
previewImgUrls.value = fileList.value.map((file) => file.url!);
|
||
initialIndex.value = fileList.value.findIndex((file) => file.url === path);
|
||
viewVisible.value = true;
|
||
};
|
||
|
||
/**
|
||
* 关闭预览
|
||
*/
|
||
const closePreview = () => {
|
||
viewVisible.value = false;
|
||
};
|
||
|
||
// 修改triggerUpload方法
|
||
const triggerUpload = () => {
|
||
// 通过ref直接访问el-upload组件内的input元素
|
||
const uploadEl = uploadRef.value.$el.querySelector(".el-upload__input");
|
||
if (uploadEl) {
|
||
uploadEl.click();
|
||
}
|
||
};
|
||
</script>
|
||
<style lang="scss" scoped>
|
||
.custom-upload-list {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.custom-upload-item {
|
||
position: relative;
|
||
width: v-bind("props.style.width");
|
||
height: v-bind("props.style.height");
|
||
overflow: hidden;
|
||
border-radius: 6px;
|
||
|
||
.upload-thumbnail {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.upload-actions {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgb(0 0 0 / 50%);
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
|
||
&:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
.action-item {
|
||
padding: 8px;
|
||
color: #fff;
|
||
cursor: pointer;
|
||
|
||
&:hover {
|
||
color: var(--el-color-primary);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.custom-upload-trigger {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: v-bind("props.style.width");
|
||
height: v-bind("props.style.height");
|
||
cursor: pointer;
|
||
background-color: rgb(255 254 254 / 50%);
|
||
border: 1px dashed var(--el-border-color);
|
||
border-radius: 6px;
|
||
transition: var(--el-transition-duration);
|
||
|
||
&:hover {
|
||
color: var(--el-color-primary);
|
||
border-color: var(--el-color-primary);
|
||
}
|
||
|
||
.el-icon {
|
||
font-size: 20px;
|
||
color: #999;
|
||
}
|
||
|
||
&:hover .el-icon {
|
||
color: var(--el-color-primary);
|
||
}
|
||
}
|
||
</style>
|