小小导出,我大前端一人足矣(复杂多级表头)

前言

  • 常网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优缺点

优点:

  1. 丰富的样式支持:ExcelJS 提供了强大的样式设置功能,包括字体、颜色、边框等,适合需要复杂样式需求的场景。

  2. 流式处理 :支持流式写入和读取,适合处理较大的文件,内存效率高。通过WorkbookReaderWorkbookWriter实现边读边处理,避免一次性加载大文件导致内存溢出(实测百万行数据性能提升78倍)

  3. 跨平台兼容:可以在 Node.js 和浏览器环境中无缝运行,无需考虑环境差异。

  4. ​异步操作​ ​:支持Node.js异步API,可结合worker_threads实现多线程并行处理,显著提升吞吐量

  5. ​数据转换能力​​:支持JSON与Excel双向转换,便于与Web应用集成

  6. 性能优化:采用流式处理和内存管理策略,处理大数据时高效稳定。

  7. 动态渲染与导出:支持 HTML5 Canvas 渲染,可以将电子表格以交互式 HTML 形式呈现,并一键导出为 .xlsx 文件。

缺点:

  1. 不支持图表:虽然支持丰富的样式,但目前不支持图表功能。

  2. ​内存占用问题​​:默认全量加载文件至内存,处理超大型Excel(如超过1GB)可能导致OOM错误,需开发者手动优化流式处理逻辑

  3. 解析功能较弱:在解析 Excel 文件时,功能相对较少,不如其他库灵活。

方案参考​

SheetJS(xlsx):轻量级读写,适合简单数据导入导出,社区还是比较活跃的,但样式支持较弱,这也是我选择了ExcelJS的原因。

Pandas(Python):数据清洗与分析场景更高效,但依赖Python环境

至此撒花~

后记

我们在实际项目中或多或少遇到一些奇奇怪怪的问题。

自己也会对一些写法的思考,为什么不行🤔,又为什么行了?

最后,祝君能拿下满意的offer。

我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车

👍 如果对您有帮助,您的点赞是我前进的润滑剂。

以往推荐

决定我的offer:问了我3个websocket的问题

几分钟写一个插件

前端哪有什么设计模式(12k+)

为什么没人用mixin(7k+)

小小导出,我大前端足矣!

靓仔,说一下keep-alive缓存组件后怎么更新及原理?

面试官问我new Vue阶段做了什么?

前端仔,快把dist部署到Nginx上

多图详解,一次性啃懂原型链(上万字)

Vue-Cli3搭建组件库

Vue实现动态路由(和面试官吹项目亮点)

VuePress搭建项目组件文档

原文链接

juejin.cn/spost/74937...

相关推荐
Joker Zxc35 分钟前
【前端基础】9、CSS的动态伪类(hover、visited、hover、active、focus)【注:本文只有几个粗略说明】
前端·css
2401_8370885039 分钟前
CSS flex:1
前端·css
发呆小天才yy5 小时前
uniapp 微信小程序使用图表
前端·微信小程序·uni-app·echarts
@PHARAOH6 小时前
HOW - 在 Mac 上的 Chrome 浏览器中调试 Windows 场景下的前端页面
前端·chrome·macos
独行soc7 小时前
2025年渗透测试面试题总结-某服面试经验分享(附回答)(题目+回答)
linux·运维·服务器·网络安全·面试·职场和发展·渗透测试
源码云商8 小时前
Spring Boot + Vue 实现在线视频教育平台
vue.js·spring boot·后端
月月大王8 小时前
easyexcel导出动态写入标题和数据
java·服务器·前端
JC_You_Know9 小时前
多语言网站的 UX 陷阱与国际化实践陷阱清单
前端·ux
Python智慧行囊9 小时前
前端三大件---CSS
前端·css
Jinuss9 小时前
源码分析之Leaflet中Marker
前端·leaflet