前阵子接了个后台管理系统的改造需求,报表页要加一个“导出 Excel”按钮。数据已经在前端拉下来了,后端不提供导出接口,也不可能为了一个导出功能去加一个服务。我当时的思路就一个:纯 JS 把数据生成 Excel 能打开的文件,浏览器直接下载。做完之后我把这套方案沉淀成了一个不依赖后端、不依赖网络请求的纯 JS 导出工具,离线环境也能秒出表格文件。这篇就把它讲透:为什么有现成库还要自己写、几条技术路线怎么选、核心代码怎么组织、以及乱码、数字精度、性能这些坑是怎么一个个踩平的。
如果你是做后台管理、报表、H5 数据页的同学,应该会用到。
1. 为什么非要在浏览器里直接生成 Excel 文件
1.1 没有后端,但业务就是要导出
先说真实场景。普通的前后端分离项目,导出 Excel 一般分两种做法:一种是后端查完数据库直接生成 .xlsx 返回,前端拿个下载地址就完事;另一种是后端返回 JSON,前端本地生成,也就是常说的“纯前端导出”。后者以前被很多人嫌弃,觉得 JS 生成的 Excel 不正规、样式难控制、大文件卡顿。但这两年我得说句公道话:前端导出在某些场景下不是备选,而是最优解。
首先是部署轻。有些内网系统、离线工具、单页应用,压根没有自有后端,数据来自第三方接口或者本地缓存,这时候你不可能为了导出再搭一个服务。其次是响应快。后端导出要等接口返回文件流,如果数据体量不大,前端生成几乎是秒开。还有一个隐藏优势是可控性:后端导出的格式一旦要改,得发版,前端导出想调整列宽、加个筛选,直接改代码立刻生效。
1.2 先别急着写代码,把导出需求拆成三个等级
我见过很多同事一上来就引 SheetJS,也不管项目里到底需不需要那么重的库。其实“导出 Excel”这四个字,背后的需求可能是完全不同的三个等级:
| 需求等级 | 典型场景 | 必须能力 |
|---|---|---|
| L1 数据型 | 导出订单列表、用户表格 | 能打开、编码正确、字段完整 |
| L2 样式型 | 导出对账报表、运营周报 | 表头颜色、合并单元格、列宽 |
| L3 交互型 | 导出带公式、筛选、图表的文件 | 单元格公式、数据验证、图表 |
L1 用 CSV 就能解决,代码二十行;L2 要考虑用 HTML 表格或者 XML Spreadsheet;L3 才真正需要 SheetJS 这种能读写 .xlsx 的库。搞清楚自己处在哪个等级,再决定技术路线,这比盲目造轮子重要得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 几条“纯前端生成 Excel”路线的原理与取舍
2.1 CSV 路线:最轻量,但只适合纯数据
CSV 本质就是一个用逗号分隔的纯文本文件,Excel 能直接打开。它的优势是简单到不行,生成字符串,塞进 Blob,触发下载,完事。但它有两个致命问题:一是默认不支持样式,你想加个表头加粗、背景色,没门;二是中文容易出现编码问题,必须加上 UTF-8 BOM 才能让 Excel 正确识别。
javascript复制function exportCSV(rows, filename = 'export.csv') {
const content = rows
.map(row => row
.map(cell => {
const text = String(cell ?? '');
// 包含逗号、引号或换行时必须用引号包裹,内部引号翻倍转义
if (/[",\n]/.test(text)) {
return '"' + text.replace(/"/g, '""') + '"';
}
return text;
})
.join(','))
.join('\r\n');
const blob = new Blob(['\ufeff' + content], {
type: 'text/csv;charset=utf-8;',
});
downloadBlob(blob, filename);
}
function downloadBlob(blob, filename) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
这段代码里有几个细节
