2026/9/20 0:08:58

前端导出Excel保留样式:从HTML模板到Blob下载的完整实践

前端导出Excel保留样式:从HTML模板到Blob下载的完整实践 简介针对前端开发中常见的“表格导出Excel后样式丢失”问题这份PDF文档提供了一套基于JavaScript的完整解决方案。文档以谷歌浏览器为运行环境重点讲解两种保留表格样式的方法一是在table行内直接编写style属性二是将样式写入可由Excel识别的模板中。内容包含可直接运行的HTML示例代码从构建Excel兼容的HTML字符串到利用Blob对象与base64编码生成下载链接每一步都有清晰演示并指出了复杂样式场景下的替代库方案。资源包共1个文件为纯PDF文档大小约62KB轻量易读方便随时查阅和对照练习目前已吸引2077人学习下载。适合有一定前端基础、希望用原生JS快速实现数据导出的开发者也可作为日常开发中的实用技术笔记。1. 为什么说“前端导出 Excel”其实是在“伪装”成 Excel 的 HTML在浏览器里把一个 table 表格导出成 .xls最常用的办法并不是生成真正的 Excel 二进制文件而是构造一段带 Office XML 命名空间的 HTML 字符串再让浏览器下载。Google Chrome 和微软 Excel 会按 HTML 渲染引擎去解析它所以表格的边框、背景色、字体大小、合并单元格都能保留下来这也是“JS 导出 Excel 保留样式”这条路线能成立的前提。换句话说你得到的其实是一个“包装成 Excel 的 HTML”真正起作用的是浏览器和 Excel 对 HTML 的兼容度而不是 Excel 的写入 API。这个场景适合运营后台的报表导出、数据快照、定时生成表格等凡是页面里已经渲染好的 table 都能套用。只要记住一句话页面里style标签中的样式不会自动进入导出文件能进入的只有行内style和导出模板里的style。下面用可运行代码拆开讲。2. 核心实现用 HTML 模板 base64 拼出一个 .xls 文件2.1 Excel 能识别这段 HTML 的命名空间导出模板的头部有一串特殊的命名空间这是 Excel 能否把 HTML 当工作簿解析的关键html xmlns:ourn:schemas-microsoft-com:office:office xmlns:xurn:schemas-microsoft-com:office:excel xmlnshttp://www.w3.org/TR/REC-html40xmlns:o和xmlns:x声明了微软 Office 的 XML 命名空间。Excel 打开 HTML 文件时看到这两个声明就会按表格工作簿来处理而不是普通网页。x:ExcelWorkbook里的x:Worksheet则定义了 Sheet 名称和是否显示网格线等于是给 Excel 下达的“这是一个工作簿”的指令。没有这段头生成的文件可能只是个普通的 .htmlExcel 会弹格式确认窗口甚至直接打不开。所以模板拼接的顺序、命名空间的完整性都比普通 HTML 拼接严谨得多一个字符都不能少。2.2 base64 转码处理中文的关键data:application/vnd.ms-excel;base64,这个 data URI 要求后面的内容必须是 base64 编码。如果表格里有中文直接调window.btoa会抛异常因为btoa只支持 Latin1 字符。常见的兼容写法是先用encodeURIComponent把中文字符转成%xx形式的 ASCII再用unescape还原成字节串最后交给btoavar base64 function (s) { return window.btoa(unescape(encodeURIComponent(s))); };这个函数是整套导出方案里最容易踩坑的地方。encodeURIComponent会把“公司一”变成%E5%85%AC...这样的 ASCII 序列unescape再把%xx还原成对应的原始字节btoa才能正常完成 base64 编码。如果漏掉这层转换表头、单元格里的中文在导出后大概率变成乱码或者空字符。2.3 format 模板替换占位符不要用中文用来替换占位符的format函数看起来简单但正则表达式决定了占位符的命名规则var format function (s, c) { return s.replace(/{(\w)}/g, function (m, p) { return c[p]; }); };\w只能匹配字母、数字、下划线所以模板里的占位符写成{worksheet}、{table}是安全的写成{工作表名}就替换不到。这个函数的作用是把 sheet 名和表格的innerHTML填进模板中。调用时传入的ctx是两个键值对tableid.innerHTML里携带着表格的所有结构和行内样式因此导出的内容与页面表格保持高度一致。这里用的是innerHTML而不是outerHTML目的是避免带入table标签自身的 id 属性同时colspan这些属性会完整保留。2.4 完整示例代码和运行效果把上面几个片段串起来就是一个可以直接在谷歌浏览器里跑通的完整页面!DOCTYPE html html langzh-CN head meta charsetUTF-8 titletable 导出 Excel 保留样式/title style table td { font-size: 12px; width: 200px; height: 30px; text-align: center; background-color: #4f891e; color: #ffffff; } /style /head body a downloadtable导出Excel idexcelOut href#table导出Excel/a table cellspacing0 cellpadding0 border1 idtableToExcel thead trtd stylefont-size: 18px公司一/tdtd公司二/tdtd公司三/td/tr /thead tbody trtdA公司/tdtdB公司/tdtdC公司/td/tr trtdA公司/tdtdB公司/tdtdC公司/td/tr trtdA公司/tdtdB公司/tdtdC公司/td/tr trtd colspan3共计/td/tr /tbody /table script window.onload function () { tableToExcel(tableToExcel, 下载模板); }; var base64 function (s) { return window.btoa(unescape(encodeURIComponent(s))); }; var format function (s, c) { return s.replace(/{(\w)}/g, function (m, p) { return c[p]; }); }; function tableToExcel(tableid, sheetName) { var uri data:application/vnd.ms-excel;base64,; var template html xmlns:ourn:schemas-microsoft-com:office:office xmlns:xurn:schemas-microsoft-com:office:excel xmlnshttp://www.w3.org/TR/REC-html40 head!--[if gte mso 9]xmlx:ExcelWorkbookx:ExcelWorksheetsx:ExcelWorksheet x:Name{worksheet}/x:Namex:WorksheetOptionsx:DisplayGridlines//x:WorksheetOptions /x:ExcelWorksheet/x:ExcelWorksheets/x:ExcelWorkbook/xml![endif]-- style typetext/css table td { border: 1px solid #000000; width: 200px; height: 30px; text-align: center; background-color: #4f891e; color: #ffffff; } /style /headbodytable classexcelTable{table}/table/body/html; if (!tableid.nodeType) tableid document.getElementById(tableid); var ctx { worksheet: sheetName || Worksheet, table: tableid.innerHTML }; document.getElementById(excelOut).href uri base64(format(template, ctx)); } /script /body /html这段代码执行后页面加载时会自动调用一次tableToExcel给下载链接的href写入 base64 数据。用户点击链接时浏览器根据download属性保存.xls文件而不是打开新页面。代码里最值得留意的是样式生效顺序模板style中的background-color: #4f891e对所有td生效而表头第一个td额外写了stylefont-size: 18px所以最终 Excel 里这个单元格字体会变大其他单元格保持模板样式的绿底白字。这就是下面要展开的两种样式保留方式。3. 样式保留的两种方式行内样式与模板样式3.1 方式一行内样式直接写进 td第一种方式是在td或th上直接写style属性例如td stylefont-size: 18px; background-color: #4f891e; color: #ffffff;公司一/td这种做法的优点非常直接每个单元格的样式是自包含的动态生成表格时用 JS 拼字符串很顺手比如根据状态机切换背景色判断条件在循环里直接把样式拼进style即可。缺点也明显一个是重复代码多改一个全局背景色要遍历所有td另一个是 HTML 字符串体积变大。如果表格是后端接口返回的数据、前端用模板字符串渲染行内样式写起来会更直观因为不用去维护独立的样式表。在实际项目中我一般只把行内样式用在“少数需要特殊标记”的单元格上比如合计行加粗、异常指标标红、表头字体放大。这样既能保证 Excel 里看到重点又不会让整个表格变得冗长。你看到的上文示例中表头第一个单元格就是典型用法模板样式管全局行内样式管特殊。3.2 方式二模板 style 统一定义第二种方式是把样式写进导出模板的style标签内再接一个table包裹导出的innerHTMLstyle typetext/css table td { border: 1px solid #000000; width: 200px; height: 30px; text-align: center; background-color: #4f891e; color: #ffffff; } /style /head body table classexcelTable{table}/table /body这个写法与页面本身的style最大的区别在于模板样式是直接拼进导出的 HTML 字符串里的Excel 打开文件时能看到并应用而页面外层style定义的选择器在导出后的独立 HTML 中并不存在所以不会生效。这是新手最容易犯的错——页面表格样式调得花团锦簇导出后却一夜回到素颜。模板样式适合统一样式比如统一的边框、列宽、行高、对齐方式、背景色。这样即使页面表格结构再复杂导出模板里只用几行 CSS 就能覆盖到所有单元格。像 antd 的 Table、umy-ui 的 table 组件渲染出来的 DOM 本质上还是标准table你可以通过document.querySelector(.ant-table table)之类的方式拿到表格节点再抓取innerHTML套进这个模板组件自带的 class 样式不会进入导出文件但模板里的table td规则会重新施加到导出的表格上效果是可控的。3.3 优先级、适用场景和常见误用同样一个单元格如果行内样式和模板样式同时出现行内样式会覆盖模板样式。Excel 对优先级的处理遵循 HTML/CSS 的基本规则所以没有例外。三者的关系可以用下表概括样式位置Excel 是否识别优先级适用场景行内style识别最高单个单元格特殊处理导出模板style识别普通全局统一样式页面外层style不识别无效不能用于导出这里的“页面外层style不识别”是最容易踩的坑。你可以把页面内样式的优先级想成只要它没有进入template字符串Excel 就不会去加载它。哪怕是style写在head里、选择器命名为#tableToExcel td导出的 HTML 中根本没有这个选择器的定义自然也就没有样式。实际开发里建议按“模板兜底 行内特化”的拆分思路来做所有单元格都要有的字体、边框、对齐方式放模板需要动态变化的场景状态放行内。比如“共计”这一行的背景色可以直接在行内加一个background-color: #d0d0d0既不影响模板的批量样式又能让合计行在 Excel 中高亮。4. 参数细调与踩坑合并单元格、列宽与中文文件名4.1 colspan 与 rowspan 的导出表现表格里的td colspan3是合并列rowspan2是合并行。由于导出时直接使用tableid.innerHTML这些属性会原样进入 Excel 的 HTML 解析器。实测中colspan的支持很稳定上文的“共计”跨三列可以正确合并。rowspan在部分 Excel 版本里解析会有行高异常的问题尤其是表格下方还有其他合并区域时容易把行高撑高。如果导出的表格合并逻辑很复杂建议先导出后用 Excel 打开确认如果确认rowspan表现不稳定可以考虑把数据做平铺处理避免前端渲染复杂合并。4.2 列宽行高用 HTML 属性而不是 CSS 更稳定模板样式里写width: 200px在谷歌浏览器导出时一般有效但到了其他 Excel 版本CSS 宽度偶尔会被忽略。更稳的做法是直接把它们写成 HTML 属性table width600 cellspacing0 cellpadding0 border1 tr height30 td width200第一列/td td width200第二列/td td width200第三列/td /tr /table给tr加height、给td加widthExcel 解析时对这些属性的兼容性明显高于 CSS 规则。如果你的模板已经写了table td { width: 200px; height: 30px; }可以保留 CSS同时再在关键的表头或合计行上补 HTML 属性双保险。要注意的是width属性取像素值即可不需要写单位不同 Excel 版本对百分比的支持差异较大尽量使用固定像素。4.3 中文文件名与乱码问题downloadtable导出Excel这种写法在 Chrome 下不会出问题英文文件名更稳。如果文件名带中文且下载后乱码常见处理是在导出前对文件名做一次 URL 编码。不过更推荐的方案是切换到 Blob 下载在生成 Blob 时加入 UTF-8 BOMvar blob new Blob([\ufeff html], { type: application/vnd.ms-excel }); var url URL.createObjectURL(blob);\ufeff是 UTF-8 的字节序标记Excel 打开文件时会根据这个标记识别出文件是 UTF-8 编码中文字符就不会变成乱码。这个方案在后面的章节还会用到。如果你的导出内容里包含大量数据Blob 方案也比 data URI 更不容易触达浏览器地址长度上限。4.4 浏览器兼容性与下载触发这套导出方案在谷歌浏览器下最顺畅原因是 Chrome 对 data URI 加download属性的处理很干脆点击即下载。Edge 的 Chromium 内核表现也一致。Firefox 对 data URI 的处理有差异有时会选择直接打开而不是下载用户体验不一致。更通用的做法是用临时a元素触发点击然后立即移除同时配合URL.createObjectURL这样所有现代浏览器都能统一行为。需要注意的还有一个小点window.onload里只生成了一次href如果表格数据在页面渲染后才通过异步接口更新更新后必须重新调用tableToExcel否则下载的仍然是旧数据。下表是几个高频问题的快速定位清单现象原因处理方向导出后样式全丢样式写在页面style移到模板或行内中文变乱码base64 未做 Unicode 处理用encodeURIComponent包装或加 BOM大表格点击无反应data URI 地址过长被浏览器截断改用 Blob 下载单元格跨列失效innerHTML被截断或模板缺少命名空间检查模板头部是否完整5. 更稳的做法用 Blob 下载把导出封装成通用函数5.1 Blob 比 data URI 好在哪data URI 方案简单但把所有导出内容都塞进一个超长地址里遇到几百行甚至上千行的表格时浏览器地址栏和 DOM 的href都会面临压力。Blob 方案先生成二进制对象再用URL.createObjectURL生成临时地址地址只是一串短 ID内容体积不受地址长度限制下载完调用revokeObjectURL还能释放内存。强烈建议新代码直接用 Blob 方案老代码如果 stay on data URI至少给 base64 过程加上异常捕获。5.2 通用导出函数把模板封装成buildTemplate再把下载逻辑收敛到exportTableToExcel整个导出能力就变成一个可复用的模块。完整函数如下function exportTableToExcel(tableId, filename, sheetName) { const table typeof tableId string ? document.getElementById(tableId) : tableId; if (!table) return; const tpl html xmlns:ourn:schemas-microsoft-com:office:office xmlns:xurn:schemas-microsoft-com:office:excel xmlnshttp://www.w3.org/TR/REC-html40 head!--[if gte mso 9]xmlx:ExcelWorkbookx:ExcelWorksheetsx:ExcelWorksheet x:Name (sheetName || Sheet1) /x:Name x:WorksheetOptionsx:DisplayGridlines//x:WorksheetOptions /x:ExcelWorksheet/x:ExcelWorksheets/x:ExcelWorkbook/xml![endif]-- style typetext/css table td { border: 1px solid #000000; width: 200px; height: 30px; text-align: center; background-color: #4f891e; color: #ffffff; } /style /headbodytable classexcelTable table.innerHTML /table/body/html; const blob new Blob([\ufeff tpl], { type: application/vnd.ms-excel }); const url URL.createObjectURL(blob); const link document.createElement(a); link.href url; link.download filename.endsWith(.xls) ? filename : filename .xls; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); }函数开头做了兼容判断传入 DOM 对象或字符串 id 都能处理。filename.endsWith(.xls)用来补齐扩展名避免调用方手滑漏写。Blob 的 type 设置为application/vnd.ms-excel配合\ufeff和模板内容Excel 能正常识别。如果你需要导出.xlsx这个方案不能直接改扩展名糊弄xlsx要求的是真正的 ZIP 包不是 HTML 包装。5.3 用两步验证结果是否可信验证导出的文件是否是预期结果可以分两步。第一步用 Excel 打开下载的.xls检查表头、合并单元格、背景色和字体是否与页面一致第二步用 VS Code 或记事本打开同一个文件如果能搜到xmlns:o和table classexcelTable说明导出的确实是 HTML 包装文件扩展名只是外层壳。验证时可以临时改一个td的stylebackground-color: #ff0000重新调用导出函数如果 Excel 中同步出现红底就说明行内样式链路是通的。本文还有配套的精品资源点击获取