前端文件上传的几种交互造轮子 | 京东云技术团队

背景

前端文件上传本来是一个常规交互操作,没什么特殊性可言,但是最近在做文件上传,需要实现截图粘贴上传,去找了下有没有什么好用的组件,网上提供的方法有,但是没找完整的组件来支持cv上传,经过了解发现可以用剪贴板功能让自己的cv实现文件上传,于是自己就整合了目前几种文件上传的交互方式,码了一个支持cv的vue3文件上传组件(造个轮子)。

介绍

作为一个完整的组件内容还是挺多的,这里主要介绍下上传交互中一些主要功能,包括上传的几种交互方式,

上传进度的获取,上传类型的限制,默认上传请求和自定义上传请求。

以下代码都是非完整代码,大家用于参考实现过程,可以通过以下代码修改来完成自己想要的交互功能。

几种交互

1,点击选择上传

点击选择是最常见的上传交互,之前原生上传控件,样式修改比较麻烦,为了修改上传样式,我们可以把该控件设置隐藏,用其他元素通过从click交互, 来触发该文件选择控件。在选择文件控件上绑定onchange事件,该控件在change后获取到文件,然后调用上传方法,实现如下:

1<div class="uploader-content" @click="handleClick"> 2 <input ref="inputRef" 3 class="uploader-target" 4 :name="name" :multiple="multiple" 5 :accept="accept" type="file" 6 @change="handleChange" /> 7</div> 8<script setup> 9 const inputRef = shallowRef(null) 10 const handleClick = () => { 11 inputRef.value.value = '' 12 inputRef.value?.click() 13 } 14 const handleChange = (e) => { 15 const files = e.target.files 16 if (!files) return 17 // 获取到文件后调用附件上传方法 18 uploadFiles(files) 19 } 20</script> 21<style lang='less' scoped> 22 .uploader-target { 23 display: none; 24 } 25</style>

2,拖动上传

拖拽文件上传,首先在页面上建立一个拖放区域,在拖放区域上绑定拖放事件,监听拖放事件drop内容中datTransfer中是包含files,如果存在files,获取files然后调用上传附件方法。

拖放区域可以通过事件dragover来检查拖放文件是否进入拖放区域来设置拖放区域悬浮样式,通过dragleave来检查离开拖放区取消悬浮样式。

进行交互提示

实现如下:

1<div class="uploader-drag" v-if="props.uploadMode == 'drag'" :class="['dragger', dragover ? 'dragover' : '']" @drop.prevent="onDrop" @dragover.prevent="onDragover" 2 @dragleave.prevent="dragover = false"> 3 <div class="dragicon-box"> 4 <span>+</span> 5 </div> 6 </div> 7<script setup> 8const dragover = ref(false) 9const onDrop = (e) => { 10 const files = Array.from(e.dataTransfer?.files) 11 dragover.value = false 12 uploadFiles(files); 13 } 14const onDragover = () => { 15 dragover.value = true 16 } 17</script>

3,复制上传(复制检测区域设置)

复制上传的交互步骤

•将文件保存到剪贴板: 执行键盘快捷键或者使用鼠标复制

•将鼠标移动到可粘贴区: 判断是否移动到可粘贴区,来确定是否在执行粘贴后上传,否则整个页面都会作为粘贴区,

•执行粘贴操作:执行键盘粘贴快捷键(ctrl+v)

粘贴区绑定paste事件,在触发paste事件前将鼠标移到粘贴区,复制会被检查不在粘贴区,阻止上传操作,实现如下:

1<div class="uploader-paste" 2 v-if="props.uploadMode == 'paste'" 3 :class="['dragger', dragover ? '' : '']" 4 @mouseover.stop="clipboardover = true" 5 @mouseleave.stop="clipboardover = false" 6 @drop.prevent="onDrop" 7 @dragover.prevent="onDragover" 8 @dragleave.prevent="dragover = false" 9 @paste="pasteFun" 10 > 11 <!--默认插槽内容--> 12 <template v-if="$slots.default == null"> 13 <div class="dragicon-box"> 14 <span>+</span> 15 </div> 16 </template> 17 <slot /> 18 </div> 19<script setup> 20 const clipboardover = ref(false) 21 const pasteFun = (e) => { 22 if(!clipboardover.value) return 23 const clipboardFile = e.clipboardData.files; 24 uploadFiles(clipboardFile) 25 } 26</script>

上传模式

根据以上三种交互,大家可自由组合上传形式,比如点击和拖拽,拖拽和粘贴组合等等,我这边目前按点击,拖拽,粘贴叠加组合,设置为:

•点击上传,click

•拖拽上传 drag(包括点击上传和拖拽上传)

•粘贴上传 paste (包括点击,拖拽和复制上传)

通过传参 uploadeMode设置 (click, drag, paste)

组件设置:

1<div class="uploader-content" @click="handleClick"> 2 <input 3 ref="inputRef" 4 class="uploader-target" 5 :name="name" 6 :multiple="multiple" 7 :accept="props.accept" 8 type="file" 9 @change="handleChange" 10 v-if="props.uploadMode != 'click'" 11 /> 12 <!-- click --> 13 <div class="uploader-click" v-if="props.uploadMode == 'click'"> 14 <slot /> 15 <input 16 ref="inputRef" 17 class="uploader-target" 18 :name="name" 19 :multiple="multiple" 20 :accept="accept" 21 type="file" 22 @change="handleChange" 23 @click.stop /> 24 </div> 25 <!-- drag --> 26 <div class="uploader-drag" 27 v-if="props.uploadMode == 'drag'" 28 :class="['dragger', dragover ? 'dragover' : '']" 29 @drop.prevent="onDrop" 30 @dragover.prevent="onDragover" 31 @dragleave.prevent="dragover = false"> 32 <template v-if="$slots.default == null"> 33 <div class="dragicon-box"> 34 <span>+</span> 35 </div> 36 </template> 37 <slot /> 38 </div> 39 <!-- copy --> 40 <div class="uploader-paste" 41 v-if="props.uploadMode == 'paste'" 42 :class="['dragger', dragover ? '' : '']" 43 @mouseover.stop="clipboardover = true" 44 @mouseleave.stop="clipboardover = false" 45 @drop.prevent="onDrop" 46 @dragover.prevent="onDragover" 47 @dragleave.prevent="dragover = false" 48 @paste="pasteFun" 49 > 50 <template v-if="$slots.default == null"> 51 <div class="dragicon-box"> 52 <span>+</span> 53 </div> 54 </template> 55 <slot /> 56 </div> 57 </div> 58</template>

组件应用

1<Upload action="https://jsonplaceholder.typicode.com/posts/" uploadMode="click"> 2 <div>点击上传</div> 3</Upload> 4<script lang="ts"> 5 import Upload from '@/components/uploader'; 6</script>

文件限制

文件限制包括是否多文件上传限制multiple, 上传数量limit限制,上传类型accept限制,这些设置参考了element-plus上传组件,在其基础上做了简化。实现如下

multiple 和 accept 首先需要在点击控件上绑定,以便于在点击选择上传时就能够过滤对应文件,拖拽上传和粘贴上传,无法通过input[type=file] 组件控制需要在上传方法中判断过滤,(以粘贴上传为例)

组件实现

1<div class="uploader-content" @click="handleClick"> 2 <input ref="inputRef" 3 class="uploader-target" 4 :name="name" :multiple="multiple" :accept="props.accept" type="file" 5 @change="handleChange" v-if="props.uploadMode != 'click'" @click.stop /> 6 7 <div class="uploader-paste" v-if="props.uploadMode == 'paste'" :class="['dragger', dragover ? '' : '']" 8 @mouseover.stop="clipboardover = true" 9 @mouseleave.stop="clipboardover = false" 10 @drop.prevent="onDrop" 11 @dragover.prevent="onDragover" 12 @dragleave.prevent="dragover = false" 13 @paste="pasteFun" 14 > 15 <template v-if="$slots.default == null"> 16 <div class="dragicon-box"> 17 <span>+</span> 18 </div> 19 </template> 20 <slot /> 21 </div> 22 </div> 23<script setup> 24 import { shallowRef, ref } from 'vue'; 25 const inputRef = shallowRef(null) 26 // 上传文件 27 const uploadFiles = (files) => { 28 if (files.length === 0) return 29 const { limit, multiple, accept } = props 30 // 是否多文件限制,主要用于拖拽和粘贴上传中 31 if (!multiple) { 32 files = Array.from(files).slice(0, 1) 33 } 34 // 文件数量 35 if (limit && files.length > limit) { 36 /*具体大家需要的逻辑可自行定义*/ 37 return 38 } 39 // 文件类型限制 40 if (accept) { 41 files = filesFiltered(Array.from(files), accept) 42 } 43 //在文件符合条件后执行上传方法 44 } 45 // 文件过滤 46 const filesFiltered = (files, accept) => { 47 return files.filter((file) => { 48 const { type, name } = file 49 const extension = name.includes('.') ? `.${name.split('.').pop()}` : '' 50 const baseType = type.replace(//.*$/, '') 51 return accept 52 .split(',') 53 .map((type) => type.trim()) 54 .filter((type) => type) 55 .some((acceptedType) => { 56 if (acceptedType.startsWith('.')) { 57 return extension === acceptedType 58 } 59 if (//*$/.test(acceptedType)) { 60 return baseType === acceptedType.replace(//*$/, '') 61 } 62 if (/^[^/]+/[^/]+$/.test(acceptedType)) { 63 type === acceptedType 64 } 65 return false 66 }) 67 }) 68 } 69 70</script>

上传进度设置

获取文件上传进度,使用ajax中的progress 事件监听机制,回传数据loaded进度,和ttotal进行计算,获取到计算的百分比通过process插槽线上在界面上。

具体实现如下:

组件实现

文件限制后执行组件上传,默认情况下走内置的上传方法,如果做了自定义,上传进度也需要自己实现(自己实现过程可以参考内置方法中的实现)

1// 上传方法调用 2ajaxUpload({...props, file}) 3// 上传方法实现 4ajaxUpload = (options) => { 5const xhr = new XMLHttpRequest() 6 const action = option.action 7 console.log(xhr, xhr.upload) 8 if (xhr.upload) { 9 // 建立progress监听 10 xhr.upload.addEventListener('progress', (evt:any) => { 11 const progressEvt = evt 12 progressEvt.percent = evt.total > 0 ? (evt.loaded / evt.total) * 100 : 0 13 // 回传进度数据 14 option.onProgress(progressEvt) 15 }) 16 } 17}

同样文件上传成功,异常等方法也可以通过监听load并且判断 xhr.status 来实现,

1xhr.addEventListener('load', () => { 2 if (xhr.status < 200 || xhr.status >= 300) { 3 return option.onError(getError(action, option, xhr)) 4 } 5 option.onSuccess(getBody(xhr)) 6})

组件使用

•配置获取进度数据回调函数 onProgress

•配置接收回传的进度数据进行赋值

•配置进度条插槽显示进度数据

1<Upload action="https://jsonplaceholder.typicode.com/posts/" :limit="3" uploadMode="click" :onProgress="progress"> 2 <div class="button">点击上传</div> 3 <template v-slot:progress> 4 <!-自定义的进度条样式,大家可以根据自己的想象,自行设置进度条样式--> 5 <div class="progress-box"> 6 <div class="progress"> 7 <span class="line" :style="{'width': progressval + '%'}"></span> 8 </div> 9 <span class="val">{{progressval}} %</span> 10 </div> 11 </template> 12</Upload> 13<script setup> 14import {ref} from 'vue' 15import Upload from '@/components/uploader'; 16const progressval = ref(0) 17const progress = (evt)=>{ 18 progressval.value = evt.percent.toFixed(2) 19}, 20// 上传成功 21const uploadSucess = (e)=>{ 22 console.log('sucess', e) 23} 24// 上传异常 25const uploadError= (e)=> { 26 console.log('sucess', e) 27} 28</script>

自定义上传请求

默认情况下,不需要自定义上传请求,组件内置了上传请求,如果个人有需求可以自定义上传请求,子定义上传请求,是在文件限制流程后,检查是否有自定义请求方法,如果存在就将文件传入自定义请求方法。

组件实现:

1// 上传文件 2const uploadFiles = (files) => { 3 if (files.length === 0) return 4 const { limit, multiple, accept, httpRequest } = props 5 // 是否多文件限制,主要用于拖拽和粘贴上传中 6 if (!multiple) { 7 files = Array.from(files).slice(0, 1) 8 } 9 // 文件数量 10 if (limit && files.length > limit) { 11 /*具体大家需要的逻辑可自行定义*/ 12 return 13 } 14 // 文件类型限制 15 if (accept) { 16 files = filesFiltered(Array.from(files), accept) 17 } 18 //在文件符合条件后执行上传方法 19 // 自定义上传方法调用 20 if(httpRequest) { 21 return httpRequest(files) 22 } 23 }

组件应用:

注意点: 通过自定义上传方法实现时,在原来组件上的属性action无效

1<Upload :limit="3" uploadMode="click" :onProgress="progress" :onSuccess="uploadSucess" :onError="uploadError" :httpRequest="httpRequest"> 2 <div class="button">点击上传</div> 3 <template v-slot:progress> 4 <div class="progress-box"> 5 <div class="progress"> 6 <span class="line" :style="{'width': progressval + '%'}"></span> 7 </div> 8 <span class="val">{{progressval}} %</span> 9 </div> 10 </template> 11 </Upload> 12<script setup> 13 const httpRequest = (files)=> { 14 // 获取到文件 ,自定已上传方法 15 } 16</script>

总结

通过以上可以实现一个支持多种交互方式的文件上传组件,同时也将element-plus中文件上传的流程做了一个学习,因为该组件的实现过程就是参考了element-plus的实现,在element-plus上传的基础上添加了粘贴上传交互, 该组件的实现重在交互方式,各个样式风格通过插槽自定义。

作者:京东物流 刘海鼎

来源:京东云开发者社区

点赞
收藏

评论区

加载中...

相关推荐

vue+element UI + axios封装文件上传及进度条组件

1.前言之前在做项目的时候,需要实现一个文件上传组件并且需要有文件上传进度条,现将之前的实现过程简单记录一下,希望可以帮助到有需要的人。项目用的是Vue框架,UI库使用的是elementUI,前后端交互请求使用的是Vue官方推荐的axios。其中,UI方面主要使用了elementUI库中的Upload文件上传组件、Progress

java之大文件断点续传

需求:支持大文件批量上传(20G)和下载,同时需要保证上传期间用户电脑不出现卡死等体验;内网百兆网络上传速度为12MB/S服务器内存占用低支持文件夹上传,文件夹中的文件数量达到1万个以上,且包含层级结构。支持PC端全平台操作系统,Windows,Linux,Mac支持文件和文件夹的批量下载,断点续传。刷新页面后继续传输。关闭浏览器后保

springboot项目上传文件出现临时文件目录为空

<divid"cnblogs\_post\_body"class"blogpostbody"<p最近写文件上传到服务器读取的代码,前端使用FormData上传,服务端用MultipartFile接收,自己测试了下MultipartFile对象有什么东西,结果一般属性都能出来,测试getInputStrea()方法的时候出现了以下错误,简单一

3分钟教你用原生js实现具有进度监听的文件上传预览组件

本文主要介绍如何使用原生js,通过面向对象的方式实现一个文件上传预览的组件,该组件利用FileReader来实现文件在前端的解析,预览,读取进度等功能,并对外暴露相应api来实现用户自定义的需求,比如文件上传,进度监听,自定义样式,读取成功回调等。组件设计架构如下:(https://imghelloworld.osscnbeijing.

PlayFramework1.2.4 一些使用中的记录2(多文件上传)

在Play中进行多文件上传普遍都会想到在方法参数中加入File\\files,如: publicstaticvoidupdate(File\\files) 虽然这种方式不会报错,但是上传过来的文件仅仅是第一个文件的N个,比如5个上传框,选择了a,b,c三个文件,那么提交后会得到5个a。即使只选择一个a文件上传也会得到5个a文件,这可

ThinkPHP 使用 Layer 的文件上传组件

layer前端框架,相信大家都用过,但是最近有写小伙伴对layer的upload组件,不知道怎么用,其实很简单,以下仅做一些开发说明,实现组件的使用,仅仅提供给小白参考(高手请跳过,真的不适合你~~~)废话少说,上代码:前端主要代码htmljs!(https://oscimg.oschina.net/oscnet/1d18ba89cc