我们的项目中需要导出 Excel 的需求还是挺多的,找了一个处理导出 Excel 的库 ExcelJS ,npm包。
可以满足(但不限于)以下需求:
- 简单表格导出
- 为表格添加样式(更改背景色、更换字体、字号、颜色)
- 设置行高、列宽
- 解析 ant-design 的 Table 直接导出 excel,根据 antd 页面中设置的列宽动态计算 excel 中的列宽
- 多级表头(行合并、列合并)
- 一个 sheet 中放多张表,并实现每张表的列宽不同
ExcelJS
ExcelJS 周下载量 430k ,github star 9k ,有中文文档。虽然文档是以README 的形式,可读性不太好,但重在内容,常用的功能基本都有覆盖。
一年内有更新,试用了一下,集成简单,文档也比较丰富。
安装:
代码语言:javascript复制npm install exceljs
还需要搭配另外一个库:file-saver
代码语言:javascript复制npm install file-saver
常用接口
Workbook 工作簿
可以理解为整个表格。
创建工作簿:
代码语言:javascript复制const workbook = new ExcelJS.Workbook();
常用属性设置:
代码语言:javascript复制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 页。
添加工作表:
代码语言:javascript复制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 行
可以添加一行或者同时添加多行数据,是使用最频繁的属性。
代码语言:javascript复制// 获取一个行对象。如果尚不存在,则将返回一个新的空对象
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 列宽比例跟在线的表格是一致:
源码:
代码语言:javascript复制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 支持大纲;行或列可以根据用户希望查看的详细程度展开或折叠。
大纲级别可以在列设置中定义:
代码语言:javascript复制worksheet.columns = [
{ header: 'Id', key: 'id', width: 10 },
{ header: 'Name', key: 'name', width: 32 },
{ header: 'D.O.B.', key: 'DOB', width: 10, outlineLevel: 1 }
];
或直接在行或列上设置:
代码语言:javascript复制worksheet.getColumn(3).outlineLevel = 1; // 设置列
worksheet.getRow(3).outlineLevel = 1; // 设置行
也可以在工作表上设置:
代码语言:javascript复制// 设置列大纲级别
worksheet.properties.outlineLevelCol = 1;
// 设置行大纲级别
worksheet.properties.outlineLevelRow = 1;
注意:调整行或列上的大纲级别或工作表上的大纲级别将产生副作用,即还修改受属性更改影响的所有行或列的折叠属性。 例如:
代码语言:javascript复制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文件