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

本文介绍了一个在前端实现批量图片上传并异步处理的解决方案,针对已有的单张图片处理接口,通过Promise.all()确保所有图片处理完毕后再进行下一步操作,同时在前端以列表形式实时更新每个文件的上传状态,提供友好的用户体验。在处理过程中,根据文件状态显示不同的提示信息,如加载中、成功或失败,确保用户能够清晰了解上传进度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

问题描述

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

解决方案

现在的实现方式是,做一个列表,每次选完文件后,展示这个列表,并给每个文件一个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>
1.页面结构搭建: 首页:设计一个简洁的导航栏,包含 “录入试题”“管理试题”“导出试题” 等主要功能入口。 录入试题页面: 1.使用 Vue.js 的组件化思想,为每种题型(单选题、判断题、填空题、编程题、简答题)创建单独的录入表单组件。例如,单选题表单包含题目内容输入框、多个选项输入框、正确答案选择框、答案解析输入框等。 2.表单使用 Element - UI 的表单组件进行布局和样式设计,确保输入框、选择框等元素排列整齐,具有良好的视觉效果。并为每个输入框添加必要的表单验证,如题目内容不能为空、单选题选项至少有两个等。 管理试题页面: 1.设计一个表格,展示所有已录入试题的关键信息,如题型、题目内容、创建时间等。使用 Element - UI 的表格组件实现,该组件支持排序、筛选等功能,方便用户查找特定试题。 2.为每一行数据添加操作按钮,包括 “查看详情”“编辑”“删除”。点击 “查看详情” 按钮弹出对话框,展示该试题的完整信息,包括所有选项、正确答案、答案解析等;“编辑” 按钮则跳转到对应的录入表单页面,并预先填充好该试题的已有数据,方便用户修改;“删除” 按钮点击后弹出确认框,确认后从数据库中删除该试题(这里只是前端触发删除操作,实际删除需与后端交互,后续前后端集成时实现)。 导出试题页面: 1.创建一个简单的页面,包含导出格式选择(Word 和 Excel)以及导出范围选择(全部试题或自定义筛选条件后的试题)的功能。提供一个按钮,点击后根据用户选择的格式和范围进行试题导出。 2.交互逻辑实现: 录入功能:用户在录入表单中输入数据并点击 “提交” 按钮后,使用 JavaScript 收集表单数据,进行前端验证(如格式是否正确、必填项是否填写等)。验证通过后,将数据以合适的格式(如 JSON)暂存(实际发送到后端保存将在前后端集成时完成),并弹出提示框告知用户录入成功。 增删查改功能: 1.查询:在管理试题页面的表格上方添加搜索框,用户输入关键词后,通过 JavaScript 过滤表格数据,实时展示符合条件的试题。同时,利用表格自带的排序和筛选功能,实现多条件查询。 2.新增:点击 “录入试题” 导航进入录入页面,用户录入数据提交后,在管理试题页面的表格中实时更新显示新录入的试题(模拟数据更新,实际更新需与后端交互)。 3.修改:用户在编辑页面修改数据并提交后,同样在管理试题页面实时更新该试题的显示信息(模拟数据更新)。 4.删除:点击 “删除” 按钮并确认后,从管理试题页面的表格中移除该试题记录(模拟数据删除)。 导出功能: 1.Excel 导出:用户在导出试题页面选择 Excel 格式并点击导出按钮后,使用 XLSX.js 库将暂存的试题数据(或从后端获取的最新数据,前后端集成后实现)转换为 Excel 文件格式,并触发浏览器下载。 2.Word 导出:选择 Word 格式并点击导出按钮后,利用 Docxtemplater 库结合预先设计好的 Word 模板,将试题数据填充到模板中生成 Word 文件。通过调用 Pandoc 工具将生成的临时文件转换为最终的 Word 文件,并触发浏览器下载。
最新发布
04-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值