文章目录

  • 概要
  • 编写这个组件的原因
  • 组件的HTML
  • 逻辑模块
  • 父组件传的数据
  • Data的变量
  • 1. 文件上传前
  • 2. 利用watch 监听
  • 大文件初始化
  • 大文件合并的请求
  • 循环上传
  • 组件的郑示例
  • 注意点
  • 优化后的完整代码


概要

提示:批量上传大于5Mb的小文件或者批量上传 大于5Mb文件使用切片上传

编写这个组件的原因

后端不能处理5Mb  以上的文件

组件的HTML

试图模块相对简单,没有书写复杂的删除文件的操作 之展示上传后的文件名称

<template>
  <div>
    <!-- 上传组件 -->
    <el-upload
      action
      :auto-upload="false"
      :show-file-list="false"
      :on-change="handleChange"
      :accept="accept"
      multiple
      ref="upload"
    >
      <el-button size="small" type="primary">点击上传</el-button>
    </el-upload>
    <!-- {{ FileAllListArray }} -->
    <ul>
      <li v-for="(item, index) in FileAllArrayLst" :key="index">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

逻辑模块

引入了 spark-md5 版本是 spark-md5": "^3.0.2 如果使用这个组件的话 npm i 下载一下

import SparkMD5 from "spark-md5";
import axios from "axios";
import { getToken, getIsVersion } from "@/utils/auth";

父组件传的数据

这个就不做过多的解释了有开发经验的应该都可以看懂

props: {
    accept: {
      type: String,
      default: ".doc,.docx",
    },
    FileAllListArray: {
      type: Array,
      default: ()=>[],
    },
    type: {
      type: Number,
      default: null,
    },
    Url: {
      type: Object,
      default: ()=>({
        GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
        PosetUploadPart: window.service.url + "/system/oss/uploadPart", //  上传 切片
        PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
        uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
      }),
    }
  },

Data的变量

因为当时赶项目比较急所有没有写具体的作用 主要的变量还是标记了 下面是 Js 逻辑模块

data() {
    return {
      loadings: false,//  加载页面开关
      percent: 0,
      videoUrl: "",
      upload: true,
      percentCount: 0,
      chunkSizes: "",
      chunkAll: 0,
      fileId: "", // 文件ID
      fileName: "", // 文件名称
      FileArray: [], // 上传选中的文件
      FileAllArray: [], // 文件列表
      FileAllArrayLst: [], // 文件列表
      chunkList: [],
      // FileIdlist: [],
      // fileIndes: [],
      FileAZon5M: [], //上传的所有的 file 文件 5 MB
      FileDaYu5M: [], //上传的所有的 file 文件 大于 5 MB
      batch: false,
      asde: [],
      asde5: [],
      loading: "",
    };
  },

1. 文件上传前

首先 调用的是 Upload 的 :on-change="handleChange" 事件 用于获取上传后的File文件流的 大小用于区分是否大于或者小于5Mb的文件并且打开上传的 加载页面

// 文件上传前
    async handleChange(file) {
      this.openFullScreen2();
      this.loadings = true;
      this.FileAllArray.push(file);
      this.FileAllArrayLst.push(file);
      // 当前判断: 当上传的文件小于5Mb的时候 将Filer文件放进 FileAZon5M 这个数组里面
      if (file.size < 5 * 1024 * 1024) {
        this.FileAZon5M.push(file);
      } else {
      // 当前判断: 当上传的文件大于5Mb的时候 将Filer文件放进 FileDaYu5M 这个数组里面
        this.FileDaYu5M.push(file);
      }
    },
  // 文件loading
    openFullScreen2() {
      this.loading = this.$loading({
        lock: true,
        text: "文件上传中...",
        spinner: "el-icon-loading",
        background: "rgba(0, 0, 0, 0.7)",
      });
    },

2. 利用watch 监听

第一:监听 FileAZon5M 小于 5Mb 的数据 判断想应的情况
第二:监听 FileDaYu5M 存放大于5Mb的数组

watch: {
    // 监听存放小于5Mb的数组 
    FileAZon5M: {
      handler(n, o) {
        // 循环n这个新数组
        n.filter((item) => {
          // 当前判断是判断是否是最后一个并且 大于5Mb的不是最后一个 如果符合将小于5Mb的数组清空, 加载动画继续打开
          if (item.uid == n[n.length - 1].uid && this.FileDaYu5M.length != 0) {
            this.pullXiaoyu5M(item, true);
            this.FileAZon5M = []
          } else if (
            // 当前判断是判断是否是最后一个并且 大于5Mb的也是最后一个 如果符合将小于5Mb的数组清空, 加载动画关闭
            item.uid == n[n.length - 1].uid &&
            this.FileDaYu5M.length == 0
          ) {
            this.pullXiaoyu5M(item, false);
            this.FileAZon5M = []
          } else {
            this.pullXiaoyu5M(item, true);
          }
        });
      },
    },
    // 监听存放大于5Mb的数组 
    FileDaYu5M: {
      handler(n, o) {
        n.filter((item) => {
          console.log(999999)
          // 调用初始化 切片的接口
          this.initialize(item);
        });
      },
    },
  },

大文件初始化

// 切片上传的初始化
    initialize(file) {
      let i = 0;
      // 重新定义 this 预防 指向丢失
      let that = this;
      //  文件的名称
      this.fileName = file.name;
      axios({
        url: this.Url.GeTinitUpload, // 初始化切片的接口
        method: "post",
        headers: {
          "Content-Type": "multipart/form-data", // 请求头
          Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
        },
        //参数
        params: { fileName: this.fileName },
      }).then((res) => {
        if (res.data.code != 200) {
          this.FileAllArray = [];
          this.batch = true;
          return;
        }
        console.log(res.data.msg)
        // 切片对应的 文件留的Id
        that.fileId = res.data.msg;
        if (!file) return;

        this.percent = 0;
        this.videoUrl = "";
        // 获取文件并转成 ArrayBuffer 对象
        const fileObj = file.raw;
        let buffer;
        try {
          buffer = this.fileToBuffer(fileObj);
        } catch (e) { }
        // 将文件按固定大小(3M)进行切片,注意此处同时声明了多个常量
        const chunkSize = 5 * 1024 * 1024;
        this.chunkSizes = chunkSize;
        let chunkList = [], // 保存所有切片的数组
          chunkListLength = Math.ceil(fileObj.size / chunkSize); // 计算总共多个切片
        this.chunkAll = chunkListLength;
        let suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后缀名
        // 根据文件内容生成 hash 值
        const spark = new SparkMD5.ArrayBuffer();
        spark.append(buffer);
        const hash = spark.end();
        // 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
        let curChunk = 0; // 切片时的初始位置
        for (let i = 0; i < chunkListLength; i++) {
          const item = {
            chunk: fileObj.slice(curChunk, curChunk + chunkSize),
            // fileName: `${hash}_${i}.${suffix}`, // 文件名规则按照 hash_1.jpg 命名
            fileName: file.name, // 文件名规则按照 hash_1.jpg 命名
            fileId: that.fileId,
          };
          curChunk += chunkSize;
          chunkList.push(item);
        }
        console.log(chunkList);
        this.chunkList = chunkList; // sendRequest 要用到
        this.hash = hash; // sendRequest 要用到
        this.sendRequest();
      });
    },

大文件合并的请求

// 发送请求
    sendRequest() {
      const requestList = []; // 请求集合
      let fileIdS = null;
      let fileName = null;
      this.chunkList.forEach((item, index) => {
        console.log(item, "item");
        const fn = () => {
          const formData = new FormData();
          formData.append("file", item.chunk);
          formData.append("partNumber", index + 1);
          formData.append("partSize", item.chunk.size);
          formData.append("uploadId", item.fileId);
          return axios({
            url: this.Url.PosetUploadPart,
            method: "post",
            headers: {
              "Content-Type": "multipart/form-data",
              Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
            },
            data: formData,
          }).then((res) => {
            if (index == this.chunkList.length && res.code == 200) {
              complete();
            }
            if (res.data.code === 0) {
              // 成功
              if (this.percentCount === 0) {
                // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
                this.percentCount = 100 / this.chunkList.length;
              }
              this.percent += this.percentCount; // 改变进度
              console.log( this.percent ,' this.percent ')
              this.chunkList.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传
            }
          });
        };
        fileIdS = item.fileId;
        fileName = item.fileName;
        requestList.push(fn);
      });
      let i = 0; // 记录发送的请求个数
      // 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
      const complete = (value) => {
        let that = this;
        axios({
          url: this.Url.PostCompleteUpload, // 合并切片接口
          method: "post",
          headers: {
            "Content-Type": "multipart/form-data",
            Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
          },
          params: { fileName: fileName, uploadId: fileIdS },
        }).then((res) => {
          this.asde5.push(res.data.data);
        
          if (res.data.code === 200) {
            // 请求发送成功
            this.FileAllArray = [] 
            // 上传成功  将每个文件 传给父组件
            this.$emit("event_Upload", res);
            this.$refs.upload.clearFiles()
            this.FileArray.shift();
            if (that.FileArray.length != 0) {
              this.batch = true;
              this.circulation(that.FileArray[0]);
            }
            if (
              that.FileArray.length == 0 &&
              fileIdS == this.chunkList[this.chunkList.length - 1].fileId
            ) {
              this.FileDaYu5M = []
              setTimeout(() => {
                this.loading.close();
              }, 10000)
            }
          }
        });
      };
      const send = async () => {
        if (!this.upload) return;
        if (i >= requestList.length) {
          // 发送完毕
          complete();
          return;
        }
        await requestList[i]();
        i++;
        send();
      };
      send(); // 发送请求
    },

循环上传

// 循环上传
    circulation(value) {
      console.log(value, "value");
      let that = this;
      this.openFullScreen2();
      that.initialize(value);
    },
       // 将 File 对象转为 ArrayBuffer
    fileToBuffer(file) {
      return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = (e) => {
          resolve(e.target.result);
        };
        fr.readAsArrayBuffer(file);
        fr.onerror = () => {
          reject(new Error("转换文件格式发生错误"));
        };
      });
    },

组件的郑示例

<template>
  <div>
    <!-- 上传组件 -->
    <el-upload
      action
      :auto-upload="false"
      :show-file-list="false"
      :on-change="handleChange"
      :accept="accept"
      multiple
      ref="upload"
    >
      <el-button size="small" type="primary">点击上传</el-button>
    </el-upload>
    <!-- {{ FileAllListArray }} -->
    <ul v-if="type != 1">
      <li v-for="(item, index) in FileAllArrayLst" :key="index">
        {{ item.name }}
      </li>
    </ul>
    <ul v-if="type == 1">
      <li
        v-for="(item, index) in FileAllListArray"
        :key="index"
        style="width: 100%; display: flex; justify-content: space-between"
      >
        <div>{{ item.newName }}</div>
        <div @click="FileAllList(item)">X</div>
      </li>
    </ul>
  </div>
</template>

<script>
import SparkMD5 from "spark-md5";
import axios from "axios";
import { getToken, getIsVersion } from "@/utils/auth";
export default {
  name: "App3",
  props: {
    accept: {
      type: String,
      default: ".doc,.docx",
    },
    FileAllListArray: {
      type: Array,
      default: ()=>[],
    },
    type: {
      type: Number,
      default: null,
    },
    Url: {
      type: Object,
      default: ()=>({
        GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
        PosetUploadPart: window.service.url + "/system/oss/uploadPart", //  上传 切片
        PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
        uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
      }),
    }
  },
  data() {
    return {
      loadings: false,//  加载页面开关
      percent: 0,
      videoUrl: "",
      upload: true,
      percentCount: 0,
      chunkSizes: "",
      chunkAll: 0,
      fileId: "", // 文件ID
      fileName: "", // 文件名称
      FileArray: [], // 上传选中的文件
      FileAllArray: [], // 文件列表
      FileAllArrayLst: [], // 文件列表
      chunkList: [],
      // FileIdlist: [],
      // fileIndes: [],
      FileAZon5M: [], //上传的所有的 file 文件 5 MB
      FileDaYu5M: [], //上传的所有的 file 文件 大于 5 MB
      batch: false,
      asde: [],
      asde5: [],
      loading: "",
    };
  },
  watch: {
    // 监听存放小于5Mb的数组 
    FileAZon5M: {
      handler(n, o) {
        // 循环n这个新数组
        n.filter((item) => {
          // 当前判断是判断是否是最后一个并且 大于5Mb的不是最后一个 如果符合将小于5Mb的数组清空, 加载动画继续打开
          if (item.uid == n[n.length - 1].uid && this.FileDaYu5M.length != 0) {
            this.pullXiaoyu5M(item, true);
            this.FileAZon5M = []
          } else if (
            // 当前判断是判断是否是最后一个并且 大于5Mb的也是最后一个 如果符合将小于5Mb的数组清空, 加载动画关闭
            item.uid == n[n.length - 1].uid &&
            this.FileDaYu5M.length == 0
          ) {
            this.pullXiaoyu5M(item, false);
            this.FileAZon5M = []
          } else {
            this.pullXiaoyu5M(item, true);
          }
        });
      },
    },
    // 监听存放大于5Mb的数组 
    FileDaYu5M: {
      handler(n, o) {
        n.filter((item) => {
          console.log(999999)
          // 调用初始化 切片的接口
          this.initialize(item);
        });
      },
    },
  },
  methods: {
    pullXiaoyu5M(item, type) {
      console.log(item);
      let formData = new FormData();
      formData.append("file", item.raw);
      axios({
        url: this.Url.uploadDan,
        method: "post",
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
        },
        data: formData,
      }).then((res) => {
        this.asde.push(res.data.data);
        this.FileAllArray = []
        this.$emit("event_Upload", res);
        this.$refs.upload.clearFiles()
        this.loadings = false;
        this.$message({
          type: "success", // success error warning
          message: "上传成功",
          duration: 2000,
        });
        if (type) {

        } else {
          setTimeout(() => {
            this.loading.close();
          }, 10000);
        }
      })
    },
    FileAllList(value) {
      this.$emit("FileAllList", value);
    },
    // 文件loading
    openFullScreen2() {
      this.loading = this.$loading({
        lock: true,
        text: "文件上传中...",
        spinner: "el-icon-loading",
        background: "rgba(0, 0, 0, 0.7)",
      });
    },

    // 文件上传前
    async handleChange(file) {
      this.openFullScreen2();
      this.loadings = true;
      this.FileAllArray.push(file);
      this.FileAllArrayLst.push(file);
      // 当前判断: 当上传的文件小于5Mb的时候 将Filer文件放进 FileAZon5M 这个数组里面
      if (file.size < 5 * 1024 * 1024) {
        this.FileAZon5M.push(file);
      } else {
      // 当前判断: 当上传的文件大于5Mb的时候 将Filer文件放进 FileDaYu5M 这个数组里面
        this.FileDaYu5M.push(file);
      }
    },

    // 切片上传的初始化
    initialize(file) {
      let i = 0;
      // 重新定义 this 预防 指向丢失
      let that = this;
      //  文件的名称
      this.fileName = file.name;
      axios({
        url: this.Url.GeTinitUpload, // 初始化切片的接口
        method: "post",
        headers: {
          "Content-Type": "multipart/form-data", // 请求头
          Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
        },
        //参数
        params: { fileName: this.fileName },
      }).then((res) => {
        if (res.data.code != 200) {
          this.FileAllArray = [];
          this.batch = true;
          return;
        }
        console.log(res.data.msg)
        // 切片对应的 文件留的Id
        that.fileId = res.data.msg;
        if (!file) return;

        this.percent = 0;
        this.videoUrl = "";
        // 获取文件并转成 ArrayBuffer 对象
        const fileObj = file.raw;
        let buffer;
        try {
          buffer = this.fileToBuffer(fileObj);
        } catch (e) { }
        // 将文件按固定大小(3M)进行切片,注意此处同时声明了多个常量
        const chunkSize = 5 * 1024 * 1024;
        this.chunkSizes = chunkSize;
        let chunkList = [], // 保存所有切片的数组
          chunkListLength = Math.ceil(fileObj.size / chunkSize); // 计算总共多个切片
        this.chunkAll = chunkListLength;
        let suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后缀名
        // 根据文件内容生成 hash 值
        const spark = new SparkMD5.ArrayBuffer();
        spark.append(buffer);
        const hash = spark.end();
        // 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
        let curChunk = 0; // 切片时的初始位置
        for (let i = 0; i < chunkListLength; i++) {
          const item = {
            chunk: fileObj.slice(curChunk, curChunk + chunkSize),
            // fileName: `${hash}_${i}.${suffix}`, // 文件名规则按照 hash_1.jpg 命名
            fileName: file.name, // 文件名规则按照 hash_1.jpg 命名
            fileId: that.fileId,
          };
          curChunk += chunkSize;
          chunkList.push(item);
        }
        console.log(chunkList);
        this.chunkList = chunkList; // sendRequest 要用到
        this.hash = hash; // sendRequest 要用到
        this.sendRequest();
      });
    },
    // 发送请求
    sendRequest() {
      const requestList = []; // 请求集合
      let fileIdS = null;
      let fileName = null;
      this.chunkList.forEach((item, index) => {
        console.log(item, "item");
        const fn = () => {
          const formData = new FormData();
          formData.append("file", item.chunk);
          formData.append("partNumber", index + 1);
          formData.append("partSize", item.chunk.size);
          formData.append("uploadId", item.fileId);
          return axios({
            url: this.Url.PosetUploadPart,
            method: "post",
            headers: {
              "Content-Type": "multipart/form-data",
              Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
            },
            data: formData,
          }).then((res) => {
            if (index == this.chunkList.length && res.code == 200) {
              complete();
            }
            if (res.data.code === 0) {
              // 成功
              if (this.percentCount === 0) {
                // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
                this.percentCount = 100 / this.chunkList.length;
              }
              this.percent += this.percentCount; // 改变进度
              console.log( this.percent ,' this.percent ')
              this.chunkList.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传
            }
          });
        };
        fileIdS = item.fileId;
        fileName = item.fileName;
        requestList.push(fn);
      });
      let i = 0; // 记录发送的请求个数
      // 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
      const complete = (value) => {
        let that = this;
        axios({
          url: this.Url.PostCompleteUpload, // 合并切片接口
          method: "post",
          headers: {
            "Content-Type": "multipart/form-data",
            Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
          },
          params: { fileName: fileName, uploadId: fileIdS },
        }).then((res) => {
          this.asde5.push(res.data.data);
        
          if (res.data.code === 200) {
            // 请求发送成功
            this.FileAllArray = [] 
            // 上传成功  将每个文件 传给父组件
            this.$emit("event_Upload", res);
            this.$refs.upload.clearFiles()
            this.FileArray.shift();
            if (that.FileArray.length != 0) {
              this.batch = true;
              this.circulation(that.FileArray[0]);
            }
            if (
              that.FileArray.length == 0 &&
              fileIdS == this.chunkList[this.chunkList.length - 1].fileId
            ) {
              this.FileDaYu5M = []
              setTimeout(() => {
                this.loading.close();
              }, 10000)
            }
          }
        });
      };
      const send = async () => {
        if (!this.upload) return;
        if (i >= requestList.length) {
          // 发送完毕
          complete();
          return;
        }
        await requestList[i]();
        i++;
        send();
      };
      send(); // 发送请求
    },
    // 循环上传
    circulation(value) {
      console.log(value, "value");
      let that = this;
      this.openFullScreen2();
      that.initialize(value);
    },

    // 将 File 对象转为 ArrayBuffer
    fileToBuffer(file) {
      return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = (e) => {
          resolve(e.target.result);
        };
        fr.readAsArrayBuffer(file);
        fr.onerror = () => {
          reject(new Error("转换文件格式发生错误"));
        };
      });
    },
  },
};
</script>

<style scoped>
.progress-box {
  box-sizing: border-box;
  width: 360px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
  padding: 8px 10px;
  background-color: #ecf5ff;
  font-size: 14px;
  border-radius: 4px;
}
</style>

注意点

在开发中发现 在大小文件混合上传的时候 大文件的合并接口状态与返回的 文件链接不同步导致后面提交上传的时候会丢失,所以 我在大小文件上传成功的接口位置加了一个计时器 延迟这个加载页面的关闭 , 如果不需要 可以自行关闭

setTimeout(() => {
                this.loading.close();
              }, 10000)

优化后的完整代码

<template>
  <div>
    <!-- 上传组件 -->
    <el-upload action :auto-upload="false" :show-file-list="false" :on-change="handleChange" :accept="accept" multiple>
      <el-button size="small" type="primary">点击上传</el-button>
    </el-upload>
    <ul v-if="type != 1">
      <li v-for="(item, index) in FileAllArray" :key="index">
        {{ item.name }}
      </li>
    </ul>
    <ul v-if="type == 1">
      <li v-for="(item, index) in FileAllListArray" :key="index"
        style="width: 100%; display: flex; justify-content: space-between">
        <div>{{ item.newName }}</div>
        <div @click="FileAllList(item)">X</div>
      </li>
    </ul>
  </div>
</template>

<script>
import SparkMD5 from "spark-md5";
import axios from "axios";
// const Url = {
//   GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
//   PosetUploadPart: window.service.url + "/system/oss/uploadPart", //  上传 切片
//   PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
//   uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
// };
import { getToken, getIsVersion } from "@/utils/auth";
export default {
  name: "App3",
  filters: {
    btnTextFilter(val) {
      return val ? "暂停" : "继续";
    },
  },
  props: {
    accept: {
      type: String,
      default: ".doc,.docx",
    },
    FileAllListArray: {
      type: Array,
      default: [],
    },
    type: {
      type: Number,
      default: null,
    },
    Url: {
      type: Object,
      default: {
        GeTinitUpload: window.service.url + "/system/oss/initUpload", // 初始化
        PosetUploadPart: window.service.url + "/system/oss/uploadPart", //  上传 切片
        PostCompleteUpload: window.service.url + "/system/oss/completeUpload", // 合并切片
        uploadDan: window.service.url + "/system/oss/upload", // 小于 5mb
      },
    }
  },
  data() {
    return {
      loadings: false,
      percent: 0,
      videoUrl: "",
      upload: true,
      percentCount: 0,
      chunkSizes: "",
      chunkAll: 0,
      fileId: "", // 文件ID
      fileName: "", // 文件名称
      FileArray: [], // 上传选中的文件
      FileAllArray: [], // 文件列表
      chunkList: [],
      // FileIdlist: [],
      // fileIndes: [],
      FileAZon5M: [], //上传的所有的 file 文件 5 MB
      FileDaYu5M: [], //上传的所有的 file 文件 大于 5 MB
      batch: false,
      asde: [],
      asde5: [],
      loading: "",
      dayu5: [],
    };
  },
  watch: {
    FileAZon5M: {
      handler(n, o) {
        n.filter((item) => {
          if (item.uid == n[n.length - 1].uid && this.dayu5.length != 0) {
            this.pullXiaoyu5M(item, true);
            this.FileAZon5M = []
          } else if (
            item.uid == n[n.length - 1].uid &&
            this.dayu5.length == 0
          ) {
            this.pullXiaoyu5M(item, false);
            this.FileAZon5M = []

          } else {
            this.pullXiaoyu5M(item, true);
          }
        });
      },
    },
    FileDaYu5M: {
      handler(n, o) {
        console.log(n,'NNNNNNNNNNNNNNNNNN');
        // 大文件初始化切片
        this.initialize(n[0])
        // n.filter((item) => {
        //   this.initialize(item);
        // });
      },
    },
  },
  methods: {
    pullXiaoyu5M(item, type) {
      let formData = new FormData();
      formData.append("file", item.raw);
      axios({
        url: this.Url.uploadDan,
        method: "post",
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
        },
        data: formData,
      }).then((res) => {
        this.asde.push(res.data.data);
        this.$emit("event_Upload", res);
        this.loadings = false;
        this.$message({
          type: "success", // success error warning
          message: "上传成功",
          duration: 2000,
        });
        if (type) {
        } else {
          this.loading.close();

          // setTimeout(() => {
          //   this.loading.close();

          // }, 10000);
        }
      })
    },
    FileAllList(value) {
      this.$emit("FileAllList", value);
    },
    // 文件loading
    openFullScreen2() {
      this.loading = this.$loading({
        lock: true,
        text: "文件上传中...",
        spinner: "el-icon-loading",
        background: "rgba(0, 0, 0, 0.7)",
      });
    },

    // 文件上传前
    async handleChange(file) {
      this.openFullScreen2();
      this.loadings = true;
      this.FileAllArray.push(file);
      if (file.size < 5 * 1024 * 1024) {
        this.FileAZon5M.push(file);
      } else {
        this.FileDaYu5M.push(file);
        this.dayu5.push(file)
      }
    },

    // 切片上传的初始化
    initialize(file) {
      let i = 0;
      let that = this;
      this.fileName = file.name;
      axios({
        url: this.Url.GeTinitUpload,
        method: "post",
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
        },
        params: { fileName: this.fileName },
      }).then((res) => {
        if (res.data.code != 200) {
          this.FileAllArray = [];
          this.batch = true;
          return;
        }
        that.fileId = res.data.msg;
        if (!file) return;
        this.percent = 0;
        this.videoUrl = "";
        // 获取文件并转成 ArrayBuffer 对象
        const fileObj = file.raw;
        let buffer;
        try {
          buffer = this.fileToBuffer(fileObj);
        } catch (e) { }
        // 将文件按固定大小(3M)进行切片,注意此处同时声明了多个常量
        const chunkSize = 5 * 1024 * 1024;
        this.chunkSizes = chunkSize;
        let chunkList = [], // 保存所有切片的数组
          chunkListLength = Math.ceil(fileObj.size / chunkSize); // 计算总共多个切片
        this.chunkAll = chunkListLength;
        let suffix = /\.([0-9A-z]+)$/.exec(fileObj.name)[1]; // 文件后缀名
        // 根据文件内容生成 hash 值
        const spark = new SparkMD5.ArrayBuffer();
        spark.append(buffer);
        const hash = spark.end();
        // 生成切片,这里后端要求传递的参数为字节数据块(chunk)和每个数据块的文件名(fileName)
        let curChunk = 0; // 切片时的初始位置
        for (let i = 0; i < chunkListLength; i++) {
          const item = {
            chunk: fileObj.slice(curChunk, curChunk + chunkSize),
            // fileName: `${hash}_${i}.${suffix}`, // 文件名规则按照 hash_1.jpg 命名
            fileName: file.name, // 文件名规则按照 hash_1.jpg 命名
            fileId: that.fileId,
          };
          curChunk += chunkSize;
          chunkList.push(item);
        }
        this.chunkList = chunkList; // sendRequest 要用到
        this.hash = hash; // sendRequest 要用到
        this.sendRequest();
      });
    },
    // 发送请求
    sendRequest() {
      const requestList = []; // 请求集合
      let fileIdS = null;
      let fileName = null;
      this.chunkList.forEach((item, index) => {
        const fn = () => {
          const formData = new FormData();
          formData.append("file", item.chunk);
          formData.append("partNumber", index + 1);
          formData.append("partSize", item.chunk.size);
          formData.append("uploadId", item.fileId);
          return axios({
            url: this.Url.PosetUploadPart,
            method: "post",
            headers: {
              "Content-Type": "multipart/form-data",
              Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
            },
            data: formData,
          }).then((res) => {
            if (index == this.chunkList.length && res.code == 200) {
              complete();
            }
            if (res.data.code === 0) {
              // 成功
              if (this.percentCount === 0) {
                // 避免上传成功后会删除切片改变 chunkList 的长度影响到 percentCount 的值
                this.percentCount = 100 / this.chunkList.length;
              }
              this.percent += this.percentCount; // 改变进度
              this.chunkList.splice(index, 1); // 一旦上传成功就删除这一个 chunk,方便断点续传
            }
          });
        };
        fileIdS = item.fileId;
        fileName = item.fileName;
        requestList.push(fn);
      });
      let i = 0; // 记录发送的请求个数
      // 文件切片全部发送完毕后,需要请求 '/merge' 接口,把文件的 hash 传递给服务器
      const complete = (value) => {
        let that = this;
        axios({
          url: this.Url.PostCompleteUpload,
          method: "post",
          headers: {
            "Content-Type": "multipart/form-data",
            Authorization: "Bearer " + getToken(), // 让每个请求携带自定义token 请根据实际情况自行修改
          },
          params: { fileName: fileName, uploadId: fileIdS },
        }).then((res) => {
          this.asde5.push(res.data.data);
          if (res.data.code === 200) {
            // 请求发送成功
            this.$emit("event_Upload", res);
            this.dayu5.shift();
            if (that.dayu5.length != 0) {
              this.batch = true;
               // 循环上传
              this.circulation(that.dayu5[0]);
            }
            if (
              that.dayu5.length == 0 &&
              fileIdS == this.chunkList[this.chunkList.length - 1].fileId
            ) {
              that.dayu5 = []
              this.loading.close();
              // setTimeout(() => {
              //   this.loading.close();
              // }, 10000)
            }
          }
        });
      };
      const send = async () => {
        if (!this.upload) return;
        if (i >= requestList.length) {
          // 发送完毕
          complete();
          return;
        }
        await requestList[i]();
        i++;
        send();
      };
      send(); // 发送请求
    },
    // 循环上传
    circulation(value) {
      let that = this;
      this.openFullScreen2();
      that.initialize(value);
    },
    // 按下暂停按钮
    handleClickBtn() {
      this.upload = !this.upload;
      // 如果不暂停则继续上传
      if (this.upload) this.sendRequest();
    },

    // 将 File 对象转为 ArrayBuffer
    fileToBuffer(file) {
      return new Promise((resolve, reject) => {
        const fr = new FileReader();
        fr.onload = (e) => {
          resolve(e.target.result);
        };
        fr.readAsArrayBuffer(file);
        fr.onerror = () => {
          reject(new Error("转换文件格式发生错误"));
        };
      });
    },
  },
};
</script>

<style scoped>
.progress-box {
  box-sizing: border-box;
  width: 360px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 10px;
  padding: 8px 10px;
  background-color: #ecf5ff;
  font-size: 14px;
  border-radius: 4px;
}
</style>

提示:这里可以添加总结

提供先进的推理,复杂的指令,更多的创造力。