前端文件上传的几种交互造轮子

news/2024/11/7 16:58:16/

背景

前端文件上传本来是一个常规交互操作,没什么特殊性可言,但是最近在做文件上传,需要实现截图粘贴上传,去找了下有没有什么好用的组件,网上提供的方法有,但是没找完整的组件来支持 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.filesif (!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 = falseuploadFiles(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) returnconst 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) returnconst { 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 } = fileconst 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.actionconsole.log(xhr, xhr.upload)if (xhr.upload) {// 建立progress监听xhr.upload.addEventListener('progress', (evt:any) => {const progressEvt = evtprogressEvt.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) returnconst { 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 上传的基础上添加了粘贴上传交互,该组件的实现重在交互方式,各个样式风格通过插槽自定义。

原文:前端文件上传的几种交互造轮子 | 京东云技术团队 - 京东云开发者的个人空间 - OSCHINA - 中文开源技术交流社区


http://www.ppmy.cn/news/615612.html

相关文章

基于STM32的多功能秒表

基于STM32的多功能秒表 当上电运行后&#xff0c;按下KEY1,秒表开始正计时&#xff0c;再次按下KEY1秒表停止计时&#xff0c;按下KEY2秒表清零&#xff0c;按下KEY3&#xff0c;手动调节秒表时间递增&#xff0c;按下KEY4手动调节秒表时间递减&#xff0c;按下KEY5秒表倒计时…

stm32倒计时秒表proteus_倒计时秒表 单片机

目录 一、设计目的 ........................................................................................................ 1 二、设计任务及要求 ............................................................................................ 2 三、总体方案设计 …

MSP430 G2553 单片机 口袋板 日历 时钟 闹钟 万年历 电子时钟 秒表显示

一、实验目的本次实验使用 MSp430G2553 以及 GZ 扩展版的 LCO 显示、蜂鸣器、机械按键&#xff0c;实现具有多功能电子钟的设计。功能包括&#xff0c;按键切换工作界面、设置时间、秒表计时、闹钟的设定、闹钟的暂停与休眠。二、实验内容与设计思路电子钟功能综述本次电子表设…

Python 内置模块tkinter —— 秒表计时器

# 秒表计时器""" 思考&#xff1a;1、怎么创建一个最简单的窗口&#xff1f;2、怎么保持窗口一直运行&#xff1f;3、不用方法mainloop&#xff08;&#xff09;怎么实现窗口一直运行&#xff1f;4、请问窗口的宽度*高度默认值为多少个像素点&#xff1f;5、利用…

stm32倒计时秒表proteus_单片机课程设计倒计时秒表

精品文档 。 1 欢迎下载 一、设计目的 此次设计是我们更进一步了解基本电路的设计流程&#xff0c;提高自己的设计理念&#xff0c; 丰富自己的理论知识&#xff0c;巩固所学知识&#xff0c;使自己的动手动脑能力有更进一步提高&#xff0c; 为自己今后的学习和工作打好基础&a…

基于单片机的秒表计时器系统设计(#0400)

当今社会在很多比赛中&#xff0c;都要用到时间的长短来衡量比赛的结果。本设计是一个利用单片机控制的多功能秒表系统。 功能描述 1、采用51单片机作为控制芯片&#xff1b; 2、数码管前两位显示分钟&#xff0c;后两位显示秒&#xff1b; 2、正计时、倒计时可以切换&#…

axios请求、 Excel 表格导出

import axios from axios;//用于导出excel表格 export const exportExcel ({ method get, url, data {}, fileName }) > {const field method get ? params : data;axios({method,url,[field]: data,responseType: blob}).then((res) > {//导出接口失败 返回的也是…

上海EMBA项目|上国会-美国亚利桑那州立大学合办金融财务EMBA

上海EMBA项目|上国会-美国亚利桑那州立大学合办金融财务EMBA 项目愿景 21世纪注定是一个颠覆的时代。技术和基础设施的嬗变正在催生新的大众生活方式和新经济模式。数字化触动了每个人的神经&#xff0c;互联网浪潮将带来社会经济的指数级增长。在这一变革的时代&#xff0c;转…