
简介这份资源面向需要在浏览器端将网页表格导出为Excel并保留样式的开发者尤其适合使用谷歌浏览器、希望快速落地导出功能的前端人员。内容围绕两种样式保留思路展开一是在td行内直接写style二是把CSS规则写入导出模板并给出可直接运行的完整代码与tableToExcel函数实现帮助读者理解Blob、base64与模板拼接的配合方式。资源包共1个文件为PDF文档大小约62KB篇幅精炼便于快速查阅与对照练习。目前已有2077人学习下载说明该方案在实际项目中具有一定参考价值。读者可从中获得可复用的导出脚本、样式保留的取舍思路以及常见局限说明例如复杂CSS在部分Excel版本中可能无法完全还原并了解js-xlsx、SheetJS等第三方库的进阶方向适合作为表格导出功能的入门与排错参考。1. 前端表格导出 Excel 保留样式为什么原生 API 总是丢边框和背景色做过管理后台的同行大概率都遇到过这个场景产品经理指着页面上一张带斑马纹、表头深色背景、金额列红色高亮的 table说「导出成 Excel要和页面长得一样」。你打开搜索抄了一段Blobtable.innerHTML的代码导出成功但打开文件一看——边框没了背景色没了列宽全乱中文还偶尔乱码。这不是你代码写错了而是路线选错了。浏览器原生能做的是把 HTML 表格序列化成一个.xls后缀的 HTML 文件Excel 打开时靠自身的 HTML 解析器去还原。它只认最基础的border、bgcolor这类上古属性对 CSS 里的background-color、border-collapse、padding基本视而不见。想真正保留样式必须换一条路用 SheetJS 这类库生成真正的 xlsx再叠加样式层。这篇就把这条链路从选型、实现、参数到踩坑完整走一遍适合正在做后台导出、报表下载、对账文件的前端和全栈同学。2. 选型先想清楚HTML 直出、SheetJS 还是 ExcelJS2.1 三条路线的能力边界对比在动手前先把可选方案摆到台面上。很多人一上来就搜「JS 导出 Excel」拿到的答案五花八门本质上是三类完全不同的技术路线能力上限差得很远。方案原理样式支持体积适用场景HTML 直出table 序列化为 HTML伪装 xls仅 border/bgcolor0 依赖纯数据、无样式要求SheetJS (xlsx)生成标准 xlsx 二进制社区版样式弱需 Pro~400KB数据导出为主ExcelJS生成标准 xlsx完整样式 API字体/填充/边框/列宽全支持~900KB报表、对账、需还原样式结论很直接只要标题里出现「保留样式」四个字HTML 直出就可以直接排除SheetJS 社区版在样式上也会让你难受——它的cell.s样式对象在开源版本里对填充和边框的支持并不完整很多写法在导出后不生效。真正能稳定还原表头背景、单元格边框、字体颜色的是 ExcelJS。那 SheetJS 是不是就没用了也不是。如果只是导数据、对性能敏感、包体积卡得死SheetJS 依然是首选。选型的判断标准就一条样式是不是硬需求。是就 ExcelJS不是SheetJS 更轻。2.2 为什么样式必须走 xlsx 二进制而不是 HTML这里补一句原理理解了就不会再走回头路。xlsx 本质是一个 zip 包里面是若干 XMLxl/styles.xml定义所有样式xl/worksheets/sheet1.xml定义单元格内容和它引用的样式索引s。ExcelJS 做的事就是帮你把这些 XML 结构用 JS 对象描述出来再打包成 zip。而 HTML 直出根本没有 styles.xml 这一层Excel 打开时只能用它内置的 HTML 兼容解析器去猜猜得到 border 就算运气好猜不到 background-color 是必然的。所以「保留样式」这个需求从原理上就决定了必须生成真正的 xlsx。这也是为什么很多同学换了三四个库样式还是丢——路线没对换库无用。3. 用 ExcelJS 在浏览器里跑通最小导出3.1 安装与最小可运行代码先装依赖。ExcelJS 同时支持 Node 和浏览器浏览器端直接用打包工具引入即可。npm install exceljs file-saver下面是一段能在浏览器里直接跑通的最小代码导出一张带表头背景和边框的表。import ExcelJS from exceljs; import { saveAs } from file-saver; async function exportTable(tableEl, fileName export.xlsx) { const workbook new ExcelJS.Workbook(); const sheet workbook.addWorksheet(Sheet1); // 1. 读取页面 table 的表头和表体 const rows Array.from(tableEl.querySelectorAll(tr)); const data rows.map(tr Array.from(tr.querySelectorAll(th,td)).map(td td.innerText.trim()) ); // 2. 写入数据 data.forEach(row sheet.addRow(row)); // 3. 给表头加样式深色背景 白色加粗字体 居中 const headerRow sheet.getRow(1); headerRow.eachCell(cell { cell.fill { type: pattern, pattern: solid, fgColor: { argb: FF2F54EB } // 注意是 ARGB前面两位是透明度 }; cell.font { color: { argb: FFFFFFFF }, bold: true, size: 12 }; cell.alignment { vertical: middle, horizontal: center }; cell.border { top: { style: thin, color: { argb: FFD9D9D9 } }, left: { style: thin, color: { argb: FFD9D9D9 } }, bottom: { style: thin, color: { argb: FFD9D9D9 } }, right: { style: thin, color: { argb: FFD9D9D9 } } }; }); // 4. 生成 buffer 并触发下载 const buffer await workbook.xlsx.writeBuffer(); saveAs(new Blob([buffer]), fileName); }逻辑说明addRow按行写入第一行默认就是表头getRow(1)拿到表头行后逐格设置样式。参数上最容易翻车的是颜色格式——ExcelJS 用的是ARGBFF2F54EB里前两位FF是不透明度写成2F54EB六位会直接报错或渲染异常。border的四个方向必须分别写只写bottom就只有下边框。3.2 列宽、行高与合并单元格怎么设数据能导出了接下来是让版式接近页面。列宽和行高不设Excel 会按默认宽度铺开长文本挤成一团。// 按页面列宽估算Excel 列宽单位约等于字符数 sheet.columns [ { width: 12 }, // 序号列 { width: 24 }, // 名称列 { width: 18 }, // 金额列 { width: 30 } // 备注列 ]; // 行高单位是磅 sheet.getRow(1).height 28; // 合并单元格从 A1 到 D1 合并做标题 sheet.mergeCells(A1:D1); sheet.getCell(A1).value 2024 年度对账单; sheet.getCell(A1).alignment { horizontal: center, vertical: middle };参数说明width是字符宽度中文大约占 2 个字符宽所以中文列要按字数乘 2 再留点余量。mergeCells之后只有左上角单元格能写值其他格子的值会被清掉这是 xlsx 的规范决定的不是库的 bug。合并后如果还要加边框得手动给合并区域的每个格子都设一遍否则只有左上角有框。3.3 从页面 table 自动映射样式的思路如果表格列很多手写每一列的样式不现实。常见做法是遍历页面 DOM把getComputedStyle拿到的背景色、字体色、对齐方式映射到 ExcelJS 的样式对象上。function mapStyle(td) { const cs getComputedStyle(td); return { fill: { type: pattern, pattern: solid, // rgb() 转 ARGB补上 FF 不透明度 fgColor: { argb: FF rgbToHex(cs.backgroundColor) } }, font: { color: { argb: FF rgbToHex(cs.color) }, bold: parseInt(cs.fontWeight) 600 }, alignment: { horizontal: cs.textAlign center ? center : left, vertical: middle } }; }这里的关键是rgbToHex转换函数把rgb(47, 84, 235)转成2F54EB。注意getComputedStyle返回的可能是rgba带透明度的要单独处理Excel 的填充不支持半透明遇到rgba只能取近似实色。这套自动映射能覆盖 80% 的常规样式剩下的特殊列再手动覆盖。4. 大数据量与中文场景下的性能与编码处理4.1 万行以上表格的分批写入与内存控制ExcelJS 在浏览器里是纯内存操作一万行以内问题不大超过五万行就可能把标签页卡死甚至崩溃。血泪经验是不要一次性addRow几万次也不要在主线程里同步跑完。const CHUNK 2000; async function writeInChunks(sheet, data) { for (let i 0; i data.length; i CHUNK) { const slice data.slice(i, i CHUNK); slice.forEach(row sheet.addRow(row)); // 让出主线程避免页面假死 await new Promise(r setTimeout(r, 0)); } }参数说明CHUNK取 1000 到 3000 之间比较稳太小会让出太频繁拖慢总时长太大又起不到防卡死的作用。如果数据量真的到了十万级更靠谱的做法是把导出放到 Web Worker 里主线程只负责接收 buffer 和触发下载这样页面完全不会卡。4.2 中文乱码与日期格式的两个必调项中文乱码在 xlsx 路线里其实很少见因为 xlsx 内部就是 UTF-8 的 XML。真正会出问题的是从 HTML 直出那条路需要加 BOM 头。既然我们走的是 ExcelJS重点应该放在日期和数字格式上。// 日期列写入 Date 对象再设 numFmt sheet.getColumn(3).numFmt yyyy-mm-dd; sheet.getColumn(4).numFmt #,##0.00; // 金额千分位保留两位 // 如果数据源是字符串日期先转 Date const d new Date(2024-01-15); sheet.getCell(C2).value d;参数说明numFmt用的是 Excel 的格式代码yyyy-mm-dd是日期#,##0.00是千分位金额。如果直接把字符串2024-01-15写进去Excel 会当文本处理排序和求和都会失效。金额同理写数字再设格式不要写带逗号的字符串。5. 导出保留样式的避坑清单5 个真实翻车现场5.1 现象表头背景色导出来是黑的原因颜色写成了六位2F54EBExcelJS 按 ARGB 解析时把前两位当成了不透明度剩下的位数错位渲染成异常颜色。解决所有颜色统一补FF前缀写成FF2F54EB。5.2 现象边框只显示一部分或者完全没有原因只给单元格设了bottom或者合并单元格后只设了左上角。解决四个方向分别写全合并区域要给区域内每个格子都设边框或者用sheet.getCell(range)遍历。5.3 现象导出的文件 Excel 提示「格式与扩展名不一致」原因文件后缀写成了.xls但内容是 xlsx 二进制。解决ExcelJS 生成的就是 xlsx后缀必须用.xlsx不要为了兼容老版本改后缀。5.4 现象大数据量导出时页面卡死几秒原因几万次addRow同步执行阻塞主线程。解决分批写入并await让出主线程或直接上 Web Worker。5.5 现象中文列宽怎么调都显示不全原因按英文字符数估的列宽中文实际占两个字符宽。解决中文列宽按「字数 × 2 4」估算或者干脆设一个偏大的固定值再让用户自己拖。6. 把导出封装成可复用工具一个进阶技巧走到这里导出功能基本能用了。但如果项目里有十几个页面都要导出每个页面复制一遍样式代码就是灾难。我一般的做法是封装一个exportWithStyle(tableEl, options)把「读 DOM → 映射样式 → 分批写入 → 下载」整条链路收进去页面只传一个 table 元素和文件名。进阶技巧在于样式模板的抽离。把表头、斑马纹、金额列这些常用样式定义成配置对象页面按需引用而不是每次重写。const STYLE_PRESETS { header: { fill: { type: pattern, pattern: solid, fgColor: { argb: FF2F54EB } }, font: { color: { argb: FFFFFFFF }, bold: true }, alignment: { horizontal: center, vertical: middle } }, zebra: { fill: { type: pattern, pattern: solid, fgColor: { argb: FFF5F5F5 } } }, money: { numFmt: #,##0.00, alignment: { horizontal: right } } }; function applyPreset(cell, preset) { Object.assign(cell, STYLE_PRESETS[preset]); }这样页面里只需要声明「第 1 行用 header偶数行用 zebra第 4 列用 money」样式逻辑集中在一处改一次全局生效。验证方法也很简单导出后不要只看文件能不能打开一定要用 Excel 的「检查」或者直接解压 xlsx 看xl/styles.xml确认样式索引真的写进去了。我踩过最深的坑就是代码没报错、文件能打开但样式静默丢失最后靠解压对比 XML 才定位到是颜色格式写错。一个习惯送给你任何导出功能上线前我都会拿三种数据测一遍——空表格、超长中文、一万行以上。这三种能过基本就不会在用户那里翻车。希望帮到你。本文还有配套的精品资源点击获取