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

前端,文件,上传,几种,交互,轮子 · 浏览次数 : 415

小编点评

**文件限制** 组件实现文件限制后执行组件上传,默认情况下走内置的上传方法,如果做了自定义,上传进度也需要自己实现。 **自定义上传方法** 如果个人有需求可以自定义上传方法,子定义上传请求,是在文件限制流程后,检查是否有自定义请求方法,如果存在就将文件传入自定义请求方法。 **交互方式** 组件应用: * 支持多种交互方式的文件上传 * 元素-plus中文件上传的流程做了一个学习 * 实现重在交互方式,各个样式风格通过插槽自定义 **其他** * 通过自定义上传方法实现时,在原来组件上的属性action无效 * 元素-plus中文件上传的流程做了一个学习

正文

背景

前端文件上传本来是一个常规交互操作,没什么特殊性可言,但是最近在做文件上传,需要实现截图粘贴上传,去找了下有没有什么好用的组件,网上提供的方法有,但是没找完整的组件来支持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上传的基础上添加了粘贴上传交互, 该组件的实现重在交互方式,各个样式风格通过插槽自定义。

作者:京东物流 刘海鼎

来源:京东云开发者社区

与前端文件上传的几种交互造轮子相似的内容:

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

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

基于Web3.0的区块链图片上传

开始前,我们先简单了解一下基本的概念,我大致归纳为以下几个点 什么是Web3.0,和区块链又有什么关系?(上回的文章不就派上用场了) 需求:开发一个基于Python的Web 3.0图片上传系统。这个系统将允许用户上传图片,并将图片存储在去中心化的网络上,同时记录交易信息在区块链上。 本就是写着玩的,

实现脚本自动部署docker

前言: 使用场景是 我这边的一个单体项目需要多一个多副本的部署方式,一直输入重复命令我实在是嫌烦了,使用写了一个脚本来一键更新部署上去。jar包都是我手动上传的,没有把包传入公网库里。 之所以记录就是因为遇到了几个没有遇到过的问题记录以下。 示例: #!/bin/bash # 进入指定文件夹打包成i

记一次 .NET 某工控MES程序 崩溃分析

一:背景 1.讲故事 前几天有位朋友找到我,说他的程序出现了偶发性崩溃,已经抓到了dump文件,Windows事件日志显示的崩溃点在 clr.dll 中,让我帮忙看下是怎么回事,那到底怎么回事呢? 上 WinDbg 说话。 二:WinDbg 分析 1. 崩溃点在哪里 如果是托管代码引发的崩溃,在线程

分享下最近基于Avalonia UI和MAUI写跨平台时间管理工具的体验

起因 几个月前,我在寻找一款时间管理软件,类似番茄时钟的工具,但是希望可以自定义时间。 需要自定义的场景 做雅思阅读,3篇文件需要严格控制时间分配,需要一个灵活的计时器 定期提醒,每30分钟需要喝水或者上个厕所或者摸一下鱼... 总结起来就是:专注一段时间,比如30分钟,然后休息10分钟,且没有杂七

从PDF到OFD,国产化浪潮下多种文档格式导出的完美解决方案

前言 近年来,中国在信息技术领域持续追求自主创新和供应链安全,伴随信创上升为国家战略,一些行业也开始明确要求文件导出的格式必须为 OFD 格式。OFD 格式目前在政府、金融、税务、教育、医疗等需要文件开放、共享和长期保存的行业中广泛应用。这种趋势在未来几年内将进一步增强。 相较于 PDF,OFD 在

实战0-1,Java开发者也能看懂的大模型应用开发实践!!!

前言 在前几天的文章《续写AI技术新篇,融汇工程化实践》中,我分享说在RAG领域,很多都是工程上的实践,做AI大模型应用的开发其实Java也能写,那么本文就一个Java开发者的立场,构建实现一个最基础的大模型应用系统。 而大模型应用系统其实在目前阶段,可能应用最广的还是RAG领域,因此,本文也是通过

LLM探索:GPT类模型的几个常用参数 Top-k, Top-p, Temperature

## 前言 上一篇文章介绍了几个开源LLM的环境搭建和本地部署,在使用ChatGPT接口或者自己本地部署的LLM大模型的时候,经常会遇到这几个参数,本文简单介绍一下~ - temperature - top_p - top_k ### 关于LLM 上一篇也有介绍过,这次看到一个不错的图 >A rec

抢先看!美团、京东、360等大厂面试题解析,技术面试必备。

技术面试必备!美团、京东、360等大厂面试题详解,让你轻松应对各大公司面试挑战! 往期硬核面经 哦耶!冲进腾讯了! 牛逼!上岸腾讯互娱和腾讯TEG! 腾讯的面试,强度拉满! 前几篇文章分享了上岸腾讯的最新面经。 不少粉丝股东留言说别只发腾讯的啦,其他大厂的也安排一些吧,比如美团、360、京东的。 必

基于.NetCore开发博客项目 StarBlog - (20) 图片显示优化

## 前言 我的服务器带宽比较高,博客部署在上面访问的时候几乎没感觉有加载延迟,就没做图片这块的优化,不过最近有小伙伴说博客的图片加载比较慢,那就来把图片优化完善一下吧~ 目前有两个地方需要完善 - 图片瀑布流 - 图片缩略图 ## 图片瀑布流 关于瀑布流之前的文章有介绍: [基于.NetCore开