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如何重新定义搜索引擎优化的未来
情感成熟:提升人际关系的关键
詹姆斯·格罗斯:情绪成熟的发展路径
情绪成熟:心理健康的关键
折弯机的维护保养方法有哪些?
数控折弯机常见故障的综合分析和排除
王成栋左手倒右手,奥拉股份借道双成药业曲线上市
各地花样“灯”场闹元宵 春节文旅消费热度不减
怀孕初期需要注意什么?3大重点让你安心度过孕早期
开斋节必备:这些阿拉伯语表达你必须知道!
开斋节:全球穆斯林的精神盛宴
肉孜节:从古至今的庆祝变迁
古尔邦节:全球穆斯林的盛大庆典
认知矫正疗法调节重度抑郁症患者的内在神经活动
性别居然会被环境影响?
《星之彩》:尼古拉斯·凯奇带你进入克苏鲁世界
肿瘤康复新路径,整合医学理念下的康复实践
克苏鲁文化如何影响现代艺术设计?
《克苏鲁的呼唤》:一部开创性的恐怖经典
后疫情时代的克苏鲁热潮:从《纷纷水火》看当代人的恐惧与反思
洛夫克拉夫特:克苏鲁神话之父
情绪心智:提升亲子关系的关键
专家提醒:过度追求情绪价值或陷入恶性循环
安徽某企业工会:打造高情绪价值职场文化的典范
女性情绪管理:如何避免“动不动就生气”?
世界更年期关怀日 | 至少别惹她生气
家庭教育与学习动力的激发:如何引导孩子从内在动机出发,主动学习?