ExcelJS
针对Excel文件生成的封装,提供更友好的配置,减低上手难度。ExcelJS 官方中文文档。
实现源码
ts
import ExcelJS from 'exceljs';
export interface IConfig {
data: unknown[];
/**
* data里的子项为一个对象时,需要设置字段名,用于生成数据行
*/
fields?: string[];
/**
* 表头
* @example [['用户信息表'], ['姓名', '年龄', '性别']]
*/
headers: string[][];
/**
* 单元格合并,row: 行,col: 列,rowspan: 合并的行数,colspan: 合并的列数
*/
merges?: { row: number; col: number; rowspan: number; colspan: number }[];
attrs?: {
rowStart?: number;
rowEnd?: number;
colStart?: number;
colEnd?: number;
attr: Partial<ExcelJS.Style>;
}[];
views?: Partial<ExcelJS.WorksheetView>[];
columnsWidth?: number[];
protect?: { password: string; options: Partial<ExcelJS.WorksheetProtection> };
sheetName?: string;
sheetOptions?: Partial<ExcelJS.AddWorksheetOptions>;
}
/**
* 根据配置生成excel文件
* @param config
* @returns 返回excel文件的二进制数据
*/
export const createExcelFile = async (config: IConfig[] | IConfig) => {
if (!Array.isArray(config)) {
config = [config];
}
if (config.length === 0) return;
const workbook = new ExcelJS.Workbook();
for (let idx = 0; idx < config.length; idx++) {
const item = config[idx];
const sheet = workbook.addWorksheet(item.sheetName || 'sheet' + (idx + 1));
// 生成完整excel数据,包含表头和数据行。
const results = item.data.map((obj: any) => {
if (
typeof obj === 'string' ||
typeof obj === 'number' ||
typeof obj === 'boolean' ||
obj === null ||
obj === undefined
) {
return [obj];
}
return item?.fields?.map((key) => {
return obj[key] ?? '';
});
});
const excelData = [...item.headers, ...results];
sheet.addRows(excelData);
// 单元格合并处理
item?.merges?.forEach((m) => {
sheet.mergeCells(m.row, m.col, m.row + m.rowspan, m.col + m.colspan);
});
if (item.views) {
sheet.views = item.views;
}
if (item.protect) {
await sheet.protect(item.protect.password, item.protect.options);
}
// 单元格样式处理
item?.attrs?.forEach((val) => {
const attr = val.attr;
const rowStart = val.rowStart;
const rowEnd = val.rowEnd;
const colStart = val.colStart;
const colEnd = val.colEnd;
if (rowStart !== undefined && rowEnd !== undefined) {
for (let r = rowStart; r <= rowEnd; r++) {
const row = sheet.getRow(r);
if (colStart !== undefined && colEnd !== undefined) {
for (let c = colStart; c <= colEnd; c++) {
const cell = row.getCell(c);
Object.keys(attr).forEach((key) => {
// @ts-ignore
cell[key] = attr[key];
});
}
continue;
}
// 未设置列,整行设置【大纲级别】
Object.keys(attr).forEach((key) => {
// @ts-ignore
row[key] = attr[key];
});
}
return;
}
if (colStart !== undefined && colEnd !== undefined) {
for (let c = colStart; c <= colEnd; c++) {
// 获取当前列,整列设置【大纲级别】
const column = sheet.getColumn(c);
Object.keys(attr).forEach((key) => {
// @ts-ignore
column[key] = attr[key];
});
}
return;
}
// 没有设置具体的行列,则为整表设置
Object.keys(attr).forEach((key) => {
// @ts-ignore
sheet[key] = attr[key];
});
});
item?.columnsWidth?.forEach((width, index) => {
sheet.columns[index].width = width;
});
}
return workbook.xlsx.writeBuffer();
};downloadFile
ts
import FileSaver from 'file-saver';
export const downloadFile = (data: ArrayBuffer, fileName: string) => {
const blob = new Blob([data], { type: 'application/octet-stream' });
FileSaver.saveAs(blob, fileName);
};安装
需要安装 exceljs 和 file-saver 依赖。
bash
pnpm add exceljs file-saver
pnpm add @types/file-saver -D基本使用
1. 简单示例

配置如下:
ts
import { type IConfig, createExcelFile } from '...';
const onDownloadSimple = () => {
const config = {
fields: ['name', 'age', 'phone', 'address'],
// 第一行为表头,合并4个单元格
merges: [{ row: 1, col: 1, rowspan: 0, colspan: 3 }],
data: [
{ name: '张三', age: 18, phone: '12345678901', address: '北京市' },
{ name: '李四', age: 20, phone: '12345678901', address: '上海市' },
{ name: '王五', age: 22, phone: '12345678901', address: '广州市' },
{ name: '赵六', age: 24, phone: '12345678901', address: '深圳市' }
],
headers: [['用户信息表'], ['姓名', '年龄', '电话', '地址']],
sheetName: '用户信息表',
columnsWidth: Array.from({ length: 4 }, () => 30)
};
createExcelFile(config).then((buffer) => {
downloadFile(buffer!, '用户信息表.xlsx');
});
};2. 复杂示例

配置如下:
ts
import { type IConfig, createExcelFile } from '...';
const onDownloadMulti = () => {
const fields = ['name', 'age', 'phone', 'address', 'sex', 'birthday', 'email'];
const config: IConfig = {
fields,
data: [
{ name: '张三', age: 18, phone: '12345678901', address: '北京市', sex: '男', birthday: new Date('2000/01/01'), email: '无' },
{ name: '李四', age: 20, phone: '12345678901', address: '上海市', sex: '男', birthday: new Date('2000/01/02'), email: '无' },
{ name: '王五', age: 22, phone: '12345678901', address: '广州市', sex: '男', birthday: new Date('2000/01/03'), email: '无' },
{ name: '赵六', age: 24, phone: '12345678901', address: '深圳市', sex: '男', birthday: new Date('2000/01/04'), email: '无' }
],
attrs: [],
headers: [
['用户信息表'],
['姓名', '其他信息'],
['', '年龄', '电话', '地址', '性别', '生日', '邮箱']
],
merges: [
// 显示标题 '用户信息表',合并第一行所有单元格
{ row: 1, col: 1, rowspan: 0, colspan: fields.length - 1 },
// 显示表头 '姓名',合并第二行与第三行的第一个单元格
{ row: 2, col: 1, rowspan: 1, colspan: 0 },
// 显示表头 '其他信息'
{ row: 2, col: 2, rowspan: 0, colspan: 5 }
],
sheetName: '用户信息表-复杂表头',
columnsWidth: Array.from({ length: fields.length }, () => 20)
};
config?.fields?.forEach((item, index) => {
if (item === 'age') {
// 设置整数格式
config?.attrs?.push({
rowStart: 4,
rowEnd: config.data.length + 3, // 表头占用三行
colStart: index + 1, // 索引从0开始,列从1开始,所以加1
colEnd: index + 1,
attr: {
numFmt: '#,##0'
}
});
return;
}
if (item === 'birthday') {
// 设置日期格式
config?.attrs?.push({
rowStart: 4,
rowEnd: config.data.length + 3,
colStart: index + 1,
colEnd: index + 1,
attr: {
numFmt: 'yyyy"年"mm"月"dd"日"'
}
});
return;
}
});
// 设置单元格样式
config?.attrs?.push({
rowStart: 1,
rowEnd: config.data.length + 3,
colStart: 1,
colEnd: config?.fields?.length,
attr: {
alignment: { vertical: 'middle', horizontal: 'center' },
border: {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' }
}
}
});
// 设置表头标题样式
config?.attrs?.push({
rowStart: 1,
rowEnd: 1,
colStart: 1,
colEnd: config?.fields?.length,
attr: {
fill: {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'e2efda' }
},
font: {
size: 20,
bold: true
}
}
});
// 设置表头填充颜色,字体加粗
config?.attrs?.push({
rowStart: 2,
rowEnd: 3,
colStart: 1,
colEnd: config?.fields?.length,
attr: {
fill: {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'c6e0b4' }
},
font: {
bold: true
}
}
});
createExcelFile(config).then((buffer) => {
downloadFile(buffer!, '用户信息表-复杂表头.xlsx');
});
};