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