
简介面向Web前端开发者的实用技术资料讲解基于JavaScript将HTML表格导出为Excel并保留样式的方法解决默认导出时样式丢失的问题适用于后台管理、数据报表等需要导出表格的场景。资源为1个PDF文档大小仅62KB内容精炼便于快速阅读。目前已有2077人次学习下载。文中详细介绍两种样式保留方案一是在表格行内直接书写style样式二是在导出模板的style标签中统一定义样式并附完整可运行的HTML与JS代码示例涵盖Blob对象创建、base64转码、下载链接动态生成等核心实现。作者还提示了浏览器安全限制对复杂CSS样式的影响并建议在需要更强大功能时可引入js-xlsx、SheetJS等第三方库扩展。1. 用 JS 把 table 导出成 Excel先分清「导出数据」和「带走样式」是两件事“用 JS 把 table 导出成 Excel”这个需求搜进来的人多半已经在后台管理系统里点过十几次「导出」按钮了。第一次做的时候代码照着 SheetJS 的 README 抄table 也传进去了Excel 也下载了打开一看表头的高亮背景没了红黄蓝的告警色没了合并的单元格全平铺开了只剩白底黑字的数据。这不是写错了是思路选错了——你调用的导出 API 只负责搬数据不负责搬样式。样式想保留得换一条实现路径。接下来按三件事展开为什么默认导出没样式xlsx 的内部结构决定的、两条能真正带走样式的落地路径HTML 转 XLS 和 SpreadML 2003 XML、以及导出后几乎必踩的坑和验证方法。适合正在写后台报表、管理界面、运营数据页导出功能的前端如果你手头是 antd 的表格或 element 表格要接导出同样适用。2. 为什么默认导出“没样式”xlsx 的内部结构和 SheetJS 的边界2.1 xlsx 不是一个表格文件而是一个 zip 包样式住在 styles.xml先弄清楚你要面对的是什么格式。真正带样式的 .xlsx本质是一个 ZIP 压缩包里面按固定目录摆着一组 XML。Excel 打开文件时先解压再按 [Content_Types].xml 找到工作簿、工作表、样式表、共享字符串这些部件。跟样式强相关的有两个文件xl/worksheets/sheet1.xml 存单元格内容和对应的样式索引xl/styles.xml 存样式定义。两者用“索引”串起来。sheet1.xml 里一格是这样写的c rA1 s1 tsv0/v/cr 是坐标s1 表示“A1 单元格使用 styles.xml 里索引为 1 的样式”ts 表示 v 里的 0 指向共享字符串表。Excel 渲染的时候先去 styles.xml 的 cellXfs 里取第 1 个 XF 记录看它引用了哪个 fontId、fillId、borderId再把字体、底色、边框画出来。styles.xml 里对应的片段长这样cellXfs count2 xf numFmtId0 fontId0 fillId2 borderId0 xfId0 applyFill1/ /cellXfs所以“保留样式”这件事在 xlsx 层面就是你得同时把 sheet1.xml 里的 s 索引和 styles.xml 里的样式定义都写出来。任何只写单元格数据的工具都不可能产出带样式的 xlsx。网上很多教程让你用 SheetJS 的 XLSX.utils.table_to_sheet 导出然后告诉你“样式自己加”。加在哪社区版 xlsx 库根本没有写 styles.xml 的能力你就算给单元格塞了 style 对象写出去也会被丢掉。这不是库有 bug是社区版本身的边界——写样式是专业版才支持的功能普通版本只保证数据不错位。2.2 社区版丢掉的到底是什么从 DOM 到数据模型样式在第一步就被过滤了先用一段代码看看社区版常见写法的实际行为import * as XLSX from xlsx; const table document.getElementById(reportTable); const ws XLSX.utils.table_to_sheet(table); // 拿到的是值合并范围 const wb XLSX.utils.book_new(); XLSX.utils.book_append_sheet(wb, ws, 报表); XLSX.writeFile(wb, 报表.xlsx); // 下载后只有数据没有任何样式table_to_sheet 做的事是遍历 DOM 表格提取每个单元格的文本内容、坐标、合并信息!merges组装成一个二维数组。这个数据模型里根本没有 backgroundColor、fontWeight、border 这些字段。页面上的样式存在 CSS 规则和浏览器计算样式里不在 DOM 属性里更不会被复制进 sheet 对象。还有一个常见误区有人给 td 写了内联 style以为 table_to_sheet 会把它收进数据模型。实际上社区版的 table_to_sheet 解析时只读 cell 的文本和合并属性style 属性从头到尾没有进入数据模型。你写在页面上的内联样式和最终 xlsx 里那个单元格之间隔着一道“社区版不写 styles.xml”的墙。这道墙不是配置项能打开的所以搜“SheetJS 导出保留样式配置”出来的答案最后都会指向换方案。如果项目必须产出真 .xlsx 又要带样式业内有一个绕过社区版限制的 hack先用 SheetJS 生成不含样式的 xlsx再用 JSZip 解包手工补一份 styles.xml并在 sheet1.xml 里把每个单元格的 s 属性改成对应的样式索引最后重新压包。这个办法能跑但你需要同步改 [Content_Types].xml、xl/_rels/workbook.xml.rels以及最麻烦的样式索引对齐——十几个单元格还好几百行的报表很容易改错一个索引让整个工作表打不开。我不推荐在生产里这么干除非你只是要给一个固定模板套样式。多数项目的正解是下面两种导出 HTML 让 Excel 渲染或者导出 SpreadML 用样式字典直接生效。方案实现成本样式控制力输出文件实际内容典型场景HTML 转 XLSBlob 导出最低半小时能跑通中吃内联样式html 片段扩展名 .xls后台简单报表、临时导出SpreadML 2003 XML中样式字典可控高颜色/边框/合并精确控制XML 格式扩展名 .xls需要严格对版、交付给客户的报表ExcelJS 逐格映射高要写样式映射函数高输出真 xlsx真 .xlsx前后端一体、还要公式和条件格式如果你已经在项目里用了 ExcelJS 这个 excel 处理框架思路是把 getComputedStyle 的结果映射到每个 cell 的 fill / font / border / alignment 上工作量主要在映射函数。下面两章分别展开前两条路径。3. 方案一把 table 原样装进 HTML 再导出——最小代码和它的样式边界3.1 能直接跑的最小实现Blob HTML 模板 样式内联先交代思路Excel 在打开 .xls 文件时如果发现内容是一段 HTML会尝试用内置的 HTML 渲染器解析它。表格标签、内联样式、rowspan/colspan、colgroup 这些它都能认。所以最快的导出方案不是生成特殊格式而是把页面上那个 table 的 HTML 原样包出去。下面是完整的最小函数直接丢到浏览器控制台也能用function exportTableToExcel(tableId, fileName 报表) { const table document.getElementById(tableId); const clone table.cloneNode(true); // 克隆避免内联样式污染原 DOM inlineStyles(clone); // 关键把计算样式写进 style 属性 const html html xmlns:ourn:schemas-microsoft-com:office:office xmlns:xurn:schemas-microsoft-com:office:excel head meta charsetutf-8 / !--[if gte mso 9] xmlx:ExcelWorkbookx:ExcelWorksheets x:ExcelWorksheetx:Name${fileName}/x:Name x:WorksheetOptionsx:DisplayGridlines//x:WorksheetOptions /x:ExcelWorksheet/x:ExcelWorksheets/x:ExcelWorkbook/xml ![endif]-- /head body${clone.outerHTML}/body/html; const blob new Blob([\ufeff, html], { type: application/vnd.ms-excel;charsetutf-8 }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download ${fileName}.xls; document.body.appendChild(a); a.click(); setTimeout(() { URL.revokeObjectURL(url); a.remove(); }, 100); } function inlineStyles(root) { root.querySelectorAll(table, td, th).forEach(el { const cs window.getComputedStyle(el); let css background-color:${cs.backgroundColor};; css color:${cs.color};border:${cs.border};; css font-family:${cs.fontFamily};font-size:${cs.fontSize};font-weight:${cs.fontWeight};; css text-align:${cs.textAlign};vertical-align:${cs.verticalAlign};; el.style.cssText ; css; }); }这段代码有四个细节决定成败。第一个是 Blob 的 typeapplication/vnd.ms-excel 告诉浏览器“这是 Excel 文件”下载时才会给 .xls 扩展名而不是 .txt。第二个是 \ufeff 前缀这是 UTF-8 BOM没有它中文内容在部分 Windows 老版 Office 里会直接乱码。第三个是 mso 条件注释里的 x:ExcelWorkbook它告诉 Excel“用表格模式打开这个 HTML别用网页模式”没有它导出的文件有时会被 Excel 当成网页源码展示。第四个是 inlineStyles这一步才是“保留样式”的核心getComputedStyle 拿到的是浏览器计算后的最终样式class 里定义的、内联写的、继承来的都会被还原成 style 属性而 Excel 的 HTML 渲染器只吃内联样式。3.2 列宽、行高、合并单元格怎么一起带走colgroup、rowspan 和默认兜底HTML 方案里列宽靠 colgroup 或 td 的 width 属性行高靠 tr 的 styleheight:XXpx合并靠原表格的 rowspan/colspan 属性。这些 Excel 的 HTML 渲染器都认但有几个边界要注意。首先是 table 要有默认样式。如果页面上表格没设过边框getComputedStyle 拿到的 border 可能是 0px none rgb(0,0,0)导出去就是无边框表格。我一般会在导出前给克隆的 table 补一句 border-collapse:collapse再给 td、th 一个兜底边框这样即便原表格没设计边框导出也不至于裸奔。其次是列宽。colgroup 里的 col width 对像素值和百分比都生效但某列如果靠内容撑开导出后列宽会被重排。更稳的做法是导出前量一下实际列宽写回克隆节点function syncColumnWidths(clone, original) { const origCols original.querySelectorAll(col); const cloneCols clone.querySelectorAll(col); origCols.forEach((col, i) { if (col.width || col.style.width) { cloneCols[i]?.setAttribute(width, col.width || parseInt(col.style.width, 10)); } }); }逻辑是先把原 table 的列宽读出来再写回克隆节点的 col 上。原 table 用百分比这里就回填百分比是像素宽就转成数字。Excel 解析 HTML 表格时优先认 width 属性所以 setAttribute 比 style 宽度更稳。第三点如果你的页面表格来自 antd 的 table要小心它渲染出来的 DOM。antd 表格在固定列时会额外渲染一份 .ant-table-fixed 的表头/表体直接把整个 .ant-table 导出去会导出两份表格斑马纹、hover 高亮这些用 CSS 类控制的效果也会因为“类样式不被导出”而失真。干净做法是先删掉 .ant-table-fixed 节点再从 columns 配置和 dataSource 重新组装一个纯 table 元素或者干脆在克隆后先做一次“净化”再内联样式。还有两个 HTML 方案的细节。一个是数字前导零单元格内容是 007 时Excel 打开后会自动判成数字 7前导零丢光要保留 007 这种编号得给 td 加 mso-number-format:强行让 Excel 当文本处理。另一个是空单元格原 table 里存在空 td 时Excel 解析偶尔会跳过导致后续列错位常见做法是把空 td 补一个 占位。另外 HTML 方案永远只能导出一个工作表多 sheet 需求请直接看下一章。4. 方案二用 SpreadML 2003 XML 生成带样式字典的 xls——样式彻底可控4.1 先搭骨架Workbook → Worksheet → Table → Row样式名与单元格绑定SpreadML 是微软在 Office 2003 时代推出的 XML 表格格式扩展名可以叫 .xls但内容是一段格式严格的 XML。它跟 HTML 方案最大的区别是样式不是“渲染出来的”而是被显式声明在 字典里单元格通过 ss:StyleID 引用。你让一个格子什么底色、什么字体、什么边框白纸黑字写在 XML 里Excel 打开时按字典查找样式表现非常稳定。先看骨架?xml version1.0? ?mso-application progidExcel.Sheet? Workbook xmlnsurn:schemas-microsoft-com:office:spreadsheet xmlns:ssurn:schemas-microsoft-com:office:spreadsheet Styles Style ss:IDDefault ss:NameNormal Alignment ss:VerticalBottom/ Font ss:FontName宋体 ss:Size11/ /Style Style ss:IDHeader Interior ss:Color#4472C4 ss:PatternSolid/ Borders Border ss:PositionTop ss:Color#999999 ss:LineStyleContinuous ss:Weight1/ Border ss:PositionBottom ss:Color#999999 ss:LineStyleContinuous ss:Weight1/ Border ss:PositionLeft ss:Color#999999 ss:LineStyleContinuous ss:Weight1/ Border ss:PositionRight ss:Color#999999 ss:LineStyleContinuous ss:Weight1/ /Borders Alignment ss:HorizontalCenter ss:VerticalCenter/ Font ss:FontName微软雅黑 ss:Size11 ss:Bold1/ /Style /Styles Worksheet ss:Name销量报表 Table Column ss:Width80/ Column ss:Width100/ Row ss:Height21 Cell ss:StyleIDHeaderData ss:TypeString姓名/Data/Cell Cell ss:StyleIDHeaderData ss:TypeString销量/Data/Cell /Row Row Cell ss:StyleIDDefaultData ss:TypeString张三/Data/Cell Cell ss:StyleIDDefaultData ss:TypeNumber128/Data/Cell /Row /Table /Worksheet /Workbook第二行 是关键它告诉操作系统“这个文件应该用 Excel 打开”没有这行双击 .xls 会用文本编辑器打开。Styles 段是样式字典单元格通过 ss:StyleID 引用字典里的 ID。Interior 是背景色Color 属性注意是 AARRGGBB 格式和 CSS 的 #RRGGBB 不一样转换时要补 FF 前缀。Borders 里按 Position 分别定义上、下、左、右边框LineStyle 支持 Continuous、Dash、Dot 等。Alignment 控制水平和垂直对齐Font 控制字体、字号、加粗。这些属性用起来跟写 CSS 差不多但属性名全部走 Excel 的命名空间。SpreadML 还能一个 Workbook 塞多个 Worksheet每个 Worksheet 是一块独立的 Table交付时打开就是一个多标签的 ExcelHTML 方案做不到。如果想在单元格里写公式用 Cell 上的 ss:Formula 属性比如 ss:FormulaSUM(R[-3]C:R[-1]C)。注意这里的引用是 R1C1 风格不是页面里常见的 A1 风格转换时别直接把 A1 抄进去。还有个容易栽跟头的点单位。HTML 方案的列宽行高都是像素Excel 的 HTML 渲染器能直接消费但 SpreadML 里 Column 的 ss:Width 单位是英文字符数Excel 默认列宽是 8.43 字符Row 的 ss:Height 单位是磅pt。从页面量出来 120px 的列宽直接填 120列宽会严重膨胀经验换算是字符数约等于像素数除以 7行高 28px 约等于 21pt。拿不准时列宽宁可留一点余量也别按像素原样填。4.2 把 DOM 样式映射进 XML一份 getComputedStyle 到 ss:Style 的映射函数骨架有了剩下就是把页面 table 里的样式翻译成上面的