我们的项目中需要导出 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();
常用属性设置:
workbook.creator = 'Me'; // 创建人 workbook.lastModifiedBy = 'Her'; // 最后编辑 workbook.created = new Date(1985, 8, 30); // 创建日期 workbook.modified = new Date(); // 修改日期 workbook.lastPrinted = new Date(2016, 9, 27); // 最后打印 // 将工作簿日期设置为 1904 年日期系统 workbook.properties.date1904 = true;
worksheet 工作表
即 Excel 中的 sheet 页。
添加工作表:
const sheet = workbook.addWorksheet('My Sheet');
指定工作表的选项:
使用 addWorksheet
函数的第二个参数来指定工作表的选项。
// 创建带有红色标签颜色的工作表 const sheet = workbook.addWorksheet('My Sheet', {properties:{tabColor:{argb:'FFC0000'}}}); // 创建一个隐藏了网格线的工作表 const sheet = workbook.addWorksheet('My Sheet', {views: [{showGridLines: false}]}); // 创建一个第一行和列冻结的工作表 const sheet = workbook.addWorksheet('My Sheet', {views:[{xSplit: 1, ySplit:1}]}); // 使用A4设置的页面设置设置创建新工作表 - 横向 const worksheet = workbook.addWorksheet('My Sheet', { pageSetup:{paperSize: 9, orientation:'landscape'} }); // 创建一个具有页眉页脚的工作表 const sheet = workbook.addWorksheet('My Sheet', { headerFooter:{firstHeader: "Hello Exceljs", firstFooter: "Hello World"} }); // 创建一个冻结了第一行和第一列的工作表 const sheet = workbook.addWorksheet('My Sheet', {views:[{state: 'frozen', xSplit: 1, ySplit:1}]});
columns 列
通过 worksheet.columns
可设置表头。
// 添加列标题并定义列键和宽度 // 注意:这些列结构仅是构建工作簿的方便之处,除了列宽之外,它们不会完全保留。 worksheet.columns = [ { header: 'Id', key: 'id', width: 10 }, { header: 'Name', key: 'name', width: 32 }, { header: 'D.O.B.', key: 'DOB', width: 10, outlineLevel: 1 } ]; // 通过键,字母和基于1的列号访问单个列 const idCol = worksheet.getColumn('id'); const nameCol = worksheet.getColumn('B'); const dobCol = worksheet.getColumn(3); // 设置列属性 // 注意:将覆盖 C1 单元格值 dobCol.header = 'Date of Birth'; // 注意:这将覆盖 C1:C2 单元格值 dobCol.header = ['Date of Birth', 'A.K.A. D.O.B.']; // 从现在开始,此列将以 “dob” 而不是 “DOB” 建立索引 dobCol.key = 'dob'; dobCol.width = 15; // 如果需要,隐藏列 dobCol.hidden = true; // 为列设置大纲级别 worksheet.getColumn(4).outlineLevel = 0; worksheet.getColumn(5).outlineLevel = 1; // 列支持一个只读字段,以指示基于 `OutlineLevel` 的折叠状态 expect(worksheet.getColumn(4).collapsed).to.equal(false); expect(worksheet.getColumn(5).collapsed).to.equal(true); // 遍历此列中的所有当前单元格 dobCol.eachCell(function(cell, rowNumber) { // ... }); // 遍历此列中的所有当前单元格,包括空单元格 dobCol.eachCell({ includeEmpty: true }, function(cell, rowNumber) { // ... }); // 添加一列新值 worksheet.getColumn(6).values = [1,2,3,4,5]; // 添加稀疏列值 worksheet.getColumn(7).values = [,,2,3,,5,,7,,,,11]; // 剪切一列或多列(右边的列向左移动) // 如果定义了列属性,则会相应地对其进行切割或移动 // 已知问题:如果拼接导致任何合并的单元格移动,结果可能是不可预测的 worksheet.spliceColumns(3,2); // 删除一列,再插入两列。 // 注意:第4列及以上的列将右移1列。 // 另外:如果工作表中的行数多于列插入项中的值,则行将仍然被插入,就好像值存在一样。 const newCol3Values = [1,2,3,4,5]; const newCol4Values = ['one', 'two', 'three', 'four', 'five']; worksheet.spliceColumns(3, 1, newCol3Values, newCol4Values);
row 行
可以添加一行或者同时添加多行数据,是使用最频繁的属性。
// 获取一个行对象。如果尚不存在,则将返回一个新的空对象 const row = worksheet.getRow(5); // Get multiple row objects. If it doesn't already exist, new empty ones will be returned const rows = worksheet.getRows(5, 2); // start, length (>0, else undefined is returned) // 获取工作表中的最后一个可编辑行(如果没有,则为 `undefined`) const row = worksheet.lastRow; // 设置特定的行高 row.height = 42.5; // 隐藏行 row.hidden = true; // 为行设置大纲级别 worksheet.getRow(4).outlineLevel = 0; worksheet.getRow(5).outlineLevel = 1; // 行支持一个只读字段,以指示基于 `OutlineLevel` 的折叠状态 expect(worksheet.getRow(4).collapsed).to.equal(false); expect(worksheet.getRow(5).collapsed).to.equal(true); row.getCell(1).value = 5; // A5 的值设置为5 row.getCell('name').value = 'Zeb'; // B5 的值设置为 “Zeb” - 假设第2列仍按名称键入 row.getCell('C').value = new Date(); // C5 的值设置为当前时间 // 获取行并作为稀疏数组返回 // 注意:接口更改:worksheet.getRow(4) ==> worksheet.getRow(4).values row = worksheet.getRow(4).values; expect(row[5]).toEqual('Kyle'); // 通过连续数组分配行值(其中数组元素 0 具有值) row.values = [1,2,3]; expect(row.getCell(1).value).toEqual(1); expect(row.getCell(2).value).toEqual(2); expect(row.getCell(3).value).toEqual(3); // 通过稀疏数组分配行值(其中数组元素 0 为 `undefined`) const values = [] values[5] = 7; values[10] = 'Hello, World!'; row.values = values; expect(row.getCell(1).value).toBeNull(); expect(row.getCell(5).value).toEqual(7); expect(row.getCell(10).value).toEqual('Hello, World!'); // 使用列键按对象分配行值 row.values = { id: 13, name: 'Thing 1', dob: new Date() }; // 在该行下方插入一个分页符 row.addPageBreak(); // 遍历工作表中具有值的所有行 worksheet.eachRow(function(row, rowNumber) { console.log('Row ' + rowNumber + ' = ' + JSON.stringify(row.values)); }); // 遍历工作表中的所有行(包括空行) worksheet.eachRow({ includeEmpty: true }, function(row, rowNumber) { console.log('Row ' + rowNumber + ' = ' + JSON.stringify(row.values)); }); // 连续遍历所有非空单元格 row.eachCell(function(cell, colNumber) { console.log('Cell ' + colNumber + ' = ' + cell.value); }); // 遍历一行中的所有单元格(包括空单元格) row.eachCell({ includeEmpty: true }, function(cell, colNumber) { console.log('Cell ' + colNumber + ' = ' + cell.value); }); // 提交给流一个完成的行 row.commit(); // 行尺寸 const rowSize = row.cellCount; const numValues = row.actualCellCount;
表格导出
用 Ant Design Table 写了一个简单的表格,并设置了列宽:
可以看到,导出的 excel 列宽比例跟在线的表格是一致:
源码:
import { Table, Button } from 'antd'; import React from 'react'; import type { ColumnsType } from 'antd/es/table'; import * as ExcelJs from 'exceljs'; import { saveAs } from 'file-saver'; interface StudentInfo { id: number; name: string; age: number; } const tableData: StudentInfo[] = [ { id: 1, name: '张秀英', age: 44, }, { id: 2, name: '江勇', age: 20, }, { id: 3, name: '余军', age: 32, }, { id: 4, name: '曹刚', age: 44, }, { id: 5, name: '易艳', age: 32, }, ]; const columns: ColumnsType<StudentInfo> = [ { key: 'id', dataIndex: 'id', title: '编号', width: 150, }, { key: 'name', dataIndex: 'name', title: '姓名', width: 200, }, { key: 'age', dataIndex: 'age', title: '年龄', width: 150, }, ]; const List: React.FC = () => { // 根据 antd 的 column 生成 exceljs 的 column const DEFAULT_COLUMN_WIDTH = 20; function generateHeaders(columns: any[]) { return columns?.map((col) => { const obj = { // 显示的 name header: col.title, // 用于数据匹配的 key key: col.dataIndex, // 列宽 width: col.width / 5 || DEFAULT_COLUMN_WIDTH, }; return obj; }); } function saveWorkbook(workbook: any, fileName: string) { // 导出文件 workbook.xlsx.writeBuffer().then((data) => { const blob = new Blob([data], { type: '' }); saveAs(blob, fileName); }); } function onExportBasicExcel() { // 创建工作簿 const workbook = new ExcelJs.Workbook(); // 添加sheet const worksheet = workbook.addWorksheet('demo sheet'); // 设置 sheet 的默认行高 worksheet.properties.defaultRowHeight = 20; // 设置列 worksheet.columns = generateHeaders(columns); // 添加行 worksheet.addRows(tableData); // 导出excel saveWorkbook(workbook, 'simple-demo.xlsx'); } return ( (tableData && ( <div style={{ padding: '50px' }}> <Button type={'primary'} style={{ marginBottom: 10 }} onClick={onExportBasicExcel} > 导出excel </Button> <Table columns={columns} dataSource={tableData} /> </div> )) || null ); }; export default List;
真正导出的代码只有几行,重点看 onExportBasicExcel
方法:
先创建工作簿和 sheet 页,这两行是固定代码。如果需要多 sheet,则创建多个 sheet 即可。后续对表格的所有操作,都是对 worksheet 的操作。
设置表格的默认行高。这步非必要,但是设置了更美观。否则会出现有内容的行跟没有内容的行行高不一致的情况。
设置列数据(表头)和每行的数据。
导出 excel。
解析 AntD Table 的 columns
和 dataSource
:
// 根据 antd 的 column 生成 exceljs 的 column const DEFAULT_COLUMN_WIDTH = 20; function generateHeaders(columns: any[]) { return columns?.map((col) => { const obj = { // 显示的 name header: col.title, // 用于数据匹配的 key key: col.dataIndex, // 列宽 width: col.width / 5 || DEFAULT_COLUMN_WIDTH, }; return obj; }); }
在ExcelJS中,header 字段表示显示的表头内容,key 是用于匹配数据的 key,width 是列宽。在 Table 的 column 中都有对应的字段,取出来赋值即可。
注意设置列宽的时候,在线表格和 excel 的单位可能不一致,需要除以一个系数才不至于太宽。至于具体除多少,可以不断试验得出个最佳值,我试的除以 5 效果比较好。
通过 worksheet.addRows()
方法可以为工作表添加多行数据,因为上面我们已经设置了表头,程序知道了每列数据应该匹配哪个字段,所以这里直接传入 Table 的 dataSource
即可。
也可以通过 worksheet.addRow()
逐行添加数据。
下载 excel
下载是使用 file-saver
库。
function saveWorkbook(workbook: any, fileName: string) { // 导出文件 workbook.xlsx.writeBuffer().then((data) => { const blob = new Blob([data], { type: '' }); saveAs(blob, fileName); }); }
设置大纲级别
Excel 支持大纲;行或列可以根据用户希望查看的详细程度展开或折叠。
大纲级别可以在列设置中定义:
worksheet.columns = [ { header: 'Id', key: 'id', width: 10 }, { header: 'Name', key: 'name', width: 32 }, { header: 'D.O.B.', key: 'DOB', width: 10, outlineLevel: 1 } ];
或直接在行或列上设置:
worksheet.getColumn(3).outlineLevel = 1; // 设置列 worksheet.getRow(3).outlineLevel = 1; // 设置行
也可以在工作表上设置:
// 设置列大纲级别 worksheet.properties.outlineLevelCol = 1; // 设置行大纲级别 worksheet.properties.outlineLevelRow = 1;
注意:调整行或列上的大纲级别或工作表上的大纲级别将产生副作用,即还修改受属性更改影响的所有行或列的折叠属性。 例如:
worksheet.properties.outlineLevelCol = 1; worksheet.getColumn(3).outlineLevel = 1; expect(worksheet.getColumn(3).collapsed).to.be.true; worksheet.properties.outlineLevelCol = 2; expect(worksheet.getColumn(3).collapsed).to.be.false;
未经允许不得转载:前端资源网 - w3h5 » ExcelJS导出Ant Design Table数据为Excel文件