前端处理excel数据上传

前端处理excel数据上传

先看实现代码

const XLSX = require("xlsx");
export class BatchRequest {

  constructor(api) {

    this.file = null;

    this.arr = null; // 从文件中读取的数据

    this.total = 0;

    this.complete = 0; // 已完成数量

    this.error = []; // 失败的信息

    this.success = []; // 成功的信息

    this.reqAPI = api;

    this.isStopped = false; // 用于跟踪进程是否已停止

    this.progressCallbackTimer = null; // 用于保存计时器引用

    this.progressCallbackDelay = 1000; // 进度回调的延迟时间(毫秒)

    this.concurrentRequests = 5; //这个是允许最大并发请求  不添加相关逻辑 会依次执行 效率很低   最大不要超过8

  }

  

  // 新增方法用于停止批处理请求进程 考虑到用户可能不想继续执行了  可以使用这个终止

  stopBatchRequest() {

    this.isStopped = true;

  }

  // 批量请求  这是一个生成器函数

  *batchRequestGenerator() {

    for (const item of this.arr) {

      yield this.sendRequest(item);

    }

  }

  

  async sendRequest(item) {

    try {

      const response = await this.reqAPI(item);

      if (response.code === 200) {

        this.success.push(item);

      } else {

        item.error = response.message;

        this.error.push(item);

      }

  

      return response;

    } catch (error) {

      item.error = error;

      this.error.push(item);

      return error;

    }

  }

  // 这里是核心逻辑

  async startBatchRequest(progressCallback) {

    if (this.arr) {

      // 创建一个过滤后的数据迭代器

      const filteredDataIterator = this.batchRequestGenerator();

  

      // 设置最大并发请求数,如果未设置则默认为 5

      const maxConcurrentRequests = this.concurrentRequests || 5;

  

      // 当前并发请求数

      let concurrentCount = 0;

  

      // 存储请求 Promise

      const requestPromises = [];

  

      for await (const item of filteredDataIterator) {

        // 如果批处理请求已停止,退出循环

        if (this.isStopped) {

          console.log("批处理请求过程已停止。");

          break;

        }

  

        console.log("当前数据:", item);

  

        // 增加已完成请求数

        this.complete++;

  

        if (concurrentCount < maxConcurrentRequests) {

          concurrentCount++;

          console.log("concurrentCount++", concurrentCount);

  

          // 发起请求并将 Promise 添加到 requestPromises

          const requestPromise = this.sendRequest(item);

          requestPromises.push(requestPromise);

  

          // 处理请求成功、失败和进度更新

          requestPromise

            .then(() => {

              concurrentCount--;

              console.log("concurrentCount--", concurrentCount);

            })

            .catch((error) => {

              concurrentCount--;

              console.error("请求错误:", error);

            })

            .finally(() => {

              if (!this.progressCallbackTimer) {

                // 设置一个定时器以触发进度回调

                this.progressCallbackTimer = setTimeout(() => {

                  const progress = this.complete / this.total;

                  progressCallback(progress);

                  // 清除计时器引用

                  this.progressCallbackTimer = null;

                }, this.progressCallbackDelay);

              }

            });

        } else if (concurrentCount >= maxConcurrentRequests) {

          console.log("已达到最大并发请求数");

  

          // 如果达到最大并发请求数,等待最早完成的请求

          await Promise.race(requestPromises);

        }

      }

      // 等待所有请求完成

      await Promise.all(requestPromises);

    } else {

      console.error("数据未加载");

    }

  }

  

  // 读取 excel 文件,返回数据

  async readFile(file) {

    this.file = file;

    try {

      const dataBinary = await this.readBinaryData(file);

      const workBook = XLSX.read(dataBinary, {

        type: "binary",

        cellDates: true,

      });

      const workSheet = workBook.Sheets[workBook.SheetNames[0]];

      /*

      excel 生成的数据不能直接使用  从objectArr转为二维数据  同时清除空数据

      */

      let data = XLSX.utils.sheet_to_json(workSheet);

      data = data.map((item) => {

        var arr = [];

        for (const iterator in item) {

          arr.push(item[iterator]);

        }

        return arr;

      });

      data = data.filter((item) => item[0]);

      this.arr = data;

      this.total = this.arr.length;

      console.log("转换后的json", data);

    } catch (error) {

      console.error("文件读取失败", error);

    }

  }

  

  // 读取二进制数据

  readBinaryData(file) {

    return new Promise((resolve, reject) => {

      const reader = new FileReader();

      reader.readAsBinaryString(file);

      reader.onload = (ev) => {

        resolve(ev.target.result);

      };

      reader.onerror = (error) => {

        reject(error);

      };

    });

  }

  

  // 获取当前完成进度 这里的数据比较多 不适合实时更新 用户点击之后把数据返回渲染

  getProgress(callback) {

    callback({

      progress: this.complete / this.total,

      complete: this.complete,

      success: this.success,

      error: this.error,

      total: this.total,

    });

  }

}

核心思路 通过XLSX读取Excel内容 返回适合js处理的数据 通过生成器函数 创建一个批量请求器 请求依次进入 并将请求结果保存 同时通过外部回调函数传输请求进度 添加getProgress() 方法 方便调用者可以获取任务执行的具体进度信息 stopBatchRequest() 给外部提供一个终止的方法

实际使用中可以将 Excel数据放进一个table中 隔断时间获取实际进度渲染table 错误的table中可以添加 重新请求方法 导出结果(全部、成功、错误) 可以通过进度条的方式增强用户感知

使用场景: 后端处理大excel 单个任务消耗大量服务器性能 同时存在请求时间过长 可能受用户网络影响,导致连接中断,导致结果无法反馈给用户 使用前端处理之后,大任务变成多个小任务,单个失败不会影响整体,同时用户可以明确感知到数据处理的进度,增加用户的耐心。 失败的任务可以单个进行重试,也可以所有失败的结果导出之后重新执行。整体而言,减少了服务器压力,增强用户感受