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

7次阅读

共计 7967 个字符,预计需要花费 20 分钟才能阅读完成。

背景

前端文件上传原本是一个惯例交互操作,没什么特殊性可言,然而最近在做文件上传,须要实现截图粘贴上传,去找了下有没有什么好用的组件,网上提供的办法有,然而没找残缺的组件来反对 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 上传的根底上增加了粘贴上传交互, 该组件的实现重在交互方式,各个款式格调通过插槽自定义。

作者:京东物流 刘海鼎

起源:京东云开发者社区

正文完
 0