前言
- 常网IT源码上线啦!
- 本篇录入技术选型专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
- 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
- 接下来想分享一些自己在项目中遇到的技术选型以及问题场景。
每个老人都年轻过,但年轻人从来都没有老过。人在年轻的时候,其实很难意识到,自己老了的时候,是很需要这笔钱的。坐吃山空和每个月都有现金流的安全感,是不一样的。--养老

一、前言
最近在做导出,需要前端做。
使用ExcelJS插件做。
如果还有印象的话,我之前写过一篇 小小导出,我大前端足矣!
但这一次需求升级,导出的样式复杂化了些。
可能有人会问,叫后端做就好啊。
如果后端处理的话,是有一个文件的模板,直接往下面插入数据即可。
假设是动态表头呢?合并表头?
后端人员说:暂时没处理过,就前端了。
让我们来安装他进入正文。
npm install exceljs
二、分析
看看效果。

这种复杂表头,又合并单元格的,需要特殊处理。
我们一步步分析。
先看看数据结构

我认为需要一行行分析,一行行设置样式,设置值。
先看第一行

第一行其实就是表名。
设置值:
java
// 插入一行到指定位置
const rowIndex = 1; // 要插入的行位置
const newRow = worksheet.insertRow(rowIndex);
// 设置新行的单元格值
newRow.getCell(1).value = tableName;
接下来就是合并单元格,其实就是数据结构的field数组的个数
java
worksheet.mergeCells(`A1:${ce[props.length]}1`); // 第一行
而这个ce对应xlsx表格的字母
arduino
const ce = {
1: "A",
2: "B",
3: "C",
4: "D",
};
接下来分析第二行

值很好处理,只需要遍历field,获取label赋值即可
比较麻烦的是合并单元格。

遍历这个组下面的children,如果children下面(上图序号7、8上面的单元格)还有孩子,接着计算数量。
从起点开始:第一次遍历假设到8,那么第二个就要从8开始作为起点
结尾:同理,上一个的结尾(8) + 这一次遍历的节点个数
我们来看看代码
java
let tmp_num = 1; // 记录上一次的结束位置
// 合并单元格(第二行)
props.forEach((p, index) => {
// 结束位置
// 看看这个节点下面的children合计就是结束的位置
const num = p.children.reduce((acc, item) => {
if (!item.children) acc++;
if (item.children) {
acc += item.children.length;
}
return acc;
}, 0);
// 从第二个开始数
if (index != 0) {
// 上一次的 + 这一次的结束位置 = 当前起点
tmp_num += props[index - 1].children.reduce((acc, item) => {
if (!item.children) acc++;
if (item.children) {
acc += item.children.length;
}
return acc;
}, 0);
}
worksheet.mergeCells(`${ce[tmp_num]}2:${ce[tmp_num - 1 + num]}2`); // 第二行合并
worksheet.getCell(`${ce[tmp_num]}2`).value = p.label // 赋值
});
第三行
第三行开始就是地狱模式了,要计算的地方太多了。
如果都是下图的(人口)这种还好,那如果是(简介),他合并是跨行的,又要计算,跨多少行,合并多少列,很复杂的。放弃了。

第三行直接拿末行的数据项。

这个好办,遍历fileld数组,如果有children就把节点存起来。
java
let thirdArr = []
props.forEach((p, index) => {
p.children.reduce((acc, item) => {
if (!item.children) thirdArr.push(item);
if (item.children) {
thirdArr.push(...item.children);
}
return acc;
}, []);
});
接着赋值
java
thirdArr.forEach((f, fIndex) =>{
worksheet.getCell(`${ce[fIndex+1]}3`).value = f.label // 赋值
})
数据项
接着就是看看数据有多少条,往下插入了。
java
// 批量插入数据
// thirdArr是我们表头数据项的数组;data是接口返回的数据数组
data.forEach((item, fIndex) => {
for(let key in item) {
let index = thirdArr.findIndex((f) => f.prop == key)
if (index != -1) {
worksheet.getCell(`${ce[index+1]}${fIndex+4}`).value = item[key] // 赋值
}
}
});
写到这,导出基本效果就有了。

三、细节
比如第三行的宽度,需要根据单元格的内容长度,动态计算宽度。
PS:我是以第三行为例,设置column的宽度
java
// 第三行
thirdArr.forEach((f, fIndex) =>{
worksheet.getCell(`${ce[fIndex+1]}3`).value = f.label // 赋值
// 新增动态列宽设置(根据第三行标题长度)
const colNumber = fIndex + 1;
const column = worksheet.getColumn(colNumber);
const textLength = f.label.length;
// 计算规则:中文按2字符宽度计算,最小8,最大30
const chineseChars = (f.label.match(/[\u4e00-\u9fa5]/g) || []).length;
const width = Math.min(Math.max(
(textLength + chineseChars) * 1.5, // 宽度系数
25 // 最小宽度
), 60); // 最大宽度
column.width = width;
})
高度自动换行
设置wrapText: true
可以自动换行,但需要注意的是,他会根据row.height来决定。
也就是说:只在height的范围内,换行
java
// 批量设置所有表格数据的样式
worksheet.eachRow((row, rowNumber) => {
let size = rowNumber == 1 ? 16 : rowNumber == 2 ? 12 : "";
//设置表头样式
row.eachCell((cell) => {
cell.font = {
size,
// color:{ argb: 'FF8B008B' },
bold: true,
};
cell.alignment = { horizontal: "center", vertical: "middle", wrapText: true };
});
//设置所有行高
row.height = 30;
});
四、ExcelJS优缺点
优点:
-
丰富的样式支持:ExcelJS 提供了强大的样式设置功能,包括字体、颜色、边框等,适合需要复杂样式需求的场景。
-
流式处理 :支持流式写入和读取,适合处理较大的文件,内存效率高。通过
WorkbookReader
和WorkbookWriter
实现边读边处理,避免一次性加载大文件导致内存溢出(实测百万行数据性能提升78倍) -
跨平台兼容:可以在 Node.js 和浏览器环境中无缝运行,无需考虑环境差异。
-
异步操作 :支持Node.js异步API,可结合
worker_threads
实现多线程并行处理,显著提升吞吐量 -
数据转换能力:支持JSON与Excel双向转换,便于与Web应用集成
-
性能优化:采用流式处理和内存管理策略,处理大数据时高效稳定。
-
动态渲染与导出:支持 HTML5 Canvas 渲染,可以将电子表格以交互式 HTML 形式呈现,并一键导出为 .xlsx 文件。
缺点:
-
不支持图表:虽然支持丰富的样式,但目前不支持图表功能。
-
内存占用问题:默认全量加载文件至内存,处理超大型Excel(如超过1GB)可能导致OOM错误,需开发者手动优化流式处理逻辑
-
解析功能较弱:在解析 Excel 文件时,功能相对较少,不如其他库灵活。
方案参考
SheetJS(xlsx):轻量级读写,适合简单数据导入导出,社区还是比较活跃的,但样式支持较弱,这也是我选择了ExcelJS的原因。
Pandas(Python):数据清洗与分析场景更高效,但依赖Python环境
至此撒花~
后记
我们在实际项目中或多或少遇到一些奇奇怪怪的问题。
自己也会对一些写法的思考,为什么不行🤔,又为什么行了?
最后,祝君能拿下满意的offer。
我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车
👍 如果对您有帮助,您的点赞是我前进的润滑剂。
以往推荐
前端哪有什么设计模式(12k+)
为什么没人用mixin(7k+)