在VUE中使用Plupload实现文件分片上传

公司需要开发一个管理系统,其中包含一个文件管理模块。文件模块主要包含公司的各个部门的文件归类和文件分配权限。文件上传中需要处理大文件的上传,防止网络超时失败,其中就涉及到文件分片上传。

效果

SDGIF_Rusult_1.gif

Plupload有以下功能和特点:

  • 拥有多种上传方式:HTML5、flash、silverlight以及传统的。Plupload会自动侦测当前的环境,选择最合适的上传方式,并且会优先使用HTML5的方式。所以你完全不用去操心当前的浏览器支持哪些上传方式,Plupload会自动为你选择最合适的方式。
  • 支持以拖拽的方式来选取要上传的文件。
  • 支持在前端压缩图片,即在图片文件还未上传之前就对它进行压缩。
  • 可以直接读取原生的文件数据,这样的好处就是例如可以在图片文件还未上传之前就能把它显示在页面上预览。
  • 支持把大文件切割成小片进行上传,因为有些浏览器对很大的文件比如几G的一些文件无法上传。

Plupload使用步骤

  • 引入plupload,可以到github上去下载,我使用的是npm install –save plupload。
  • 实例化一个plupload对象,传入一个配置参数对象进行各方面的配置。
  • 调用plupload实例对象的init()方法进行初始化。
  • 在plupload实例对象上注册各种你需要的事件。plupload从选取文件到文件上传完成这个过程中,会触发很多事件。我们可以通过这些事件来跟plupload进行交互。

相关文档推荐这个(解说的很详细了)www.cnblogs.com/2050/p/3913…

具体实现

分片的同时需要用到spark-md5,获取文件的md5

  • 安装 npm install –save plupload和npm install –save spark-md5
  • 引入:在需要的页面引入 import plupload from ‘plupload/js/plupload.full.min.js’ 和 import FileMd5 from ‘@/utils/file-md5.js’(其中file-md5.js是需要自己创建一个文件,相关代码在文章最后)
  • 由于传的参数已表单的形式传给后端,可以借助qs这个库来实现。

相关代码

选择框代码(支持拖拽相关API看文档)

视图层

<el-row type="flex">
  <el-tooltip class="item" effect="dark" :content="`支持扩展名:${acceptFiles}`" placement="bottom-start">
    <el-button id="browseButton" type="primary" icon="el-icon-upload2">
      请选择文件
    </el-button>
  </el-tooltip>
  <span class="tip-txt">或拖拽文件到下框</span>
</el-row>
复制代码

在data定义的变量

acceptFiles:'.txt,.png,.jpg,jpeg,.mp3,.mp4,.doc,.docx,.xls,.xlsx,.ppt,.pdf,.rar,.zip',
limit:20,//上传的最多数量
fileRawList:[],//插件返回来选择的文件列表
复制代码

plupload初始化和相关配置代码

流程和思路

  • 需要设置实例的变量可以调用setOption来修改。
  • 提示:移除指定文件记得调用实例的splice(实例的、实例的、实例的…..)。
  • 其中里面的逻辑需要和后端沟通好,那步需要干嘛,需要传什么数据。
  • 具体的流程就是,选择文件 。
  • 获取文件的md5,并且调用接口初始化文件获取到后端返回来的文件file_id追加到当前文件对象下面 。
  • 用户点击上传,当你配置好了插件一些参数,其实这一步就交给插件上传了 。
  • 每一个文件上传成功都会有一个回调,这时候就需要调后台接口告诉后端那些文件已经上传好了需要进行合并一下。这就是整个流程

pluploadInit() {
      var that = this

      var uploader = new plupload.Uploader({
        browse_button: 'browseButton',
        url:  '',//在BeforeUpload方法中动态配置
        chunk_size: '1MB',//这里需要配置和file-md5中的一个切片大小一至,1mb=1048576b,最好是配置固定1MB,不要做修改了
        headers: that.headers,
        multipart: true, // 为true时将以multipart/form-data
        max_retries: 1, // 当发生plupload.HTTP_ERROR错误时的重试次数,为0时表示不重试
        drop_element:'drop_id',
        headers:{
          'Authorization':'Bearer '+localStorage.getItem("pass_neutral_access_token")
        },
        filters: {
          mime_types: [
            { extensions: that.acceptFiles.replace(/\./g, '') }
          ],
          prevent_duplicates: true, // 不允许选取重复文件
          max_file_size: '1024mb' // 最大只能上传的文件
        },
        init: {
          BeforeUpload(up, file) {
            //重置上传路径,后端要求把文件id拼在路径上
            up.setOption('url',`${baseUrl['defaultBaseUrl']}file/upload/${file.file_id}`)
            // 上传时的附加参数,自定义参数,以键/值对的形式传入
            up.setOption('multipart_params', {
              'size': file.size,
              'md5': file.md5,
              type:2
            })
          },
          FileFiltered(up, files) {
            console.log('FileFiltered', up, files)
          },
          FilesAdded(up, files) {
            // console.warn("11",files)

            //添加文件并限制文件的数量
            //移除文件一定要调用实例的splice移除一些文件,要不然第二次就选择不了这个文件是没有回调触发不了方法
            if(that.limit > that.fileRawList.length){
              // 选择中的文件是否超过限制
              if(files.length>that.limit){
                up.splice(that.limit,files.length-that.limit)//移除超出的部分
              }
              that.fileRawList.push(...files.slice(0,that.limit - that.fileRawList.length))//缺多少个加多少个
              files = that.fileRawList
            }else{
              that.$message({
                message: `最多能选择${that.limit}个文件`,
                type: 'error'
              });
              up.splice(0,files.length)//全部移除
              return
            }
            //以下操作都是异步的,每一步都需要上一步全部结束才能执行下一步,所以用到Promise.all
            let queueList = []//promise队列,遍历请求结束后再处理文件
            let md5Queue = []//md5的队列
            files.forEach(async (f) => {
              // 添加md5队列
              md5Queue.push(
                new Promise((res,rej)=>{
                  f.status = -1
                  FileMd5(f.getNative(), (e, md5) => {
                    f['md5'] = md5
                    f.status = 1
                    //等解析成功再处理promise状态
                    res()
                  })
                })
              )
            })
            // 等待获取md5队列执行完毕
            Promise.all(md5Queue).then(()=>{

              files.map(async (item)=>{
                // 添加队列
                queueList.push(
                  new Promise((resole,reject)=>{
                    that.fetch('文件初始化接口,这里后端会反回来文件file_id',{
                      headers:{
                        'Content-Type':'application/x-www-form-urlencoded'
                      },
                      data:qs.stringify(
                        {
                          dirId:that.currentFolderObj.file_id,
                          name:item.name,
                          size:item.size,
                          md5:item.md5,
                          chunks:Math.ceil(item.size / 1048576),
                        }
                      )
                    }).then(res=>{
                      resole()
                      item['file_id'] = res.file_id//对象修改
                    })
                  })
                )
              })
              //所有文件初始化promise成功之后,执行下一步
              Promise.all(queueList).then(()=>{
                //在这里才把上传文件的按钮给展示出来,具体什么操作自己定
              })

            })
          },
          FilesRemoved(upp, files) {
            
          },
          ChunkUploaded(uploader,file,responseObject){
            
          },
          FileUploaded(up, file, info) {
            let res = JSON.parse(info.response)
            if (res.code === 1) {
            /**
               fetch是封装的网络请求工具,当一个文件被切成了5片,这5片都上传成功之后就回调到这里,需要告诉后端那些文件上传成功,让后端合并这些文件
            */
              that.fetch('换成你的接口',{
                method:'put',
                headers:{
                  'Content-Type':'application/x-www-form-urlencoded'
                },
                data:qs.stringify(
                  {
                    fileId:file.file_id
                  }
                )
              })
              that.up.refresh()
            }
          },
          UploadComplete(up, files) {
            
          },
          Error(up, args) {
            console.warn("错误提示",args)
            if(args.code == -600){
              that.$message({
                type:'error',
                message:'文件内容不能为空'
              })
            }
          }
        }
      })
      uploader.init()
      this.up = uploader
    },
复制代码

项目快结尾的时候,给用户一些更好的交互就用transition,增加了回收站和文件切换的一些动画(加了一些过渡效果那种感觉就上来了)

SDGIF_Rusult2.gif

这个模块到这里基本也就结束了,不过后续可能会增加文件的暂停和续传功能

/utils/file-md5.js文件的代码

'use strict'
import SparkMD5 from 'spark-md5'
export default function(file, callback) {
  var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
  var chunkSize = 1048576 // Read in chunks of 1MB
  var chunks = Math.ceil(file.size / chunkSize)
  var currentChunk = 0
  var spark = new SparkMD5.ArrayBuffer()
  var fileReader = new FileReader()

  fileReader.onload = function(e) {
    // console.log('read chunk nr', currentChunk + 1, 'of', chunks)
    spark.append(e.target.result) // Append array buffer
    currentChunk++

    if (currentChunk < chunks) {
      loadNext()
    } else {
      callback(null, spark.end())
      // console.log('finished loading')
    }
  }

  fileReader.onerror = function() {
    callback('oops, something went wrong.')
  }

  function loadNext() {
    var start = currentChunk * chunkSize
    var end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize

    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end))
  }

  loadNext()
}
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享