仿写el-upload组件,彻底搞懂文件上传

用了那么久的Upload组件,你知道是怎么实现的么,今天就来仿写一个饿了么el-upload vue组件,彻底搞懂前端的文件上传相关知识!

要实现的props

参数说明
action必选参数,上传的地址
headers设置上传的请求头部
multiple是否支持多选文件
data上传时附带的额外参数
name上传的文件字段名
with-credentials支持发送 cookie 凭证信息
show-file-list是否显示已上传文件列表
drag是否启用拖拽上传
accept接受上传的文件类型
on-preview点击文件列表中已上传的文件时的钩子
on-remove文件列表移除文件时的钩子
on-success文件上传成功时的钩子
on-error文件上传失败时的钩子
on-progress文件上传时的钩子
on-change添加文件时被调用
before-upload上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise 且被 reject,则停止上传。
before-remove删除文件之前的钩子,参数为上传的文件和文件列表,若返回 false 或者返回 Promise 且被 reject,则停止删除。
list-type文件列表的类型
auto-upload是否在选取文件后立即进行上传
file-list上传的文件列表, 例如: [{name: ‘food.jpg’, url: ‘https://xxx.cdn.com/xxx.jpg’}]
limit最大允许上传个数
on-exceed文件超出个数限制时的钩子

参考:https://element.eleme.cn/#/zh-CN/component/upload

这里面有几个重要的点:

  1. input file 的美化
  2. 多选
  3. 拖拽

一个个实现

创建upload组件文件

src/components/upload/index.vue

<template></template>
<script setup>// 属性太多,把props单独放一个文件引入进来import property from './props'const props = defineProps(property)
</script>
<style></style>

./props.js

export default {action: {type: String},headers: {type: Object,default: {}},multiple: {type: Boolean,default: false},data: {type: Object,default: {}},name: {type: String,default: 'file'},'with-credentials': {type: Boolean,default: false},'show-file-list': {type: Boolean,default: true,},drag: {type: Boolean,default: false},accept: {type: String,default: ''},'list-type': {type: String,default: 'text' // text、picture、picture-card},'auto-upload': {type: Boolean,default: true},'file-list': {type: Array,default: []},disabled: {type: Boolean,default: false},limit: {type: Number,default: Infinity},'before-upload': {type: Function,default: () => {return true}},'before-remove': {type: Function,default: () => {return true}}

具体的编写upload组件代码

1. 文件上传按钮的样式

我们都知道,<input type="file">的默认样式是这样的:

很丑,并且无法改变其样式。

解决办法:可以把input隐藏,重新写个按钮点击来触发input的文件选择。

<template><input type="file" id="file" @change="handleChange"><button class="upload-btn" @click="choose">点击上传</button>
</template>
<script setup>// 触发选择文件const choose = () => {document.querySelector('#file').click()}// input选择文件回调const handleChange = (event) => {files = Array.from(event.target.files)console.log('[ files ] >', files)}
</script>
<style scoped>#file {display: none;}.upload-btn {border: none;background-color: #07c160;color: #fff;padding: 6px 10px;cursor: pointer;}
</style>

效果:

这样也是可以调起文件选择框,并触发input的onchange事件。

2. 多选

直接在input上加一个Booelan属性multiple,根据props中的值动态设置

顺便把accept属性也加上

<template><input type="file" id="file" :multiple="multiple":accept="accept"@change="handleChange">
</template>
3. 拖拽

准备一个接收拖拽文件的区域,props传drag=true就用拖拽,否则就使用input上传。

<template><input type="file" id="file" :multiple="multiple":accept="accept"@change="handleChange"><button class="upload-btn" v-if="!drag" @click="choose">点击上传</button><div v-else class="drag-box" @dragover="handleDragOver"@dragleave="handleDragLeave"@drop="handleDrop"@click="choose":class="{'dragging': isDragging}">将文件拖到此处,或<span>点击上传</span></div>
</template>

dragging用来拖拽鼠标进入时改变样式

<script setup>const isDragging = ref(false)// 拖放进入目标区域const handleDragOver = (event) => {event.preventDefault()isDragging.value = true}const handleDragLeave = (event) => {isDragging.value = false}let files = []// 拖拽放置const handleDrop = (event) => {event.preventDefault()isDragging.value = falsefiles = Array.from(event.dataTransfer.files);console.log(files);}
</script>
.drag-box {width: 240px;height: 150px;line-height: 150px;text-align: center;border: 1px dashed #ddd;cursor: pointer;border-radius: 8px;}.drag-box:hover {border-color: cornflowerblue;}.drag-box.dragging {background-color: rgb(131, 161, 216, .2);border-color: cornflowerblue;}.drag-box span {color: cornflowerblue;}


跟使用input上传效果一样

4. 上传到服务器

并实现on-xxx钩子函数

  const emit = defineEmits()const fileList = ref([])let files = []// 拖拽放置const handleDrop = (event) => {event.preventDefault()isDragging.value = falsefiles = Array.from(event.dataTransfer.files);console.log('[ files ] >', files)handleBeforeUpload(files)}// input选择文件回调const handleChange = (event) => {files = Array.from(event.target.files)console.log('[ files ] >', files)handleBeforeUpload(files)}const handleBeforeUpload = (files) => {if (files.length > props.limit - fileList.value.length) {console.error(`当前限制选择 ${props.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.value.length} 个文件`)emit('on-exceed', files, toRaw(fileList.value))return}// 可以把锁哥文件放到一个formData中一起上传,// 遍历文件一个个上传,这里一个个上传是为了实现钩子函数回调时返回对应的file对象。files.forEach(async file => {emit('on-change', file, files)if (!props.beforeUpload()) {return}if (props.autoUpload) {uploadRequest(file, files)}})}// 手动上传已选择的文件const submit = () => {files.forEach(async file => {uploadRequest(file, files)})}// 保存xhr对象,用于后面取消上传let xhrs = []const uploadRequest = async (file, files) => {let xhr = new XMLHttpRequest();// 调用open函数,指定请求类型与url地址。请求类型必须为POSTxhr.open('POST', props.action);// 设置自定义请求头Object.keys(props.headers).forEach(k => {xhr.setRequestHeader(k, props.headers[k])})// 额外参数const formData = new FormData()formData.append('file', file);Object.keys(props.data).forEach(k => {formData.append(k, props.data[k]);})// 携带cookiexhr.withCredentials = props.withCredentialsxhr.upload.onprogress = (e) => {emit('on-progress', e, file, files)}// 监听状态xhr.onreadystatechange = () => {if (xhr.readyState === 4) {const res = JSON.parse(xhr.response)const fileObj = {name: file.name,percentage: 100,raw: file,response: res,status: 'success',size: file.size,uid: file.uid,}fileList.value.push(fileObj)if (xhr.status === 200 || xhr.status === 201) {emit('on-success', res, fileObj, toRaw(fileList.value))} else {emit('on-error', res, fileObj, toRaw(fileList.value))}}}// 发起请求xhr.send(formData);xhrs.push({xhr,file})}const preview = (file) => {emit('on-preview', file)}const remove = (file, index) => {if (!props.beforeRemove()) {return}fileList.value.splice(index, 1)emit('on-remove', file, fileList.value)}// 取消上传const abort = (file) => {// 通过file对象找到对应的xhr对象,然后调用abort// xhr.abort()}defineExpose({abort,submit})

全部代码

<template><input type="file" id="file" :multiple="multiple":accept="accept"@change="handleChange"><button class="upload-btn" v-if="!drag" @click="choose">点击上传</button><div v-else class="drag-box" @dragover="handleDragOver"@dragleave="handleDragLeave"@drop="handleDrop"@click="choose":class="{'dragging': isDragging}">将文件拖到此处,或<span>点击上传</span></div><template v-if="showFileList"><template v-if="listType === 'text'"><p class="file-item" v-for="(file, index) in fileList" :key="index" @click="preview(file)"><span>{{file.name}}</span><span class="remove" @click.stop="remove(file, index)">×</span></p></template></template>
</template><script setup>import { ref, toRaw, onMounted } from 'vue'import property from './props'const props = defineProps(property)const emit = defineEmits()const fileList = ref([])const isDragging = ref(false)// 触发选择文件const choose = () => {document.querySelector('#file').click()}// 拖放进入目标区域const handleDragOver = (event) => {event.preventDefault()isDragging.value = true}const handleDragLeave = (event) => {isDragging.value = false}let files = []// 拖拽放置const handleDrop = (event) => {event.preventDefault()isDragging.value = falsefiles = Array.from(event.dataTransfer.files);console.log('[ files ] >', files)handleBeforeUpload(files)}// input选择文件回调const handleChange = (event) => {files = Array.from(event.target.files)console.log('[ files ] >', files)handleBeforeUpload(files)}const handleBeforeUpload = (files) => {if (files.length > props.limit - fileList.value.length) {console.error(`当前限制选择 ${props.limit} 个文件,本次选择了 ${files.length} 个文件,共选择了 ${files.length + fileList.value.length} 个文件`)emit('on-exceed', files, toRaw(fileList.value))return}files.forEach(async file => {emit('on-change', file, files)if (!props.beforeUpload()) {return}if (props.autoUpload) {uploadRequest(file, files)}})}// 手动上传已选择的文件const submit = () => {files.forEach(async file => {uploadRequest(file, files)})}let xhrs = []const uploadRequest = async (file, files) => {let xhr = new XMLHttpRequest();// 调用open函数,指定请求类型与url地址。请求类型必须为POSTxhr.open('POST', props.action);// 设置自定义请求头Object.keys(props.headers).forEach(k => {xhr.setRequestHeader(k, props.headers[k])})// 额外参数const formData = new FormData()formData.append('file', file);Object.keys(props.data).forEach(k => {formData.append(k, props.data[k]);})// 携带cookiexhr.withCredentials = props.withCredentialsxhr.upload.onprogress = (e) => {emit('on-progress', e, file, files)}// 监听状态xhr.onreadystatechange = () => {if (xhr.readyState === 4) {const res = JSON.parse(xhr.response)const fileObj = {name: file.name,percentage: 100,raw: file,response: res,status: 'success',size: file.size,uid: file.uid,}fileList.value.push(fileObj)if (xhr.status === 200 || xhr.status === 201) {emit('on-success', res, fileObj, toRaw(fileList.value))} else {emit('on-error', res, fileObj, toRaw(fileList.value))}}}// 发起请求xhr.send(formData);xhrs.push({xhr,file})}const preview = (file) => {emit('on-preview', file)}const remove = (file, index) => {if (!props.beforeRemove()) {return}fileList.value.splice(index, 1)emit('on-remove', file, fileList.value)}// 取消上传const abort = (file) => {// 通过file对象找到对应的xhr对象,然后调用abort// xhr.abort()}defineExpose({abort,submit})
</script><style scoped>#file {display: none;}.upload-btn {border: none;background-color: #07c160;color: #fff;padding: 6px 10px;cursor: pointer;}.drag-box {width: 240px;height: 150px;line-height: 150px;text-align: center;border: 1px dashed #ddd;cursor: pointer;border-radius: 8px;}.drag-box:hover {border-color: cornflowerblue;}.drag-box.dragging {background-color: rgb(131, 161, 216, .2);border-color: cornflowerblue;}.drag-box span {color: cornflowerblue;}.file-item {display: flex;justify-content: space-between;align-items: center;margin-top: 12px;padding: 0 8px;border-radius: 4px;cursor: pointer;}.file-item:hover {background-color: #f5f5f5;color: cornflowerblue;}.file-item .remove {font-size: 20px;}
</style>

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

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

相关文章

图像超分辨率超分辨率NeRF论文阅读

文章目录 前置知识图像超分辨率《High-resolution image reconstruction with latent diffusion models from human brain activity》【CVPR23】《Dynamic High-Pass Filtering and Multi-Spectral Attention for Image Super-Resolution》【ICCV21】《DiffBIR: Towards Blind …

线性代数-Python-01:向量的基本运算 - 手写Vector及numpy的基本用法

文章目录 一、代码仓库二、向量的基本运算2.1 加法2.2 数量乘法2.3 向量运算的基本性质2.4 零向量2.5 向量的长度2.6 单位向量2.7 点乘/内积&#xff1a;两个向量的乘法 --答案是一个标量 三、手写Vector代码3.1 在控制台测试__repr__和__str__方法3.2 创建实例测试代码3.3 完整…

vue3实现在element Dialog 对话框中预览pdf文件

最近有一个需求就是点击按钮在弹框中去预览pdf文件&#xff0c;于是发现了一个HTML中比较重要的标签&#xff1a;embed&#xff0c;前面说的需求就可以用这个标签来实现&#xff0c;一起来学习一下吧。 embed标签是HTML中的一个非常重要的标签&#xff0c;它可以在你的网页上插…

车规MCU开发工具之Vector DaVinci Configurator执行arxml合并操作

环境 Step1 导入要合并的arxml 、 Step 2 比较、合并过程 <完>

xlive.dll下载安装方法分享,教你快速修复xlive.dll文件

在运行某些应用程序或游戏时&#xff0c;你可能会遭遇到"xlive.dll缺失"错误提示&#xff0c;这可能导致程序无法正常运行。本文将向你介绍一些可行的解决方法教你下载xlive.dll文件&#xff0c;并详细阐述xlive.dll是什么文件以及导致其缺失的原因。 一.理解"x…

移动设备管理对企业IT 安全的增强

移动设备管理 &#xff08;MDM&#xff09; 是通过定义策略和部署安全控制&#xff08;如移动应用程序管理、移动内容管理和条件 Exchange 访问&#xff09;来管理移动设备的过程。 完整的MDM解决方案可以管理在Android&#xff0c;iOS&#xff0c;Windows&#xff0c;macOS&a…

Elasticsearch 8.X 分词插件版本更新不及时解决方案

1、关于 Elasticsearch 8.X IK 分词插件相关问题 球友在 ElasticSearch 版本选型问题中提及&#xff1a;如果要使用ik插件&#xff0c;是不是就使用目前最新的IK对应elasticsearch的版本“8.8.2”&#xff1f; https://github.com/medcl/elasticsearch-analysis-ik/releases/ta…

K8s 概念及组件

K8s 的全称为Kubernetes&#xff0c;是一种开源的容器编排平台&#xff0c;用于自动化部署以及扩展和管理容器化的应用程序&#xff0c;它提供了一种容器编排和管理的方式&#xff0c;可以帮助开发人员更轻松的管理容器化的应用程序&#xff0c;并且提供了一种跨多个主机的自动…

Jmeter性能测试 —— jmeter之使用ServerAgent监控服务器

ServerAgent 性能测试时我们关注的重要指标是&#xff1a;并发用户数&#xff0c;TPS&#xff0c;请求成功率&#xff0c;响应时间&#xff0c;服务器的CPU&#xff0c;memory&#xff0c; I/O disk等。Jmeter的聚合报告可以查看并发数、吞吐量、请求成功率、响应时间等&#…

ERR_PNPM_LINKING_FAILED Error: EPERM: operation not permitted, rename

webstorm终端pnpm报错  ERR_PNPM_LINKING_FAILED  Error: EPERM: operation not permitted, rename ’ 报错原因&#xff1a;powershell权限不够 解决办法&#xff1a;提升权限/在文件打开Powershell安装依赖

发现一款非常好用的学术GPT,可形成知识库,并分析论文,根据观点生成文字

发现一款非常好用的学术GPT&#xff0c;支持CHATGPT3.5交互、论文分析与生成&#xff0c;目前作者并未全面推广&#xff0c;仅在小圈子里使用&#xff0c;可以保证后端api的使用稳定性&#xff0c;不会出现大量用户共享gpt 服务&#xff0c;导致gpt调用超时的情况。 使用方法&a…

关系数据库-postgresql-基础

文章目录 介绍linux下安装postgresql源码安装navicat连接 介绍 Postgresql官网开源的关系型数据库&#xff1b; linux下安装 Ubuntu下可以使用apt包管理器安装&#xff1b;参考地址CentOS下可以使用yum包管理器安装&#xff1b;OpenSuse下可以使用zypper包管理器安装&#xf…

基于Python3的Scapy构造DNS报文

一&#xff1a;DNS协议 DNS&#xff08;Domain Name System&#xff09;协议是计算机网络中的一种基础协议&#xff0c;它用于将域名&#xff08;如www.baidu.com&#xff09;转换为IP地址&#xff08;如192.168.0.1&#xff09;&#xff0c;从而实现计算机之间的通信。 DNS 分…

React基础: 项目创建 JSX 基础语法 React基础的组件使用 useState状态 基础样式控制

01 React 文章目录 01 React一、React是什么1、React的优势 二、React开发环境搭建1、创建项目2、运行项目3、项目的目录结构 三、JSX基础1、什么是 JSX代码示例&#xff1a; 2、JSX使用场景2.1代码示例&#xff1a; 3、JSX中实现列表渲染4、JSX - 实现基本的条件渲染5、JSX - …

喜讯!持安科技入选2023年北京市知识产权试点单位!

近日&#xff0c;北京市知识产权局发布了“2023年度北京市知识产权试点示范单位及2020年度北京市知识产权试点示范单位复审通过名单”名单。 经过严格的初审、形式审核和专家评审&#xff0c;北京持安科技有限公司入选“2023年北京市知识产权试点单位”。 北京市知识产权试点示…

并发性Socket通信源码(基于linux环境下多线程)

服务器端&#xff1a;server.c 1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <string.h>5 #include <arpa/inet.h>6 #include <pthread.h>7 void* working(void *arg);8 //信息结构体9 struct sockinfo10 …

《数据结构、算法与应用C++语言描述》-队列的应用-图元识别问题

《数据结构、算法与应用C语言描述》-队列的应用-图元识别问题 图元识别 问题描述 数字化图像是一个 mxm 的像素矩阵。在单色图像中&#xff0c;每一个像素要么为0&#xff0c;要么为 1。值为0的像素表示图像的背景。值为1的像素表示图元上的一个点&#xff0c;称其为图元像素…

分布式定时任务xxljob

xxl-job的xxl为作者名徐雪里拼音首字母。 xxl-job的作者是2015年开始开发这个项目&#xff0c;那时候springmvcbootstrapadminlte 大行其道&#xff0c;所以这个框架调度器一直沿用这个架构。 一、运行调度器 调度器可以集群或单点运行&#xff0c;以单点运行为例 下载代码…

Linux shell编程学习笔记14:编写和运行第一个shell脚本hello world!

* 20231020 写这篇博文断断续续花了好几天&#xff0c;为了说明不同shell在执行同一脚本文件时的差别&#xff0c;我分别在csdn提供线上Linux环境 &#xff08;使用的shell是zsh&#xff09;和自己的电脑上&#xff08;使用的shell是bash&#xff09;做测试。功夫不负有心人&am…