Vue3封装一个ElementPlus自定义上传组件2--无弹窗
Vue3封装一个ElementPlus自定义上传组件2--无弹窗
写在前面: 无弹窗的上传组件它来了,依旧是小巧又好用,只不过这回我用的是前端直传的方式,采用http-request进行文件上传,中间有一些小坑,但幸运的是全都解决啦,组件很简单,但是用来学习是最好不过了,个人感觉我的注释应该也是浅显易懂的,希望大家在查看的同时不要忘了给我点个赞哦,当然,如果有写的不恰当或者写的不对的地方,也欢迎大家在底下评论
展示效果:
上传前:

上传后:

代码展示:
- 首先就是定义一个ElementPlus的上传组件,我这边就叫NewUpload:
<el-upload
ref="upload"
multiple
:limit="limit"
action="#"
v-model:file-list="fileList"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleDelete"
:on-preview="handlePreview"
:http-request="handleRequest"
>
<el-button type="primary">上传</el-button>
<template #tip>
<div class="el-upload__tip">
注意:请上传{{ fileSize }}m以下文件,最多支持上传文件数量:{{ limit }}个!
</div>
</template>
</el-upload>
<!-- 如果是图片类型的直接预览,如果是其他类型,则自行添加方法处理 -->
<el-dialog v-model="dialogVisible">
<img :src="dialogImageUrl" alt="Preview Image"/>
</el-dialog>
- 定义js事件处理,里面注释都有,就不说明了
import {ElMessage} from "element-plus";
import axios from "axios";
const props = defineProps({
//文件列表
modelValue: {
type: Array,
default: () => []
},
limit: {
type: Number,
default: 5
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 2,
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => [".jpg", ".jpeg", ".png", ".doc", ".xls", ".xlsx", ".ppt", ".txt", ".pdf"],
},
// 是否显示提示框
isShowTip: {
type: Boolean,
default: true
},
})
const emit = defineEmits(['update:modelValue']);
//文件ref
const upload = ref()
//文件列表
const fileList = ref([]);
//单文件的参数--这边我定义成全局的,但是需要注意深浅拷贝的问题
const fileData = {
name: "",
url: "",
}
//文件url列表--为了解决ElementPlus的文件属性和我们定义上传的文件属性不一致的问题
let fileUrls = []
//初始化
onMounted(() => {
if (props.modelValue.length > 0) {
fileUrls = props.modelValue;
fileList.value = fileUrls
}
});
//处理事件
const beforeUpload = async (options) => {
//校验文件类型
if (props.fileType) {
let fileExtension = "";
if (options.name.lastIndexOf(".") > -1) {
fileExtension = options.name.substring(options.name.lastIndexOf("."));
}
const isTypeOk = props.fileType.some(type => {
if (options.type.indexOf(type) > -1) return true;
return !!(fileExtension && fileExtension.indexOf(type) > -1);
});
if (!isTypeOk) {
ElMessage.error(`文件格式不正确,请上传${props.fileType.join("/")}格式文件!`)
return false;
}
}
try {
//准备上传的数据--这边我是使用前端直传的方式,所以第一步是获取预上传的Url信息
const {name, url} = await preUpload({fileName: options.name});
fileData.name = name;
fileData.url = url;
fileUrls.push(JSON.parse(JSON.stringify(toRaw(fileData))))
fileList.value = fileUrls
} catch (err) {
ElMessage.error("获取上传地址失败,请稍后再试!");
return false;
}
}
//正式上传
const handleRequest = async ({file, onSuccess, onError}) => {
try {
const config = {
//控制文件类型,方便进行显示还是下载
headers: {
"Content-Type": file.type,
},
};
let newFile = new File([file], fileData.name, {type: file.type});
//这边使用的是minio,用的是put方法,如果是自定义的上传方式,请使用对应的方法
const response = await axios.put(fileData.url, newFile, config);
//minio回调会有个status,具体还得看服务器回调
if (response.status === 200) {
//更新文件列表
//ElementPlus的文件hub属性和我们定义上传的文件属性不一致,所以需要手动更新
//所以这边我直接选择添加一个新的Url属性
fileList.value.forEach((item, index) => {
if (item.name === fileData.name) {
fileList.value[index] = {
...fileList.value[index],
newURL: getUrl(fileData.url)
}
}
})
onSuccess(response, file);
//通知父组件,更新v-model
emit('update:modelValue', fileList.value)
} else {
onError(response);
}
} catch (error) {
onError(error);
}
};
//minio的回调url会有多余的数据,这边处理了一下
const getUrl = (originalUrl) => {
const url = new URL(originalUrl);
return url.origin + url.pathname;
};
//处理删除
const handleDelete = (index) => {
//这边两个列表都需要删除,以免出现数据不一致的情况
fileList.value.slice(index, 1)
fileUrls.splice(index, 1)
emit('update:modelValue', fileList.value)
}
const handleExceed = () => {
ElMessage.error(`只允许上传${props.limit}个文件`)
}
const handleUploadError = (err) => {
ElMessage.error("上传失败,请重试");
}
//上传成功的回调,我们这里不需要处理
const handleUploadSuccess = (file) => {
}
const dialogVisible = ref(false);
const dialogImageUrl = ref("");
//弹出框进行文件预览
const handlePreview = (file) => {
const isImage = checkImageType(file.url);
if (isImage) {
if (file.newURL) {
dialogImageUrl.value = file.newURL;
dialogVisible.value = true;
}
}
};
//校验文件类型
const checkImageType = (urlString) => {
// 定义有效的图片扩展名
const validImageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff'];
// 获取 URL 的后缀
const url = new URL(urlString);
const pathname = url.pathname;
const extension = pathname.slice((Math.max(0, pathname.lastIndexOf(".")) || Infinity) + 1).toLowerCase();
return validImageExtensions.includes(`.${extension}`);
};
调用方式:
调用就超级简单啦
在随便哪个父组件的template中:
<NewUpload v-model="uploadList" />
然后是js中定义uploadList
const uploadList = ref([]);
---到此完成---
全部代码:
<template>
<el-upload
ref="upload"
multiple
:limit="limit"
action="#"
v-model:file-list="fileList"
:before-upload="beforeUpload"
:on-exceed="handleExceed"
:on-success="handleUploadSuccess"
:on-error="handleUploadError"
:on-remove="handleDelete"
:on-preview="handlePreview"
:http-request="handleRequest"
>
<el-button type="primary">上传</el-button>
<template #tip>
<div class="el-upload__tip">
注意:请上传{{ fileSize }}m以下文件,最多支持上传文件数量:{{ limit }}个!
</div>
</template>
</el-upload>
<!-- 如果是图片类型的直接预览,如果是其他类型,则下载 -->
<el-dialog v-model="dialogVisible">
<img :src="dialogImageUrl" alt="Preview Image"/>
</el-dialog>
</template>
<script setup>
import {ElMessage} from "element-plus";
import axios from "axios";
const props = defineProps({
//文件列表
modelValue: {
type: Array,
default: () => []
},
limit: {
type: Number,
default: 5
},
// 大小限制(MB)
fileSize: {
type: Number,
default: 2,
},
// 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: {
type: Array,
default: () => [".jpg", ".jpeg", ".png", ".doc", ".xls", ".xlsx", ".ppt", ".txt", ".pdf"],
},
// 是否显示提示框
isShowTip: {
type: Boolean,
default: true
},
})
const emit = defineEmits(['update:modelValue']);
//文件ref
const upload = ref()
//文件列表
const fileList = ref([]);
//单文件的参数--这边我定义成全局的,但是需要注意深浅拷贝的问题
const fileData = {
name: "",
url: "",
}
//文件url列表--为了解决ElementPlus的文件属性和我们定义上传的文件属性不一致的问题
let fileUrls = []
//初始化
onMounted(() => {
if (props.modelValue.length > 0) {
fileUrls = props.modelValue;
fileList.value = fileUrls
}
});
//处理事件
const beforeUpload = async (options) => {
//校验文件类型
if (props.fileType) {
let fileExtension = "";
if (options.name.lastIndexOf(".") > -1) {
fileExtension = options.name.substring(options.name.lastIndexOf("."));
}
const isTypeOk = props.fileType.some(type => {
if (options.type.indexOf(type) > -1) return true;
return !!(fileExtension && fileExtension.indexOf(type) > -1);
});
if (!isTypeOk) {
ElMessage.error(`文件格式不正确,请上传${props.fileType.join("/")}格式文件!`)
return false;
}
}
try {
//准备上传的数据--这边我是使用前端直传的方式,所以第一步是获取预上传的Url信息
const {name, url} = await preUpload({fileName: options.name});
fileData.name = name;
fileData.url = url;
fileUrls.push(JSON.parse(JSON.stringify(toRaw(fileData))))
fileList.value = fileUrls
} catch (err) {
ElMessage.error("获取上传地址失败,请稍后再试!");
return false;
}
}
//正式上传
const handleRequest = async ({file, onSuccess, onError}) => {
try {
const config = {
//控制文件类型,方便进行显示还是下载
headers: {
"Content-Type": file.type,
},
};
let newFile = new File([file], fileData.name, {type: file.type});
//这边使用的是minio,用的是put方法,如果是自定义的上传方式,请使用对应的方法
const response = await axios.put(fileData.url, newFile, config);
//minio回调会有个status,具体还得看服务器回调
if (response.status === 200) {
//更新文件列表
//ElementPlus的文件hub属性和我们定义上传的文件属性不一致,所以需要手动更新
//所以这边我直接选择添加一个新的Url属性
fileList.value.forEach((item, index) => {
if (item.name === fileData.name) {
fileList.value[index] = {
...fileList.value[index],
newURL: getUrl(fileData.url)
}
}
})
onSuccess(response, file);
//通知父组件,更新v-model
emit('update:modelValue', fileList.value)
} else {
onError(response);
}
} catch (error) {
onError(error);
}
};
//minio的回调url会有多余的数据,这边处理了一下
const getUrl = (originalUrl) => {
const url = new URL(originalUrl);
return url.origin + url.pathname;
};
//处理删除
const handleDelete = (index) => {
//这边两个列表都需要删除,以免出现数据不一致的情况
fileList.value.slice(index, 1)
fileUrls.splice(index, 1)
emit('update:modelValue', fileList.value)
}
const handleExceed = () => {
ElMessage.error(`只允许上传${props.limit}个文件`)
}
const handleUploadError = (err) => {
ElMessage.error("上传失败,请重试");
}
//上传成功的回调,我们这里不需要处理
const handleUploadSuccess = (file) => {
}
const dialogVisible = ref(false);
const dialogImageUrl = ref("");
//弹出框进行文件预览
const handlePreview = (file) => {
const isImage = checkImageType(file.url);
if (isImage) {
if (file.newURL) {
dialogImageUrl.value = file.newURL;
dialogVisible.value = true;
}
}
};
//校验文件类型
const checkImageType = (urlString) => {
// 定义有效的图片扩展名
const validImageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff'];
// 获取 URL 的后缀
const url = new URL(urlString);
const pathname = url.pathname;
const extension = pathname.slice((Math.max(0, pathname.lastIndexOf(".")) || Infinity) + 1).toLowerCase();
return validImageExtensions.includes(`.${extension}`);
};
</script>
Vue3封装一个ElementPlus自定义上传组件2--无弹窗的更多相关文章
- 基于SqlSugar的开发框架循序渐进介绍(13)-- 基于ElementPlus的上传组件进行封装,便于项目使用
在我们实际项目开发过程中,往往需要根据实际情况,对组件进行封装,以更简便的在界面代码中使用,在实际的前端应用中,适当的组件封装,可以减少很多重复的界面代码,并且能够非常简便的使用,本篇随笔介绍基于El ...
- 分享一个react 图片上传组件 支持OSS 七牛云
react-uplod-img 是一个基于 React antd组件的图片上传组件 支持oss qiniu等服务端自定义获取签名,批量上传, 预览, 删除, 排序等功能 需要 react 版本大于 v ...
- 文件上传的一个坑 Apache上传组件和SpringMVC自带上传冲突
List list = upload.parseRequest(request); 接受不到数据,size=0; 原因就是下面这货造成的 ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ bean id=&qu ...
- Vue + Element 自定义上传封面组件
前一段时间做项目,频繁使用到上传图片组件,而且只上传一个封面,于是想着自定义一个图片封面上传组件.先来看一下效果: 第一张图片是上传之前,第二张图片是上传 ...
- JS组件系列——Bootstrap文件上传组件:bootstrap fileinput
前言:之前的三篇介绍了下bootstrap table的一些常见用法,发现博主对这种扁平化的风格有点着迷了.前两天做一个excel导入的功能,前端使用原始的input type='file'这种标签, ...
- Jquery图片上传组件,支持多文件上传
Jquery图片上传组件,支持多文件上传http://www.jq22.com/jquery-info230jQuery File Upload 是一个Jquery图片上传组件,支持多文件上传.取消. ...
- Bootstrap文件上传组件
前言:之前的三篇介绍了下bootstrap table的一些常见用法,发现博主对这种扁平化的风格有点着迷了.前两天做一个excel导入的功能,前端使用原始的input type='file'这种标签, ...
- bootstrap-fileinput文件上传组件和laravel引用(未完)
前言:之前的三篇介绍了下bootstrap table的一些常见用法,发现博主对这种扁平化的风格有点着迷了.前两天做一个excel导入的功能,前端使用原始的input type='file'这种标签, ...
- UI组件--element-ui--Upload多组件自定义上传
需求: 提交详细信息的表单, 并上传对应图片(如下图), 后台接口要求表单数据和图片需要一次上传完成.. 分析: 实际上, 每个element-ui Upload组件都应发送一次请求, 很明显不符合我 ...
- element-ui上传组件,通过自定义请求上传文件
记录使用element-ui上传组件,通过自定义请求上传文件需要注意的地方. <el-upload ref="uploadMutiple" :auto-upload=&quo ...
随机推荐
- slot原理
vue提供组件插槽能力, 允许开发者在封装组件时,把不确定的部分定义为插槽 : 作用:再不确定的内容位置占位,在子组件内使用 slot 标签写占位的内容 : 插槽的分类 : 默认插槽: 具名插槽:使用 ...
- 43.v-if和v-for的优先级
v-for 的优先级高 延申问题:v-for 和 v-if 为什么不能在一起使用 ? 会造成性能的浪费,因为v-for 的优先级高,所以每次渲染都会执行v-if 判断条件,浪费时间 :比如 渲染 10 ...
- 云原生周刊:OpenTofu 宣布正式发布 | 2023.1.15
开源项目推荐 kubeaudit kubeaudit 是一个开源项目,旨在帮助用户对其 Kubernetes 集群进行常见安全控制的审计.该项目提供了工具和检查规则,可以帮助用户发现潜在的安全漏洞和配 ...
- 一文搞懂SaaS应用架构:应用服务、应用结构、应用交互设计
大家好,我是汤师爷~ 今天系统性地聊聊SaaS应用架构设计. 应用架构概述 我们已经完成了SaaS系统的定位分析,明确了系统的目标和核心能力.这为接下来的应用架构设计奠定了基础. 应用架构就像整个Sa ...
- RocketMQ Streams拓扑构建与数据处理过程
本文作者:倪泽,Apache RocketMQ committer.RSQLDB/RocketMQ Streams Maintainer 01 背景 RocketMQ Streams 1.1.0版本已 ...
- 『玩转Streamlit』--页面布局
一个优秀的数据应用不仅仅是功能的强大,更在于其用户体验的打造. 而良好的页面布局,作为用户体验的重要组成部分,不仅能够提升信息的可读性,还能引导用户高效地完成操作. 反之,混乱的布局会让人感到困惑和挫 ...
- 国内SRC信息收集
SRC之信息收集 前言: 关于SRC信息收集不在于工具是否全面,工具固然重要,它们能帮我们节省大量的时间收集资产,但不是说我们一定要用到所有工具,收集到某个SRC的所有资产才罢休.资产总会有遗漏, ...
- DataGrip 2024.2.2 最新安装教程(附激活-2099年~)
下载 DataGrip 2024.2.2 版本的安装包. 下载补丁https://pan.quark.cn/s/fcc23ab8cadf 检查 免责声明:本文中的资源均来自互联网,仅供个人学习和交流使 ...
- openresty操作mongodb
最近项目中使用openresty,需要通过openresty连接mongo,经过几番折腾终于有了一个结果,现将其记录下来,也感谢模块提供者 使用openresty操作mongo 1.引入第三方的模块 ...
- Grafana如何利用Karpenter消除50%的云资源浪费?|落地案例
原文链接: https://grafana.com/blog/2023/11/09/how-grafana-labs-switched-to-karpenter-to-reduce-costs-and ...