Vue实现封装一个切片上传组件

目录
  • 组件效果
    • 使用文档
  • 封装过程
    • 1. 文件切片
    • 2. 构造切片请求参数
    • 3. 控制分片请求的并发
  • 完整代码
  • 待完善

组件效果

单文件切片上传

多文件切片上传

组件使用案例

<template>
  <div id="app">
    <div class="upload-wrap">
      <UploadSlice
        :action="uploadInfoSlice.actionChunk"
        :headers="uploadInfoSlice.headers"
        :limit="uploadInfoSlice.limit"
        :accept="uploadInfoSlice.accept"
        :show-file-list="false"
        cancelable
        :on-success="handleSuccess"
        :on-remove="handleRemove"
        :on-cancel="handleCancel"
        :on-upload-pre="handleUploadPre"
        :on-upload-merge="handleUploadMerge"
        :on-form-data="genFormData"
      />
    </div>
  </div>
</template>

<script>
import UploadSlice from './components/UploadSlice.vue'
import { uploadPre, uploadMerge } from '@/api/upload-slice'

export default {
  name: 'App',
  components: {
    UploadSlice
  },
  data() {
    return {
      // 上传部分
      uploadInfoSlice: {
        actionChunk: process.env.VUE_APP_BASE_API + '/storage/file/v3/chunk', // 切片请求上传路径
        headers: { 'Authorization': 'Bearer XXX' }
      }
    }
  },
  methods: {
    // 分片预请求
    async handleUploadPre(file) {
      const form = new FormData()
      form.append('fileSource', 'APPLICATION')
      form.append('originFileName', file.name)
      let res = ''
      try {
        res = await uploadPre(form)
      } catch (error) {
        throw new Error(error)
      }
    },
    // 构造分片参数
    genFormData(chunks, uid) {
      const prepareId = this.getCurrentPrepareId(uid)
      return chunks.map(chunk => {
        const form = new FormData()
        form.append('chunk', chunk.file)
        form.append('uploadId', prepareId)
        form.append('partNumber', chunk.index)
        return form
      })
    },
    // 合并请求
    async handleUploadMerge(file, uid) {
      const prepareId = this.getCurrentPrepareId(uid)
      const form = new FormData()
      form.append('fileSource', 'APPLICATION')
      form.append('hash', prepareId)
      form.append('filename', file.name)
      // return 建议使用, 用于handleSuccess获取数据
      try {
        const res = await uploadMerge(form)
        return res
      } catch (error) {
        return error
      }
    },
    // 判断当前处理prepareId
    getCurrentPrepareId(uid) {
      for (const item of this.progressFileList) {
        if (item.uid === uid) {
          return item.prepareId
        }
      }
    }
  }
}
</script>

使用文档

Attribute

标红色部分为二次封装处理过的功能,其他为el-upload自带属性

参数 说明 类型 可选值 默认值 备注
action 必选参数,分片上传的地址,预请求和合并请求在组件外操作 String - -
headers 设置上传的请求头部 String - -
multiple 是否支持多选文件 boolean -
accept 可上传文件类型,多种类型用","分隔 (格式不符合自动提示) String - -
on-remove 文件列表移除文件时的钩子 function(file, fileList)
on-success 文件上传成功时的钩子 function(response, file, fileList)
on-error 文件上传失败时的钩子 function(err, file, fileList)
on-progress 文件上传时的钩子 function(event, file, fileList)
on-change 文件状态改变时的钩子,添加文件、上传成功和上传失败时都会被调用 function(file, fileList)
on-exceed 文件超出个数限制时的钩子 function(files, fileList)
list-type 文件列表的类型 string text/picture/picture-card text
show-file-list 是否显示已上传文件列表(文件分片上传时建议设置false,否则会有两个进度条) boolean true
file-list 上传的文件列表, 例如: [{name: 'food.jpg', url: 'xxx.cdn.com/xxx.jpg'}] array []
disabled 是否禁用 boolean false
cancelable 是否支持取消 boolean false
limit 最多允许上传个数(超出个数自动提示) number
size 限制大小 String
hideBtn 是否在上传过程中隐藏上传按钮 boolean false

Slot

插槽名 说明
trigger 触发文件选择框的内容
tip 提示说明文字
more-tips 在默认提示后补充说明

封装过程

切片上传组件是基于el-upload进行的二次封装,文章开头组件效果演示可以看到上传一个文件会发送三个请求:prepare,chunk, merge,也就是整个上传过程,主要分为三步:1.预请求 2.分片请求 3.合并请求,预请求和合并请求就是我们正常的http请求,主要处理的是分片请求,分片请求主要的步骤是:

  • 将文件切片
  • 构造切片请求参数
  • 控制分片请求的并发

1. 文件切片

el-upload上传后, 在on-change属性的回调里可以获取文件file,通过file.raw.slice对文件进行切片,目前的切片规则是:1.小于10M 固定一片 2.小于50M 文件10%为一片 3.大于50M 固定5M 一片(可以根据自己的需求进行修改)

genFileChunks(file) {
  const chunks = []
  let cur = 0
  // 小于10M 固定一片
  if (file.size < (10 * 1024 * 1024)) {
    chunks.push({
      index: cur,
      file: file.raw.slice(cur, file.size),
      originFilename: file.name
    })
    return chunks
  }
  // 小于50M 文件10%为一片
  if (file.size < (50 * 1024 * 1024)) {
    const chunkSize = parseInt(file.size * 0.1)
    while (cur < file.size) {
      chunks.push({
        index: cur,
        file: file.raw.slice(cur, cur + chunkSize),
        originFilename: file.name
      })
      cur += chunkSize
    }
    return chunks
  }
  // 大于50M 固定5M 一片
  const chunkSize = parseInt(5 * 1024 * 1024)
  while (cur < file.size) {
    chunks.push({
      index: cur,
      file: file.raw.slice(cur, cur + chunkSize),
      originFilename: file.name
    })
    cur += chunkSize
  }
  return chunks
},

一个32M的文件按照10%切一片,构造好的切片数据是这样的

2. 构造切片请求参数

切片请求不同业务的参数是变化的,所以参数部分可以抛出给父组件处理,增加组件的复用性

父组件

<template>
  <UploadSlice
    :action="uploadInfoSlice.actionChunk"
    :headers="uploadInfoSlice.headers"
    :on-form-data="genFormData"
  />
</template>

<script>
  methods: {
    // 构造分片参数
    genFormData(chunks, uid) {
      const prepareId = this.getCurrentPrepareId(uid)
      return chunks.map(chunk => {
        const form = new FormData()
        form.append('chunk', chunk.file)
        form.append('uploadId', prepareId)
        form.append('partNumber', chunk.index)
        return form
      })
    },
  },
</script>

子组件

<template>
  <el-upload
      action=""
      :accept="accept"
  >
</template>

<script>
  props: {
    onFormData: {
      type: Function,
      default: () => {}
    },
  },
  methods: {
    async uploadChunks(uid) {
      // 预请求
      // ---------------

      // 上传切片
      const requests = this._genRequest(this._genUploadData(uid), uid)
      // 控制并发
      await this.sendRequest(requests)

      // 合并请求
      // ---------------
    },

    // 构造分片参数
    _genUploadData(uid) {
      const chunks = this.getCurrentChunks(uid)
      return this.onFormData(chunks, uid)
    },

    // 生成调用请求:[Promise, Promise]
    _genRequest(uploadData, uid) {
      console.log('uploadData', uploadData)
      const file = this.getCurrentFile(uid)
      const chunks = this.getCurrentChunks(uid)
      return uploadData.map((form, index) => {
        const options = {
          headers: this.$attrs.headers,
          file: file,
          data: form,
          action: this.action,
          onProgress: progress => {
            chunks[index].progress = Number(
              ((progress.loaded / progress.total) * 100).toFixed(2)
            )
            this.handleProgress(progress, file, uid)
          }
        }
        return options
      })
    },
  },
</script>

3. 控制分片请求的并发

切片上传如果不控制并发,在分片很多时,就会同时发送很多个http请求,导致线程阻塞,影响页面其他请求的操作,所以控制并发是需要的。我设置的是最多允许3个并发请求。

    sendRequest(requests, limit = 3) {
      return new Promise((resolve, reject) => {
        const len = requests.length
        let counter = 0
        let isTips = false // 只提示一次失败
        let isStop = false // 如果一个片段失败超过三次 认为当前网洛有问题 停止全部上传
        const startRequest = async() => {
          if (isStop) return
          const task = requests.shift()
          if (task && task.file.status !== 'cancel') {
            // 利用try...catch捕获错误
            try {
              // 具体的接口  抽离出去了
              await ajax(task)
              if (counter === len - 1) { // 最后一个任务
                resolve()
              } else { // 否则接着执行
                counter++
                startRequest() // 启动下一个任务
              }
            } catch (error) {
              // 网络异常
              if (error === 'NETWORK_ERROR' && !isTips) {
                Message.error('网络异常,文件上传失败')
                this.upLoading = false
                this.preLoading = false
                isTips = true
                this.handleRemove('', [])
              }

              // 接口报错重试,限制为3次
              if (task.error < 3) {
                task.error++
                requests.unshift(task)
                startRequest()
              } else {
                isStop = true
                reject(error)
              }
            }
          }
        }
        // 启动任务
        while (limit > 0) {
          // 模拟不同大小启动
          setTimeout(() => {
            startRequest()
          }, Math.random() * 2000)
          limit--
        }
      })
    }
  }

完整代码

文章只整理了核心代码,对于格式数量限制、进度条处理、取消上传等操作也进行了封装,详细请看完整代码

https://github.com/Xmengling/el-upload-slice

待完善

  • 目前还没有做断点续传的处理
  • 可能还有一些异常边界情况没有考虑完整

到此这篇关于Vue实现封装一个切片上传组件的文章就介绍到这了,更多相关Vue封装切片上传组件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue项目使用Websocket大文件FileReader()切片上传实例

    目录 使用技术 upfile.js文件 新增需求:对上传文件流进行加密,并传给后端做验证 还是在upfile.js文件(也可以单独放一个文件) 大文件上传,本地1.3G文件不到一分钟上传完毕 使用技术 Vue框架 WebSocket双向传输 FileReader读取文件 封装的WebSocket请求文件上传方法,目前只支持单文件上传,有研究出来多文件上传,记得评论哦 upfile.js文件 //file.slice(起始字节,终止字节)与FileReader实现文件切片读取 function P

  • vue 文件切片上传的项目实现

    目录 流程简说 获取文件的 MD5 唯一标识码 文件切片 获取文件名 name 分片文件大小 chunkSize 文件切片 chunkList 列表 切片总数 chunks 切片大小 size 合并 在实际开发项目过程中有时候需要上传比较大的文件,然后呢,上传的时候相对来说就会慢一些,so,后台可能会要求前端进行文件切片上传,很简单哈,就是把比如说1个G的文件流切割成若干个小的文件流,然后分别请求接口传递这个小的文件流. 流程简说 实现文件切片导入,首先我们使用 elementUI 也好,原生的

  • Vue实现文件切片上传功能的示例代码

    目录 流程简说 获取文件的 MD5 唯一标识码 文件切片 获取文件名 name 分片文件大小 chunkSize 文件切片 chunkList 列表 切片总数 chunks 切片大小 size 合并 在实际开发项目过程中有时候需要上传比较大的文件,然后呢,上传的时候相对来说就会慢一些,so,后台可能会要求前端进行文件切片上传,很简单哈,就是把比如说1个G的文件流切割成若干个小的文件流,然后分别请求接口传递这个小的文件流. 流程简说 实现文件切片导入,首先我们使用 elementUI 也好,原生的

  • Vue实现封装一个切片上传组件

    目录 组件效果 使用文档 封装过程 1. 文件切片 2. 构造切片请求参数 3. 控制分片请求的并发 完整代码 待完善 组件效果 单文件切片上传 多文件切片上传 组件使用案例 <template> <div id="app"> <div class="upload-wrap"> <UploadSlice :action="uploadInfoSlice.actionChunk" :headers=&quo

  • 基于vue-upload-component封装一个图片上传组件的示例

    需求分析 业务要求,需要一个图片上传控件,需满足 多图上传 点击预览 图片前端压缩 支持初始化数据 相关功能及资源分析 基本功能 先到https://www.npmjs.com/search?q=vue+upload上搜索有关上传的控件,没有完全满足需求的组件,过滤后找到 vue-upload-component 组件,功能基本都有,自定义也比较灵活,就以以此进行二次开发. 预览 因为项目是基于 vant 做的,本身就提供了 ImagePreview 的预览组件,使用起来也简单,如果业务需求需要

  • Vue + Node.js + MongoDB图片上传组件实现图片预览和删除功能详解

    本文实例讲述了Vue + Node.js + MongoDB图片上传组件实现图片预览和删除功能.分享给大家供大家参考,具体如下: 公司要写一些为自身业务量身定制的的组件,要基于Vue,写完后扩展了一下功能,选择写图片上传是因为自己之前一直对这个功能比较迷糊,所以这次好好了解了一下.演示在网址打开后的show.gif中. 使用技术:Vue.js | node.js | express | MongoDB. github网址:https://github.com/neroneroffy/privat

  • vue tree封装一个可选的树组件方式

    目录 组件实现的基本功能 先看效果图 组件实现的基本功能 1,根据后端返回的数据格式,传入组件动态的渲染出当前角色有哪些权限(新建,修改) 2,适配有2级和只有一级多选的数据 3,有全选(√) ,全不选 ,部分已选(-)的3装状态,每一级都支持(用的iview2次封装) 4,改变之后返回当前选中的所有权限的id,用于提交 5,手风琴效果,小屏适配 先看效果图 有部分权限没打开 打开 小屏 权限数据结构,select_status=1表示选中 默认添加没有权限的初始数据结构 有些数据只有一级子菜单

  • 基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件功能

    1. 前言 之前公司要在管理系统中做一个全局上传插件,即切换各个页面的时候,上传界面还在并且上传不会受到影响,这在vue这种spa框架面前并不是什么难题.然而后端大佬说我们要实现分片上传.秒传以及断点续传的功能,听起来头都大了. 很久之前我写了一篇webuploader的文章,结果使用起来发现问题很多,且官方团队不再维护这个插件了, 经过多天调研及踩雷,最终决定基于vue-simple-uploader插件实现该功能,在项目中使用起来无痛且稳定. 如果你只是想实现基本的(非定制化的)上传功能,直

  • JavaScript大文件上传的处理方法之切片上传

    目录 前言 切片后上传 生成hash 文件秒传 暂停上传 中断请求示例 添加暂停上传功能 恢复上传 添加功能总结 前言 本篇介绍了切片上传的基本实现方式(前端),以及实现切片上传后的一些附加功能,切片上传原理较为简单,代码注释比较清晰就不多赘述了,后面的附加功能介绍了实现原理,并贴出了在原本代码上的改进方式.有什么错误希望大佬可以指出,感激不尽. 切片后上传 切片上传的原理较为简单,即获取文件后切片,切片后整理好每个切片的参数并发请求即可. 下面直接上代码: HTML <template> &

  • 封装一个Vue文件上传组件案例详情

    目录 前言 1. 子组件 2 父组件使用 3.效果 4.总结 前言 在面向特定用户的项目中,引 其他ui组件库导致打包体积过大,首屏加载缓慢,还需要根据UI设计师设计的样式,重写大量的样式覆盖引入的组件库的样式.因此尝试自己封装一个自己的组件,代码参考了好多前辈的文章 1. 子组件 <template> <div class="digital_upload"> <input style="display: none" @change=&

  • vue webuploader 文件上传组件开发

    最近项目中需要用到百度的webuploader大文件的分片上传,对接后端的fastdfs,于是着手写了这个文件上传的小插件,步骤很简单,但是其中猜到的坑也不少,详细如下: 一.封装组件 引入百度提供的webuploader.js.Uploader.swf css样式就直接写在组件里面了 <template> <div> <div id="list" class="uploader-list"></div> <di

  • Vue上传组件vue Simple Uploader的用法示例

    在日常开发中经常会遇到文件上传的需求,vue-simple-uploader 就是一个基于 simple-uploader.js 和 Vue 结合做的一个上传组件,自带 UI,可覆盖.自定义:先来张动图看看效果: 其主要特点就是: 支持文件.多文件.文件夹上传 支持拖拽文件.文件夹上传 统一对待文件和文件夹,方便操作管理 可暂停.继续上传 错误处理 支持"快传",通过文件判断服务端是否已存在从而实现"快传" 上传队列管理,支持最大并发上传 分块上传 支持进度.预估剩

随机推荐