npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

excel-write-plugin

v1.0.0

Published

纯浏览器端 Excel 导出插件(零依赖、无任何服务端内容)。支持 .xls(Excel 2003 SpreadsheetML)与标准 .xlsx(OOXML),全部在内存中组装并产出 Blob,可直接触发浏览器下载 / 预览 / 上传。提供多工作表、单元格合并、样式与下拉选项列、日期/数字格式、反科学计数法、模板模式、五类数据源、生命周期钩子与插件化、进度/中断/上限/超时、全链路异常兜底。

Readme

excel-write-plugin

纯浏览器端 Excel 导出插件(零依赖、无任何服务端内容)。支持 .xls(Excel 2003 SpreadsheetML) 与标准 .xlsx(OOXML) 两种格式,全部在浏览器内存中组装并产出 Blob,可直接触发下载 / 预览 / 上传,无需任何 Node 服务参与。

与浏览器端的 excel-read-plugin(读取)形成读写互补。


目录


特性一览

| 能力 | 说明 | | --- | --- | | 🖥️ 纯浏览器运行 | 零依赖、无 Node 内置模块、无服务端代码,可直接打包或用 <script> 引入 | | 📦 双格式输出 | .xls(SpreadsheetML)+ 标准 .xlsx(OOXML),format 一键切换 | | 💾 Blob 输出 | 产出 Blob,可下载 / 预览 / 上传 / 结合 fetch 发送 | | ⬇️ 一键下载 | download() 自动生成文件名并在浏览器触发下载 | | 🔤 特殊字符自动转义 | 自动处理 & < > " ',杜绝文件损坏/乱码 | | 📄 多 Sheet 工作表 | 单文件多工作表,各自独立配置表头/数据/样式/合并 | | 📋 下拉选项列 | type: 'select' + options 快速启用整列下拉枚举 | | 🔢 防科学计数法 | 超长数字(身份证/订单号/手机号)强制文本渲染 | | 🔗 单元格合并 | 自动合并(同列连续相同值)+ 手动精准合并(范围坐标) | | 🎨 样式系统 | 表头样式 / 单元格样式 / 条件样式 / 样式 ID 复用压缩体积 | | 🔌 五类数据源 | 数组 / 分批推送 / 异步迭代器 / 数据库游标 / 事件流 | | 📈 进度监听 | 实时进度回调,可驱动前端进度条 | | 🛑 中断 / 上限 / 超时 | 主动中断、行数上限保护、超时自动回收资源 | | 🧩 生命周期钩子 + 插件 | beforeExport / beforeHeader / beforeRow / afterRow / beforeClose / onError |


安装

npm install excel-write-plugin
# 或
yarn add excel-write-plugin

环境要求:现代浏览器(Chrome / Edge / Firefox / Safari 等),无需 Node 环境。核心模块仅依赖 Web 标准 TextEncoder / Blob / URL。

插件由 TypeScript 编写,构建产物同时提供 ESM(exports.import)与 UMD/CJS(exports.require),并附带 .d.ts 类型声明,产物自包含、零外部依赖。


快速上手

1. 一键下载 .xlsx

import { ExcelExporter } from 'excel-write-plugin';

const exporter = new ExcelExporter();

const result = await exporter.download(
  {
    sheets: [
      {
        sheetName: '订单',
        columns: [
          { label: '订单号', field: 'id', asText: true },   // 反科学计数法
          { label: '名称', field: 'name' },
          { label: '数量', field: 'qty', type: 'number' },
        ],
        data: [
          { id: '10000000001', name: '苹果', qty: 3 },
          { id: '10000000002', name: '香蕉', qty: 5 },
        ],
      },
    ],
  },
  { filename: 'orders.xlsx' }
);

2. 获取 Blob(预览 / 上传 / 发送)

const res = await exporter.toBlob({ sheets: [{ columns, data }] });
// res.blob 为 Blob,可直接交给上传组件 / URL.createObjectURL 预览 / fetch 发送
const url = URL.createObjectURL(res.blob);

3. 直接取字节(.xlsx)

const res = await exporter.exportToXlsx({ sheets: [{ columns, data }] });
// res.buffer 为 Uint8Array,.xlsx 文件字节

体积优化:.xlsx 默认采用 ZIP STORE(不压缩)。当数据量大、文件体积敏感时,可开启 zipCompress 启用 DEFLATE 压缩,文本型 XML 通常可减小 3~6 倍体积(逐文件比较,压缩无收益时自动回落 STORE):

await exporter.download({ sheets }, { zipCompress: true, filename: 'orders.xlsx' });

4. 一次即用函数

import { downloadExcel, toExcelBlob, exportXlsx, exportExcel } from 'excel-write-plugin';

await downloadExcel({ sheets });                       // 直接下载
const blobRes = await toExcelBlob({ sheets });         // 取 Blob
const byteRes = await exportXlsx({ sheets });          // .xlsx 字节
const xlsRes = await exportExcel({ sheets }, { format: 'xls' }); // .xls 字节

核心 API

| API | 类型 | 说明 | | --- | --- | --- | | new ExcelExporter(presets?) | class | 导出编排器;presets 为该实例默认配置 | | exporter.toBlob(config, options?) | 方法 | 生成 Blob(按 options.format 决定 .xls/.xlsx) | | exporter.download(config, options?) | 方法 | 生成 Blob 并自动触发浏览器下载 | | exportToBuffer(config, options?) | 方法 | 生成 .xls(SpreadsheetML)内存字节 Uint8Array | | exportToXlsx(config, options?) | 方法 | 生成标准 .xlsx(OOXML) 内存字节 Uint8Array | | exporter.export(config, options?) | 方法 | 生成 .xls 内存字节(等价 exportToBuffer) | | exporter.abort(reason?) | 方法 | 主动中断当前导出(下次行迭代生效,抛 ABORTED) | | exporter.getProgress() | 方法 | 获取进度快照 | | exporter.setDefaults(partial) | 方法 | 设置本实例默认配置 | | ExcelExporter.setDefaults(partial) | 静态 | 设置全局默认配置(所有后续实例生效) | | exportExcel(config, options?) | 函数 | 一次性 .xls 便利函数 | | exportXlsx(config, options?) | 函数 | 一次性 .xlsx 便利函数 | | downloadExcel(config, options?) | 函数 | 一次性下载便利函数 | | toExcelBlob(config, options?) | 函数 | 一次性 Blob 便利函数 | | createBatchSource() | 函数 | 创建分批推送数据源({ pusher, iterable }) | | ExcelError | class | 结构化导出错误 |

返回对象统一为 ExportResult / BlobResult:含 buffer(Uint8Array)或 blob、以及 bytes / rows / sheets / totalTime。


配置参考

ExportOptions 导出选项

| 配置项 | 必填 | 默认值 | 类型 | 含义 | | --- | --- | --- | --- | --- | | format | 否 | 'xlsx' | 'xls'\|'xlsx' | 输出格式:.xls(SpreadsheetML)/ .xlsx(OOXML) | | filename | 否 | 'export.xlsx' | string | download() 下载文件名(自动修正 .xls/.xlsx 扩展名) | | batchSize | 否 | 1000 | number | 分批消费批次大小 | | maxRows | 否 | 0 | number | 最大导出行数阈值;0 表示不限制 | | timeout | 否 | 0 | number | 导出超时(毫秒);0 表示不超时 | | bufferSize | 否 | 65536 | number | XML 缓冲分块字节数 | | zipCompress | 否 | false | boolean | 开启 .xlsx 的 ZIP DEFLATE 压缩(method 8);对文本型 XML 可减小 3~6 倍体积,逐文件比较、压缩不更小则自动回落 STORE | | headerStyle | 否 | 商务简约 | HeaderStyle | 全局默认表头样式 | | defaultCellStyle | 否 | 细线边框 | DefaultCellStyle | 全局默认单元格样式 | | conditionalStyle | 否 | — | (value, ctx) => Partial<CellStyle> | 全局条件样式函数 | | cellFormat | 否 | — | (value, ctx) => unknown | 全局单元格展示值格式化 | | freezeHeader | 否 | true | boolean | 全局默认冻结表头 | | autoFilter | 否 | false | boolean | 全局默认开启数据自动筛选 | | hideEmptyRows | 否 | true | boolean | 全局默认隐藏全空数据行 | | autoWidth | 否 | false | boolean | 全局默认列宽自适应 | | hooks | 否 | {} | ExportHooks | 生命周期钩子 | | plugins | 否 | [] | ExportPlugin[] | 插件列表 | | onProgress | 否 | — | (p: ExportProgress) => void | 进度回调 | | onError | 否 | — | (e) => void | 错误回调(与 hooks.onError 取并集) |

SheetConfig 工作表配置

| 配置项 | 必填 | 默认值 | 类型 | 含义 | | --- | --- | --- | --- | --- | | sheetName | 否 | — | string | 工作表名(自动过滤非法字符、去重、截断 31 字符) | | columns | 是 | — | ColumnDef[] | 列配置 | | data | 是 | — | DataSource | 数据源(数组 / 分批 / 异步迭代器 / 游标 / 事件流) | | mergeAuto | 否 | [] | MergeAuto[] | 自动合并(按列连续相同值跨行合并) | | mergeCustom | 否 | [] | MergeCustom[] | 手动精准合并(行/列范围) | | headerHeight | 否 | 继承 | number | 表头行高 | | rowHeight | 否 | 继承 | number | 数据行高 | | freezeHeader | 否 | 继承 | boolean | 冻结表头 | | autoFilter | 否 | 继承 | boolean | 数据自动筛选 | | hideEmptyRows | 否 | 继承 | boolean | 隐藏全空数据行 | | autoWidth | 否 | 继承 | boolean | 列宽自适应 | | templateRows | 否 | 0 | number | 模板模式行数:>0 时将列类型(select 下拉 / date / number 等 numberFormat)覆盖整列模板行,供作导入模板逐行填写 | | headerStyle | 否 | 继承 | HeaderStyle | 单表表头样式覆盖 |

ColumnDef 列配置

| 配置项 | 必填 | 默认值 | 类型 | 含义 | | --- | --- | --- | --- | --- | | label | 是 | — | string | 表头文案 | | field | 是 | — | string | 数据字段名(从数据行取值) | | type | 否 | 自动推断 | ColumnDataType | string/number/boolean/date/datetime/select | | options | 条件 | — | SelectOption[] | type:'select' 时的下拉选项 | | width | 否 | 自动 | number | 列宽(字符数) | | hidden | 否 | false | boolean | 是否隐藏该列 | | sort | 否 | 声明顺序 | number | 列排序序号(越小越靠前) | | align | 否 | 继承 | 'left'\|'center'\|'right' | 水平对齐 | | wrapText | 否 | false | boolean | 是否自动换行 | | height | 否 | 继承 | number | 列级行高 | | backgroundColor | 否 | 继承 | string | 背景色(十六进制,不含 #) | | fontColor | 否 | 继承 | string | 字体颜色 | | bold | 否 | false | boolean | 是否加粗 | | numberFormat | 否 | — | string | 数字/日期显示格式,如 '#,##0.00'、'yyyy-mm-dd' | | format | 否 | — | (value, ctx) => unknown | 单列展示值格式化 | | default | 否 | — | unknown | 空值/非法值默认替换 | | asText | 否 | false | boolean | 强制以文本渲染,杜绝科学计数法 | | invalidReplace | 否 | 保留原值 | ((v)=>unknown)\|unknown | 值超出 select 枚举时的替换 | | conditionalStyle | 否 | — | (value, ctx) => Partial<CellStyle> | 单列条件样式 |

样式

HeaderStyle(表头)

| 配置项 | 默认值 | 含义 | | --- | --- | --- | | height | 24 | 表头行高 | | bold | true | 加粗 | | backgroundColor | 4472C4 | 背景色 | | fontColor | FFFFFF | 字体颜色 | | fontSize | 11 | 字号 | | align | center | 水平对齐 | | verticalAlign | center | 垂直对齐 | | borders | ['Top','Bottom','Left','Right'] | 边框 | | borderColor | 8EAADB | 边框颜色 |

DefaultCellStyle(单元格)

| 配置项 | 默认值 | 含义 | | --- | --- | --- | | rowHeight | 22 | 数据行高 | | align | left | 水平对齐 | | verticalAlign | center | 垂直对齐 | | wrapText | false | 自动换行 | | borders | ['Top','Bottom','Left','Right'] | 边框 | | borderColor | D9D9D9 | 边框颜色 | | fontSize | 11 | 字号 | | bold | false | 加粗 | | fontColor | 000000 | 字体颜色 | | backgroundColor | FFFFFF | 背景色 |

样式通过 样式 ID 复用 机制生成:相同样式单元格复用同一 ID,大幅压缩 XML 体积。

合并配置

mergeAuto(按列连续相同值自动合并)

| 配置项 | 必填 | 默认值 | 含义 | | --- | --- | --- | --- | | field | 是 | — | 字段名 | | across | 否 | false | 是否同时跨列合并(单行内左侧连续相同值) |

mergeCustom(手动精准合并)

| 配置项 | 必填 | 含义 | | --- | --- | --- | | startRow / endRow | 是 | 起始/结束行(Excel 1 基,含) | | startCol / endCol | 是 | 起始/结束列(Excel 1 基,A=1) |

自动校验范围合法性,重叠/越界/无效合并自动静默跳过并日志提示。

多级表头设计(createMergeDesign)

多级表头(标题整行 / 分组跨列 / 纵向跨行)的手动坐标计算繁琐易错。createMergeDesign 用「表格行模型」描述表头,一键生成 columns 与 mergeCustom(Excel 1 基),直接交付给 exportToXlsx 使用。纯工具方法,零侵入核心渲染链路。

layout(表头布局对象)

| 配置项 | 必填 | 默认值 | 类型 | 含义 | | --- | --- | --- | --- | --- | | title | 否 | — | string | 便捷标题行:自动作为第一行整行合并 | | rows | 是 | — | HeaderDesignRow[] | 多级表头(顶行到底行),最后一行即数据列(叶子)所在行 |

HeaderDesignCell(单元格)

| 配置项 | 必填 | 默认值 | 类型 | 含义 | | --- | --- | --- | --- | --- | | label | 否 | — | string | 文案 | | field | 条件 | — | string | 叶子列数据字段名(叶子列推荐必填) | | colspan | 否 | 1 | number | 向右跨列数 | | rowspan | 否 | 1 | number | 向下跨行数 | | column | 否 | — | Partial<ColumnDef> | 叶子列透传(width/type/align/asText/options/...) |

options(生成选项)

| 配置项 | 必填 | 默认值 | 含义 | | --- | --- | --- | --- | | dataStartRow | 否 | 表头紧贴数据 | 正式数据起始 Excel 行号(表头位于其上方);不设时默认表头占 headerRows 行后即数据。优先于 startRow | | startRow | 否 | 1 | 表头起始 Excel 行号(与 dataStartRow 取一,用于在已有数据表上方插入多级表头) | | data | 否 | — | DataRow[] 数据行。传入后额外计算「数据区纵向合并」,返回值携带 rows / dataMerges / dataPreview | | nestedField | 否 | —(可从 data 自动识别) | 嵌套数组字段名(如 cj)。提供后把每个数据行按该数组展开成多行;字段名以 ${nestedField}. 开头的列视为子表叶子列(逐行变化,不合并),其余父列在同一展开块内纵向合并。不传时,若 mergeOn 也未指定,会自动从 data 首个数据行中唯一的数组字段提取该字段 | | mergeOn | 否 | 父列 | 需要「相邻行相同值即纵向合并」的列字段名列表;不传且有 nestedField 时自动取父列 | | dataPreviewLimit | 否 | 50 | 生成数据区预览矩阵的最大行数(仅可视化,不影响合并) | | extraCustom | 否 | [] | 自定义合并规则:额外补充的合并范围(Excel 1 基) | | mergeAuto | 否 | [] | 自动合并规则,原样透传给 SheetConfig.mergeAuto | | headerStyle | 否 | — | 表头默认样式,原样透传(返回值携带) |

返回值 HeaderDesignResult

| 字段 | 类型 | 含义 | | --- | --- | --- | | columns | ColumnDef[] | 最底层叶子列 → 直接作 SheetConfig.columns | | mergeCustom | DesignRange[] | 全部合并范围(表头 + 数据区,Excel 1 基)→ 直接作 mergeCustom | | preview | MergePreviewCell[][] | 表头二维预览矩阵(可渲染可视化预览) | | rows | DataRow[] | 展开后的平铺数据行(提供 data 时生成,可直接作 SheetConfig.data) | | dataRowCount | number | 展开后数据总行数(未传 data 为 0) | | dataMerges | DesignRange[] | 数据区纵向合并范围(已并入 mergeCustom,单独列出便于排查) | | dataPreview | MergePreviewCell[][] | 数据区合并预览矩阵(0 基,按 dataPreviewLimit 截断) | | warnings | MergeWarning[] | 冲突/非法/缺 field 提示 | | headerRows | number | 表头占用设计行数(含 title) | | columnCount | number | 表头总列数 | | mergeAuto | MergeAuto[] | 透传的自动合并规则 |

合并区域数据填充策略 resolveMergeFill(strategy, values)

| 策略 | 说明 | | --- | --- | | first(默认) | 取首个非空值(表头合并场景即左上角 label) | | last | 取最后一个非空值 | | center | 取中间位置值 | | concat | 所有非空值用逗号连接(字符串化) | | average | 数值均值(无数值返回 null) |

常用合并场景模板 MERGE_TEMPLATES

| 名称 | 场景 | | --- | --- | | quarterReport | 季度报表:标题整行 + 部门纵向 + 季度组跨列 | | scorecard | 成绩单:姓名 + 语/数/英三科 + 总分 | | annualSummary | 年度汇总:标题整行 + 上/下半年分组 + 年度合计 |

合并冲突检测与预览

| 函数 | 说明 | | --- | --- | | detectMergeConflicts(ranges) | 返回 MergeWarning[](overlap / invalid / 1x1) | | previewMerge | 通用合并占用矩阵(0 基行列) |

方案保存 / 加载(纯 JSON,无存储副作用)

| 函数 | 说明 | | --- | --- | | serializeMergeScheme(layout) | 序列化为 JSON 字符串 | | parseMergeScheme(json) | 反序列化回 layout(非法 JSON 抛错并提示) |

使用示例

import { ExcelExporter, downloadBuffer, createMergeDesign } from 'excel-write-plugin';

// ① 表格行模型描述多级表头
const layout = {
  title: '2026 销售季度报表', // 标题整行合并
  rows: [
    { cells: [
      { label: '部门', rowspan: 2 },
      { label: '季度', colspan: 3 },
      { label: '年度合计', rowspan: 2 },
    ] },
    { cells: [
      { label: '一季度', field: 'q1', width: 12 },
      { label: '二季度', field: 'q2', width: 12 },
      { label: '三季度', field: 'q3', width: 12 },
    ] },
  ],
};

// ② 一键生成列配置与合并范围
const design = createMergeDesign(layout);
// design.mergeCustom[0] === { startRow: 1, endRow: 1, startCol: 1, endCol: 5 }

// ③ 直接交付导出器(headerDesign 自动渲染多级合并表头,数据从表头之下开始)
const res = await new ExcelExporter().exportToXlsx({
  sheets: [{
    sheetName: '季度报表',
    headerDesign: design,
    data: [
      { dept: '研发部', q1: 100, q2: 120, q3: 90 },
      { dept: '产品部', q1: 80, q2: 60, q3: 70 },
    ],
  }],
});
downloadBuffer(res.buffer, '季度报表.xlsx');

说明:SheetConfig.columns 现在是可选的 —— 提供 headerDesign 时数据列取 design.columns,并按设计渲染多行合并表头。 若只想拿到合并范围自行处理,可不用 headerDesign,改用 columns: design.columns, mergeCustom: design.mergeCustom。

嵌套子表 → 数据行纵向合并(传入 data + nestedField)

当数据行内嵌子表数组(如 cj)时,可把 data 一并传入:createMergeDesign 会将子表展开成多行,并自动合并父列(班级/姓名/金额)在子表行之间重复的纵向格子,结果 rows / mergeCustom 直接交付导出:

import { ExcelExporter, downloadBuffer, createMergeDesign } from 'excel-write-plugin';

const data = [
  { dept: '三班', name: '张三', amount: 100, cj: [
    { name: '语文', fs: 60 }, { name: '数学', fs: 70 } ] },
  { dept: '三班', name: '李四', amount: 200, cj: [
    { name: '语文', fs: 60 }, { name: '数学', fs: 70 } ] },
];

const design = createMergeDesign({
  title: '班级成绩汇总',
  rows: [
    { cells: [
      { label: '学生信息', colspan: 3 },
      { label: '学科成绩(嵌套展开)', colspan: 2 },
    ] },
    { cells: [
      { label: '班级', field: 'dept' },
      { label: '姓名', field: 'name' },
      { label: '金额', field: 'amount', column: { type: 'number' } },
      { label: '学科', field: 'cj.name' },
      { label: '成绩', field: 'cj.fs', column: { type: 'number' } },
    ] },
  ],
}, { data, nestedField: 'cj' });
// ↑ `nestedField` 可省略:不传且未指定 `mergeOn` 时,会自动从 `data` 中唯一的数组字段识别 'cj'

// design.rows         → 展开后的平铺数据(每行 cj 变为单个科目对象)
// design.dataMerges   → 父列纵向合并范围(已并入 mergeCustom)
// design.dataPreview  → 数据区合并预览矩阵(可渲染可视化预览)

const res = await new ExcelExporter().exportToXlsx({
  sheets: [{
    sheetName: 'NestedMerge',
    headerDesign: design,        // 渲染多级表头
    mergeCustom: design.mergeCustom, // 应用数据区纵向合并
    data: design.rows,           // 展开后的平铺数据
  }],
});
downloadBuffer(res.buffer, 'nested.xlsx');

独立工具函数

| 函数 | 说明 | | --- | --- | | expandNestedData(data, nestedField) | 把嵌套数组字段展开为平铺行(父列合并与导出用) | | computeDataMerges(data, columns, { nestedField, mergeOn, firstRow }) | 对指定列做「相邻相同值」纵向分组合并,返回 { rows, merges, warnings } |

数据源

| 形式 | 说明 | 适用场景 | | --- | --- | --- | | DataRow[] | 普通数组 | 小数据 | | createBatchSource().iterable | 分批推送(pusher.push(batch)) | 分页数据,几十万~百万级 | | async function* (){} | 异步迭代器 | 自定义流式生成 | | 数据库游标 | 与 AsyncIterable 同形 | MySQL / MongoDB 游标 | | 事件流 | 兼容 on/asyncIterable | 浏览器事件流 / ReadableStream |

// 分批推送示例(内存仅保留单批次)
const { pusher, iterable } = createBatchSource();
const promise = new ExcelExporter().exportToXlsx({ sheets: [{ columns, data: iterable }] });
// 模拟游标分批喂入
for (const batch of dbQueryBatches()) {
  const shouldPause = pusher.push(batch); // 背压感知
  if (shouldPause) await new Promise((r) => setTimeout(r, 0));
}
pusher.end();
const res = await promise;

分功能示例

1. 下拉选项列(select)

const columns = [
  {
    label: '订单状态',
    field: 'status',
    type: 'select', // 启用整列下拉枚举
    options: [
      { label: '待支付', value: '0' },
      { label: '已支付', value: '1' },
      { label: '已取消', value: '2' },
    ],
    width: 12,
  },
  { label: '金额', field: 'amount', type: 'number', width: 12 },
];
// 数据中 status 为 '1' → 单元格展示 "已支付";越界值自动保留原值兜底
await new ExcelExporter().download({ sheets: [{ columns, data }] }, { filename: '下拉.xlsx' });

2. 反科学计数法(超长数字)

{ label: '身份证', field: 'idcard', asText: true, width: 20 }
// 11 位以上数字强制文本,避免 Excel 科学计数法

3. 单元格自动合并 + 手动合并

const sheet = {
  sheetName: '订单报表',
  columns,
  data,
  // 状态列连续相同值自动纵向合并
  mergeAuto: [{ field: 'status' }],
  // 手动精准合并:第 1 行 A~C 列(Excel 1 基)
  mergeCustom: [{ startRow: 1, endRow: 1, startCol: 1, endCol: 3 }],
};
await new ExcelExporter().download({ sheets: [sheet] }, { filename: '合并.xls', format: 'xls' });

4. 条件样式(数据超标标红)

const cond = (value) => (value > 10000 ? { fontColor: 'D1242F', bold: true } : null);
const columns = [
  { label: '金额', field: 'amount', type: 'number', conditionalStyle: cond },
];

5. 自定义格式化 + 日期

const columns = [
  {
    label: '创建时间',
    field: 'createdAt',
    type: 'datetime',
    numberFormat: 'yyyy-mm-dd hh:mm:ss',
  },
  {
    label: '金额',
    field: 'amount',
    type: 'number',
    numberFormat: '#,##0.00',
    format: (v) => `¥${v}`, // 展示值拼接
  },
];

6. 模板导出(整列下拉 / 日期 / 数字类型)

适合「导入模板」场景:将下拉、日期、数字等列类型覆盖到整列模板行,用户下载后可直接逐行填写,无需手动设置单元格格式。 推荐用 .xlsx(exportToXlsx):下拉用标准 dataValidation 整列(sqref=C2:C1048576)生效,WPS/Excel 编辑时均可下拉,日期/数字用 numFmt 整列格式化。.xls(exportToBuffer)也支持 templateRows 兜底。

const config = {
  sheets: [
    {
      sheetName: '人事模板',
      // 数据源可为空数组,只生成表头 + 模板空行
      data: [],
      // 模板行数:整列类型覆盖到第 1+templateRows 行
      templateRows: 200,
      columns: [
        {
          label: '性别',
          field: 'gender',
          type: 'select', // 整列下拉
          options: [
            { label: '男', value: 'M' },
            { label: '女', value: 'F' },
          ],
        },
        { label: '姓名', field: 'name' },
        { label: '入职日期', field: 'joinDate', type: 'date' }, // 整列日期格式
        { label: '月薪', field: 'salary', type: 'number', numberFormat: '#,##0' }, // 整列数字格式
      ],
    },
  ],
};
await new ExcelExporter().download(config, { filename: '人事模板.xlsx' }); // 标准 .xlsx 模板(推荐)

7. 生命周期钩子(过滤 / 改写)

await new ExcelExporter().download({ sheets }, {
  filename: '钩子.xlsx',
  hooks: {
    beforeExport: () => console.log('导出开始'),
    beforeRow: (row) => (row.active === 1 ? row : null), // 过滤已离职
    beforeClose: () => console.log('文件闭合前,可追加统计'),
    onError: (e) => console.error(e.code, e.message),
  },
});

8. 进度回调 + 主动中断

const exporter = new ExcelExporter();
const promise = exporter.exportToXlsx(config, {
  onProgress: (p) => console.log(`${p.processedRows} 行,占用 ${p.bytesWritten} 字节`),
});

// 任意时刻主动中断
setTimeout(() => exporter.abort(), 3000);

await promise.catch((e) => console.log(e.code)); // 'aborted'

9. 上限保护 + 超时

// 防止误操作导出千万级
await new ExcelExporter().exportToXlsx(config, { maxRows: 1000000 });

// 长时导出无响应自动回收
await new ExcelExporter().exportToXlsx(config, { timeout: 60_000 });

10. 多工作表

await new ExcelExporter().download(
  {
    sheets: [
      { sheetName: '员工', columns: userCols, data: users },
      { sheetName: '订单', columns: orderCols, data: orders },
    ],
  },
  { filename: '多表.xlsx' }
);

11. 数据库游标流式导出

// 以 MySQL 为例:queryStream 返回 AsyncIterable / Readable
const cursor = db.query('SELECT * FROM orders').stream(); // MySQL2 stream
await new ExcelExporter().download({ sheets: [{ columns, data: cursor }] });

内存与性能说明

  • 浏览器内存模型:浏览器无法落盘,导出必须一次产出整个文件,内存峰值约等于输出文件体积,属浏览器环境固有约束。适合常规至较大规模数据;超大导出请合理评估浏览器内存上限。
  • 流式缓冲分块:bufferSize(默认 64KB)控制写缓冲阈值,减少分片数量。
  • 样式 ID 复用:相同样式单元格复用同一 ID,压缩 XML 体积。
  • 非阻塞事件循环:每 YIELD_EVERY_ROWS(2000)行让出一次事件循环,页面不卡顿。
  • 装配顺序(.xls):为兼顾「<Styles> 须在 <Worksheet> 前」与「流式」,先把各工作表内容渲染到内存分片(同时完成样式全量注册),再一次性装配表头 + <Styles> + 各分片 + 表尾。

大数据分批推送 + .xlsx 组合:createBatchSource 分批喂入,内存仅驻留单批次数据,适合十万级以上的分页导出。

.xls 与 .xlsx 能力对照

| 能力 | .xls(exportToBuffer) | .xlsx(exportToXlsx) | | --- | --- | --- | | 输出格式 | Excel 2003 SpreadsheetML | 标准 OOXML(ZIP 容器) | | 多工作表 | ✅ | ✅ | | 表头样式 / 列对齐 / 自动换行 / 背景色 / 字体色 / 加粗 | ✅ | ✅ | | 列宽 / 列排序 / 列隐藏(hidden/sort) | ✅ | ✅ | | mergeCustom 手动合并 | ✅ | ✅ | | mergeAuto 自动合并(含 a.b 点路径字段) | ✅ | ✅ | | 多级表头 createMergeDesign | ✅ | ✅ | | 数据流式读取(数组 / 分批 / AsyncIterable / 游标 / 流) | ✅ | ✅ | | beforeRow / afterRow / 生命周期钩子 | ✅ | ✅(已对齐) | | hideEmptyRows 空行隐藏 | ✅ | ✅(已对齐) | | maxRows / timeout / abort 中断 | ✅ | ✅(已对齐) | | freezeHeader 冻结首行 | ✅ | ✅(已对齐) | | autoFilter 数据筛选 | ✅ | ✅(已对齐) | | 全局条件样式(bold / fontColor / backgroundColor) | ✅ | ✅(已对齐) | | 行高(rowHeight / headerHeight) | ✅ | ✅(已对齐) | | select 下拉 | DataValidation | 标准 dataValidation,WPS/Excel 均可靠识别 | | date / datetime / number 整列格式 | numberFormat | 自定义 numFmt 整列格式化 | | 进度回调 onProgress | ✅ | ✅ | | 体积 / 内存 | 流式分片,体积更小 | 整表内存组装,适合模板/常规规模 | | 推荐场景 | 超大数据的极低内存流式导出 | 模板、常规数据、对外交付的标准 .xlsx |

说明:.xlsx 管线在本次评估中补齐了与 .xls 的系统特性对齐(生命周期钩子、进度、上限/超时/中断、冻结、筛选、空行隐藏、全局条件样式、mergeAuto、行高)。超大数据的极低内存导出仍推荐使用 .xls 流式方案。


错误码

ExcelError.code 取值(ExcelExportErrorCode):

| 错误码 | 含义 | | --- | --- | | CONFIG_ERROR | 导出配置有误 | | DATASOURCE_ERROR | 数据源读取异常 | | OUTPUT_ERROR | 输出异常 | | FILE_ERROR | 文件(内部资源)异常 | | STREAM_ERROR | 流写入异常 | | MAX_ROWS_EXCEEDED | 导出行数超过上限 | | TIMEOUT | 导出超时,已自动中断 | | ABORTED | 导出被主动中断 | | INTERNAL_ERROR | 内部异常 |

try {
  await new ExcelExporter().download({ sheets });
} catch (e) {
  if (e instanceof ExcelError) console.log(e.code, e.message, e.detail);
}

浏览器演示

仓库内置 demo/ Vue3 演示页(Vite,端口 5178),左侧功能示例、右侧代码示例,全部为纯浏览器导出,无需任何服务端。

# 1. 构建核心产物
npm run build

# 2. 启动演示页
npm run demo:dev            # http://localhost:5178

License

MIT © SEMS Team