前端处理excel数据上传
- 16 Nov, 2023

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