vue2.x+Element 实现大文件切片上传

功能要求

  1. 支持切片上传
  2. 支持并发上传
  3. 支持显示上传进度
<template>
  <div>
    <el-row>
      <el-upload
        :http-request="upload"
        :action="uploadUrl"
        :data="uploadData"
        :before-remove="beforeRemove"
        name="file"
      >
        <el-button size="small" type="primary">点击上传</el-button>
      </el-upload>
    </el-row>
    <el-row style="margin-top: 50px">
      <el-col :span="6">
        <el-progress
          :text-inside="true"
          :stroke-width="18"
          :percentage="value"
        ></el-progress>
      </el-col>
    </el-row>
  </div>
</template>  

<script>
export default {
  data() {
    return {
      value: 0,
      uploadData: {
        //这里面放额外携带的参数
      },
      //文件上传的路径
      uploadUrl: "http://127.0.0.1:8980/api/platform/files/mapFile" //文件上传的路径
    };
  },
  methods: {
    //错误信息
    getError(action, option, xhr) {
      const err = new Error("上传错误");
      err.status = xhr.status;
      err.method = "post";
      err.url = action;
      return err;
    },

    // 分片上传的自定义请求,以下请求会覆盖element的默认上传行为
    upload(option) {
      if (typeof XMLHttpRequest === "undefined") {
        return;
      }
      const fileReader = new FileReader(); // 文件读取类
      const action = option.action; // 文件上传上传路径
      const chunkSize = 1024 * 1024 * 1; // 单个分片大小,这里测试用1m
      const optionFile = option.file; // 需要分片的文件
      let fileChunkedList = []; // 文件分片完成之后的数组
      const percentage = []; // 文件上传进度的数组,单项就是一个分片的进度

      // 文件开始分片,push到fileChunkedList数组中
      for (let i = 0; i < optionFile.size; i = i + chunkSize) {
        const tmp = optionFile.slice(
          i,
          Math.min(i + chunkSize, optionFile.size)
        );
        if (i === 0) {
          fileReader.readAsArrayBuffer(tmp);
        }
        fileChunkedList.push(tmp);
      }

      // 在文件读取完毕之后,开始计算文件
      fileReader.onload = async (e) => {
        // 将fileChunkedList转成FormData对象,并加入上传时需要的数据
        fileChunkedList = fileChunkedList.map((item, index) => {
          const formData = new FormData();
          if (option.data) {
            Object.keys(option.data).forEach((key) => {
              formData.append(key, option.data[key]);
            });
            // 看后端需要哪些,就传哪些,也可以自己追加额外参数
            formData.append(option.filename, item, option.file.name); // 文件
            formData.append("chunkNumber", index + 1); // 当前文件数
            formData.append("filename", option.file.name); // 文件名
            formData.append("totalChunks", fileChunkedList.length); // 总块数
          }
          return { formData: formData, index: index };
        });

        // 创建队列上传任务,limit是上传并发数
        function sendRequest(chunks, limit = 2) {
          return new Promise((resolve, reject) => {
            const len = chunks.length;
            let counter = 0;
            let isStop = false;
            const start = async () => {
              if (isStop) {
                return;
              }
              const item = chunks.shift();
              if (item) {
                const xhr = new XMLHttpRequest();
                const index = item.index;
                // 分片上传失败回调
                xhr.onerror = function error(e) {
                  isStop = true;
                  reject(e);
                };
                // 分片上传成功回调
                xhr.onload = function onload() {
                  if (xhr.status !== 200) {
                    isStop = true;
                    reject(getError(action, option, xhr));
                  }
                  if (counter === len - 1) {
                    // 最后一个上传完成
                    resolve();
                  } else {
                    counter++;
                    start();
                  }
                };
                // 分片上传中回调
                if (xhr.upload) {
                  xhr.upload.onprogress = (e) => {
                    percentage[index] = e.loaded;
                    updataPercentage(e);
                  };
                }
                xhr.open("post", action, true);
                if (option.withCredentials && "withCredentials" in xhr) {
                  xhr.withCredentials = true;
                }
                const headers = option.headers || {};
                for (const item in headers) {
                  if (headers.hasOwnProperty(item) && headers[item] !== null) {
                    xhr.setRequestHeader(item, headers[item]);
                  }
                }
                // 文件开始上传
                xhr.send(item.formData);
              }
            };
            while (limit > 0) {
              setTimeout(() => {
                start();
              }, Math.random() * 1000);
              limit -= 1;
            }
          });
        }
        // 更新上传进度条百分比的方法
        const updataPercentage = (e) => {
          let loaded = 0; // 当前已经上传文件的总大小
          percentage.forEach((item) => {
            loaded += item;
          });
          e.percent = (loaded / optionFile.size) * 100;
          if (
            this.value > parseFloat(Number(e.percent).toFixed(0)) ||
            this.value === parseFloat(Number(e.percent).toFixed(0))
          )
            return;
          this.value = parseFloat(Number(e.percent).toFixed(0));
          console.log(this.value);
        };
        try {
          // 调用上传队列方法 等待所有文件上传完成
          await sendRequest(fileChunkedList, 1);
          // 可以在这通知服务端合并
        } catch (error) {
          option.onError(error);
        }
      };
    },
    beforeRemove(file) {
      // 如果正在分片上传, 可用 XMLHttpRequest.abort() 取消分片上传
    }
  }
};
</script>  

<style>
</style>

效果

在这里插入图片描述

Logo

前往低代码交流专区

更多推荐