ExcelJS导出Ant Design Table数据为Excel文件

我们的项目中需要导出 Excel 的需求还是挺多的,找了一个处理导出 Excel 的库 ExcelJS ,npm包

可以满足(但不限于)以下需求:

  • 简单表格导出

  • 为表格添加样式(更改背景色、更换字体、字号、颜色)

  • 设置行高、列宽

  • 解析 ant-design 的 Table 直接导出 excel,根据 antd 页面中设置的列宽动态计算 excel 中的列宽

  • 多级表头(行合并、列合并)

  • 一个 sheet 中放多张表,并实现每张表的列宽不同

ExcelJS 

ExcelJS 周下载量 430k+,github star 9k+,有中文文档。虽然文档是以README 的形式,可读性不太好,但重在内容,常用的功能基本都有覆盖。

一年内有更新,试用了一下,集成简单,文档也比较丰富。

安装:

npm install exceljs

还需要搭配另外一个库:file-saver

npm install file-saver

在页面中引用

import * as ExcelJs from 'exceljs';
import { saveAs } from 'file-saver';

常用接口

Workbook 工作簿

可以理解为整个表格。

创建工作簿:

const workbook = new ExcelJS.Workbook();

常用属性设置:

  1. workbook.creator = 'Me'; // 创建人
  2. workbook.lastModifiedBy = 'Her'; // 最后编辑
  3. workbook.created = new Date(1985, 8, 30); // 创建日期
  4. workbook.modified = new Date(); // 修改日期
  5. workbook.lastPrinted = new Date(2016, 9, 27); // 最后打印
  6.  
  7. // 将工作簿日期设置为 1904 年日期系统
  8. workbook.properties.date1904 = true;

worksheet 工作表

即 Excel 中的 sheet 页。

添加工作表:

const sheet = workbook.addWorksheet('My Sheet');

指定工作表的选项:

使用 addWorksheet 函数的第二个参数来指定工作表的选项。

  1. // 创建带有红色标签颜色的工作表
  2. const sheet = workbook.addWorksheet('My Sheet', {properties:{tabColor:{argb:'FFC0000'}}});
  3.  
  4. // 创建一个隐藏了网格线的工作表
  5. const sheet = workbook.addWorksheet('My Sheet', {views: [{showGridLines: false}]});
  6.  
  7. // 创建一个第一行和列冻结的工作表
  8. const sheet = workbook.addWorksheet('My Sheet', {views:[{xSplit: 1, ySplit:1}]});
  9.  
  10. // 使用A4设置的页面设置设置创建新工作表 - 横向
  11. const worksheet =  workbook.addWorksheet('My Sheet', {
  12.   pageSetup:{paperSize: 9, orientation:'landscape'}
  13. });
  14.  
  15. // 创建一个具有页眉页脚的工作表
  16. const sheet = workbook.addWorksheet('My Sheet', {
  17.   headerFooter:{firstHeader: "Hello Exceljs", firstFooter: "Hello World"}
  18. });
  19.  
  20. // 创建一个冻结了第一行和第一列的工作表
  21. const sheet = workbook.addWorksheet('My Sheet', {views:[{state: 'frozen', xSplit: 1, ySplit:1}]});

columns 列

通过 worksheet.columns 可设置表头。

  1. // 添加列标题并定义列键和宽度
  2. // 注意:这些列结构仅是构建工作簿的方便之处,除了列宽之外,它们不会完全保留。
  3. worksheet.columns = [
  4.   { header: 'Id', key: 'id', width: 10 },
  5.   { header: 'Name', key: 'name', width: 32 },
  6.   { header: 'D.O.B.', key: 'DOB', width: 10, outlineLevel: 1 }
  7. ];
  8.  
  9. // 通过键,字母和基于1的列号访问单个列
  10. const idCol = worksheet.getColumn('id');
  11. const nameCol = worksheet.getColumn('B');
  12. const dobCol = worksheet.getColumn(3);
  13.  
  14. // 设置列属性
  15.  
  16. // 注意:将覆盖 C1 单元格值
  17. dobCol.header = 'Date of Birth';
  18.  
  19. // 注意:这将覆盖 C1:C2 单元格值
  20. dobCol.header = ['Date of Birth', 'A.K.A. D.O.B.'];
  21.  
  22. // 从现在开始,此列将以 “dob” 而不是 “DOB” 建立索引
  23. dobCol.key = 'dob';
  24.  
  25. dobCol.width = 15;
  26.  
  27. // 如果需要,隐藏列
  28. dobCol.hidden = true;
  29.  
  30. // 为列设置大纲级别
  31. worksheet.getColumn(4).outlineLevel = 0;
  32. worksheet.getColumn(5).outlineLevel = 1;
  33.  
  34. // 列支持一个只读字段,以指示基于 `OutlineLevel` 的折叠状态
  35. expect(worksheet.getColumn(4).collapsed).to.equal(false);
  36. expect(worksheet.getColumn(5).collapsed).to.equal(true);
  37.  
  38. // 遍历此列中的所有当前单元格
  39. dobCol.eachCell(function(cell, rowNumber) {
  40.   // ...
  41. });
  42.  
  43. // 遍历此列中的所有当前单元格,包括空单元格
  44. dobCol.eachCell({ includeEmpty: true }, function(cell, rowNumber) {
  45.   // ...
  46. });
  47.  
  48. // 添加一列新值
  49. worksheet.getColumn(6).values = [1,2,3,4,5];
  50.  
  51. // 添加稀疏列值
  52. worksheet.getColumn(7).values = [,,2,3,,5,,7,,,,11];
  53.  
  54. // 剪切一列或多列(右边的列向左移动)
  55. // 如果定义了列属性,则会相应地对其进行切割或移动
  56. // 已知问题:如果拼接导致任何合并的单元格移动,结果可能是不可预测的
  57. worksheet.spliceColumns(3,2);
  58.  
  59. // 删除一列,再插入两列。
  60. // 注意:第4列及以上的列将右移1列。
  61. // 另外:如果工作表中的行数多于列插入项中的值,则行将仍然被插入,就好像值存在一样。
  62. const newCol3Values = [1,2,3,4,5];
  63. const newCol4Values = ['one', 'two', 'three', 'four', 'five'];
  64. worksheet.spliceColumns(3, 1, newCol3Values, newCol4Values);

row 行

可以添加一行或者同时添加多行数据,是使用最频繁的属性。

  1. // 获取一个行对象。如果尚不存在,则将返回一个新的空对象
  2. const row = worksheet.getRow(5);
  3.  
  4. // Get multiple row objects. If it doesn't already exist, new empty ones will be returned
  5. const rows = worksheet.getRows(5, 2); // start, length (>0, else undefined is returned)
  6.  
  7. // 获取工作表中的最后一个可编辑行(如果没有,则为 `undefined`)
  8. const row = worksheet.lastRow;
  9.  
  10. // 设置特定的行高
  11. row.height = 42.5;
  12.  
  13. // 隐藏行
  14. row.hidden = true;
  15.  
  16. // 为行设置大纲级别
  17. worksheet.getRow(4).outlineLevel = 0;
  18. worksheet.getRow(5).outlineLevel = 1;
  19.  
  20. // 行支持一个只读字段,以指示基于 `OutlineLevel` 的折叠状态
  21. expect(worksheet.getRow(4).collapsed).to.equal(false);
  22. expect(worksheet.getRow(5).collapsed).to.equal(true);
  23.  
  24.  
  25. row.getCell(1).value = 5; // A5 的值设置为5
  26. row.getCell('name').value = 'Zeb'; // B5 的值设置为 “Zeb” - 假设第2列仍按名称键入
  27. row.getCell('C').value = new Date(); // C5 的值设置为当前时间
  28.  
  29. // 获取行并作为稀疏数组返回
  30. // 注意:接口更改:worksheet.getRow(4) ==> worksheet.getRow(4).values
  31. row = worksheet.getRow(4).values;
  32. expect(row[5]).toEqual('Kyle');
  33.  
  34. // 通过连续数组分配行值(其中数组元素 0 具有值)
  35. row.values = [1,2,3];
  36. expect(row.getCell(1).value).toEqual(1);
  37. expect(row.getCell(2).value).toEqual(2);
  38. expect(row.getCell(3).value).toEqual(3);
  39.  
  40. // 通过稀疏数组分配行值(其中数组元素 0 为 `undefined`)
  41. const values = []
  42. values[5] = 7;
  43. values[10] = 'Hello, World!';
  44. row.values = values;
  45. expect(row.getCell(1).value).toBeNull();
  46. expect(row.getCell(5).value).toEqual(7);
  47. expect(row.getCell(10).value).toEqual('Hello, World!');
  48.  
  49. // 使用列键按对象分配行值
  50. row.values = {
  51.   id: 13,
  52.   name: 'Thing 1',
  53.   dob: new Date()
  54. };
  55.  
  56. // 在该行下方插入一个分页符
  57. row.addPageBreak();
  58.  
  59. // 遍历工作表中具有值的所有行
  60. worksheet.eachRow(function(row, rowNumber) {
  61.   console.log('Row ' + rowNumber + ' = ' + JSON.stringify(row.values));
  62. });
  63.  
  64. // 遍历工作表中的所有行(包括空行)
  65. worksheet.eachRow({ includeEmpty: true }, function(row, rowNumber) {
  66.   console.log('Row ' + rowNumber + ' = ' + JSON.stringify(row.values));
  67. });
  68.  
  69. // 连续遍历所有非空单元格
  70. row.eachCell(function(cell, colNumber) {
  71.   console.log('Cell ' + colNumber + ' = ' + cell.value);
  72. });
  73.  
  74. // 遍历一行中的所有单元格(包括空单元格)
  75. row.eachCell({ includeEmpty: true }, function(cell, colNumber) {
  76.   console.log('Cell ' + colNumber + ' = ' + cell.value);
  77. });
  78.  
  79. // 提交给流一个完成的行
  80. row.commit();
  81.  
  82. // 行尺寸
  83. const rowSize = row.cellCount;
  84. const numValues = row.actualCellCount;

表格导出

用 Ant Design Table 写了一个简单的表格,并设置了列宽:

ExcelJS导出Ant Design Table数据为Excel文件 JavaScript 第1张

可以看到,导出的 excel 列宽比例跟在线的表格是一致:

ExcelJS导出Ant Design Table数据为Excel文件 JavaScript 第2张

源码:

  1. import { Table, Button } from 'antd';
  2. import React from 'react';
  3. import type { ColumnsType } from 'antd/es/table';
  4. import * as ExcelJs from 'exceljs';
  5. import { saveAs } from 'file-saver';
  6.  
  7. interface StudentInfo {
  8.   id: number;
  9.   name: string;
  10.   age: number;
  11. }
  12. const tableData: StudentInfo[] = [
  13.   {
  14.     id: 1,
  15.     name: '张秀英',
  16.     age: 44,
  17.   },
  18.   {
  19.     id: 2,
  20.     name: '江勇',
  21.     age: 20,
  22.   },
  23.   {
  24.     id: 3,
  25.     name: '余军',
  26.     age: 32,
  27.   },
  28.   {
  29.     id: 4,
  30.     name: '曹刚',
  31.     age: 44,
  32.   },
  33.   {
  34.     id: 5,
  35.     name: '易艳',
  36.     age: 32,
  37.   },
  38. ];
  39. const columns: ColumnsType<StudentInfo> = [
  40.   {
  41.     key: 'id',
  42.     dataIndex: 'id',
  43.     title: '编号',
  44.     width: 150,
  45.   },
  46.   {
  47.     key: 'name',
  48.     dataIndex: 'name',
  49.     title: '姓名',
  50.     width: 200,
  51.   },
  52.   {
  53.     key: 'age',
  54.     dataIndex: 'age',
  55.     title: '年龄',
  56.     width: 150,
  57.   },
  58. ];
  59.  
  60. const List: React.FC = () => {
  61.   // 根据 antd 的 column 生成 exceljs 的 column
  62.   const DEFAULT_COLUMN_WIDTH = 20;
  63.   function generateHeaders(columns: any[]) {
  64.     return columns?.map((col) => {
  65.       const obj = {
  66.         // 显示的 name
  67.         header: col.title,
  68.         // 用于数据匹配的 key
  69.         key: col.dataIndex,
  70.         // 列宽
  71.         width: col.width / 5 || DEFAULT_COLUMN_WIDTH,
  72.       };
  73.       return obj;
  74.     });
  75.   }
  76.   function saveWorkbook(workbook: any, fileName: string) {
  77.     // 导出文件
  78.     workbook.xlsx.writeBuffer().then((data) => {
  79.       const blob = new Blob([data], { type: '' });
  80.       saveAs(blob, fileName);
  81.     });
  82.   }
  83.   function onExportBasicExcel() {
  84.     // 创建工作簿
  85.     const workbook = new ExcelJs.Workbook();
  86.     // 添加sheet
  87.     const worksheet = workbook.addWorksheet('demo sheet');
  88.     // 设置 sheet 的默认行高
  89.     worksheet.properties.defaultRowHeight = 20;
  90.     // 设置列
  91.     worksheet.columns = generateHeaders(columns);
  92.     // 添加行
  93.     worksheet.addRows(tableData);
  94.  
  95.     // 导出excel
  96.     saveWorkbook(workbook, 'simple-demo.xlsx');
  97.   }
  98.   return (
  99.     (tableData && (
  100.       <div style={{ padding: '50px' }}>
  101.         <Button
  102.           type={'primary'}
  103.           style={{ marginBottom: 10 }}
  104.           onClick={onExportBasicExcel}
  105.         >
  106.           导出excel
  107.         </Button>
  108.         <Table columns={columns} dataSource={tableData} />
  109.       </div>
  110.     )) ||
  111.     null
  112.   );
  113. };
  114.  
  115. export default List;

真正导出的代码只有几行,重点看 onExportBasicExcel 方法:

  1. 先创建工作簿和 sheet 页,这两行是固定代码。如果需要多 sheet,则创建多个 sheet 即可。后续对表格的所有操作,都是对 worksheet 的操作。

  2. 设置表格的默认行高。这步非必要,但是设置了更美观。否则会出现有内容的行跟没有内容的行行高不一致的情况。

  3. 设置列数据(表头)和每行的数据。

  4. 导出 excel。

解析 AntD Table 的 columnsdataSource

  1. // 根据 antd 的 column 生成 exceljs 的 column
  2. const DEFAULT_COLUMN_WIDTH = 20;
  3. function generateHeaders(columns: any[]) {
  4.   return columns?.map((col) => {
  5.     const obj = {
  6.       // 显示的 name
  7.       header: col.title,
  8.       // 用于数据匹配的 key
  9.       key: col.dataIndex,
  10.       // 列宽
  11.       width: col.width / 5 || DEFAULT_COLUMN_WIDTH,
  12.     };
  13.     return obj;
  14.   });
  15. }

在ExcelJS中,header 字段表示显示的表头内容,key 是用于匹配数据的 key,width 是列宽。在 Table 的 column 中都有对应的字段,取出来赋值即可。

注意设置列宽的时候,在线表格和 excel 的单位可能不一致,需要除以一个系数才不至于太宽。至于具体除多少,可以不断试验得出个最佳值,我试的除以 5 效果比较好。

通过 worksheet.addRows() 方法可以为工作表添加多行数据,因为上面我们已经设置了表头,程序知道了每列数据应该匹配哪个字段,所以这里直接传入 Table 的 dataSource 即可。

也可以通过 worksheet.addRow() 逐行添加数据。

下载 excel

下载是使用 file-saver 库。

  1. function saveWorkbook(workbook: any, fileName: string) {
  2.   // 导出文件
  3.   workbook.xlsx.writeBuffer().then((data) => {
  4.     const blob = new Blob([data], { type: '' });
  5.     saveAs(blob, fileName);
  6.   });
  7. }

设置大纲级别

Excel 支持大纲;行或列可以根据用户希望查看的详细程度展开或折叠。

大纲级别可以在列设置中定义:

  1. worksheet.columns = [
  2.   { header: 'Id', key: 'id', width: 10 },
  3.   { header: 'Name', key: 'name', width: 32 },
  4.   { header: 'D.O.B.', key: 'DOB', width: 10, outlineLevel: 1 }
  5. ];

或直接在行或列上设置:

  1. worksheet.getColumn(3).outlineLevel = 1; // 设置列
  2. worksheet.getRow(3).outlineLevel = 1; // 设置行

也可以在工作表上设置:

  1. // 设置列大纲级别
  2. worksheet.properties.outlineLevelCol = 1;
  3.  
  4. // 设置行大纲级别
  5. worksheet.properties.outlineLevelRow = 1;

注意:调整行或列上的大纲级别或工作表上的大纲级别将产生副作用,即还修改受属性更改影响的所有行或列的折叠属性。 例如:

  1. worksheet.properties.outlineLevelCol = 1;
  2.  
  3. worksheet.getColumn(3).outlineLevel = 1;
  4. expect(worksheet.getColumn(3).collapsed).to.be.true;
  5.  
  6. worksheet.properties.outlineLevelCol = 2;
  7. expect(worksheet.getColumn(3).collapsed).to.be.false;


未经允许不得转载:前端资源网 - w3h5 » ExcelJS导出Ant Design Table数据为Excel文件

赞 (1)
分享到: +

评论 沙发

Avatar

换个身份

  • 昵称 (必填)
  • 邮箱 (选填)