使用可组合声明式 API 构造 Excel

687 阅读4分钟

excelogimage.png

@composize/excel 是一个通过可组合声明式 API 创建 Excel 文件的 DSL(领域特定语言)。

它提供了一种直观的接口来创建复杂的 Excel 工作簿,它在底层使用 ExcelJS

主要特性

  • 声明式语法:使用简单、可读的语法创建 Excel 文件,结构直观。
  • 层级结构:DSL 遵循 Excel 文档模型的层级结构,易于理解和维护。
  • 可组合函数:每一层级都用函数表示,并通过回调定义子元素,自然形成嵌套结构。
  • 单元格合并:支持横向和纵向合并单元格。
  • 自动列宽:根据每个单元格内容自动调整列宽。

安装

npm install @composize/excel

基本示例

这是一个使用 DSL 创建 Excel 文件的简单示例:

import { cell, row, workbook } from '@composize/excel';

workbook(() => {
  row(() => {
    cell('title1');
    cell('title2');
  });
  row(() => {
    cell('value1');
    cell('value2');
  });
});

预览效果:

核心概念

@composize/excel DSL 遵循 Excel 文档模型的层级结构,每一层都用可组合函数表示。

graph LR
    workbook --> worksheet
    worksheet --> row
    row --> cell
    row --> borderedCell
    row --> centeredCell

@composize/excel DSL 采用函数式组合模式,父元素通过回调定义子元素,代码结构与最终 Excel 文档结构高度一致。

API 参考

@composize/excel 包含以下主要 API:

函数说明参数返回值
workbook()创建一个新的工作簿composable: () => voidWorkbook
worksheet()向当前工作簿添加新工作表name: string, composable: () => voidWorksheet
row()向当前工作表添加新行composable: () => voidRow
cell()向当前行添加单元格value: string, options?: CellOptionsCell
borderedCell()向当前行添加带细边框的单元格value: string, options?: CellOptionsCell
centeredCell()向当前行添加一个居中对齐的单元格value: string, options?: CellOptionsCell

CellOptions

cell()borderedCell() 支持可选的 options 参数,用于单元格样式和合并:

选项类型说明
colSpannumber合并的列数
rowSpannumber合并的行数
numFmtstring数字格式字符串
fontFont字体属性(大小、名称、颜色等)
alignmentAlignment文本对齐属性
borderBorders单元格边框属性
fillFill单元格背景填充属性

基本用法

最简单的方式是通过嵌套函数调用,结构与表格一致:

import { workbook, row, cell } from '@composize/excel';

const book = workbook(() => {
  row(() => {
    cell('Title 1');
    cell('Title 2');
  });
  row(() => {
    cell('Value 1');
    cell('Value 2');
  });
});

// 保存工作簿到文件
book.xlsx.writeFile('./example.xlsx');

这会创建一个带有表头和数据的 2x2 表格。

工作表

如果没有显式创建工作表,@composize/excel 会自动创建名为 "Sheet1" 的工作表:

const book = workbook(() => {
  row(() => {
    cell('Data without explicit worksheet');
  });
});

你也可以通过 worksheet() 显式创建命名工作表:

const book = workbook(() => {
  worksheet('First Sheet', () => {
    row(() => {
      cell('Data in first sheet');
    });
  });

  worksheet('Second Sheet', () => {
    row(() => {
      cell('Data in second sheet');
    });
  });
});

单元格合并

@composize/excel DSL 支持横向(colSpan)和纵向(rowSpan)合并单元格:

横向合并(列合并)

使用 colSpan 选项横向合并单元格:

workbook(() => {
  row(() => {
    cell('Wide Header', { colSpan: 3 });
  });
  row(() => {
    cell('Data 1');
    cell('Data 2');
    cell('Data 3');
  });
});

这会创建一个跨三列的表头单元格:

纵向合并(行合并)

使用 rowSpan 选项纵向合并单元格:

workbook(() => {
  row(() => {
    cell('Tall Cell', { rowSpan: 2 });
    cell('Top Cell');
  });
  row(() => {
    // 第一列已被合并单元格覆盖,无需设置
    cell('Bottom Cell');
  });
});

这会创建一个跨两行的单元格:

组合合并

可以同时横向和纵向合并:

workbook(() => {
  row(() => {
    cell('Large Cell', { rowSpan: 2, colSpan: 2 });
    cell('Top Right');
  });
  row(() => {
    cell('Bottom Right');
  });
});

这会在左上角创建一个跨两行两列的大单元格。

单元格样式

基本样式

cell() 支持 ExcelJS 单元格对象的样式选项:

cell('Styled Text', {
  font: { bold: true, size: 14 },
  alignment: { vertical: 'middle', horizontal: 'center' },
  fill: { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFFF0000' } }
});

预览效果:

带边框单元格

@composize/excel 提供了 borderedCell(),可快速添加细边框:

row(() => {
  borderedCell('Bordered Content');
  borderedCell('Custom Format', { numFmt: '0.00' });
});

borderedCell() 支持与 cell() 相同的 options,并自动添加细边框。

居中单元格

为了方便起见,DSL 还提供了一个 centeredCell() 函数,它建立在 borderedCell() 之上,并对单元格应用居中对齐。

row(() => {
  centeredCell('Centered Text');
  centeredCell('Custom Format', { numFmt: '0.00' });
});

centeredCell() 函数接受与 borderedCell() 相同的选项,但自动添加居中对齐。

自动列宽

@composize/excel 会根据单元格内容自动调整列宽。对于合并单元格,自动换行而不是拉宽列宽:

完整示例

以下是一个展示 DSL 多种特性的复杂示例:

const book = workbook(() => {
  worksheet('Report', () => {
    // 合并标题行
    row(() => {
      centeredCell('Quarterly Report', {
        colSpan: 4,
        font: { bold: true, size: 16 }
      });
    });
    
    // 空出一行
    row(() => {});

    // 列标题
    row(() => {
      const headers = ['Category', 'Q1', 'Q2', 'Q3'];
      for (const header of headers) {
        borderedCell(header, { font: { bold: true } });
      }
    });

    // 数据行
    row(() => {
      borderedCell('Revenue');
      borderedCell(10000, { numFmt: '$#,##0' });
      borderedCell(12000, { numFmt: '$#,##0' });
      borderedCell(15000, { numFmt: '$#,##0' });
    });

    row(() => {
      borderedCell('Expenses');
      borderedCell(8000, { numFmt: '$#,##0' });
      borderedCell(8500, { numFmt: '$#,##0' });
      borderedCell(9000, { numFmt: '$#,##0' });
    });

    row(() => {
      borderedCell('Profit', { font: { bold: true } });
      borderedCell(2000, { numFmt: '$#,##0', font: { bold: true } });
      borderedCell(3500, { numFmt: '$#,##0', font: { bold: true } });
      borderedCell(6000, { numFmt: '$#,##0', font: { bold: true } });
    });
  });
});

book.xlsx.writeFile('quarterly_report.xlsx');

预览效果:

支持

喜欢 ✨ composize ✨ 吗?欢迎为 本项目 点 Star!