在已有异步接口且每次只能处理一个文件的前提下,一种前端批量导入的处理方式

在实际开发中,经常会遇到一些文件上传处理,但往往还会伴随一些上传后的异步处理以及异步条件下批量处理。在解决这种矛盾事件时,会有点头痛。

问题描述

本人在开发时有一个需求,实现上传图片,提取图片信息,并自动推送图片信息到数据库,这本来并不复杂,但是加了前提条件,就有些头痛了。现在的情况是,提取图片并推送图片已有现成的接口(不可以更改接口,这是一个公用的接口,不会因为需求随便变动),但是该接口每次只能处理一张图片,且提取完成后接口会自动推送图片信息到数据库,前端只需在上传成功后刷新页面即可。但问题是,本来接口提取的成功率就不是很大,现在需求方又要求必须支持批量导入,其实导入方面也是没什么问题,问题就在每次导入成功或失败后前端如何友好的提示,接口每完成一次前端提示一次,这明显使用起来体验不是很好。

解决方案

现在的实现方式是,做一个列表,每次选完文件后,展示这个列表,并给每个文件一个loading状态,在相应的文件处理完后,更改其状态,在列表中所有的文件都处理完后,通过Promise.all()在进行下一步处理,这样解决了前端频繁弹出提示框的问题。效果图类似如下(示意效果没有做过多的css处理):
在这里插入图片描述

实现这种效果很简单,知识点就一点就是 Promise.all()的应用。

实现方式(Vue)

首先通过input标签创建出类型为file的文件上传按钮

 <input type="file" multiple accept="image/*, .pdf" @change="getFileList"/>

multiple属性控制是否支持多选,accept控制文件的类型(image/* 所有的图片类型)change事件
其次展示列表
**注:**如果想使用谷歌的icon需要在最开始的index.html中引入,在main.js中引用也可以,需要export导出

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">

想实现loading动画请参考:css实现loading动画
列表:

<div class="listarea">
      <!-- 通过for循环循环列表 -->
      <template v-for="(item, i) in fileList">
        <div class="filelist" :key="i">
          <div class="file" style="width: 300px">
            {{ item.name }}
          </div>
          <div class="file">{{ item.type }}</div>
          <div class="file">{{ item.size }}</div>
           <!-- 通过文件的状态判断显示,这里loading效果可以用文字表示,我上图的loading效果是通过css动画单独写的一个文件 -->
          <div class="file">
            <div v-if="item.status === 'loading'" class="loading-icon">
              <div>
                <loading-icon class="icon" />
              </div>
              <div style="width: 70px">
                <span>正在上传</span>
              </div>
            </div>
            <div
              v-if="item.status === 'success'"
              class="loading-icon"
              style="color: #19be6b"
            >
              <div style="display: table; vertical-align: middle">
                <i class="material-icons" style="font-size: 24px"
                  >check_circle</i
                >
              </div>
              <div style="width: 70px">
                <span>上传成功</span>
              </div>
            </div>
            <div
              v-if="item.status === 'error'"
              class="loading-icon"
              style="color: #ed4014"
            >
              <div style="display: table; vertical-align: middle">
                <i class="material-icons" style="font-size: 24px"
                  >highlight_off</i
                >
              </div>
              <div style="width: 70px">
                <span>上传失败</span>
              </div>
            </div>
          </div>
          <div class="file" style="width: 300px">{{ item.message }}</div>
        </div>
      </template>
    </div>

在js中主要两个函数一个是模拟后端异步接口处理函数,一个是change事件触发函数
异步接口:

 asyncFun(file, callback) {
      console.log(file); // 假设对file文件处理
      const n = Math.floor(Math.random() * 10 + 1); // 生成一个随机数
      // 模拟异步
      setTimeout(() => {
        if (n % 2 === 0) {
          const res = {
            success: true,
            message: "上传成功",
          };
          return callback(res);
        } else {
          const res = {
            success: false,
            message: "上传失败, 错误编码" + n,
          };
          return callback(res);
        }
      }, n * 1000);
    },

change事件触发函数

getFileList(e) {
      this.completeText = "";
      this.fileList = [];
      const files = e.target.files; // 获取文件列表
      if (files.length) {
        let promiseArr = [];
        for (let file of files) {
          const fileObj = {
            name: file.name, // 文件name
            type: file.type, // 文件类型
            size: parseFloat(file.size / 1024 / 1024).toFixed(2) + "MB", // 文件大小
            status: "loading", // 文件状态 人为赋值
            message: "", // 报错信息
          };
          this.fileList.push(fileObj); // 展示列表
          // 异步接收后端接口返回
          let fileUpload = new Promise((resolve) => {
            this.asyncFun(file, (res) => {
              if (res.success) {
                fileObj.status = "success"; // 更改文件上传后状态
                fileObj.message = res.message;
              } else {
                fileObj.status = "error"; // 更改文件上传后状态
                fileObj.message = res.message;
              }
              resolve(true);
            });
          });
          promiseArr.push(fileUpload);
        }
        Promise.all(promiseArr).then(() => {
          // 异步函数处理完下一步处理如:刷新列表
          this.completeText = "上传全部完成";
        });
      }
    },

完整代码:

<template>
  <div class="main">
    <div>
      <input
        type="file"
        multiple
        accept="image/*, .pdf"
        @change="getFileList"
      />
      <span style="color: red">{{ completeText }}</span>
    </div>
    <div class="listarea">
      <!-- <loading-icon class="icon" /> -->
      <template v-for="(item, i) in fileList">
        <div class="filelist" :key="i">
          <div class="file" style="width: 300px">
            {{ item.name }}
          </div>
          <div class="file">{{ item.type }}</div>
          <div class="file">{{ item.size }}</div>
          <div class="file">
            <div v-if="item.status === 'loading'" class="loading-icon">
              <div>
                <loading-icon class="icon" />
              </div>
              <div style="width: 70px">
                <span>正在上传</span>
              </div>
            </div>
            <div
              v-if="item.status === 'success'"
              class="loading-icon"
              style="color: #19be6b"
            >
              <div style="display: table; vertical-align: middle">
                <i class="material-icons" style="font-size: 24px"
                  >check_circle</i
                >
              </div>
              <div style="width: 70px">
                <span>上传成功</span>
              </div>
            </div>
            <div
              v-if="item.status === 'error'"
              class="loading-icon"
              style="color: #ed4014"
            >
              <div style="display: table; vertical-align: middle">
                <i class="material-icons" style="font-size: 24px"
                  >highlight_off</i
                >
              </div>
              <div style="width: 70px">
                <span>上传失败</span>
              </div>
            </div>
          </div>
          <div class="file" style="width: 300px">{{ item.message }}</div>
        </div>
      </template>
    </div>
  </div>
</template>

<script>
import loadingIcon from "./loadingIcon.vue";

export default {
  components: { loadingIcon },
  name: "FileUpload",

  data() {
    return {
      fileList: [],
      completeText: "",
    };
  },

  methods: {
    getFileList(e) {
      this.completeText = "";
      this.fileList = [];
      const files = e.target.files;
      if (files.length) {
        let promiseArr = [];
        for (let file of files) {
          const fileObj = {
            name: file.name,
            type: file.type,
            size: parseFloat(file.size / 1024 / 1024).toFixed(2) + "MB",
            status: "loading",
            message: "",
          };
          this.fileList.push(fileObj);
          let fileUpload = new Promise((resolve) => {
            this.asyncFun(file, (res) => {
              console.log("res", res);
              if (res.success) {
                fileObj.status = "success";
                fileObj.message = res.message;
              } else {
                fileObj.status = "error";
                fileObj.message = res.message;
              }
              resolve(true);
            });
          });
          promiseArr.push(fileUpload);
        }
        Promise.all(promiseArr).then(() => {
          this.completeText = "上传全部完成";
        });
      }
    },

    asyncFun(file, callback) {
      console.log(file);
      const n = Math.floor(Math.random() * 10 + 1);
      setTimeout(() => {
        if (n % 2 === 0) {
          const res = {
            success: true,
            message: "上传成功",
          };
          return callback(res);
        } else {
          const res = {
            success: false,
            message: "上传失败, 错误编码" + n,
          };
          return callback(res);
        }
      }, n * 1000);
    },
  },
};
</script>

<style scoped>
.main {
  width: 1000px;
}
.listarea {
  margin: 8px;
  width: 100%;
  height: 500px;
  border: 1px solid #ccc;
  border-radius: 8px;
  overflow-y: auto;
}
.filelist {
  display: flex;
  justify-content: space-between;
  text-align: left;
}
.filelist .file {
  margin: 8px;
  padding: 8px;
}
.loading-icon {
  display: flex;
  justify-content: space-between;
}
.icon {
  width: 20px;
  height: 20px;
  display: inline-block;
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值