Vue3+Element Plus+el-upload文件上传组件二次封装
创作时间:
作者:
@小白创作中心
Vue3+Element Plus+el-upload文件上传组件二次封装
引用
CSDN
1.
https://m.blog.csdn.net/mark885/article/details/144715894
本文将介绍如何使用Vue3、Element Plus和el-upload组件进行文件上传功能的二次封装。通过本教程,你将能够创建一个支持图片和视频上传、预览、删除等功能的自定义上传组件。
最终效果图
该组件支持限制上传大小、数量、预览查看、删除以及自动更新父组件值。
使用方法
<el-form-item label="附件上传" prop="imagesAndVideos">
<uploadView
v-model:img-list="formData.imagesAndVideos"
:boxStyle="{ width: '80px', height: '80px' }"
:limit="5"
:imageSize="300"
:videoSize="50 * 1024"
></uploadView>
<div class="input-tip">支持图片和视频上传,图片大小不超过300KB,视频大小不超过50M</div>
</el-form-item>
组件代码
<template>
<div class="custom-uploader-img-box">
<div v-for="(item, i) in imageUrls" :key="item" class="image-container">
<!-- 图片 -->
<template v-if="getUploadFileType(item) == 'image'">
<el-image class="image" :style="props.boxStyle" :src="item" :preview-src-list="imageUrls" fit="cover" />
</template>
<!-- 视频 -->
<template v-if="getUploadFileType(item) == 'video'">
<video class="image" :src="item" controls :style="props.boxStyle"></video>
</template>
<div class="icon-container">
<el-icon @click="handlePictureCardPreview(item)"><zoom-in /></el-icon>
<el-icon @click="handleRemove(i)"><Delete /></el-icon>
</div>
</div>
<el-upload
v-if="imageUrls.length < Number(props.limit)"
ref="elUploadViewRef"
class="avatar-uploader"
action="#"
:headers="uploadHeaders"
:http-request="handleUploadForm"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
:on-change="handleUploadChange"
:file-list="fileList"
:show-file-list="false"
:limit="Number(props.limit)"
:multiple="Number(props.limit) > 1"
>
<el-icon class="custom-uploader-icon" :style="props.boxStyle">
<Plus />
</el-icon>
<template v-for="(value, name) in slots" #[name]="scope">
<slot :name="name" v-bind="scope || {}"></slot>
</template>
</el-upload>
</div>
<el-dialog v-model="dialogVisible" title="预览" width="50%" align-center>
<template v-if="dialogViewData.type == 'image'">
<img class="dialog-view-obj" w-full :src="dialogViewData.url" />
</template>
<template v-if="dialogViewData.type == 'video'">
<video class="dialog-view-obj" :src="dialogViewData.url" controls></video>
</template>
</el-dialog>
</template>
<script setup>
import { ref, watch, useSlots, getCurrentInstance, defineProps, defineEmits, reactive } from 'vue';
import { uploadImageApi, uploadFileApi } from '@apis/system.js';
import { useAdminStore } from '@/store';
const adminStore = useAdminStore();
const { proxy } = getCurrentInstance();
const elUploadViewRef = ref(null);
const slots = useSlots();
const props = defineProps({
//列表数据 ['http://1111.jpg', 'http://222.jpg']
imgList: {
type: Array,
required: true,
default: () => {
return [];
},
},
//样式大小限制
boxStyle: {
type: Object,
default: () => {
return { width: '80px', height: '80px' };
},
},
//上传数量限制
limit: {
type: [Number, String],
default: () => {
return 1;
},
},
//上传图片 大小限制 0为不限制 - 50 * 1024 = 50MB,300 = 300KB
imageSize: {
type: [Number, String],
default: () => {
return 0;
},
},
//上传视频 大小限制 0为不限制 - 50 * 1024 = 50MB,300 = 300KB
videoSize: {
type: [Number, String],
default: () => {
return 0;
},
},
//上传文件 大小限制 0为不限制 - 50 * 1024 = 50MB,300 = 300KB
fileSize: {
type: [Number, String],
default: () => {
return 0;
},
},
});
const imageUrl = ref('');
const imageUrls = ref([]);
const fileList = ref([]);
const emit = defineEmits(['update:imgList']);
watch(
() => props.imgList,
(newVal, oldVal) => {
if (Array.isArray(newVal) && newVal.length > 0) {
imageUrls.value = newVal;
}
},
);
//上传文件需要 token
const merToken = computed(() => adminStore.merToken);
const uploadHeaders = reactive({
'Authori-zation': merToken,
});
//上传成功
const handleAvatarSuccess = (response, uploadFile) => {
const URL = window.URL || window.webkitURL;
imageUrl.value = URL.createObjectURL(uploadFile.raw);
};
//上传前判断
const beforeAvatarUpload = (rawFile) => {
//根据不同的文件类型判断
const fType = getUploadFileType(rawFile.name);
if (!fType) {
proxy.$MsgBox.MessageError('不支持上传文件:' + rawFile.name);
return false;
}
//上传图片
if (fType == 'image') {
if (Number(props.imageSize) > 0 && rawFile.size > Number(props.imageSize) * 1024) {
proxy.$MsgBox.MessageError(`图片大小超过上传限制`);
return false;
}
}
//上传视频
if (fType == 'video') {
if (Number(props.videoSize) > 0 && rawFile.size > Number(props.videoSize) * 1024) {
proxy.$MsgBox.MessageError(`视频大小超过上传限制`);
return false;
}
}
//上传文件
if (fType == 'file') {
if (Number(props.fileSize) > 0 && rawFile.size > Number(props.fileSize) * 1024) {
proxy.$MsgBox.MessageError(`文件大小超过上传限制`);
return false;
}
}
return true;
};
const dialogViewData = reactive({ type: '', url: '' });
const dialogVisible = ref(false);
const handlePictureCardPreview = (uploadFile) => {
dialogViewData.type = getUploadFileType(uploadFile);
dialogViewData.url = uploadFile;
dialogVisible.value = true;
};
const handleRemove = (i) => {
// 从 fileList 中移除被删除的文件
// fileList.value = fileList.value.filter(file => file.uid !== uploadFile.uid)
imageUrls.value.splice(i, 1);
console.log(i, imageUrls);
emit('update:imgList', imageUrls.value);
};
//清空文件
const handleUploadChange = () => {
if (Number(props.limit) <= 1) {
elUploadViewRef.value && elUploadViewRef.value.clearFiles();
}
};
// 上传
const handleUploadForm = (param) => {
const formData = new FormData();
formData.append('model', 'chat_img');
formData.append('pid', 7);
formData.append('multipart', param.file);
//图片上传
if (param.file.type.startsWith('image/')) {
return uploadImage(formData); // 上传视频 与 上传图片 接口一样
}
//视频上传
if (param.file.type.startsWith('video/')) {
return uploadImage(formData); // 上传视频 与 上传图片 接口一样
}
return proxy.$MsgBox.MessageError(`上传文件类型错误,请重新选择`);
};
//图片上传
const uploadImage = (formData) => {
const loading = proxy.$loading({
lock: true,
text: '上传中,请稍候...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)',
});
uploadImageApi(formData)
.then((res) => {
imageUrls.value.push(res.data.url);
console.log('图片提交返回', res, imageUrls, fileList);
emit('update:imgList', imageUrls.value);
loading.close();
})
.catch((res) => {
loading.close();
proxy.$MsgBox.MessageError(res.message || '上传失败');
})
.finally(() => {
loading.close();
});
};
//文件上传
const uploadFile = (formData) => {
const loading = proxy.$loading({
lock: true,
text: '上传中,请稍候...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)',
});
uploadFileApi(formData)
.then((res) => {
imageUrls.value.push(res.data.url);
emit('update:imgList', imageUrls.value);
loading.close();
})
.catch((res) => {
loading.close();
proxy.$MsgBox.MessageError(res.message || '上传失败');
})
.finally(() => {
loading.close();
});
};
//判断连接地址类型
const checkUrlPath = (path = '', ext = ['mp4']) => {
const fileExtension = path.split('.').pop().toLowerCase();
return ext.includes(fileExtension);
};
//根据文件名 得到文件类型
const getUploadFileType = (fileName = '') => {
//视频
if (checkUrlPath(fileName, ['mp4', 'flv', 'avi'])) {
return 'video';
}
//图片
if (checkUrlPath(fileName, ['jpg', 'jpeg', 'png', 'gif', 'bmp'])) {
return 'image';
}
//文件
if (checkUrlPath(fileName, ['txt', 'word', 'excel', 'pdf', 'ppt', 'zip', 'rar', '7z'])) {
return 'file';
}
return '';
};
</script>
<style lang="scss" scoped>
.custom-uploader-img-box {
width: 100%;
display: flex;
flex-wrap: wrap;
.image-container {
position: relative;
display: inline-block;
cursor: pointer;
margin: 0 5px;
img {
border: 1px dashed var(--el-border-color);
font-size: 28px;
color: #8c939d;
width: 80px;
height: 80px;
text-align: center;
border-radius: 10px;
object-fit: cover;
margin: 0 1px;
}
}
.image-container:first-child {
margin-left: 0;
}
}
.icon-container {
position: absolute;
right: 0;
bottom: 0;
transform: translate(0, -11px);
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.5);
color: #fff;
height: 30px;
border-radius: 10px 0 0 0;
opacity: 0;
transition: opacity 0.3s;
.el-icon {
font-size: 15px;
margin: 0 6px;
}
}
.image-container:hover .icon-container {
opacity: 1;
}
</style>
<style lang="scss">
.avatar-uploader .el-upload {
border: 1px dashed var(--el-border-color);
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: var(--el-transition-duration-fast);
}
.avatar-uploader .el-upload:hover {
border-color: var(--el-color-primary);
}
.el-icon.custom-uploader-icon {
font-size: 24px;
color: #8c939d;
width: 80px;
height: 80px;
text-align: center;
}
.el-upload-list__item {
align-items: center;
}
.el-dialog__body {
margin-top: 20px;
text-align: center;
img {
min-height: 50vh;
}
}
.dialog-view-obj {
max-width: 400px;
height: auto;
}
</style>
热门推荐
哪些情况下需要提供社保证明?
深度解析提升集成显卡性能的五大策略,从基础到高级技巧全面升级
看电影的更佳位置不是正中间
海菜花:水性扬花的生态指示植物
最新研究成果!海菜爽、海菜花茶问世
天天洗头发有什么危害
柔性生产装配线与输送线平衡的设计与优化研究【附数据】
冲裁模具材料
厦门北站南广场:一座免费的“彩色小镇”
断肠草全面解析:从形态特征到应用价值
断肠草全面解析:形态、功效、应用与禁忌
哪里出产的珍珠品质最好?五种珍珠产地详解
应用文写作指南:定义、特征、分类与写作技巧
霍元甲为何能影响全国,成为家喻戓晓的明星人物?
智能的本质:多学科视角下的AI发展之路
人社部能力建设中心的主要职责是什么?
试用期包吃包住单位是不是可以不给工资
仓库如何做好防盗措施管理
显卡长期开启一键超频对显卡寿命有影响吗?
绿萝是否为裸子植物?(探究绿萝的生物分类与特征)
古代的大将军到底有多大?
钯金价格一夜之间狂飙超10%! 美国“制裁大棒”欲挥向俄罗斯钯
串起京冀!北京地铁22号线最新进展来了
严查!越南榴莲、菠萝蜜再爆重金属超标!海关将逐批检验
梦见家人之间很融洽的深层含义
父母失信对子女的哪些方面有影响
法律视角下的亲子鉴定:证明血缘关系的关键证据
亲子鉴定在法律中的应用:如何影响亲权纠纷?
“3小时旅游圈”为何持续圈粉?
《我正常吗?》:在标准与个性间寻找自我