vue中使用webuploader做断点续传


之前做的一个项目中,由于经常上传几百兆的压缩包,导致经常上传失败,所以就找了webuploader插件做了断点续传
断点续传除了需要前端分片,也需要后台去支持,所以做的时候做好对接协调

1、下载webuploader

npm i webuploader

2、导入并挂载到原型上

import WebUploader from 'webuploader';
Vue.prototype.WebUploader = WebUploader;
import "webuploader/dist/webuploader.css";

挂载到原型上之后就可以通过 this.WebUploader 去调用
之所以还要导入 webuploader.css 文件,是因为这样在初始化时,才会有正常的按钮样式,样式不符合需求也可以通过自己写样式去覆盖掉,否则初始化之后就只是一个type = file的input框,不方便调整样式

3、md5校验

上传一半取消了,再次上传,怎么接着上传?就需要进行md5校验,后台才知道原来这个文件之前已经上传过了,不需要重头上传
另外,md5的校验必须要写在初始化之前

// 断点续传的按钮初始化之前调用
webUploadBeforeInit(chunkSize) {
  this.WebUploader.Uploader.register({
    "before-send-file": "beforeSendFile",
    "before-send": "beforeSend",
  }, {
      // 时间点1:所有分块进行上传之前调用此函数
      beforeSendFile: (file) => {
        let deferred = this.WebUploader.Deferred();
        let Uploader = new this.WebUploader.Uploader()
        Uploader.md5File(file, 0, chunkSize).progress(percentage => {
            debugger;
            console.log("校验MD5中...")
        }).then(md5 => {
            file.md5 = md5;
            file.uid = this.WebUploader.Base.guid();
            // 进行md5判断
            this.axios({
              url: this.commonUrl + "/FileUploadController/checkFile",
              method: "get",
              params: {
                fileName: file.name,
                md5: file.md5,
              },
            }).then(res => {
                let status = res.code;
                deferred.resolve();
                switch (status) {
                  case 0: // 表示上传成功或者已经上传过了
                    // 忽略上传过程,直接标识上传成功;
                    Uploader.skipFile(file);
                    file.pass = true;
                    break;
                  case 16: // 部分已经上传到服务器了,但是差几个模块。
                    file.missChunks = res.data;
                    break;
                  default:
                    break;
                  // 另外我这里,12表示未上传,1表示添加失败,这里不需要做其他处理
                }
            }).catch((err) => {
              deferred.reject();
            });
        });
        return deferred.promise();
      },
      beforeSend: (block) => {
        let deferred = this.WebUploader.Deferred();
        // 当前未上传分块
        let missChunks = block.file.missChunks;
        // 当前分块
        let blockChunk = block.chunk;
        if (missChunks !== null && missChunks !== undefined &&missChunks !== "") {
          let flag = true;
          for (let i = 0; i < missChunks.length; i++) {
            if (blockChunk === parseInt(missChunks[i])) {
              // 存在还未上传的分块
              flag = false;
              break;
            }
          }
          if (flag) {
            deferred.reject();
          } else {
            deferred.resolve();
          }
        } else {
          deferred.resolve();
        }
        return deferred.promise();
      },
    }
  );
},

4、封装初始化方法

有关webuploader配置项、事件等,可详细参考 WebUploader API文档

webUploadResume(opt, evt){
  if(!(opt && this.isObj(opt))) return console.error("请传类型为对象的配置项opt,pick必传")
  // md5校验在初始化之前
  this.webUploadBeforeInit(opt.chunkSize || 10485760) 

  // 初始化
  let uploader = this.WebUploader.create({
    // 文件上传调的便是此接口
    server: this.commonUrl + "/FileUploadController/breakpointUpload",
    method: opt.method || "post",
    // 这个外部是必传,要选一个dom初始化,无法给默认值。
    // 可以直接给ID,如"#abc",可以是对象,如 
    //  { 
    //    id:"#abc", // 虽然名是id,但实际也可以是类名或标签名
    //    innerHTML:"按钮文字,可不传",
    //    multiple:true/false 是否多选,
    //  }
    pick: opt.pick, // 制定对那个dom进行初始化
    // 指定上传的哪些类型的文件
    // {
    //    title:"文字描述" ,
    //    extensions:"允许的文件后缀,不带点,多个用逗号分割,比如gif,jpg,jpeg,bmp,png",
    //    mimeTypes: "多个用逗号分割,如image/*或者.gif,.jpg,.bmp"
    // }
    accept: opt.accept || null,
    resize: opt.resize || false, // 不压缩img
    auto: opt.auto || true, // 是否开启自动上传
    threads: opt.threads || 1, // 上传并发数。允许同时最大上传进程数
    chunked: opt.chunked || true, // 是否分片上传
    chunkSize: opt.chunkSize || 10485760, // 分片大小,以B为单位,这里是10M = 10 * 1024 * 1024 B
    chunkRetry: opt.chunkRetry || 2, // 如果某个分片由于网络问题出错,允许自动重传多少次
    duplicate: opt.duplicate || true, // 重复上传,为了解决上传一个后若上传失败,需再次上传,会无反应
    formData: opt.formData || {}, // 文件上传请求的额外参数
    fileNumLimit: opt.fileNumLimit || undefined, // 验证文件总数量, 超出则不允许加入队列
    fileSizeLimit: opt.fileSizeLimit || undefined, // 验证文件总大小是否超出限制, 超出则不允许加入队列
    fileSingleSizeLimit: opt.fileSingleSizeLimit || undefined, // 验证单个文件大小是否超出限制, 超出则不允许加入队列
    // 主要看接口是否需要token,不需要的话,下方headers可以去掉
    headers:{
      token: sessionStorage.tkn
    }
  })

  // 绑定事件
  // beforeFileQueued, 当文件被加入队列之前触发
  // fileQueued, 当文件被加入队列以后触发
  // filesQueued, 当一批文件添加进队列以后触发
  // startUpload, 当开始上传流程时触发
  // stopUpload, 当开始上传流程暂停时触发
  // uploadBeforeSend, 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数
  // uploadProgress, 上传过程中触发,携带上传进度
  // uploadSuccess, 当文件上传成功时触发
  // uploadError, 当文件上传出错时触发
  // uploadComplete, 不管成功或者失败,文件上传完成时触发
  // uploadFinished, 当所有文件上传结束时触发
  // error, 当validate不通过时,会以派送错误事件的形式通知调用者
  // addFail, 当add接口失败后执行(非webuploader插件所带,为自定义)
  if(evt && this.isObj(evt)){
    for(let k in evt){
      if(k == "uploadError" || k == "uploadSuccess"){
        uploader.on(k, (file, resp) => {
          this.axios({
            url:this.commonUrl + "/FileUploadController/add",
            method:"post",
            data:{
              fileName: file.name, // 文件名
              suffix: file.ext, // 文件后缀
              uploadStatus: k == "uploadSuccess"?1:0,
            },
          }).then(res => {
            evt[k](res,file,resp)
          }).catch(err => {
            evt.addFail && evt.addFail(file, resp)
          })
        })
      }else if(k != "addFail"){
        uploader.on(k, evt[k])
      }
    }
  }else{
    console.error("断点续传事件evt未传或传的类型错误")
  }
},

5、调用封装的初始化方法进行初始化

<template>
  <div id="addFail"></div>
</template>

<script>
data(){
  return {}
},
mounted(){
  this.webUploaderInit()
},
methods:{
  webUploaderInit(){
    // 封装的方法也是挂载到原型上的,方便调用
    this.$tu.webUploadResume({
      pick: "#addFail",
      accept: {
        title: "rar,zip",
        extensions: "rar,zip",
        mimeTypes: ".rar,.zip",
      },
      fileSizeLimit: 1073741824, // 总大小不超过1G
    },{
      beforeFileQueued: this.beforeFileQueued,
      uploadProgress: this.uploadProgress,
      uploadSuccess: this.uploadSuccess,
      uploadError: this.uploadError,
      error: this.error,
      addFail: this.addFail,
    })
  },
  beforeFileQueued(file){
    // 注意区分beforeFileQueued和uploadBeforeSend
    // beforeFileQueued是选择文件时,即将加入要上传的文件队列但还没加入时
    // uploadBeforeSend是即将开始调用上传接口,但还没调用
  },
  uploadProgress(file, percentage){
    // percentag,即上传进度
    // 一般用法 percentag * 100 + "%" 或者 percentag * width + "px"
  },
  // 注意我这里uploadSuccess和uploadError跟其他事件注册不太一样,所以接受参数与api文档并不完全一致
  uploadSuccess(res,file,resp){……},
  uploadError(err,file,resp){……},
  error(type){
    // 当 type == "Q_EXCEED_SIZE_LIMIT",就意味着选中的文件总大小超过了设置的fileSizeLimit
    // 当 type == "Q_EXCEED_NUM_LIMIT",就意味着选中的文件数量超过了设置的fileNumLimit
    // 当 type == "Q_TYPE_DENIED",就意味着选中的文件不符合accept中设置的文件格式
    // 另外,如果设置了 fileSingleSizeLimit,即使不符合也不会进入到这里
    // 因为如果选择了A文件超过,B文件未超,那么会将A文件自动过滤掉,只将B加入到文件队列
  },
  // 无论uploadSuccess还是uploadError都会调用add接口,那么add也可能会因为某些情况调用失败
  // 针对此情况,增加了自定义addFail方法,当add接口调用失败时会执行下面方法
  addFail(file, resp){……},
},
</script>

初始化成功之后,页面便能看到这样一个按钮
在这里插入图片描述
点击即可上传。若样式不符合需求,可以F12找到相关类名,进行样式覆盖
在这里插入图片描述

另外,在覆盖样式时,注意:

  • 你看到的“断点续传”的按钮样式是,类名为webuploader-pick的div
  • 而你点击时,之所以能打开选择文件的弹窗,并非是点击了这个div,实际是点击了下面的lable标签
  • 所以,覆写样式时,注意label标签要和div的位置保持一致,否则就会出现点击但没有任何反应,因为只是点了div,并没有点击到label

参考文档:
WebUploader API文档
vue中大文件上传webuploader前端用法
基于jquery使用webuploader示例

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
文件断点是在上文件过程,如果因为网络等原因导致上断,下次上可以从断的地方继,而不是重新上整个文件。在 Vue 实现上文件断点的步骤如下: 1. 安装 `axios` 和 `qs` 库 ```bash npm install axios qs --save ``` 2. 在组件引入 `axios` 和 `qs` 库,并定义上方法 ```vue <template> <div> <input type="file" ref="fileInput" @change="handleFileChange"> <button @click="uploadFile">上文件</button> </div> </template> <script> import axios from 'axios' import qs from 'qs' export default { data() { return { file: null, uploadUrl: '/api/upload' } }, methods: { handleFileChange(event) { this.file = event.target.files[0] }, uploadFile() { const chunkSize = 1024 * 1024 // 每片文件的大小,这里设置为 1MB const fileReader = new FileReader() const chunks = Math.ceil(this.file.size / chunkSize) // 计算文件需要被分成几片 let currentChunk = 0 // 当前上的片数 fileReader.onload = (event) => { const chunkData = event.target.result // 读取的文件内容 const formData = new FormData() formData.append('file', chunkData) formData.append('filename', this.file.name) formData.append('totalChunks', chunks) formData.append('currentChunk', currentChunk) axios.post(this.uploadUrl, qs.stringify(formData)).then((response) => { console.log('上成功', response) currentChunk++ if (currentChunk < chunks) { this.uploadFile() } }).catch((error) => { console.log('上失败', error) }) } // 上文件的具体实现 const start = currentChunk * chunkSize const end = Math.min(start + chunkSize, this.file.size) fileReader.readAsArrayBuffer(this.file.slice(start, end)) } } } </script> <style> /* 样式省略 */ </style> ``` 在上述示例,我们定义了一个上文件的方法 `uploadFile`,该方法会将文件分成多个片段,并逐个上。在上每个片段时,我们使用 `FileReader` 对象读取该片段的内容,然后将该内容作为表单数据使用 `axios` 发送到后端进行处理。如果上成功,就将当前片数加 1,并继下一片,直到上完整个文件。 需要注意的是,由于每个片段上时都是独立的,所以需要在每个请求携带文件名、总片数、当前片数等相关信息,以便后端进行文件的合并操作。 以上就是在 Vue 实现上文件断点的步骤。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值