elUpload上传失败之后重试

390 阅读6分钟

elUpload上传失败之后重试

背景

在公司项目中图片上传组件用户不少,每次使用都需要重新写一次,非常不好维护,前段公司将华为云​存储和七牛云​存储切换到火山引擎​,正好趁此机会将图片上传进行了 封装。

在需要使用的地方直接调用组件就可以了

封装组件

<template>
  <div>
    <el-upload
      class="volcanoUpImg"
      :http-request="UploadImage"
      v-bind="$attrs"
      ref="upload"
    >
      <slot></slot>
​
      <template name="default">
        <template slot="default"> </template>
      </template>
      <!-- 文件列表插槽 -->
      <template slot="file" slot-scope="{ file }" v-if="isCustomFileSlot">
        <slot name="file" :scope="file" class="file-item-slot"></slot>
      </template>
​
      <div slot="tip" class="file-item">
        <slot name="tip" class="file-item-slot"></slot>
      </div>
    </el-upload>
  </div>
</template><script>
import "./tos.umd.production.min.js";
export default {
  name: "volcanoUpImg",
  data() {
    return {
      // cdnUrl: "https://pic-vol.aiwei365.com/",
    };
  },
  props: {
    region: {
      type: String,
      default: "cn-beijing",
    },
    // 上传类型  pic 图片   file 文件  video 音视频
    type: {
      type: String,
      default: "pic",
    },
    suffix: {
      type: String,
      default: ".jpg",
    },
    // 自定义文件插槽
    isCustomFileSlot: {
      type: Boolean,
      default: true,
    },
    // 是否限制上传大小
    isLimitSize: {
      type: Boolean,
      default: false,
    },
    // 上传大小限制
    maxSize: {
      type: Number,
      default: 2 * 1024 * 1024,
    },
    // 大小超出提示文字
    maxSizeText: {
      type: String,
      default: "上传失败,请上传2M以内的图片",
    },
    // 是否限制类型
    isLimitType: {
      type: Boolean,
      default: false,
    },
    // 上传类型
    acceptList: {
      type: Array,
      default: () => ["jpg", "png", "jpeg"],
    },
  },
  methods: {
    // 自定义上传
    async UploadImage(param) {
      console.log("file: index.vue:42 ~ param:", param);
      if (this.isLimitSize && param.file.size > this.maxSize) {
        this.$message.error(this.maxSizeText);
        this.$refs.upload.clearFiles();
        return;
      }
      let suffix = param.file.name.slice(param.file.name.lastIndexOf(".") + 1);
      console.log("file: index.vue:92 ~ suffix:", suffix);
      if (this.isLimitType && !this.acceptList.includes(suffix)) {
        this.$message.error(`请上传${this.acceptList.join("、")}格式的文件`);
        this.$refs.upload.clearFiles();
        return;
      }
      // 初始化实例
      const client = new TOS({
        // 从 STS 服务获取的临时访问密钥 AccessKeyId
        accessKeyId: "XXXXXXXXXXX",
        // 从 STS 服务获取的临时访问密钥 AccessKeySecret
        accessKeySecret:"XXXXXXXXX",
        // 填写 Bucket 所在地域。以华北2(北京)为例,Region 填写为cn-beijing
        // https://www.volcengine.com/docs/6349/107356
        region: this.region, //cn-beijing
        // 填写 Bucket 名称
        bucket: `aw-${this.type}`, //aw-pic
      });
​
      // 判断是否自定义上传路径,在文件 file对象中的 uploadPath
      let uploadPath = "";
      if (param.file.uploadPath) {
        uploadPath = JSON.parse(JSON.stringify(param.file.uploadPath));
        delete param.file.uploadPath;
      }
​
      //   创建文件名称
      const imgKeys = uploadPath
        ? uploadPath
        : "web/" + this.getkey("infoPic", "") + this.suffix;
      console.log("file: index.vue:119 ~ this.uploadPath:", param);
​
      //   上传文件
      const result = await client.putObject({
        key: imgKeys, //文件名称
        body: param.file, //文件对象
        // headers,
      });
      console.log("file: index.vue:64 ~ result:", result);
      try {
        let url = `https://${this.type}-vol.aiwei365.com/` + imgKeys;
        // 返回上传图片的url
        this.$emit("getUpUrl", url);
        // 返回文件对象和url
        this.$emit("getUpData", { ...param, url });
      } catch (error) {
        console.log("file: index.vue:62 ~ error:", error);
      }
    },
    getkey(tag, aim) {
      var date = new Date();
      var result = tag + "/";
      result += aim + date.valueOf() + this.MathRand(6);
      return result;
    },
    MathRand(n) {
      var Num = "";
      for (var i = 0; i < n; i++) {
        Num += Math.floor(Math.random() * 10);
      }
      return Num;
    },
  },
};
</script><style></style>

需求

现在有一个需求,需要手动上传图片,并且图片上传失败之后能够点击按钮进行重新上传

拷贝一份封装的上传组件进行修改

<template>
  <div>
    <el-upload
      class="volcanoUpImg"
      :http-request="UploadImage"
      v-bind="$attrs"
      ref="upload"
    >
      <template slot="trigger">
        <slot name="trigger"> </slot>
      </template>
​
      <slot></slot>
​
      <!-- 文件列表插槽 -->
      <template slot="file" slot-scope="{ file }" v-if="isCustomFileSlot">
        <slot name="file" :scope="file" class="file-item-slot"></slot>
      </template>
​
      <div slot="tip" class="file-item">
        <slot name="tip" class="file-item-slot"></slot>
      </div>
    </el-upload>
  </div>
</template><script>
import "./tos.umd.production.min.js";
export default {
  name: "volcanoUpImgManual",
  data() {
    return {
      // cdnUrl: "https://pic-vol.aiwei365.com/",
    };
  },
  props: {
    region: {
      type: String,
      default: "cn-beijing",
    },
    // 上传类型  pic 图片   file 文件  video 音视频
    type: {
      type: String,
      default: "pic",
    },
    suffix: {
      type: String,
      default: ".jpg",
    },
    // 自定义文件插槽
    isCustomFileSlot: {
      type: Boolean,
      default: true,
    },
    // 是否限制上传大小
    isLimitSize: {
      type: Boolean,
      default: false,
    },
    // 上传大小限制
    maxSize: {
      type: Number,
      default: 2 * 1024 * 1024,
    },
    // 大小超出提示文字
    maxSizeText: {
      type: String,
      default: "上传失败,请上传2M以内的图片",
    },
    // 是否限制类型
    isLimitType: {
      type: Boolean,
      default: false,
    },
    // 上传类型
    acceptList: {
      type: Array,
      default: () => ["jpg", "png", "jpeg"],
    },
  },
  methods: {
    // 自定义上传
    async UploadImage(param) {
​
​
      if (this.isLimitSize && param.file.size > this.maxSize) {
        this.$message.error(this.maxSizeText);
        this.$refs.upload.clearFiles();
        return;
      }
      let suffix = param.file.name.slice(param.file.name.lastIndexOf(".") + 1);
      console.log("file: index.vue:92 ~ suffix:", suffix);
      if (this.isLimitType && !this.acceptList.includes(suffix)) {
        this.$message.error(`请上传${this.acceptList.join("、")}格式的文件`);
        this.$refs.upload.clearFiles();
        return;
      }
      // 初始化实例
      const client = new TOS({
        // 从 STS 服务获取的临时访问密钥 AccessKeyId
        accessKeyId: "XXXXXX",
        // 从 STS 服务获取的临时访问密钥 AccessKeySecret
        accessKeySecret:"XXXXXXXX",
        // 填写 Bucket 所在地域。以华北2(北京)为例,Region 填写为cn-beijing
        // https://www.volcengine.com/docs/6349/107356
        region: this.region, //cn-beijing
        // 填写 Bucket 名称
        bucket: `aw-${this.type}`, //aw-pic
      });
​
      // 判断是否自定义上传路径,在文件 file对象中的 uploadPath
      let uploadPath = "";
      if (param.file.uploadPath) {
        uploadPath = JSON.parse(JSON.stringify(param.file.uploadPath));
        delete param.file.uploadPath;
      }
​
      //   创建文件名称
      const imgKeys = uploadPath
        ? uploadPath
        : "web/" + this.getkey("infoPic", "") + this.suffix;
      console.log("file: index.vue:119 ~ this.uploadPath:", param);
​
      //   上传文件
      const result = await client.putObject({
        key: imgKeys, //文件名称
        body: param.file, //文件对象
        // headers,
      });
      console.log("file: index.vue:64 ~ result:", result);
      try {
        let url = `https://${this.type}-vol.aiwei365.com/` + imgKeys;
        // 返回上传图片的url
        this.$emit("getUpUrl", url);
        let urlData = {
          ...param,
          name: param.file.name,
          url,
        };
        console.log("file: manual.vue:137 ~ urlData:", urlData);
​
        // 返回文件对象和url
        this.$emit("getUpData", urlData);
      } catch (error) {
        console.log("file: index.vue:62 ~ error:", param);
      }
    },
    getkey(tag, aim) {
      var date = new Date();
      var result = tag + "/";
      result += aim + date.valueOf() + this.MathRand(6);
      return result;
    },
    MathRand(n) {
      var Num = "";
      for (var i = 0; i < n; i++) {
        Num += Math.floor(Math.random() * 10);
      }
      return Num;
    },
  },
};
</script><style></style>

修改组件

  • 去掉default​插槽,原因没啥用处,原组件不修改
  • 修改组件的名称
  • 修改组件返回的内容,之前返回了params​和图片url​,新增一个图片的名称

在代码中引入该组件

<volcanoUpImgManual
        v-if="isReload"
        :auto-upload="false"
        accept=".jpg,.png,.jpeg"
        class="upload upload_EBD0038028644B59A6FDA2137AE6B0DA"
        ref="uploadImage"
        :on-exceed="handleExceed"
        action="string"
        :isCustomFileSlot="false"
        :on-success="uploadSuccess"
        :on-error="uploadError"
        :before-remove="beforeRemove"
        :multiple="true"
        :limit="500"
        :file-list="fileList"
        @getUpData="getUpData"
      >
        <template slot="trigger">
          <el-button size="small" :disabled="isUpload">请选择</el-button>
        </template>
        <el-button
          size="small"
          type="primary"
          :disabled="isUpload"
          @click="selectFile"
          >开始上传</el-button
        >
        <el-button
          size="small"
          type="primary"
          @click="saveToAlbum"
          v-if="isUploadFinish"
          >保存至相册</el-button
        >
​
        <el-button
          size="small"
          type="primary"
          @click="retry"
          v-if="isUploadFinish && uploadFailNum"
          >重试</el-button
        >
​
        <template slot="tip">
          <div class="tips">请上传jpg、png、jpeg格式,最多500张图片</div>
          <div class="d-flex uploadStatus" v-if="hideUpload">
            <div class="success">上传成功:{{ uploadSuccessNum }}</div>
            <div class="fail">上传失败:{{ uploadFailNum }}</div>
          </div>
        </template>
      
      </volcanoUpImgManual>
  • 通过getUpData​获取上传之后的信息并插入到图片列表中,

        getUpData(data) {
          this.fileList.push(data);
          this.uploadSuccessNum++;
          this.uploadComplete();
          // 删除失败的数据
          if (this.isReloadUp) {
            this.errorList = this.errorList.filter((e) => !e.name !== data.name);
            this.uploadFailNum--;
          }
        },
    
  • 通过uploadSuccess​获取并设置上传成功的张数

        // 上传成功
        uploadSuccess(file, fileList) {
          this.uploadSuccessNum++;
          this.uploadComplete();
        },
    
  • 通过uploadError​获取失败的数据,并设置失败的张数,重点是将文件转成base64​图片,将失败的图片还需要单独上传到一个变量中保存

        // 上传失败
        async uploadError(err, file, fileList) {
          // 上传失败之后将文件主体 file.raw 转成 base64图片,并且将数据保存到变量中
          let base64Img = await fileToBase64(file.raw);
          let uuid = getUUID();
          let errorFile = {
            data: base64Img, //图片
            name: file.name, //图片名称
            status: "error", //状态
            type: file.raw.type, //图片类型
            UUID: uuid,
          };
          // 插入到变量中
          this.fileList.push(errorFile);
          this.errorList.push(errorFile);
          this.uploadFailNum++;
          this.uploadComplete();
        },
    

再点击上传按钮的时候需要获取到需要上传的图片数量

// 开始上传
    selectFile() {
      let dom = document.querySelectorAll(".el-upload-list");
      if (dom && dom[0] && dom[0].children) {
        this.selectImgNum = dom[0].children.length;
      }
      // 开始校验
    
      if (this.selectImgNum < 1) {
        this.$message({
          type: "error",
          message: "请先选择资源",
        });
        return;
      }
​
      this.isUpload = true;
​
      this.$refs.uploadImage.$refs.upload.submit();
      let _this = this;
      this.$nextTick(() => {
        _this.hideUpload = true;
      });
    },

在成功和失败的时候需要判断是否已经全部执行过上传,如果成功的张数和失败的张数之和是张数的综合,那么就会出现保存到相册,如果有失败的张数,就会出现重试按钮

    // 判断是否全部上传
    uploadComplete() {
      if (this.uploadSuccessNum + this.uploadFailNum == this.selectImgNum) {
        let _this = this;
        this.$nextTick(() => {
          _this.isUploadFinish = true;
        });
      }
    },

点击重试按钮,将展示的图片列表fileList​中去除上传失败的图片,通过UUID​进行判断,便利上传失败的图片列表errorList​得到每一项数据,将数据中的base64​图片转成file​文件,按照element ui​上传的格式组装上传对象,直接通过组件的ref​调用组件内部的上传方法。设置isReloadUp​的作用是上传成功之后去掉失败图片列表中的数据并修改失败的张数数量

retry() {
      this.isReloadUp = true;
      this.fileList = this.fileList.filter((e) => !e.UUID);
      // 遍历上传失败的图片变量列表
      this.errorList.map((item) => {
        let fileObj = item;
        // 价格base64图片转成file文件
        let file = base64ToFile(fileObj.data, fileObj.name, fileObj.type);
        // 这是上传的对象,固定的格式
        let upDataObj = {
          action: "string",
          filename: "file",
          headers: {},
          withCredentials: false,
          file,
        };
        // 调用子组件里面的上传
        this.$refs.uploadImage.UploadImage(upDataObj);
      });
    },

思路:

  1. 上传失败之后将上传失败的图片分别插入到展示列表和失败列表,
  2. 对插入的数据添加UUID​,将文件转成base64​存储到变量中
  3. 点击重试的时候,开启重试状态,在getUpData​函数中会减小失败图片的张数;将失败的图片在展示列表中过滤掉,遍历失败数据,将数据中的base64​图片转成file​文件,拼装上传的对象,然后调用组件中的上传函数

完整代码

<template>
  <div class="app-container">
    <el-row class="border-bottom p-b-10">
      <span>
        <span class="blue-tips"></span>
        <span style="vertical-align: -8px">图片上传</span>
      </span>
    </el-row>
    <div class="albumRow">
      <div class="label">选择相册:</div>
      <el-select
        v-model="teacherUuid"
        class="teacherUuid"
        placeholder="选择教师"
        clearable
        filterable
        size="mini"
        no-match-text="暂无教师"
        no-data-text="暂无教师"
        @change="teacherChange"
        :disabled="isUpload"
      >
        <el-option
          v-for="item in teacherList"
          :key="item.teacherUuid"
          :label="item.teacherName"
          :value="item.teacherUuid"
        >
        </el-option>
      </el-select>
      <el-select
        v-model="albumUuid"
        class="albumUuid"
        placeholder="选择相册"
        clearable
        filterable
        size="mini"
        no-match-text="暂无相册"
        :no-data-text="teacherUuid ? '暂无相册' : '请先选择教师'"
        :disabled="isUpload"
      >
        <el-option
          v-for="item in albumList"
          :key="item.photoAlbumUuid"
          :label="item.photoAlbumName"
          :value="item.photoAlbumUuid"
        >
        </el-option>
      </el-select>
      <el-button type="text" size="mini" @click="addAlbum" :disabled="isUpload"
        >新增相册</el-button
      >
    </div>
    <div class="d-flex selectFile">
      <div class="label">选择文件:</div>

      <volcanoUpImgManual
        v-if="isReload"
        :auto-upload="false"
        accept=".jpg,.png,.jpeg"
        class="upload upload_EBD0038028644B59A6FDA2137AE6B0DA"
        ref="uploadImage"
        :on-exceed="handleExceed"
        action="string"
        :isCustomFileSlot="false"
        :on-success="uploadSuccess"
        :on-error="uploadError"
        :before-remove="beforeRemove"
        :multiple="true"
        :limit="500"
        :file-list="fileList"
        @getUpData="getUpData"
      >
        <template slot="trigger">
          <el-button size="small" :disabled="isUpload">请选择</el-button>
        </template>
        <el-button
          size="small"
          type="primary"
          :disabled="isUpload"
          @click="selectFile"
          >开始上传</el-button
        >
        <el-button
          size="small"
          type="primary"
          @click="saveToAlbum"
          v-if="isUploadFinish"
          >保存至相册</el-button
        >

        <el-button
          size="small"
          type="primary"
          @click="retry"
          v-if="isUploadFinish && uploadFailNum"
          >重试</el-button
        >

        <template slot="tip">
          <div class="tips">请上传jpg、png、jpeg格式,最多500张图片</div>
          <div class="d-flex uploadStatus" v-if="hideUpload">
            <div class="success">上传成功:{{ uploadSuccessNum }}</div>
            <div class="fail">上传失败:{{ uploadFailNum }}</div>
          </div>
        </template>
      
      </volcanoUpImgManual>
    </div>

    <!-- 新增相册弹框 -->
    <el-dialog
      title="新增相册"
      :visible.sync="isAddAlbum"
      width="40%"
      center
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      @close="closeDialog"
    >
      <div class="d-flex albumOwnership">
        <div class="label">相册归属:</div>
        <el-select
          v-model="dialogData.teacherUuid"
          class="teacherUuid"
          placeholder="选择教师"
          clearable
          filterable
          size="mini"
          no-match-text="暂无教师"
          no-data-text="暂无教师"
          @change="teacherChangeAdd"
        >
          <el-option
            v-for="item in teacherList"
            :key="item.teacherUuid"
            :label="item.teacherName"
            :value="item.teacherUuid"
          >
          </el-option>
        </el-select>
        <el-select
          v-model="dialogData.classId"
          class="classId"
          placeholder="选择班级"
          clearable
          filterable
          size="mini"
          no-match-text="暂无班级"
          :no-data-text="dialogData.teacherUuid ? '暂无班级' : '请先选择教师'"
        >
          <el-option
            v-for="item in classList"
            :key="item.cid"
            :label="item.className"
            :value="item.cid"
          >
          </el-option>
        </el-select>
      </div>
      <div class="d-flex albumName">
        <div class="label">相册名:</div>
        <el-input
          class="albumNameInp"
          v-model.trim="dialogData.albumName"
          placeholder="请输入相册名"
          size="mini"
          clearable
          maxlength="20"
        ></el-input>
      </div>
      <span slot="footer">
        <el-button @click="closeDialog">取消</el-button>
        <el-button type="primary" @click="saveAlbum">确认</el-button>
      </span>
    </el-dialog>
    <!-- 提示弹窗 -->
    <el-dialog
      title="提示"
      :visible.sync="hintDialog"
      width="30%"
      center
      @close="closeHintDialog"
    >
      <div class="hint">
        请确认是否忽略上传失败的{{ uploadFailNum }}张图片,直接保存上传成功的{{
          uploadSuccessNum
        }}张图片至相册?
      </div>
      <span slot="footer">
        <el-button @click="closeHintDialog">取消</el-button>
        <el-button type="primary" @click="sureSave">确认</el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
import { default as api } from "@/utils/api";
import { default as apiJson } from "@/utils/apijd";
import { base64ToFile, fileToBase64, getUUID } from "@/utils/publicMethod";
export default {
  data() {
    return {
      // 是否重试
      isReloadUp: false,
      isReload: true,
      // 选择教师
      teacherUuid: null,
      // 教师列表
      teacherList: [],
      // 选择相册
      albumUuid: null,
      // 相册列表
      albumList: [],
      // 是否开始上传
      isUpload: false,

      // 上传的文件列表
      fileList: [],
      // 选择的图片数量
      selectImgNum: 0,
      // 是否显示状态
      hideUpload: false,
      // 是否上传完成
      isUploadFinish: false,
      // 上传成功
      uploadSuccessNum: 0,
      // 上传失败
      uploadFailNum: 0,
      // 是否新增相册
      isAddAlbum: false,
      // 弹框的数据
      dialogData: {
        // 教师
        teacherUuid: null,
        // 相册名称
        albumName: null,
        // 班级id
        classId: null,
      },
      // 班级列表
      classList: [],
      // 上传失败的文件列表
      errorList: [],
      // 是否打开弹框
      hintDialog: false,
    };
  },
  mounted() {
    // 获取教师列表
    this.getTeacherList();
  },
  methods: {
    // 获取教师列表
    getTeacherList() {
      api({
        url: "/teacher/getAll",
        method: "post",
        data: {},
      }).then((data) => {
        if (data.result) {
          this.teacherList = data.result;
        }
      });
    },
    // 获取该教师的相册
    teacherChange(val) {
      let _this = this;
      if (val) {
        this.$nextTick(() => {
          _this.getAlbumList();
        });
      } else {
        this.albumList = [];
        this.albumUuid = null;
      }
    },
    // 获取相册
    getAlbumList() {
      let params = {
        teacherUuid: this.teacherUuid,
      };
      api({
        url: "/album/getTeacherAlbumList",
        method: "get",
        params,
      }).then((data) => {
        if (data.result) {
          this.albumList = data.result;
        }
      });
    },

    // 开始上传
    selectFile() {
      let dom = document.querySelectorAll(".el-upload-list");
      if (dom && dom[0] && dom[0].children) {
        this.selectImgNum = dom[0].children.length;
      }
      // 开始校验
      if (!this.teacherUuid) {
        this.$message({
          type: "error",
          message: "请选择教师",
        });
        return;
      }
      if (!this.albumUuid) {
        this.$message({
          type: "error",
          message: "请选择相册",
        });
        return;
      }
      if (this.selectImgNum < 1) {
        this.$message({
          type: "error",
          message: "请先选择资源",
        });
        return;
      }

      this.isUpload = true;

      this.$refs.uploadImage.$refs.upload.submit();
      let _this = this;
      this.$nextTick(() => {
        _this.hideUpload = true;
      });
    },

    getUpData(data) {
      this.fileList.push(data);
      this.uploadSuccessNum++;
      this.uploadComplete();
      // 删除失败的数据
      if (this.isReloadUp) {
        this.errorList = this.errorList.filter((e) => !e.name !== data.name);
        this.uploadFailNum--;
      }
    },

    // 超出上传限制
    handleExceed() {
      this.$message({
        type: "warning",
        message: "最多上传500张图片",
      });
    },
    // 上传成功
    uploadSuccess(file, fileList) {
      this.uploadSuccessNum++;
      this.uploadComplete();
    },

    // 上传失败
    async uploadError(err, file, fileList) {
      // 上传失败之后将文件主体 file.raw 转成 base64图片,并且将数据保存到变量中
      let base64Img = await fileToBase64(file.raw);
      let uuid = getUUID();
      let errorFile = {
        data: base64Img, //图片
        name: file.name, //图片名称
        status: "error", //状态
        type: file.raw.type, //图片类型
        UUID: uuid,
      };
      // 插入到变量中
      this.fileList.push(errorFile);
      this.errorList.push(errorFile);
      this.uploadFailNum++;
      this.uploadComplete();
    },
    // 判断是否全部上传
    uploadComplete() {
      if (this.uploadSuccessNum + this.uploadFailNum == this.selectImgNum) {
        let _this = this;
        this.$nextTick(() => {
          _this.isUploadFinish = true;
        });
      }
    },
    // 删除之前
    beforeRemove(file, fileList) {
      if (file.status === "success" || file.status === "error") {
        return false;
      }
    },
    // 保存到相册
    saveToAlbum() {
      if (this.uploadFailNum > 0) {
        this.hintDialog = true;
      } else {
        this.sureSave();
      }
    },
    // 关闭提示弹窗
    closeHintDialog() {
      this.hintDialog = false;
    },
    // 确定保存
    sureSave() {
      let params = {
        teacherUuid: this.teacherUuid,
        albumUuid: this.albumUuid,
        picList: this.fileList.filter((i) => i.url).map((e) => e.url),
      };
      apiJson({
        url: "/album/uploadPhotos",
        method: "post",
        data: params,
      }).then((data) => {
        if (data.status === 0) {
          this.$message.success("保存成功");
          this.closeHintDialog();
          // 刷新的当前页面
          this.isReload = false;
          let _this = this;
          this.$nextTick(() => {
            _this.isReload = true;
            _this.teacherUuid = null;
            _this.albumUuid = null;
            _this.albumList = null;
            _this.isUpload = false;
            _this.fileList = [];
            _this.selectImgNum = 0;
            _this.hideUpload = false;
            _this.isUploadFinish = false;
            // 上传成功
            _this.uploadSuccessNum = 0;
            // 上传失败
            _this.uploadFailNum = 0;
          });
        }
      });
    },

    // 重试
    retry() {
      this.isReloadUp = true;
      this.fileList = this.fileList.filter((e) => !e.UUID);
      // 遍历上传失败的图片变量列表
      this.errorList.map((item) => {
        let fileObj = item;
        // 价格base64图片转成file文件
        let file = base64ToFile(fileObj.data, fileObj.name, fileObj.type);
        // 这是上传的对象,固定的格式
        let upDataObj = {
          action: "string",
          filename: "file",
          headers: {},
          withCredentials: false,
          file,
        };
        // 调用子组件里面的上传
        this.$refs.uploadImage.UploadImage(upDataObj);
      });
    },

    // 新增相册
    addAlbum() {
      this.isAddAlbum = true;
      this.getTeacherList();
    },
    // 关闭弹框
    closeDialog() {
      this.isAddAlbum = false;
      this.dialogData = {
        // 教师
        teacherUuid: null,
        // 相册名称
        albumName: null,
        // 班级id
        classId: null,
      };
    },
    // 保存相册
    saveAlbum() {
      if (!this.dialogData.teacherUuid) {
        this.$message({
          type: "error",
          message: "请选择教师",
        });
      } else if (!this.dialogData.classId) {
        this.$message({
          type: "error",
          message: "请选择班级",
        });
      } else if (!this.dialogData.albumName) {
        this.$message({
          type: "error",
          message: "请输入相册名称",
        });
      } else {
        let params = JSON.parse(JSON.stringify(this.dialogData));
        api({
          url: "/album/createAlbum",
          method: "post",
          params,
        }).then((data) => {
          if (data.status === 0) {
            this.$message.success("新增成功");
            this.closeDialog();
            if (this.teacherUuid) {
              this.getAlbumList();
            }
          } else {
            this.$message({
              type: "error",
              message: data.message,
            });
          }
        });
      }
    },
    // 获取该教师的相册
    teacherChangeAdd(val) {
      let _this = this;
      if (val) {
        this.$nextTick(() => {
          _this.getClassList();
        });
      } else {
        this.classList = [];
        this.dialogData.classId = null;
      }
    },
    // 获取班级
    getClassList() {
      let params = {
        teacherUuid: this.dialogData.teacherUuid,
      };
      api({
        url: "/teacher/getTeacherAllClasses",
        method: "get",
        params,
      }).then((data) => {
        if (data.result) {
          this.classList = data.result;
        }
      });
    },
  },
};
</script>

<style lang="scss" scoped>
.d-flex {
  display: flex;
  align-items: center;
}
.label {
  &::before {
    content: "*";
    color: red;
  }
}
.albumRow {
  display: flex;
  align-items: center;
  margin: 20px 0;

  .albumUuid {
    margin: 0 20px;
  }
  .addAlbum {
    font-size: 16px;
    color: #409eff;
    cursor: pointer;
  }
}
.selectFile {
  align-items: flex-start;
  .label {
    height: 32px;
    line-height: 32px;
  }
  .tips {
    color: #ccc;
    margin: 5px 0;
  }
}

// 弹框
.albumOwnership {
  margin-bottom: 20px;
  .label {
    width: 80px;
  }
  .teacherUuid {
    margin-right: 10px;
  }
}
.albumName {
  .label {
    width: 80px;
  }
  .albumNameInp {
    width: 200px;
  }
}
</style>
<style lang="scss">
.upload_EBD0038028644B59A6FDA2137AE6B0DA {
  .is-success {
    .el-icon-close,
    .el-icon-close-tip {
      display: none !important;
    }
    .el-icon-upload-success {
      display: inline-block;
      &::before {
        display: inline-block;
      }
    }
    .el-upload-list__item-status-label {
      display: inline-block !important;
    }
    .el-icon-circle-check:before {
      content: "成功" !important;
      display: inline-block;
    }
  }
  .is-error {
    .el-upload-list__item-name {
      color: red;
    }
    .el-upload-list__item-status-label {
      display: inline-block;
      .el-icon-upload-success::before {
        content: "失败" !important;
        color: red;
      }
    }
    .el-icon-close,
    .el-icon-close-tip {
      display: none !important;
    }
    .el-icon-upload-success {
      display: inline-block;
      &::before {
        display: inline-block;
      }
    }
  }
  .el-upload-list {
    height: calc(100vh - 300px);
    overflow-y: auto;
  }
}
</style>

用到的公共函数

// 获取uuid
export const getUUID = (isLink = false) => {
  let str = isLink
    ? "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx"
    : "xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx";
  return str.replace(/[xy]/g, (c) => {
    return (c === "x" ? (Math.random() * 16) | 0 : "r&0x3" | "0x8").toString(
      16
    );
  });
};
// Base64 转 ArrayBuffer
export const base64ToBuffer = (base64String) => {
  const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
  const base64 = (base64String + padding)
    .replace(/-/g, "+")
    .replace(/_/g, "/");
  const rawData = window.atob(base64);
  const outputArray = new Uint8Array(rawData.length);
  for (let i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
};
​
// 一个文件的纯base64,无头部信息,文件格式需要指定,文件名需要带上扩展名。普遍适用
export const base64ToFile = function (base64, filename, type) {
  if (typeof base64 != "string" || !filename || !type) {
    console.log("参数不正确");
    return;
  }
  const u8arr = base64ToBuffer(base64);
  return new File([u8arr], filename, {
    type: type,
  });
};
​
// File 转 Base64export const fileToBase64 = (file) => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = function() {
      const base64Data = reader.result.split(',')[1]; // 去除"data:image/*;base64,"前缀
      resolve(base64Data);
    };
    reader.onerror = reject;
    reader.readAsDataURL(file); // 读取文件为DataURL,即Base64编码形式
  });
}