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>
热门推荐
手机上如何查询房屋买卖合同
五险一金对人均可支配收入的影响分析
男篮国手杨瀚森离NBA又近了一步?
千年传承:福建古田的红曲文化
洗衣机能平放运输吗?关于家电搬运你不得不知的细节
中山市十大标志农产品公布
色卡提取:设计师必备的色彩搭配技巧
阿拉伯沙蟒:沙漠中的“铲子头”蛇类
V2L、V2V、V2H、V2G?一文看懂新能源車四種外放電功能
新能源汽车外放电功能的优劣各有哪些?
银行的外汇兑换业务流程是怎样的?
羊肉泡馍的营养价值
为避免营养流失,蔬菜焯水时,是尽量不切小块还是时间越长越好?
水煮菜,真的“绝”吗?健康“吃瘦”大揭秘!
超实用整理,澳洲租房攻略全面透析
墨尔本留学食堂费用高吗?具体花费是多少?
钟离是七神中战力最强这一结论是怎么得出来的?
了解乙肝传播途径与预防措施,构建健康生活环境的重要性
乙肝一起吃饭会传染吗
安卓ADB驱动是什么?如何安装和使用?
地中海贫血分为几种类型
适合单眼皮的眼妆怎么画?有哪些技巧和步骤?
黄石火山为什么是世界最大的超级火山之一?
小宝宝便秘能不能用开塞露排便
乌鲁木齐景点:2025必去主题热门景点推荐
旌德县十大旅游景点
光圈与景深的7个艺术秘密:深入理解并实操
贵阳市“菜篮子”市长负责制考核居全国前列
安徽清明节习俗:插柳节
家里水压太小怎么办?水压异常的10个常见原因及解决方案