二次封装el-upload组件包含文件上传进度条、复制粘贴上传等的功能

功能

1. 支持图片上传进度条

2. 支持粘贴上传图片行为

3. 支持最大图片上传数量

4. 支持图片大小限制

5. 支持图片类型限制

6. 支持图片预览

具体实现

  • 图片上传进度条

由于 :on-progress 钩子没触发,因此使用了 :on-change 钩子代替实现。进度条的值先用定时器递增,等图片上传完成将进度条的值改成100即可。

  • 粘贴上传图片

使用@paste 绑定粘贴事件,获取粘贴的对象,使用getAsFile 获取图片内容即可实现上传

  • 所有代码
<template><div class="upload-box" @paste="handlePaste"><div><div class="upload-body"><el-input class="input-wrap" readonly></el-input><el-uploadaction="#"list-type="picture-card":class="['upload', self_disabled ? 'disabled' : '', drag ? 'no-border' : '']"v-model:file-list="fileList":multiple="true":disabled="self_disabled":limit="limit":http-request="handleHttpUpload":on-change="onProgress":on-progress="handleProgress":before-upload="beforeUpload":on-exceed="handleExceed":on-success="uploadSuccess":on-error="uploadError":drag="drag":accept="fileType.join(',')"><!-- 空状态可以使用插槽 --><div class="upload-empty"><slot name="empty"><el-icon><Plus /></el-icon></slot></div><template #file="{ file }"><img :src="showImage(file)" class="upload-image" /><!-- 在上传中的时候 不出现删除按钮 --><el-icon class="delete-icon" @click.stop="handleRemove(file)" v-if="!self_disabled && !showProgress(file)"><CircleCloseFilled/></el-icon><!-- 在上传中的时候 不出现查看按钮 --><div v-if="!showProgress(file)" class="upload-handle" @click.stop="handlePictureCardPreview(file)"><div class="handle-icon"><el-icon><ZoomIn /></el-icon><span>查看</span></div></div><!-- 进度条 --><div class="el-progress-wrap" v-if="showProgress(file)"><el-progress type="circle" :percentage="progressValue(file)" :show-text="false" /></div></template></el-upload></div><!-- 提示语 --><div class="el-upload__tip"><el-popover placement="top-start" :width="200" trigger="hover"><template #reference><el-icon :size="16" class="mr-4"><QuestionFilled /></el-icon></template><div class="tip-fixed-content warn-color mt-4 fontSize-12"><div>1. 支持扩展名:jpeg .jpg .png</div><div>2. 可以直接点击实现图片上传</div><div>3. 将图片拖拽至 请上传照片盒子内 即可上传图片</div><div>4. 复制图片Ctrl+V至灰色框内 即可上传图片</div></div></el-popover><slot name="tip-content"><slot name="tip" :fileSize="fileSize"> 单张照片大小不能超过 {{ fileSize }}M, 最多上传{{ limit }}张 </slot></slot></div></div></div>
</template>
<script setup lang="ts" name="UploadImgs">
import { ref, computed, inject, watch } from "vue";
import { Plus } from "@element-plus/icons-vue";
import { ApiUpload } from "@/api/modules/index";
import { windowOrigin } from "@/utils/util";
import { KeepAliveStore } from "@/stores/modules/keepAlive";
import type { UploadProps, UploadFile, UploadUserFile, UploadRequestOptions } from "element-plus";
import { ElNotification, formContextKey, formItemContextKey, ElMessage } from "element-plus";interface UploadFileProps {// action? :string;  // 上传的地址fileList: any[];api?: (params: any) => Promise<any>; // 上传图片的 api 方法,一般项目上传都是同一个 api 方法,在组件里直接引入即可 ==> 非必传drag?: boolean; // 是否支持拖拽上传 ==> 非必传(默认为 true)disabled?: boolean; // 是否禁用上传组件 ==> 非必传(默认为 false)limit?: number; // 最大图片上传数 ==> 非必传(默认为 无限张)fileSize?: number; // 图片大小限制 ==> 非必传(默认为 5M)fileType?: File.ImageMimeType[]; // 图片类型限制 ==> 非必传(默认为 ["image/jpeg", "image/png", "image/gif"])height?: string; // 组件高度 ==> 非必传(默认为 150px)width?: string; // 组件宽度 ==> 非必传(默认为 150px)borderRadius?: string; // 组件边框圆角 ==> 非必传(默认为 8px)progress?: boolean; // 展示上传进度盒子deleteIconSize?: string; // 删除icon的大小progressWidth?: string; // 进度条大小
}const props = withDefaults(defineProps<UploadFileProps>(), {// action: `${import.meta.env.VITE_BASE_URL }//backend/entryamount/upload`,fileList: () => [],drag: true,disabled: false,limit: Infinity,fileSize: 2,fileType: () => ["image/jpeg", "image/png", "image/gif"],height: "100px",width: "100px",borderRadius: "0px",progress: true,deleteIconSize: "18px",progressWidth: "80px"
});// 获取 el-form 组件上下文
const formContext = inject(formContextKey, void 0);
// 获取 el-form-item 组件上下文
const formItemContext = inject(formItemContextKey, void 0);
// 判断是否禁用上传和删除
const self_disabled = computed(() => {return props.disabled || formContext?.disabled;
});const fileList = ref<any[]>(props.fileList); // 图片地址集合
const baseUrl = windowOrigin(); // 图片路径的前缀// 展示的图片路径
const showImage = (uploadFile: UploadFile) => {// 成功则展示完整路径if (uploadFile.status === "success") {return `${baseUrl}${uploadFile.url}`;}// 否则展示临时路径return uploadFile.url;
};// 监听 props.fileList 列表默认值改变
watch(() => props.fileList,(n: UploadUserFile[]) => {fileList.value = n;}
);// ==========【 处理进度条 】==========
type Progress = {uid: number;value: number;show: boolean;
};
const progressList = ref<Progress[]>([]); // 存储进度条集合
// 增加进度条
const addUploadProgress = (uid: number) => {progressList.value?.push({ uid, value: 0, show: true });
};const handleProgress = (uploadFile: UploadFile) => {console.log(uploadFile, "====>handleProgress");
}// 监听文件上传进度时的钩子
const onProgress = (uploadFile: UploadFile) => {console.log(uploadFile, "=====>onProgress")if (uploadFile.status === "ready") {// 新增一个进度条addUploadProgress(uploadFile.uid);}// 找到匹配的进度条const target = progressList.value.find(item => item.uid === uploadFile.uid);if (!target) return;// 这里是模拟进度条加载,一开始先手动的慢慢加载到90,等图片上传完,再改成100const interval = setInterval(() => {if (target.value >= 90) {clearInterval(interval);return;}target.value += 10;}, 100);if (uploadFile.status === "success") {clearInterval(interval);target.value = 100;target.show = false;}
};// 控制是否展示上传进度条
const showProgress = (uploadFile: UploadFile) => {const target = progressList.value.find(item => item.uid === uploadFile.uid);return target?.show;
};
// 控制上传进度条
const progressValue = (uploadFile: UploadFile) => {const target = progressList.value.find(item => item.uid === uploadFile.uid);return target?.value;
};// ==========【 处理图片上传前的校验 】==========
// 文件数超出
const handleExceed = () => {ElNotification({title: "温馨提示",message: `当前最多只能上传 ${props.limit} 张图片,请移除后上传!`,type: "warning"});
};// 文件类型出错
const handleImgTypeError = () => {ElNotification({title: "温馨提示",message: "上传图片不符合所需的格式!",type: "warning"});
};// 文件大小出错
const handleImgSizeError = () => {ElNotification({title: "温馨提示",message: `上传图片大小不能超过 ${props.fileSize}M!`,type: "warning"});
};// 图片上传错误
const uploadError = () => {ElNotification({title: "温馨提示",message: "图片上传失败,请您重新上传!",type: "error"});
};// 文件上传之前判断
const beforeUpload = (rawFile: { size: number; type: File.ImageMimeType; uid: number }) => {const imgSize = rawFile.size / 1024 / 1024 < props.fileSize; // 返回布尔值const imgType = props.fileType.includes(rawFile.type as File.ImageMimeType); // 返回布尔值if (!imgType) {// 上传图片类型错误handleImgTypeError();}if (!imgSize)// 上传图片大小超出限制setTimeout(() => {handleImgSizeError();}, 0);return imgType && imgSize;
};// ==========【 处理图片上传 】==========
/*** @description 图片上传成功* @param response 上传响应结果* @param uploadFile 上传的文件* */
interface UploadEmits {(e: "update:fileList", value: UploadUserFile[]): void;
}
const emit = defineEmits<UploadEmits>();
const uploadSuccess = (response: { fileUrl: string } | undefined, uploadFile: UploadFile) => {if (!response) return;uploadFile.url = response.fileUrl;emit("update:fileList", fileList.value); // 传递数据给父组件// 调用 el-form 内部的校验方法(可自动校验)formItemContext?.prop && formContext?.validateField([formItemContext.prop as string]);
};// 监听请求图片上传
const handleHttpUpload = async (options: UploadRequestOptions) => {let formData = new FormData();formData.append("image", options.file);try {const api = props.api ?? ApiUpload.uploadImg;const { data } = await api(formData);const params = { ...data, fileUrl: data.url };options.onSuccess(params);} catch (error) {options.onError(error as any);}
};// 监听复制粘贴操作
const handlePaste = async (event: any) => {// 超出图片数量的前置判断if (fileList.value.length >= props.limit) {handleExceed();return;}let file = event.clipboardData.items[0]; // 获取clipboardData对象if (!file.type.includes("image")) {ElMessage.error("粘贴内容非图片!");return;}let imgFile = file.getAsFile(); // 获取图片内容if (!beforeUpload({ size: imgFile.size, type: imgFile.type, uid: event._vts })) {return;}const formData = new FormData();formData.append("image", imgFile);fileList.value.push({ uid: event._vts, url: "/" });// 处理进度条addUploadProgress(event._vts);const progressTarget = progressList.value.find(item => item.uid === event._vts);if (!progressTarget) return;const interval = setInterval(() => {if (progressTarget.value >= 90) {clearInterval(interval);return;}progressTarget.value += 10;}, 100);const { data } = await ApiUpload.uploadImg(formData);const target = fileList.value.find(item => item.uid === event._vts);target.url = data.url;clearInterval(interval);progressTarget.value = 100;setTimeout(() => {progressTarget.show = false;}, 500);emit("update:fileList", fileList.value);
};// 删除图片
const handleRemove = (file: UploadFile) => {fileList.value = fileList.value.filter(item => item.uid !== file.uid);progressList.value = progressList.value.filter(item => item.uid !== file.uid);emit("update:fileList", fileList.value);
};// 图片预览
const keepAliveStore = KeepAliveStore();
const handlePictureCardPreview: UploadProps["onPreview"] = file => {keepAliveStore.openImageViewer({ url: file.url as string });
};
</script>
<style scoped lang="scss">
.is-error {.upload {:deep(.el-upload--picture-card),:deep(.el-upload-dragger) {border: 1px dashed var(--el-color-danger) !important;&:hover {border-color: var(--el-color-primary) !important;}}}
}
.tip-fixed-content {line-height: 18px;
}
:deep(.disabled) {.el-upload--picture-card,.el-upload-dragger {cursor: not-allowed;background: var(--el-disabled-bg-color) !important;border: 1px dashed var(--el-border-color-darker);&:hover {border-color: var(--el-border-color-darker) !important;}}
}
.upload-box {display: flex;.tips {font-size: 12px;color: var(--el-color-info-light-5);}.no-border {:deep(.el-upload--picture-card) {border: none !important;}}.upload-body {position: relative;display: block;min-width: 200px;padding: 12px;}:deep(.upload) {.el-upload-dragger {display: flex;align-items: center;justify-content: center;width: 100%;height: 100%;padding: 0;// overflow: hidden;border: 1px dashed var(--el-border-color-darker);border-radius: v-bind(borderRadius);&:hover {border: 1px dashed var(--el-color-primary);}}.el-upload-dragger.is-dragover {background-color: var(--el-color-primary-light-9);border: 2px dashed var(--el-color-primary) !important;}.el-upload-list__item,.el-upload--picture-card {width: v-bind(width);height: v-bind(height);background-color: transparent;border-radius: v-bind(borderRadius);}.upload-image {width: 100%;height: 100%;object-fit: contain;cursor: pointer;}.delete-icon {position: absolute;top: 0;right: 0;z-index: 99;font-size: v-bind(deleteIconSize);color: var(--el-color-error-dark-2);cursor: pointer;}.upload-handle {position: absolute;top: 0;right: 0;box-sizing: border-box;display: flex;align-items: center;justify-content: center;width: 100%;height: 100%;cursor: pointer;background: rgb(0 0 0 / 60%);opacity: 0;transition: var(--el-transition-duration-fast);.handle-icon {display: flex;flex-direction: column;align-items: center;justify-content: center;padding: 0 6%;color: aliceblue;.el-icon {margin-bottom: 15%;font-size: 12px;}span {font-size: 12px;}}}.el-upload-list__item {&:hover {.upload-handle {opacity: 1;}}}.upload-empty {display: flex;flex-direction: column;align-items: center;font-size: 12px;line-height: 30px;color: var(--el-color-info);.el-icon {font-size: 28px;color: var(--el-text-color-secondary);}}}.el-upload__tip {display: flex;align-items: center;line-height: 15px;text-align: left;}
}
.input-wrap {position: absolute;top: 0;right: 0;bottom: 0;left: 0;width: 100%;height: 100%;:deep(.el-input__wrapper) {background-color: var(--el-color-info-light-9);}
}
.el-progress-wrap {position: absolute;top: 0;right: 0;bottom: 0;left: 0;display: flex;align-items: center;justify-content: center;background-color: rgba($color: #ffffff, $alpha: 70%);:deep(.el-progress) {width: v-bind(progressWidth) !important;height: v-bind(progressWidth) !important;}:deep(.el-progress-circle) {width: v-bind(progressWidth) !important;}
}
</style>
  • 使用上传组件
<template><UploadImgsv-model:file-list="imageList":limit="6"height="60px"width="60px"delete-icon-size="14px"progress-width="30px"><template #empty><el-icon><Picture /></el-icon></template></UploadImgs>
</template>

~~ END ~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/629760.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费电子书全集分享

分享一个在github免费电子书集合&#xff0c;如果想学编程的&#xff0c;可以收藏&#xff0c;项目地址&#xff1a;free-programming-books

行为型设计模式——中介者模式

中介者模式 中介者模式主要是将关联关系由一个中介者类统一管理维护&#xff0c;一般来说&#xff0c;同事类之间的关系是比较复杂的&#xff0c;多个同事类之间互相关联时&#xff0c;他们之间的关系会呈现为复杂的网状结构&#xff0c;这是一种过度耦合的架构&#xff0c;即…

【2024.1.17练习】C++岛屿个数/整数删除

2023蓝桥杯CB组省赛F题&#xff1a;岛屿个数 #include<iostream> #include<algorithm> #include<queue>using namespace std; typedef long long ll;const int dx[8] { 1, -1, 0, 0, -1, -1, 1, 1 }; const int dy[8] { 0, 0, 1, -1, -1, 1, -1, 1 };/* …

ruoyi-vue国产化适配之东方通TongHttpServer

1.将安装包解压 tar -zxvf TongHttpServer_6.0.0.2_x86_64.tar.gz 2.THS 主程序需要 license 才能启动&#xff0c;将 license.dat 文件放在 THS 目录内 3.启动主程序 4. http://IP:8000/ 账号&#xff1a;admin 密码&#xff1a;ths#123.com 5.可以在配置文件--文件编辑…

HFSS笔记/信号完整性分析(二)——软件仿真设置大全

文章目录 1、多核运算设置1.1 如何设置1.2 如何查看自己电脑的core呢&#xff1f;1.3 查看求解的频点 2、求解模式设置Driven Terminal vs Driven modal 3、Design settings4、自适应网格划分5、更改字体设置 仅做笔记整理与分享。 1、多核运算设置 多核运算只对扫频才有效果&…

STC8H8K蓝牙智能巡线小车——5.超声波测距(超声波检测障碍物)

超声波测距原理 所选超声波测距模组&#xff1a;HC-SR04 官方解释 HC-SR04 超声波测距模块可供 2cm提-400cm 的非接触式距离感测功能&#xff0c;测距精度可达高到 3mm&#xff1b;模块包括超声波发射器、接收器与控制电路。 基本工作原理 采用 IO 口 TRIG 触发测距&#…

ARM day2、day3 汇编

一、汇编学习&#xff1a;可以向上理解软件、向下感知硬件 二、符号&#xff08;注释&#xff09; 注释#注释&#xff08;放在行首表示注释一行&#xff09;/* */注释#数字立即数&#xff1a;一种标号&#xff08;比如main: loop:&#xff09;.text .end换行…

2024 年 SEO 您需要了解的 8 个关键 SEO 趋势

SEO的未来正趋向于更加以用户为中心、合乎道德和技术先进的方法。 为什么&#xff1f; 人工智能 &#xff08;AI&#xff09; 和机器学习在搜索引擎中的兴起使他们能够更好地理解用户意图并提供更相关的结果Google 将经验、专业知识、权威性和可信度 &#xff08;E-E-A-T&…

【flink番外篇】18、通过数据管道将table source加入datastream示例

一、Flink 专栏 Flink 专栏系统介绍某一知识点&#xff0c;并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分&#xff0c;比如术语、架构、编程模型、编程指南、基本的datastream api用…

安全基础~web攻防特性1

文章目录 知识补充ASP安全Aspx安全分析与未授权访问php特性&web89~97靶场练习ctfshow 知识补充 使用thinkphp开发的框架&#xff0c;其首页访问指向public目录&#xff0c;指向其中的index.php文件 指向的index.php打开网页后是如下情况&#xff0c;代码如下 定义应用目…

计算机网络——第三层:网络层

1. IP数据报 1.1 IPV4数据报 1.1.1 IPv4数据报的结构 如图按照RFC 791规范显示了一个IPv4数据包头部的不同字段 IPv4头部通常包括以下部分&#xff1a; 1.1.1.1 版本&#xff08;Version&#xff09; 指明了IP协议的版本&#xff0c;IPv4表示为4。 1.1.1.2 头部长度&#x…

【机器学习入门】机器学习基础概念与原理

*&#xff08;本篇文章旨在帮助新手了解机器学习的基础概念和原理&#xff0c;不深入讨论算法及核心公式&#xff09; 目录 一、机器学习概念 1、什么是机器学习&#xff1f; 2、常见机器学习算法和模型 3、使用Python编程语言进行机器学习实践 4、机器学习的应用领域 二…

nxp s32k144芯片使用J-LINK程序刷写

1.nxp s32k144 (1)打开软件&#xff1a;J-Flash V6.30j (2)新建工程&#xff1a;file->new project (3)选择芯片型号和 target interface (4)可以保存芯片和接口配置 (5)打开程序&#xff1a;File->open data file &#xff08;6&#xff09;程序刷写&#xff1a;T…

探索 GitHub:高效使用技巧与实例分享

探索 GitHub&#xff1a;高效使用技巧与实例分享 前言: 欢迎来到本篇博客&#xff0c;今天我们将深入研究 GitHub 的一些高效使用技巧&#xff0c;以便更好地利用这一强大的代码托管平台。 1. GitHub 简介&#xff1a; GitHub 是全球最大的代码托管平台之一&#xff0c;它不…

Linux下安装Mysql【CentOS7 】

Linux下安装Mysql 一、Linux下安装Mysql-5.7.41【tar包下载安装】1.1.首先检查是否已经安装过mysql1.2.下载Linux版本的Mysql-5.71.3.解压缩1.4.安装执行 rpm 安装包需要先下载 openssl-devel 插件1.5.安装 Mysql5.7 执行 rpm 安装包1.6.Mysql相关操作命令1.7.查看Mysql-5.7 临…

Hadoop集群配置及测试

Hadoop集群配置及测试 NameNode与SecondaryNameNode最好不在同一服务器 ResourceManager较为消耗资源&#xff0c;因而和NameNode与SecondaryNameNode最好不在同一服务器。 配置文件 hadoop102hadoop103hadoop104HDFSNameNodeDataNodeDataNodeSecondaryNameNodeDataNodeYAR…

Pandas实战100例 | 案例 67: 布尔运算

案例 67: 布尔运算 知识点讲解 布尔运算是数据处理中的一个重要部分&#xff0c;尤其是在处理布尔&#xff08;逻辑&#xff09;数据时。Pandas 支持常见的布尔运算&#xff0c;如 AND、OR 和 XOR。 布尔运算: & (AND): 两列都为 True 时结果为 True。| (OR): 任一列为 …

快速上手的 AI 工具-文心一言

简介 最近正打得火热的AIGC概念&#xff0c;相信大家肯定也都多少接触到了&#xff0c;那么AIGC概念股到底是什么呢&#xff1f;我个人最近也看了一些平台如&#xff1a;文心一言、通义千问、讯飞星火、豆包等等&#xff01;各位朋友也千万不要错过啦&#xff0c;真是各有各的特…

VC++中使用OpenCV读取图像、读取本地视频、读取摄像头并实时显示

VC中使用OpenCV读取图像、读取本地视频、读取摄像头并实时显示 最近闲着跟着油管博主murtazahassan&#xff0c;学习了一下LEARN OPENCV C in 4 HOURS | Including 3x Projects | Computer Vision&#xff0c;对应的Github源代码地址为&#xff1a;Learn-OpenCV-cpp-in-4-Hour…

顺序表(C/C++)

本篇将讲解一些关于顺序表的内容&#xff0c;顺序表分为静态顺序表和动态顺序表&#xff0c;其中经常用到的为动态顺序表&#xff0c;所以本篇将以动态顺序表为重点给出一些关于动态顺序表的操作。 因为顺序表的实现逻辑较为简单&#xff0c;对于代码的讲解大多以注释给出。 1…