背景
前端文件上传原本是一个惯例交互操作,没什么特殊性可言,然而最近在做文件上传,须要实现截图粘贴上传,去找了下有没有什么好用的组件,网上提供的办法有,然而没找残缺的组件来反对cv上传,通过理解发现能够用剪贴板性能让本人的cv实现文件上传,于是本人就整合了目前几种文件上传的交互方式,码了一个反对cv的vue3文件上传组件(造个轮子)。
介绍
作为一个残缺的组件内容还是挺多的,这里次要介绍下上传交互中一些次要性能,包含上传的几种交互方式,
上传进度的获取,上传类型的限度,默认上传申请和自定义上传申请。
以下代码都是非残缺代码,大家用于参考实现过程,能够通过以下代码批改来实现本人想要的交互性能。
几种交互
1,点击抉择上传
点击抉择是最常见的上传交互,之前原生上传控件,款式批改比拟麻烦,为了批改上传款式,咱们能够把该控件设置暗藏,用其余元素通过从click交互, 来触发该文件抉择控件。在抉择文件控件上绑定onchange事件,该控件在change后获取到文件,而后调用上传办法,实现如下:
<div class="uploader-content" @click="handleClick"> <input ref="inputRef" class="uploader-target" :name="name" :multiple="multiple" :accept="accept" type="file" @change="handleChange" /></div><script setup> const inputRef = shallowRef(null) const handleClick = () => { inputRef.value.value = '' inputRef.value?.click() } const handleChange = (e) => { const files = e.target.files if (!files) return // 获取到文件后调用附件上传办法 uploadFiles(files) }</script><style lang='less' scoped> .uploader-target { display: none; }</style>
2,拖动上传
拖拽文件上传,首先在页面上建设一个拖放区域,在拖放区域上绑定拖放事件,监听拖放事件drop内容中datTransfer中是蕴含files,如果存在files,获取files而后调用上传附件办法。
拖放区域能够通过事件dragover来查看拖放文件是否进入拖放区域来设置拖放区域悬浮款式,通过dragleave来查看来到拖放区勾销悬浮款式。
进行交互提醒
实现如下:
<div class="uploader-drag" v-if="props.uploadMode == 'drag'" :class="['dragger', dragover ? 'dragover' : '']" @drop.prevent="onDrop" @dragover.prevent="onDragover" @dragleave.prevent="dragover = false"> <div class="dragicon-box"> <span>+</span> </div> </div><script setup>const dragover = ref(false)const onDrop = (e) => { const files = Array.from(e.dataTransfer?.files) dragover.value = false uploadFiles(files); }const onDragover = () => { dragover.value = true }</script>
3,复制上传(复制检测区域设置)
复制上传的交互步骤
•将文件保留到剪贴板: 执行键盘快捷键或者应用鼠标复制
•将鼠标挪动到可粘贴区: 判断是否挪动到可粘贴区,来确定是否在执行粘贴后上传,否则整个页面都会作为粘贴区,
•执行粘贴操作:执行键盘粘贴快捷键(ctrl+v)
粘贴区绑定paste事件,在触发paste事件前将鼠标移到粘贴区,复制会被查看不在粘贴区,阻止上传操作,实现如下:
<div class="uploader-paste" v-if="props.uploadMode == 'paste'" :class="['dragger', dragover ? '' : '']" @mouseover.stop="clipboardover = true" @mouseleave.stop="clipboardover = false" @drop.prevent="onDrop" @dragover.prevent="onDragover" @dragleave.prevent="dragover = false" @paste="pasteFun" > <!--默认插槽内容--> <template v-if="$slots.default == null"> <div class="dragicon-box"> <span>+</span> </div> </template> <slot /> </div><script setup> const clipboardover = ref(false) const pasteFun = (e) => { if(!clipboardover.value) return const clipboardFile = e.clipboardData.files; uploadFiles(clipboardFile) }</script>
上传模式
依据以上三种交互,大家可自由组合上传模式,比方点击和拖拽,拖拽和粘贴组合等等,我这边目前按点击,拖拽,粘贴叠加组合,设置为:
•点击上传,click
•拖拽上传 drag(包含点击上传和拖拽上传)
•粘贴上传 paste (包含点击,拖拽和复制上传)
通过传参 uploadeMode设置 (click, drag, paste)
组件设置:
<div class="uploader-content" @click="handleClick"> <input ref="inputRef" class="uploader-target" :name="name" :multiple="multiple" :accept="props.accept" type="file" @change="handleChange" v-if="props.uploadMode != 'click'" /> <!-- click --> <div class="uploader-click" v-if="props.uploadMode == 'click'"> <slot /> <input ref="inputRef" class="uploader-target" :name="name" :multiple="multiple" :accept="accept" type="file" @change="handleChange" @click.stop /> </div> <!-- drag --> <div class="uploader-drag" v-if="props.uploadMode == 'drag'" :class="['dragger', dragover ? 'dragover' : '']" @drop.prevent="onDrop" @dragover.prevent="onDragover" @dragleave.prevent="dragover = false"> <template v-if="$slots.default == null"> <div class="dragicon-box"> <span>+</span> </div> </template> <slot /> </div> <!-- copy --> <div class="uploader-paste" v-if="props.uploadMode == 'paste'" :class="['dragger', dragover ? '' : '']" @mouseover.stop="clipboardover = true" @mouseleave.stop="clipboardover = false" @drop.prevent="onDrop" @dragover.prevent="onDragover" @dragleave.prevent="dragover = false" @paste="pasteFun" > <template v-if="$slots.default == null"> <div class="dragicon-box"> <span>+</span> </div> </template> <slot /> </div> </div></template>
组件利用
<Upload action="https://jsonplaceholder.typicode.com/posts/" uploadMode="click"> <div>点击上传</div></Upload><script lang="ts"> import Upload from '@/components/uploader';</script>
文件限度
文件限度包含是否多文件上传限度multiple, 上传数量limit限度,上传类型accept限度,这些设置参考了element-plus上传组件,在其根底上做了简化。实现如下
multiple 和 accept 首先须要在点击控件上绑定,以便于在点击抉择上传时就可能过滤对应文件,拖拽上传和粘贴上传,无奈通过input[type=file] 组件管制须要在上传办法中判断过滤,(以粘贴上传为例)
组件实现
<div class="uploader-content" @click="handleClick"> <input ref="inputRef" class="uploader-target" :name="name" :multiple="multiple" :accept="props.accept" type="file" @change="handleChange" v-if="props.uploadMode != 'click'" @click.stop /> <div class="uploader-paste" v-if="props.uploadMode == 'paste'" :class="['dragger', dragover ? '' : '']" @mouseover.stop="clipboardover = true" @mouseleave.stop="clipboardover = false" @drop.prevent="onDrop" @dragover.prevent="onDragover" @dragleave.prevent="dragover = false" @paste="pasteFun" > <template v-if="$slots.default == null"> <div class="dragicon-box"> <span>+</span> </div> </template> <slot /> </div> </div><script setup> import { shallowRef, ref } from 'vue'; const inputRef = shallowRef(null) // 上传文件 const uploadFiles = (files) => { if (files.length === 0) return const { limit, multiple, accept } = props // 是否多文件限度,次要用于拖拽和粘贴上传中 if (!multiple) { files = Array.from(files).slice(0, 1) } // 文件数量 if (limit && files.length > limit) { /*具体大家须要的逻辑可自行定义*/ return } // 文件类型限度 if (accept) { files = filesFiltered(Array.from(files), accept) } //在文件符合条件后执行上传办法 } // 文件过滤 const filesFiltered = (files, accept) => { return files.filter((file) => { const { type, name } = file const extension = name.includes('.') ? `.${name.split('.').pop()}` : '' const baseType = type.replace(//.*$/, '') return accept .split(',') .map((type) => type.trim()) .filter((type) => type) .some((acceptedType) => { if (acceptedType.startsWith('.')) { return extension === acceptedType } if (//*$/.test(acceptedType)) { return baseType === acceptedType.replace(//*$/, '') } if (/^[^/]+/[^/]+$/.test(acceptedType)) { type === acceptedType } return false }) }) }</script>
上传进度设置
获取文件上传进度,应用ajax中的progress 事件监听机制,回传数据loaded进度,和ttotal进行计算,获取到计算的百分比通过process插槽线上在界面上。
具体实现如下:
组件实现
文件限度后执行组件上传,默认状况下走内置的上传办法,如果做了自定义,上传进度也须要本人实现(本人实现过程能够参考内置办法中的实现)
// 上传办法调用ajaxUpload({...props, file})// 上传办法实现ajaxUpload = (options) => {const xhr = new XMLHttpRequest() const action = option.action console.log(xhr, xhr.upload) if (xhr.upload) { // 建设progress监听 xhr.upload.addEventListener('progress', (evt:any) => { const progressEvt = evt progressEvt.percent = evt.total > 0 ? (evt.loaded / evt.total) * 100 : 0 // 回传进度数据 option.onProgress(progressEvt) }) }}
同样文件上传胜利,异样等办法也能够通过监听load并且判断 xhr.status 来实现,
xhr.addEventListener('load', () => { if (xhr.status < 200 || xhr.status >= 300) { return option.onError(getError(action, option, xhr)) } option.onSuccess(getBody(xhr))})
组件应用
•配置获取进度数据回调函数 onProgress
•配置接管回传的进度数据进行赋值
•配置进度条插槽显示进度数据
<Upload action="https://jsonplaceholder.typicode.com/posts/" :limit="3" uploadMode="click" :onProgress="progress"> <div class="button">点击上传</div> <template v-slot:progress> <!-自定义的进度条款式,大家能够依据本人的设想,自行设置进度条款式--> <div class="progress-box"> <div class="progress"> <span class="line" :style="{'width': progressval + '%'}"></span> </div> <span class="val">{{progressval}} %</span> </div> </template></Upload><script setup>import {ref} from 'vue'import Upload from '@/components/uploader';const progressval = ref(0)const progress = (evt)=>{ progressval.value = evt.percent.toFixed(2)},// 上传胜利const uploadSucess = (e)=>{ console.log('sucess', e)}// 上传异样const uploadError= (e)=> { console.log('sucess', e)}</script>
自定义上传申请
默认状况下,不须要自定义上传申请,组件内置了上传申请,如果集体有需要能够自定义上传申请,子定义上传申请,是在文件限度流程后,查看是否有自定义申请办法,如果存在就将文件传入自定义申请办法。
组件实现:
// 上传文件const uploadFiles = (files) => { if (files.length === 0) return const { limit, multiple, accept, httpRequest } = props // 是否多文件限度,次要用于拖拽和粘贴上传中 if (!multiple) { files = Array.from(files).slice(0, 1) } // 文件数量 if (limit && files.length > limit) { /*具体大家须要的逻辑可自行定义*/ return } // 文件类型限度 if (accept) { files = filesFiltered(Array.from(files), accept) } //在文件符合条件后执行上传办法 // 自定义上传办法调用 if(httpRequest) { return httpRequest(files) } }
组件利用:
留神点: 通过自定义上传办法实现时,在原来组件上的属性action有效
<Upload :limit="3" uploadMode="click" :onProgress="progress" :onSuccess="uploadSucess" :onError="uploadError" :httpRequest="httpRequest"> <div class="button">点击上传</div> <template v-slot:progress> <div class="progress-box"> <div class="progress"> <span class="line" :style="{'width': progressval + '%'}"></span> </div> <span class="val">{{progressval}} %</span> </div> </template> </Upload><script setup> const httpRequest = (files)=> { // 获取到文件 ,自定已上传办法 }</script>
总结
通过以上能够实现一个反对多种交互方式的文件上传组件,同时也将element-plus中文件上传的流程做了一个学习,因为该组件的实现过程就是参考了element-plus的实现,在element-plus上传的根底上增加了粘贴上传交互, 该组件的实现重在交互方式,各个款式格调通过插槽自定义。
作者:京东物流 刘海鼎
起源:京东云开发者社区