二次封装element-plus上传组件,提供校验、回显等功能

二次封装element-plus上传组件

  • 0 相关介绍
  • 1 效果展示
  • 2 组件主体
  • 3 视频组件
  • 4 Demo

0 相关介绍

基于element-plus框架,视频播放器使用西瓜视频播放器组件

相关能力

  • 提供图片、音频、视频的预览功能
  • 提供是否为空、文件类型、文件大小、文件数量、图片宽高校验
  • 提供图片回显功能,并保证回显的文件不会重新上传
  • 提供达到数量限制不显示element自带的加号

相关文档

  • 西瓜播放器
  • element-plus

1 效果展示

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2 组件主体

<template><el-uploadlist-type="picture-card":auto-upload="false":on-change="onChange":on-remove="onChange":multiple="props.multiple":limit="props.limit":accept="accept"ref="elUploadElement":file-list="fileList":id="fileUploadId"><el-icon><Plus /></el-icon><template #file="{ file }"><div><imgclass="el-upload-list__item-thumbnail":src="file.viewUrl"alt=""v-if="isShow"/><span class="el-upload-list__item-actions"><spanclass="el-upload-list__item-preview"@click="handlePictureCardPreview(file)"><el-icon><zoom-in /></el-icon></span><span class="el-upload-list__item-delete" @click="handleRemove(file)"><el-icon><Delete /></el-icon></span></span></div></template><template #tip><div class="el-upload__tip">{{ tip }}</div></template></el-upload><!-- 文件预览弹窗 --><el-dialogv-model="dialogVisible"style="width: 800px"@close="close"@open="open"><el-imagev-if="previewFile.type === 'image'"style="width: 100%; height: 400px":src="previewFile.url"fit="contain"alt="Preview Image"/><videoComponentref="videoRef"v-if="previewFile.type === 'video'"style="width: 100%; height: 400px":url="previewFile.url":poster="previewFile.viewUrl"/><audioref="audioRef"v-if="previewFile.type === 'audio'":src="previewFile.url"controlsstyle="width: 100%; height: 400px"/></el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import { Delete, Plus, ZoomIn } from "@element-plus/icons-vue";
import type { UploadFile } from "element-plus";
// 多个组件同时使用的时候做区分
const fileUploadId = ref(`ID${Math.floor(Math.random() * 100000)}`);
type UploadFileNew = {[Property in keyof UploadFile]: UploadFile[Property];
} & {type: string;viewUrl: string | undefined;needUpload: boolean;duration: number;width: number;height: number;
};const imageRegex = RegExp(/(jpg|bmp|gif|ico|pcx|jpeg|tif|png|raw|tga)/);
const audioRegex = RegExp(/(mp3|wav|flac|ogg|aac|wma)/);
const videoRegex = RegExp(/(avi|wmv|mpeg|mp4|m4v|mov|asf|flv|f4v|rmvb|rm|3gp|vob)/
);
// 导入视频组件
import videoComponent from "./videoComponent.vue";
const props = defineProps({// 数量限制limit: {type: Number,default: 1,},// 是否多选multiple: {type: Boolean,default: false,},// 要选择的文件类型fileType: {type: String,required: true,validator(value: string) {return ["audio", "image", "video"].includes(value);},},// 追加的提示appendTip: {type: String,default: "",},widthLimit: {type: Number,default: 0,},heightLimit: {type: Number,default: 0,},
});
// 可上传文件类型
const accept = ref("");
// 最大上传文件大小
const maxSize = ref(0);
const tip = ref("");
// 根据类型设置默认值
if (props.fileType) {switch (props.fileType) {case "image":accept.value = ".png, .jpg, .jpeg";maxSize.value = 20;tip.value = `请上传${accept.value}格式的文件,图片大小不能超过${maxSize.value}MB。${props.appendTip}`;break;case "audio":accept.value = ".mp3, .wma, .aac, .flac, .ape";maxSize.value = 500;tip.value = `请上传${accept.value}格式的文件,音频大小不能超过${maxSize.value}MB。${props.appendTip}`;break;case "video":accept.value = ".mp4, .rmvb, .avi, .mov";maxSize.value = 500;tip.value = `请上传${accept.value}格式的文件,视频大小不能超过${maxSize.value}MB。${props.appendTip}`;break;case "musiVideo":accept.value = ".mp4, .rmvb, .avi, .mov, .mp3, .wma, .aac, .flac, .ape";maxSize.value = 500;tip.value = `请上传${accept.value}格式的文件,音视频大小不能超过${maxSize.value}MB。${props.appendTip}`;break;default:throw new Error("类型错误");}
}
const isShow = ref(true);
const elUploadElement = ref();
// 控制图片预览的路径
const previewFile = ref();
// 控制是否显示图片预览的弹窗
const dialogVisible = ref(false);
// 双向绑定的文件列表
const fileList = ref<UploadFileNew[]>([]);
// 定义组件ref
const videoRef = ref(),audioRef = ref();
async function onChange(uploadFile: UploadFileNew,uploadFiles: UploadFileNew[]
) {// 如果是远程原件不需要任何处理if (!uploadFile.name) return;isShow.value = false;const suffix = uploadFile.name.split(".").at(-1) as string;if (videoRegex.test(suffix.toLocaleLowerCase())) {const res = (await findvideodetail(uploadFile.url as string)) as {viewUrl: string;duration: number;};uploadFile.type = "video";uploadFile.viewUrl = res.viewUrl;uploadFile.duration = res.duration;} else if (imageRegex.test(suffix)) {uploadFile.type = "image";uploadFile.viewUrl = uploadFile.url;const res = (await findImageDetail(uploadFile.url as string)) as {width: number;height: number;};uploadFile.width = res.width;uploadFile.height = res.height;} else if (audioRegex.test(suffix)) {uploadFile.type = "audio";uploadFile.viewUrl = new URL("@/assets/goods/audio.svg",import.meta.url).href;const res = (await findAudioDetail(uploadFile.url as string)) as {duration: number;};uploadFile.duration = res.duration;}console.log(uploadFile);uploadFile.needUpload = true;fileList.value = uploadFiles;isShow.value = true;verifyLength();
}// 删除文件
function handleRemove(uploadFile: UploadFile) {elUploadElement.value.handleRemove(uploadFile);verifyLength();
}// 检验已选择的文件数量是否超过阈值,并做显示/隐藏处理
function verifyLength() {const element = document.querySelector(`#${fileUploadId.value} .el-upload--picture-card`) as HTMLDivElement;console.log(fileUploadId.value, element);if (fileList.value.length === props.limit) {element.style.visibility = "hidden";} else {element.style.visibility = "visible";}
}// 预览文件
const handlePictureCardPreview = (file: UploadFile) => {previewFile.value = file;dialogVisible.value = true;
};//截取视频第一帧作为播放前默认图片
function findvideodetail(url: string) {const video = document.createElement("video"); // 也可以自己创建videovideo.src = url; // url地址 url跟 视频流是一样的const canvas = document.createElement("canvas"); // 获取 canvas 对象const ctx = canvas.getContext("2d"); // 绘制2dvideo.crossOrigin = "anonymous"; // 解决跨域问题,也就是提示污染资源无法转换视频video.currentTime = 1; // 第一帧return new Promise((resolve) => {video.oncanplay = () => {canvas.width = video.clientWidth || video.width || 320; // 获取视频宽度canvas.height = video.clientHeight || video.height || 240; //获取视频高度// 利用canvas对象方法绘图ctx!.drawImage(video, 0, 0, canvas.width, canvas.height);// 转换成base64形式const viewUrl = canvas.toDataURL("image/png"); // 截取后的视频封面resolve({viewUrl: viewUrl,duration: Math.ceil(video.duration),width: video.width,height: video.height,});video.remove();canvas.remove();};});
}
//截取视频第一帧作为播放前默认图片
function findAudioDetail(url: string) {const audio = document.createElement("audio"); // 也可以自己创建videoaudio.src = url; // url地址 url跟 视频流是一样的audio.crossOrigin = "anonymous"; // 解决跨域问题,也就是提示污染资源无法转换视频return new Promise((resolve) => {audio.oncanplay = () => {resolve({duration: Math.ceil(audio.duration),});audio.remove();};});
}
//
function findImageDetail(url: string) {const img = document.createElement("img"); // 也可以自己创建videoimg.src = url; // url地址 url跟 视频流是一样的return new Promise((resolve) => {img.onload = () => {resolve({width: img.width,height: img.height,});img.remove();};});
}type validateReturnValue = {code: number;success: boolean;msg: string;
};
// 验证文件格式
function verification(): validateReturnValue {if (fileList.value.length <= 0) {return {code: 0,success: false,msg: "请选择上传文件",};}if (fileList.value.length > props.limit) {return {code: 0,success: false,msg: `文件数量超出限制,请上传${props.limit}以内的文件`,};}for (let i = 0; i < fileList.value.length; i++) {const element = fileList.value[i];if (!element.needUpload) break;const suffix = element.name.split(".").at(-1) as string;if (!accept.value.includes(suffix.toLowerCase())) {return {code: 0,success: false,msg: `文件类型不正确,请上传${accept.value}类型的文件`,};}if ((element.size as number) / 1024 / 1024 > maxSize.value) {return {code: 0,success: false,msg: "文件大小超出限制",};}if (element.type === "image") {if (props.widthLimit && element.width != props.widthLimit) {return {code: 0,success: false,msg: `图片宽度不等于${props.widthLimit}像素`,};}if (props.heightLimit && element.height != props.heightLimit) {return {code: 0,success: false,msg: `图片高度不等于${props.heightLimit}像素`,};}}}return {code: 200,success: true,msg: "格式正确",};
}// 添加远程图片
async function addFileList(url: string, name?: string) {const uploadFile: any = {url,name,};const suffix = url.split(".").at(-1) as string;if (videoRegex.test(suffix.toLocaleLowerCase())) {const res = (await findvideodetail(url)) as {viewUrl: string;};uploadFile.type = "video";uploadFile.viewUrl = res.viewUrl;} else if (imageRegex.test(suffix)) {uploadFile.type = "image";uploadFile.viewUrl = uploadFile.url;} else if (audioRegex.test(suffix)) {uploadFile.type = "audio";uploadFile.viewUrl = new URL("@/assets/goods/audio.svg",import.meta.url).href;}uploadFile.needUpload = false;fileList.value.push(uploadFile);verifyLength();
}
// 关闭弹窗的时候停止音视频的播放
function close() {if (previewFile.value.type === "audio") {audioRef.value.pause();}if (previewFile.value.type === "video") {videoRef.value.pause();}
}
// 打开弹窗的时候修改视频路径和封面
function open() {videoRef.value.changeUrl();
}
// 获取文件对象
function getFiles(): UploadFileNew[] {return fileList.value;
}
defineExpose({getFiles,verification,addFileList,handlePictureCardPreview,handleRemove,
});
</script>

3 视频组件

<script setup lang="ts">
import { onMounted } from "vue";import Player from "xgplayer";
import "xgplayer/dist/index.min.css";const props = defineProps({// 视频路径url: {type: String,},// 封皮poster: {type: String,},
});
let player: any;
onMounted(() => {player = new Player({id: "mse",lang: "zh",// 默认静音volume: 0,autoplay: false,screenShot: true,videoAttributes: {crossOrigin: "anonymous",},url: props.url,poster: props.poster,//传入倍速可选数组playbackRate: [0.5, 0.75, 1, 1.5, 2],});
});
// 对外暴露暂停事件
function pause() {player.pause();
}
// 对外暴露修改视频源事件
function changeUrl() {player.playNext({url: props.url,poster: props.poster,});
}
defineExpose({ pause, changeUrl });
</script><template><div id="mse" />
</template><style scoped>
#mse {flex: auto;margin: 0px auto;
}
</style>

4 Demo

<script setup lang="ts">
import { ref, onMounted } from "vue";
import FileUpload from "./FileUpload/index.vue";
import type { FormInstance } from "element-plus";
// el-form实例
const ruleFormRef = ref();
// form绑定参数
const formData = ref({headImage: undefined,headImageList: [],
});// ------上传文件校验用 start-------
const FileUploadRef = ref();
let uploadRules = ref();
const isShowUpLoad = ref(true);
onMounted(() => {isShowUpLoad.value = false;uploadRules = ref([{validator(rule: any, value: any, callback: any) {const res = FileUploadRef.value.verification();if (!res.success) {callback(new Error(res.msg));}setTimeout(() => {callback();}, 500);},trigger: "blur",},// 需要显示出星号,但是由于没有做数据绑定,所以放在后边{ required: true, message: "请上传头像", trigger: "blur" },]);isShowUpLoad.value = true;
});
// ------上传文件校验用 end-------const submitLoading = ref(false);
function submitForm(ruleFormRef: FormInstance) {// 避免必需的校验无法通过formData.value.headImageList = FileUploadRef.value.getFiles();ruleFormRef.validate(async (valid) => {if (valid) {submitLoading.value = true;const params = { ...formData.value };// 单文件这么写,多文件需要循环if (params.headImageList[0].needUpload)params.headImage = await uploadFile(params.headImageList[0].raw);delete params.headImageList;submitLoading.value = false;}});
}
function uploadFile(file: File) {return "路径";
}
</script>
<!--  -->
<template><el-formref="ruleFormRef":model="formData"label-width="120px"v-loading="submitLoading"element-loading-text="数据传输中..."style="margin-top: 20px;"><el-form-itemlabel="头像"prop="headImageList":rules="uploadRules"v-if="isShowUpLoad"><FileUpload ref="FileUploadRef" :multiple="false" fileType="image" /></el-form-item><el-form-item label=""><el-button>取 消</el-button><el-button type="primary" @click="submitForm(ruleFormRef)">确 认</el-button></el-form-item></el-form>
</template>
<style lang="scss" scoped></style>

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

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

相关文章

【Linux】【驱动】驱动框架以及挂载驱动

【Linux】【驱动】驱动框架以及挂载驱动 绪论1.配置开发环境2. 编写驱动文件3. 编译Makefile文件4.编译5. 挂载驱动注意:有些开发板打开了或者禁止了printk信息&#xff0c;导致你看到的实验现象可能不一样&#xff0c;此时已经将文件移动到了开发板中&#xff0c;开发板查看文…

Mac 调试 ios safar

1. 打开Mac的 Safari 浏览器的“开发”菜单 运行 Safari 浏览器&#xff0c;然后依次选取“Safari 浏览器”>“偏好设置”&#xff0c;点按“高级”面板&#xff0c;然后勾选“在菜单栏中显示开发菜单”。 2. 开启IPhone的Safari调试模式 启用 Web 检查 功能&#xff0c;打…

手把手教你如何实现内网搭建电影网站并进行公网访问(保姆级教学)

手把手教你如何实现内网搭建电影网站并进行公网访问 文章目录 手把手教你如何实现内网搭建电影网站并进行公网访问前言1. 把软件分别安装到本地电脑上1.1 打开PHPStudy软件&#xff0c;安装一系列电影网站所需的支持软件1.2 设置MacCNS10的运行环境1.3 进入电影网页的安装程序1…

11款UML/SysML建模工具更新(2023.7)Papyrus、UModel……

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 最近一段时间更新的工具有&#xff1a; 工具最新版本&#xff1a;drawio-desktop 21.6.5 更新时间&#xff1a;2023年7月22日 工具简介 开源绘图工具&#xff0c;用Electron编写&#xff0c;…

小程序发布注意事项

1、使用HBuildx的 发布 功能发布小程序&#xff0c;因为编译完的代码目录不是同一个 如果使用 运行 到小程序&#xff0c;最后发布的版本会显示”无法连接本地服务器“ 2、使用unicloud的云服务 uniCloud发行 | uni-app官网 阿里云的unicloud的话&#xff0c;使用request域名…

uniapp开发微信小程序底部地区选择弹框

个人项目地址&#xff1a; SubTopH前端开发个人站 &#xff08;自己开发的前端功能和UI组件&#xff0c;一些有趣的小功能&#xff0c;感兴趣的伙伴可以访问&#xff0c;欢迎提出更好的想法&#xff0c;私信沟通&#xff0c;网站属于静态页面&#xff09; SubTopH前端开发个人站…

在centos7下通过docker 安装onlyoffice

因为需要调试网盘&#xff0c;所以今天安装一下centos7的onlyoffice 官方介绍如下&#xff1a; 为了方便&#xff0c;还是通过docker方式来安装onlyoffice了&#xff0c;这里我们采用社区版本了。 1、下载docker安装包 如下&#xff1a; docker pull onlyoffice/documentserv…

如何实现浅拷贝和深拷贝

一、浅拷贝的实现方法 1.Object.assign方法 let obj1{name:"aaa",}let obj2{age:20}let obj3Object.assign(obj1,obj2)// obj3.age30console.log(obj1);console.log(obj3);console.log(obj1obj3);console.log(obj1obj3); 结果为&#xff1a; 2.直接赋值 let obj1{n…

Falco操作系统安全威胁监测利器

原理简介 Falco是一个开源的云原生安全工具&#xff0c;用于检测和防御容器和云原生环境中的安全威胁。它基于Linux内核的eBPF技术&#xff0c;通过监控系统调用和内核事件来实现安全检测和响应。 具体来说&#xff0c;Falco的实现原理如下&#xff1a; 1. 内核模块&#xf…

AI大模型自动生成PowerPoint(PPT)

1&#xff0c;使用现有开源大模型工具&#xff0c;生成markdown语言文件&#xff08;我这里使用chatGLM2-6B&#xff09; eg&#xff0c;请用Markdown语言生成一个大纲&#xff0c;主题是&#xff1a;给小白用户的第一课&#xff0c;如何快速的学好Python markdown语言文本如下…

LeetCode 33题:搜索旋转排序数组

目录 题目 思路 代码 暴力解法 分方向法 二分法 题目 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 …

字节原来这么容易进,是面试官放水,还是公司实在是太缺人?

本人211非科班&#xff0c;之前在字节和腾讯实习过&#xff0c;这次其实没抱着什么特别大的希望投递&#xff0c;没想到字节可以再给我一次机会&#xff0c;还是挺开心的。 本来以为有个机会就不错啦&#xff01;没想到能成功上岸&#xff0c;在这里要特别感谢帮我内推的同学&…

Spring security之JWT

JWT 这里写目录标题 JWT一级目录二级目录三级目录1.什么是JWT 2.JWT的组成部分3.编码/解码4.特点5. 为什么使用JWT5.1传统的验证方式 5.2基于JWT的验证方式6.JWT进行登录验证6.1依赖安装6.2编写UserDetailServiceImpl类6.3编写UserDetailsImpl类6.4 实现config.SecurityConfig类…

redis分布式集群-redis+keepalived+ haproxy

redis分布式集群架构&#xff08;RedisKeepalivedHaproxy&#xff09;至少需要3台服务器、6个节点&#xff0c;一台服务器2个节点。 redis分布式集群架构中的每台服务器都使用六个端口来实现多路复用&#xff0c;最终实现主从热备、负载均衡、秒级切换的目标。 redis分布式集…

服务器安装Tomcat

下载Tomcat 下载地址在这&#xff1a; Tomcat官网 下载完成以后把压缩包上传到服务器中&#xff08;我传到了www/java&#xff09;,进行解压(解压到)&#xff0c;如果没有进行指定解压到哪里&#xff0c;默认是到root文件夹中 tar -zxvf /www/java/apache-tomcat-9.0.103.tar.…

《格斗之王AI》使用指南

目录 一、说明 二、步骤 1. 下载 2.配置环境 3.替换 4.测试 5.训练 一、说明 该项目是 针对B站UP主 林亦LYi 的作品 格斗之王&#xff01;AI写出来的AI竟然这么强&#xff01;的使用指南&#xff0c;目的是在帮助更多小白轻松入门&#xff0c;一起感受AI的魅力。 林亦LYi…

如何定位线上CPU飙高的问题

1.问题情景 我们的接口卡死&#xff0c;CPU飙高到打不开的网页 2.问题定位 2.1 top指令 通过top命令找到CPU耗用最厉害的那个进程的PID 直接输入top Linux下的100%代表一个核心&#xff0c;如果是八核&#xff0c;最高可以到800%&#xff0c;这样才算满 然后通过PID找到CP…

微服务——es数据聚合+RestClient实现聚合

数据聚合 聚合的种类 DSL实现Bucket聚合 如图所示&#xff0c;设置了10个桶&#xff0c;那么就显示了数量最多的前10个桶&#xff0c;品牌含有7天酒店的有30家&#xff0c; 品牌含有如家的也有30家。 修改排序规则 限定聚合范围 DSL实现Metrics聚合 如下案例要求对不同的品…

EVE-NG 隐藏没有镜像的模板

eve-ng 默认情况下&#xff0c;在添加node时&#xff0c;会列出所有的模板&#xff0c;这样用着很不方便。 通过以下方式&#xff0c;可以使没有设备的模板不可见 cp /opt/unetlab/html/includes/config.php.distribution /opt/unetlab/html/includes/config.php 打开 config…

遍历集合List的五种方法以及如何在遍历集合过程中安全移除元素

一、遍历集合List的五种方法 测试数据 List<String> list new ArrayList<>(); list.add("A");list.add("B");list.add("C");1. 普通for循环 普通for循环&#xff0c;通过索引遍历 for (int i 0; i < list.size(); i) {Syst…