0


前端大文件分片上传 进度条展示 上传暂停、开始、取消

在这里插入图片描述
实现的效果:
1、多个大文件(支持10个G以上)分片上传
2、进度条展示进度
3、控制文件上传暂停和取消
实现关键点:
1、文件预处理(md5计算、请求和进度处理等)
2、分片上传的流程(查询已上传分片、文件合并等)
3、文件的暂停、开始、取消

文件预处理

首先使用file类型的input框获取文件,对文件进行深拷贝,再清空input的value值(防止input的change事件不被触发)。

let files = e.target.files;
 let copiedFiles = []
 for(let i = 0; i < files.length; i++){
   copiedFiles.push(new File([files[i]], files[i].name, { type: files[i].type }))
 }
 this.$emit("bigFileChange", copiedFiles);
 this.$refs.input.value = null;

对文件进行处理,核心思想是为每个文件构造一个对象,封装该文件的md5信息(用于标识该文件)和进度、请求、取消标识(用于文件的暂停)等信息。

async bigFileChange(files) {
      // 新增的文件
      let newFiles = [];
      // 筛选出检验合格的文件
      let okFileIndexs = this.checkRules(files);
      for (let i = 0; i < okFileIndexs.length; i++) {
        let fileIndex = okFileIndexs[i];
        // 为文件构建对象
        let fileObj = {};
        fileObj.md5 = await this.firstChunkMd5(
          files[fileIndex],
          this.chunkSize
        );
        fileObj.progress = 0;
        fileObj.isPaused = false;
        // 查询该文件合并进度的轮询计时器
        fileObj.mergeTimer = null;
        fileObj.status = "上传中";
        fileObj.newSize = this.getFileSize(files[fileIndex].size);
        fileObj.file = files[fileIndex];
        fileObj.category = this.category;
        // 该文件的所有请求
        fileObj.requests = [];
        // 该文件的取消标识
        fileObj.cancelTokens = [];
        // 将构建的对象记录下来
        newFiles.push(fileObj);
        this.bigFileList.push(fileObj);
      }
      for (const newFileObj of newFiles) {
        this.uploadBigAttachment(newFileObj);
      }
}

计算md5值采用的是SparkMD5,为了减少计算量,采用文件的第一块的md5作为整个文件的md5。

firstChunkMd5(file, chunkSize) {
      return new Promise((resolve, reject) => {
        const fileReader = new FileReader();
        const spark = new SparkMD5.ArrayBuffer();

        const chunk = file.slice(0, chunkSize);

        fileReader.onload = function (event) {
          spark.append(event.target.result);

          const md5 = spark.end();
          resolve(md5);
        };

        fileReader.onerror = function () {
          reject(new Error("File read error."));
        };

        fileReader.readAsArrayBuffer(chunk);
      });
}

在界面上为每个文件创建进度条。

<div
  class="bigFileProgress"
  v-for="(f, index) in bigFileList"
  :key="index"
>
  <div class="bigFileTop">
    <Tooltip :content="f.file.name" placement="top">
      <div class="bigFileName">
        {{ f.file.name }}
      </div>
    </Tooltip>
    <div class="bigFileSize" style="width: 20%">
      {{ f.newSize }}
    </div>
    <!-- <div class="bigFileUploadProgress" style="width: 5%">
      {{ f.progress }}%
    </div> -->
    <div
      class="bigFileStatus"
      :style="{
        width: '20%',
        color: f.status == '上传失败' ? 'red' : 'black',
      }"
    >
      {{ f.status }}
    </div>
    <div
      class="bigFileActions"
      style="position: relative; width: 20%"
    >
      <Button
        @click="pauseBigFile(f)"
        type="primary"
        size="small"
        style="position: absolute"
        v-if="!f.isPaused"
        :disabled="f.progress == 100"
      >
        暂停
      </Button>
      <Button
        @click="restartBigFile(f)"
        type="primary"
        size="small"
        :disabled="f.progress == 100"
        :style="{ opacity: f.progress == 100 ? 0 : 1 }"
      >
        开始
      </Button>
      <Button
        @click="cancelUpload(f)"
        type="error"
        size="small"
        style="margin-left: 10px"
      >
        取消
      </Button>
    </div>
  </div>
  <div class="progress">
    <Progress :percent="f.progress" :stroke-width="5"></Progress>
  </div>
</div>

分片上传

首先查询文件已经上传的分片数,如果全部上传了,进度立即更新为100%(秒传),如果没完全上传,则上传未上传的分片并实时更新进度,各分片上传完毕后请求合并,采用轮询检测合并进度。

this.checkFile(fileObj, chunks)
          .then(async (res) => {
            console.log(res);
            if (res.data.data.completed) {
              // 如果当前文件已经上传成功 则无需继续上传
              fileObj.progress = 100;
              fileObj.status = "上传成功";
              // 为成功上传的附件添加id
              if (res.data.data.attachmentId) {
                fileObj.attachmentId = res.data.data.attachmentId;
              }
              this.$forceUpdate(); // 强制重新渲染组件
              this.$emit("fileUpdate");
            } else {
              // 当前文件没有上传成功
              // 获取已经上传的分片数组
              let uploadedChunks = res.data.data.uploadChunks;
              // 获取当前的进度
              let newProgress = parseInt(
                (uploadedChunks.length / chunks) * 100
              );
              fileObj.progress = newProgress;
              this.$forceUpdate(); // 强制重新渲染组件
              // 文件均已上传完 但还未合并
              if (res.data.data.canMerge || uploadedChunks.length == chunks) {
                this.mergeBigFile(fileObj)
                  .then((res) => {
                    fileObj.status = "合并中";
                    this.$forceUpdate(); // 强制重新渲染组件
                    // 先清除该文件上次的合并计时器
                    if (fileObj.mergeTimer) {
                      clearInterval(fileObj.mergeTimer);
                    }
                    fileObj.mergeTimer = setInterval(() => {
                      this.getMergeProcess(fileObj).then((res) => {
                        if (res.data.data.completed) {
                          fileObj.status = "上传成功";
                          // 为成功上传的附件添加id
                          if (res.data.data.attachmentId) {
                            fileObj.attachmentId = res.data.data.attachmentId;
                          }
                          this.$forceUpdate(); // 强制重新渲染组件
                          // 合并完成
                          fileObj.requests = [];
                          fileObj.cancelTokens = [];
                          clearInterval(fileObj.mergeTimer);
                          this.$emit("fileUpdate");
                        }
                      });
                    }, 2000);
                  })
                  .catch((error) => {
                    console.error("上传失败:", error);
                    fileObj.status = "上传失败";
                    if (fileObj.mergeTimer) {
                      clearInterval(fileObj.mergeTimer);
                    }
                    this.$forceUpdate();
                  });
              } else {
                // 文件还没上传完
                let currentChunk = 0;
                // 上传没有上传的部分
                while (currentChunk < chunks) {
                  if (!uploadedChunks.includes(currentChunk)) {
                    const start = currentChunk * this.chunkSize;
                    const end = Math.min(
                      start + this.chunkSize,
                      fileObj.file.size
                    );
                    const chunk = fileObj.file.slice(start, end);
                    // 构造该块的上传请求
                    const formData = new FormData();
                    let fileType = fileObj.file.name.substring(
                      fileObj.file.name.lastIndexOf(".") + 1
                    );
                    formData.append("fileName", fileObj.file.name);
                    formData.append("fileType", fileType);
                    formData.append("md5", fileObj.md5);
                    formData.append("category", fileObj.category);
                    formData.append("ownerType", "bill");
                    formData.append("ownerId", this.billidParam);
                    formData.append("chunkNum", currentChunk);
                    formData.append("chunkSize", this.chunkSize);
                    formData.append("chunkTotal", chunks);
                    formData.append("file", chunk);
                    // 该块的取消令牌
                    let cancelToken = axios.CancelToken.source();
                    fileObj.cancelTokens.push(cancelToken);
                    let request = GMS.$http.post(
                      "/bsp/bjgzw/attachment/uploadChunk",
                      formData,
                      {
                        headers: this.headers,
                        cancelToken: cancelToken.token,
                      }
                    );
                    fileObj.requests.push(request);
                  }
                  currentChunk++;
                }
                // 当前文件下的所有请求
                for (let i = 0; i < fileObj.requests.length; i++) {
                  fileObj.requests[i]
                    .then((res) => {
                      console.log(res);
                      // 进行进度控制
                      let progress = parseInt(
                        (res.data.data.uploadChunks.length / chunks) * 100
                      );
                      if (progress > fileObj.progress) {
                        fileObj.progress = progress;
                        this.$forceUpdate(); // 强制重新渲染组件
                      }
                      // 进行文件的合并控制
                      if (res.data.data.canMerge) {
                        // 文件可以合并了
                        this.mergeBigFile(fileObj)
                          .then((res) => {
                            fileObj.status = "合并中";
                            this.$forceUpdate(); // 强制重新渲染组件
                            // 先清除该文件上次的合并计时器
                            if (fileObj.mergeTimer) {
                              clearInterval(fileObj.mergeTimer);
                            }
                            fileObj.mergeTimer = setInterval(() => {
                              this.getMergeProcess(fileObj).then((res) => {
                                if (res.data.data.completed) {
                                  fileObj.status = "上传成功";
                                  // 为成功上传的附件添加id
                                  if (res.data.data.attachmentId) {
                                    fileObj.attachmentId =
                                      res.data.data.attachmentId;
                                  }
                                  this.$forceUpdate(); // 强制重新渲染组件
                                  // 合并完成
                                  fileObj.requests = [];
                                  fileObj.cancelTokens = [];
                                  clearInterval(fileObj.mergeTimer);
                                  this.$emit("fileUpdate");
                                }
                              });
                            }, 2000);
                          })
                          .catch((error) => {
                            console.error("上传失败:", error);
                            fileObj.status = "上传失败";
                            if (fileObj.mergeTimer) {
                              clearInterval(fileObj.mergeTimer);
                            }
                            this.$forceUpdate();
                          });
                      }
                    })
                    .catch((error) => {
                      if (axios.isCancel(error)) {
                        console.log("上传已暂停或取消");
                      } else {
                        console.error("上传失败:", error);
                        fileObj.status = "上传失败";
                        this.$forceUpdate();
                      }
                      fileObj.requests = [];
                      fileObj.cancelTokens = [];
                    });
                }
              }
            }
          })
          .catch((error) => {
            console.error("Error:", error);
            fileObj.status = "上传失败";
            this.$forceUpdate();
});

上传暂停、开始、取消

暂停上传即根据取消标识将当前文件的所有请求进行取消。

pauseBigFile(fileObj) {
      fileObj.cancelTokens.forEach((item) => {
        item.cancel("上传暂停");
      });
      fileObj.isPaused = true;
      fileObj.status = "已暂停";
      this.$forceUpdate(); // 强制重新渲染组件
    }

开始上传即对文件重新进行上传处理。

restartBigFile(fileObj) {
      fileObj.isPaused = false;
      fileObj.status = "上传中";
      this.$forceUpdate(); // 强制重新渲染组件
      fileObj.requests = [];
      fileObj.cancelTokens = [];

      this.uploadBigAttachment(fileObj);
    }

取消上传是将文件所有请求取消并发送请求删除文件,这里不加赘述。

标签: 前端 javascript vue

本文转载自: https://blog.csdn.net/weixin_48701221/article/details/133741788
版权归原作者 藤井粟 所有, 如有侵权,请联系我们删除。

“前端大文件分片上传 进度条展示 上传暂停、开始、取消”的评论:

还没有评论