el-upload 多文件依次上传(防抖 + 递归)

需求描述

  • 多图上传组件,1-9 张图
  • 选择完文件后自动上传,不需要上传按钮来进行手动上传

难点

  • 接口有两种,多图集合上传接口 uploadImgs、单图上传接口 uploadImg
    • 使用 uploadImgs 接口,参数为图片集合 fileList,但是缺少上传按钮,无法监听用户选择完图片的时间点
    • 使用 uploadImg 接口,参数为图片 file,接口被多次同时调用造成服务器压力

解决思路

  • 关闭 auto-upload 属性,手动控制上传进程,监听组件的 change 事件
  • 使用防抖函数,监听 change 事件
  • 选择多张图片后,组件每读取到一张图片就会调用一次 change 事件
  • change 事件被触发后的某时间段内如果没有再次被触发,视为用户此次选择图片结束
  • 获取待上传的文件列表,将图片集合传递给后台
  • 如果调用单图上传,需要加入递归,重复多次调用上传接口,将待上传文件列表中的内容逐一上传
<el-upload
  action=""
  list-type="picture-card"
  multiple
  show-file-list
  :on-change="changeFile"
  :on-exceed="exceedFile"
  :auto-upload="false"
  :file-list="fileList"
  :limit="9"
  :disabled="disabled"
  ref="upload"
>
  <i class="el-icon-plus">i>
el-upload>
data() {
  return {
    timmer: null, // 用于控制防抖的定时器
    fileList: [], // 文件列表--上传结束后回显用
    disabled: false, // 控制组件是否禁用
    uploadFileList: [], // 上传文件列表--待传递
    successFileUrls: [], // 记录上传成功的文件列表--用不到,这里为了方便演示
  };
},
// 防抖函数
debounce(func, delay) {
  return (() => {
    if (this.timmer) {
      // 如果已经存在,说明之前执行过了,这里要清除,重新开始
      console.log("%c清理定时器的操作~", "color: blue;");
      clearTimeout(this.timmer);
    }
    this.timmer = setTimeout(func, delay);
  })();
},
changeFile(file, fileList) {
  console.log("触发组件change事件", file, fileList);
  // 开启禁用开关
  this.disabled = true;
  this.debounce(() => {
    this.uploadFileList = JSON.parse(JSON.stringify(fileList)); // 文件列表赋值
    // this.uploadImg(); // 执行上传事件--单图上传
    this.uploadImgs(); // 执行上传事件--多图上传
    this.timmer = null;
  }, 1000);
},
exceedFile() {
  this.$message.error("上传文件最多9个");
},
// 上传事件--多图上传
uploadImgs() {
  console.log("%c模拟接口执行上传事件----开始上传", "color: red;");
  console.log(this.uploadFileList);
  if (this.uploadFileList.length > 0) {
    this.mockFunc(2, this.uploadFileList)
      .then((res) => {
        console.log("%c模拟接口执行上传事件----成功", "color: green;");
        this.successFileUrls.push(res);
        console.log("当前已上传成功的文件", this.successFileUrls);
        // 实际应用接口,返回文件 url 集合,用 url 集合结果覆盖掉 fileList
        // fileList 被返回结果覆盖掉之后,也会同时清理掉组件中上传失败的文件
        // this.fileList = this.successFileUrls;
      })
      .catch(() => {
        console.log(
          "%c模拟接口执行上传事件----失败",
          "color: yellowgreen;"
        );
      });
  }
},
// 上传事件--单图上传
uploadImg() {
  console.log("%c模拟接口执行上传事件----开始上传", "color: red;");
  console.log(this.uploadFileList);
  if (this.uploadFileList.length > 0) {
    this.mockFunc(1, this.uploadFileList[0])
      .then((res) => {
        console.log("%c模拟接口执行上传事件----成功", "color: green;");
        this.successFileUrls.push(res);
        console.log("当前已上传成功的文件", this.successFileUrls);
        // 实际应用接口,返回文件 url 集合,用 url 集合结果覆盖掉 fileList
        // this.fileList = this.successFileUrls;
        this.uploadFileList.shift(); // 上传成功后,删除待传递列表的第一个
        this.uploadImg(); // 递归,执行下一个操作
      })
      .catch(() => {
        console.log(
          "%c模拟接口执行上传事件----失败",
          "color: yellowgreen;"
        );
      });
  } else {
    console.log("待上传文件列表全部传递完毕");
    // 关闭禁用开关
    this.disabled = false;
  }
},
 // 模拟接口--手动延时、返回正确或错误结果
mockFunc(type, file) {
  if (type == 1) {
    // 模拟单图上传
    return new Promise((resolve, reject) => {
      console.log("上传的文件为", file.name);
      let params = new FormData();
      params.append("file", file);
      console.log("file", file);
      setTimeout(() => {
        let radio = file.size / 1024 / 1024;
        if (radio <= 2) {
          // 接口得到图片云存储的路径 url,这里用 name 代替
          resolve(file.name);
        } else {
          reject();
        }
      }, 200);
    });
  } else {
    // 模拟多图上传
    return new Promise((resolve, reject) => {
      let params = new FormData();
      let fileName = []; // 模拟接口返回值,实际用不到
      file.forEach((item) => {
        let radio = item.size / 1024 / 1024;
        if (radio <= 2) {
          params.append("file", item);
          // 接口得到图片云存储的路径 url,这里用 name 代替
          fileName.push(item.name);
        }
      });
      console.log("file", file);
      setTimeout(() => {
        resolve(fileName);
      }, 200);
    });
  }
},

结果演示

  • 未调用实际接口,在 mockFunc 中记录文件名称,来模拟后台接口响应中返回的文件云存储路径。
  • 调用接口结束后,使用接口响应中返回的云存储路径覆盖掉 fileList,使上传组件回显的图片集合,与上传成功后的图片集合保持一致

调用 uploadImgs 多图上传

  • 选择三张小于 2M 的图片上传
  • change 事件被触发 3 次,在第 3 次结束后,1000ms 内,未被再次触发,开始调用上传接口
  • 上传接口调用成功,返回结果

el-upload 多文件依次上传(防抖 + 递归)_第1张图片

  • 选择两张小于 2M 的图片,一张大于 2M 的图片上传
  • change 事件被触发 3 次,每触发一次,向待上传数组 uploadFileList 中塞一个 file 对象,在第 3 次结束后,1000ms 内,未被再次触发,开始调用上传接口,将 uploadFileList 传递给后台
  • 上传接口调用成功,返回结果,结果集合的长度为 2,表示上传图片列表中有一张上传失败了,在实际接口应用中,使用接口响应结果覆盖掉 el-upload 绑定的 fileList 即可

el-upload 多文件依次上传(防抖 + 递归)_第2张图片

调用 uploadImg 单图上传

  • 选择三张小于 2M 的图片上传
  • change 事件被触发 3 次,每触发一次,向待上传数组 uploadFileList 中塞一个 file 对象,在第 3 次结束后,1000ms 内,未被再次触发,开始递归调用上传接口
  • 假设 uploadFileList = [img1,img2,img3] ,此时 uploadFileList 长度为 3,表示当前有 3 张图片待上传,调用接口,传递 uploadFileList[0] 对象,即 img1,调用结束后,将 uploadFileList 第一项删除,此时 uploadFileList = [img2,img3]
  • 继续调用上传接口,此时 uploadFileList 长度为 2,表示当前有两张图片待上传,调用接口,传递 uploadFileList[0] 对象,即 img2,调用结束后,将 uploadFileList 第一项删除,此时 uploadFileList = [img3]
  • 继续调用上传接口,此时 uploadFileList 长度为 1,表示当前有 1 张图片待上传,调用接口,传递 uploadFileList[0] 对象,即 img3,调用结束后,将 uploadFileList 第一项删除,此时 uploadFileList = []
  • 继续嗲用上传接口,此时 uploadFileList 长度为 0,表示待上传图片已经全部上传完毕,结束上传进程

el-upload 多文件依次上传(防抖 + 递归)_第3张图片

  • 选择两张小于 2M 的图片,一张大于 2M 的图片上传
  • 递归流程同上
  • 上传接口调用成功,返回结果,结果集合的长度为 2,表示上传图片列表中有一张上传失败了,在实际接口应用中,使用接口响应结果覆盖掉 el-upload 绑定的 fileList 即可

el-upload 多文件依次上传(防抖 + 递归)_第4张图片

你可能感兴趣的:(知识点,javascript,前端,开发语言)