PC端大文件上传:分片上传、断点续传、上传进度条

最近写的项目需要支持多个大文件上传、显示上传进度条、所有文件上传状态、上传失败重新上传。前面三个还好实现,但是由于目前后端接口未实现断点续传,所以现在上传失败,点击重新上传是从头开始上传。这个功能还待定,但是对于前端来说实现难度不大。效果图如下:

实现思路

FileReader

FileReader 对象允许我们读取存储在用户计算机上文件的内容,使用 File 或者**Blob** 对象指定要读取的文件或数据。大文件上传,在文件成功读取时 load 之后,我们只要知道这个文件大小是多少,按量分割,顺序上传,每次只上传当前这个区间单位的文件。

问题 复制代码
分片上传有一个很重要的点就是当前上传的分片是否已经上传过?
如何判断当前上传的是第几片?
多个上传如何区别?
上传之前校验文件格式
javascript 复制代码
async beforeUploadFile(file, val) {
      await reportApi.abortMultipartUpload({ fileId: this.currentItem.fileId });
      const ext = file.fileSuffixName || extendForFile(file);
      if (
        [
          "doc",
          "docx",
          "pdf",
          "xls",
          "xlsx",
          "zip",
          "mp4",
          "pptx",
          "ppt",
          "mp3",
        ].some((item) => item === ext)
      ) {
        this.requestName = reportApi.ossFileUploadFile;
        if (file.size / 1024 / 1024 / 1024 > 100) {
          this.$message.error("文件大小超出100M,请重新上传!");
          return false;
        }
      } else if (
        ["png", "bmp", "jpg", "jpeg", "gif", "tif"].some((item) => item === ext)
      ) {
        this.requestName = reportApi.ossFileUploadFile;
        if (file.size / 1024 / 1024 / 1024 > 40) {
          this.$message.error("图片大小超出40M,请重新上传!");
          return false;
        }
      } else {
        this.$message.error("暂不支持文件上传格式,请重新上传!");
        return false;
      }
      this.selectOptions = val;

      console.log(file, "this.$refs.cropper.showModal");
    }
上传文件解析

文件load成功后,需要先初始化文件,获取文件的切片大小、切片数量以及一些文件的唯一信息。虽然支持上传多个文件,但是上传还是按照顺序一个一个的,fileListData在每次初始化都需要记录当前文件的所有信息。上传进度只需要在初始化成功/失败、分片上传成功/失败,上传完成这5种情况下,记录当前上传分片数量。 在初始化成功后,根据当前上传分片数量和总数量大小比对,来判断是否要执行合并操作。

javascript 复制代码
// 获取文件后缀
extendForFile(file){
  var imgName = file.name
  var idx = imgName.lastIndexOf('.')
  var ext = ''
  if (idx !== -1) {
    ext = imgName.substr(idx + 1).toUpperCase()
    ext = ext.toLowerCase()
  }
  return ext
}
javascript 复制代码
 uploadVideo({ file, onSuccess, onError }, prop) {
      console.log(111111, this.fileListData);
      const that = this;
      let fileRederInstance = new FileReader();
      fileRederInstance.readAsBinaryString(file);
      fileRederInstance.addEventListener("load", (e) => {
        let fileBolb = e.target.result;
        const fileMD5 = MD5(fileBolb); // 文件秒传的关键,文件生成的md5是唯一的,如果判断上传的文件和数据库原来上传过的文件md5值相同就可以直接吐出原来的文件地址,达到文件秒传的效果
        // 1、初始化
        const ext = file.fileSuffixName || extendForFile(file);
        this.fileListData.push({
          fileName: file.name,
          fileSize: file.size,
          fileSuffixName: ext,
          fileError: false,
          size: file.size,
          video: file,
          chunkCount: Math.ceil(file.size / this.chunkSize), // 切片数量
          fileMD5,
        });
        reportApi
          .shardingInitiate({
            fileName: file.name || "111",
            md5: fileMD5,
          })
          .then((res) => {
            this.fileListData.forEach((f) => {
              if (f.fileName == file.name) {
                f.size = file.size;
                f.fileId = res.result.fileId;
                f.uploadId = res.result.uploadId;
                f.videoName = res.result.fileName;
                f.fileDirVideoKey = res.result.fileDirVideoKey;
              }
            });
            //初始化文件之后,需要触发父组件,开始记录上传状态和上传进度
            this.onFinish(this.fileListData)
            this.readChunkMD5(0, file.name);
          })
          .catch((error) => {
            console.log(error, "09----");
            this.fileListData.forEach((f) => {
              if (f.fileName == file.name) {
                f.fileHttpUrl = "";
                f.fileUrl = "";
                f.fileError = true;
                f.fileResetUpload = true;
              }
            });
            //初始化文件失败,需要触发父组件,记录上传状态
            this.onFinish(this.fileListData)
          });
      });
    },
javascript 复制代码
getChunkInfo(currentChunk, curFileName) {
  const cur = this.fileListData[0];
  console.log(cur);
  let start = currentChunk * this.chunkSize; // 起始位置
  let end = Math.min(cur.size, start + this.chunkSize); // 结束位置
  let chunk = cur.video.slice(start, end); // 切片内容
  console.log(start, end, chunk);
  console.log("0." + start, "start222");
  // let that = this;
  // let storeAs = this.videoName;
  // that.$message.loading(`${storeAs}正在上传,请稍后...${start}%`,0);
  return { start, end, chunk };
},
javascript 复制代码
readChunkMD5(num, curFileName) {
  const { chunk } = this.getChunkInfo(num, curFileName);
  const cur = this.fileListData.find((f) => f.fileName == curFileName);
  const curFileId = this.fileListData.find(
    (f) => f.fileName == curFileName
  ).fileId;
  console.log("readChunkMD5:", num)
  this.onProgress({
    currentChunk: num,
    chunks: cur.chunkCount,
    curFileId,
  })
  // 如果当前上传的文件小于总数量就执行上传操作,如果大于当前数量走合并操作
  if (num < cur.chunkCount) {
    let fetchForm = new FormData();
    fetchForm.append("chunk", num + 1); // 当前分片数
    fetchForm.append("chunks", cur.chunkCount);
    fetchForm.append("file", chunk); // 当前分片文件内容
    fetchForm.append("md5", cur.fileMD5);
    fetchForm.append("objectName", cur.videoName);
    fetchForm.append("uploadId", cur.uploadId);
    fetchForm.append("curPartSize", chunk.size);
    fetchForm.append("fileDirVideoKey", cur.fileDirVideoKey);
    fetchForm.append("fileId", cur.fileId);
    fetchForm.append("fileSize", cur.size);
    reportApi
      .shardingFile(fetchForm)
      .then((res) => {
        this.loadProgress++;
        num = num + 1;
        this.readChunkMD5(num, curFileName);
        this.onFinish(this.fileListData)
      })
      .catch((error) => {
        console.log(error, "09----");
        this.fileListData.forEach((f, i) => {
          if (i == 0) {
            f.fileHttpUrl = "";
            f.fileUrl = "";
            f.fileError = true;
            f.fileResetUpload = true;
          }
        });
        //分片上传成功后,需要触发父组件,开始记录上传状态和上传进度
        this.onFinish(this.fileListData);
        this.$forceUpdate();
      });
  } else {
    reportApi
      .complete({
        uploadId: cur.uploadId,
        objectName: cur.videoName,
        fileDirVideoKey: cur.fileDirVideoKey,
        fileSize: cur.size,
      })
      .then((res) => {
        this.fileListData.forEach((f, i) => {
          if (i == 0) {
            f.fileHttpUrl = res.result.fileHttp;
            f.fileUrl = res.result.fileUrl;
            f.fileError = false;
            f.fileResetUpload = false;
            f.uploadTime = moment().format("YYYY-MM-DD HH:mm:ss");
          }
        });
        //上传成功后,需要触发父组件,开始记录上传状态和上传进度
        this.onFinish(this.fileListData)
        this.$emit("updateRender");
      })
      .catch((error) => {
        console.log(error, "09----");
        this.fileListData.forEach((f, i) => {
          if (i == 0) {
            f.fileHttpUrl = "";
            f.fileUrl = "";
            f.fileError = true;
            f.fileResetUpload = true;
          }
        });
        //分片上传失败后,需要触发父组件,开始记录上传状态和上传进度
        this.onFinish(this.fileListData)
        this.$emit("updateRender");
      });
  }
}
javascript 复制代码
<a-upload
  :accept="uploadfileType"
  name="file"
  :multiple="false"
  :custom-request="uploadVideo"
  :show-upload-list="false"
  :before-upload="beforeUploadFile"
>
  <slot name="content">
    <div style="display:flex:align-items:center">
      <span>上传失败,</span>
      <span class="link">重新上传</span>
      <img
        :src="undo_blue"
        width="12"
        height="12"
      >
    </div>
  </slot>
</a-upload>
<video
  ref="video"
  v-show="false"
/>
</div>

完整代码也贴上去了,等后端断点续存的接口出来。就可以在上传失败的情况下,根据当前上传到第几片,当前文件的uploadId继续上传。失败之前的文件数据只要不刷新页面是会继续保留的,所以这功能问题不大。 今日的分享完毕,下次在来~~

相关推荐
snow_wind_rain4 分钟前
网页作业9
前端·css·css3
zhzhzhen_4 分钟前
如何在项目中用elementui实现分页器功能
前端·javascript·elementui
向明天乄6 分钟前
elementui el-table中给表头 el-table-column 加一个鼠标移入提示说明
前端·javascript·vue.js·elementui
Akiiiira12 分钟前
【网页设计】CSS3 进阶(动画篇)
前端·javascript·css3
Easonmax22 分钟前
【JavaScript】JavaScript开篇基础(6)
开发语言·javascript·ecmascript
走进大山的孩子1 小时前
本地项目运行提示跨域问题
vue.js
蒜蓉大猩猩1 小时前
Vue3.js - 一文看懂Vuex
前端·javascript·vue.js·前端框架·html5
excel1 小时前
three EdgeSplitModifier
前端
前端Hardy2 小时前
探索 HTML 和 CSS 实现的 3D旋转相册
前端·css·3d·html·css3
小白讲前端2 小时前
酷炫的鼠标移入效果(附源码!!)
前端·javascript·css·html·css3