1. 为什么Excel进富文本编辑器这么容易翻车
前阵子做企业内部管理系统,前端用的Vue 3,编辑区接了富文本编辑器。客户提了个很具体的要求:运营人员维护好的Excel表格,比如商品价格表、渠道分润比例表,要能直接贴进编辑器保存,之后系统生成周报时表格样式和内容都不能乱,数据不能丢。
听起来简单,真正做起来就发现,Excel数据要“无损”进富文本编辑器,坑比想象中深得多。
先看最常见的操作:从Excel里选中一片区域,Ctrl+C,然后到编辑器里Ctrl+V。第一眼看上去没问题,表格结构、文字都在。但多测几组数据你就会发现问题:合并单元格经常错位,日期变成了43831这种数字,百分比变成0.85,带公式的列贴过去只剩一个值,单元格底色和边框全没了,图片直接蒸发。
为什么会这样?因为Excel往剪贴板写数据时,同时写入了多份数据,其中浏览器能识别的是HTML片段。这个HTML片段里确实包含table结构,但样式信息极其简陋,只有最基本的展示属性。富文本编辑器拿到这堆HTML后,只能靠自己的默认样式去渲染,Excel里那些精细的格式定义就全丢了。
更要命的是公式和数据类型。剪贴板里给出的是Excel计算后的显示值,公式本身根本没有传过来。日期被转成序列号、长数字变成科学计数法,这些都是在复制粘贴这个动作里天然会发生的“损伤”。如果你还想把编辑后的表格再导出回Excel,数据可能已经只剩下文本形态,结构化信息全部丢失。
所以在动手写代码之前,我得先想清楚一个问题:所谓“无损转存”,到底保住什么才算达标?
我最终定义的“无损”,不是像素级还原Excel——那基本不可能,也不该在富文本场景里追求。真正需要保住的是四件事:表格结构完整(行列、合并单元格不乱)、数据类型可识别(日期、数字、百分比不能被转成无意义的文本)、关键样式保留(边框、底色、对齐、宽高)、以及必要时能从编辑器里的表格反向提取出结构化数据。这四条做到了,这个需求才算真正落地。
2. 无损转存的整体方案设计
2.1 三种技术路线对比
在动手之前,我先把市面上可行的方案过了一遍,列了个对比表:
| 方案 | 开发成本 | 保真程度 | 用户体验 | 适用场景 |
|---|---|---|---|---|
| 直接复制粘贴 | 零成本 | 低,样式丢失严重,公式图片丢失 | 最自然,但结果难控 | 临时性、非关键数据 |
| 后端解析Excel后返回HTML | 中,Java/Python解析再吐HTML | 高,可控性强 | 需要上传文件,实时性差 | 对数据准确度要求高、有上传条件的后台系统 |
| 前端解析Excel生成HTML再写入编辑器 | 中高,需要处理样式映射 | 高,所见即所得 | 上传文件后立即回填,体验流畅 | 现代前端架构的交互系统 |
我最终选的是第三条路线,前端解析。核心原因是项目的富文本编辑器本身就在浏览器里,用户操作路径是“拿到Excel文件 → 上传/导入 → 编辑器里继续编辑 → 保存 → 后续导出”。整个过程如果还要经过后端中转一份HTML,交互链路太长,用户等不起。
前端解析还有一个好处:数据始终在浏览器内存里,我们可以完全控制HTML的生成方式,想保留什么样式、丢掉什么冗余属性,都在自己手里。不像复制粘贴,浏览器帮我们生成的HTML是不可控的黑盒。
2.2 为什么前端解析更适合富文本场景
富文本编辑器的本质,就是一个能展示和编辑富格式内容的容器,内部存储结构基本都是HTML。Excel数据要进编辑器,从数据通路的角度看,本质上就是“把表格数据转成HTML表格”。
前端解析方案正好可以做成一条直线:上传Excel文件 → 解析工作表 → 按行列遍历单元格 → 生成带样式信息的table标签 → 塞进编辑器。全程不离开浏览器,用户看到的就是最终保存的效果,不会出现“上传后还要等后端算完再回传”的空白等待。
这里我必须强调一个关键认知:数据无损转存的瓶颈,从来不是解析,而是HTML生成时的样式映射。解析Excel拿到单元格数据,这个工作SheetJS已经做得比较成熟了。真正的难点在于,Excel的单元格样式模型和HTML的Css样式模型完全不是一回事,需要我们自己搭一座桥。
另外,富文本编辑器本身的表格能力也很关键。我实测了市面上几款编辑器:
- wangEditor 5对表格支持做得比较完善,能插入表格、调整行列、设置单元格样式,社区版也够用
- TinyMCE的表格插件很强大,但配置项多,学习成本高
- Quill原生不支持表格,需要额外扩展,自己做会踩不少坑
- CKEditor 5的表格功能体验不错,但授权和包体积需要考虑
我最后用的是wangEditor 5,因为它对Vue 3的支持好,而且表格交互能力足够覆盖业务需求。如果项目对表格编辑要求更高,比如要拖拽调整列宽、单元格合并拆分,那建议在TinyMCE和CKEditor之间选。
2.3 富文本编辑器里表格的天然局限
说到富文本编辑器里的表格,有一个天然的局限得先跟各位说明白:富文本里的table,本质是HTML表格,它的行列模型是扁平化的,靠rowspan和colspan来表达合并单元格。而Excel的表格模型有行、列、合并区域、隐藏行/列、筛选、数据验证等更复杂的东西。
这就导致一个现实问题:HTML表格能无损表达Excel表格的大部分结构和样式,但表达不了Excel的全部能力。比如:
- 隐藏行列,HTML表格没有直接的隐藏语义,只能靠样式控制
- 数据验证(下拉列表、限定输入范围),HTML表格没有对应概念
- 多级表头在Excel里是多个合并单元格,转成HTML后虽然能通过rowspan模拟,但反向解析会变麻烦
所以做这类功能,最好先和业务方对齐预期。能保住的是内容和可视化结构,Excel特有的交互性功能是保不住的。我在项目里就是把这个边界讲清楚后,才往下做技术方案的。
3. 表格结构无损迁移:解析、遍历、合并单元格转换
3.1 解析工作簿,拿到行列模型
我用的是SheetJS的社区版,平时也叫xlsx库,npm上直接装就行:
npm install xlsx解析Excel文件的核心代码很简单,读文件拿到ArrayBuffer,然后XLSX.read就可以得到workbook对象:
import * as XLSX from 'xlsx'; async function parseExcelFile(file) { const buffer = await file.arrayBuffer(); const workbook = XLSX.read(buffer, { type: 'array', cellStyles: true, // 关键:读取单元格样式 cellDates: true, // 关键:日期解析为Date对象 }); return workbook; }注意两个关键参数。cellStyles: true 如果不设置,SheetJS默认不解析单元格样式,那背景色、字体、边框这些全拿不到。cellDates: true 如果不设置,日期会被当作数字序列号,后面还得自己去转。这两个参数在最开始就踩过坑,等发现样式全没的时候,还得回头重写解析逻辑。
拿到workbook后,通常取第一个工作表:
const sheetName = workbook.SheetNames[0]; const sheet = workbook.Sheets[sheetName];Excel文件的本体是工作簿,工作簿里有工作表。工作表可以理解为一张二维大表,SheetJS把这张表映射成了一个对象,通过单元格坐标来访问。比如A1单元格就是sheet['A1']。
3.2 合并单元格的转换:从merge区域到rowspan/colspan
合并单元格是Excel表格里最常见的结构,也是转HTML最容易出错的地方。
在SheetJS里,合并区域的列表放在sheet['!merges']里,是一个数组,每个元素有s和e属性,s是start坐标,e是end坐标,每个坐标里有r(行号)和c(列号):
const merges = sheet['!merges'] || [];比如一个从第1行第0列到第3行第0列的合并区域,s是{ r: 1, c: 0 },e是{ r: 3, c: 0 }。这个区域在HTML里,就对应一个rowspan为3的td。
转HTML的核心逻辑是:遍历每个单元格时,先判断它是不是某个合并区域的起始单元格。如果是,就计算这个合并区域在行和列方向的跨度,生成对应的rowspan和colspan。如果不是起始单元格,而是被合并区域覆盖的单元格,就直接跳过,不生成td。
我当时的实现是这样:
function buildMergeMap(sheet) { const mergeMap = {}; sheet['!merges']?.forEach(merge => { const startCell = sheet[XLSX.utils.encode_cell(merge.s)]; const rowspan = merge.e.r - merge.s.r + 1; const colspan = merge.e.c - merge.s.c + 1; // 只记录起始位置,覆盖区域内的其他单元格标记为跳过 mergeMap[`${merge.s.r},${merge.s.c}`] = { rowspan, colspan }; for (let r = merge.s.r; r <= merge.e.r; r++) { for (let c = merge.s.c; c <= merge.e.c; c++) { mergeMap[`${r},${c}`] = mergeMap[`${r},${c}`] || null; } } }); return mergeMap; }这里有个小技巧:mergeMap里记录两类信息,起始单元格存的是rowspan和colspan,覆盖区域内的非起始单元格存的是null。遍历的时候,遇到null就直接跳过,遇到有rowspan和colspan的对象就生成td并带上跨行跨列属性。这样逻辑清晰,也不会重复渲染。
合并在HTML里有个天然限制:跨行合并的td,如果被合并的下方单元格里还有内容,那部分内容在Excel里其实是看不见的,因为被合并单元格覆盖了。所以转HTML时,这些被覆盖的内容必须丢弃,否则表格会出现多余的空白块。
这个听上去简单,实际开发里是出现最多bug的地方。因为很多表格数据并不规范,一个看似普通的表头区域,可能有多个合并层叠在一起。写代码前最好先拿几个真实Excel文件把merges结构打印出来,对照着确认。
3.3 样式映射:内联样式还是样式类
拿到单元格数据后,下一步是样式。SheetJS解析出来的单元格对象里,如果有cellStyles: true,会带上s属性,里面是样式对象。
样式对象里常见的属性有:
cell.s = { fill: { fgColor: { rgb: 'FFFF0000' } }, font: { bold: true, sz: 14, color: { rgb: 'FF000000' } }, alignment: { horizontal: 'center', vertical: 'middle' }, border: { top: { style: 'thin', color: { rgb: 'FF000000' } }, right: { style: 'thin', color: { rgb: 'FF000000' } }, bottom: { style: 'thin', color: { rgb: 'FF000000' } }, left: { style: 'thin', color: { rgb: 'FF000000' } } } };这里要做两个映射决策:一是把这些样式转成内联style属性,还是转成CSS类名。
我当时选了内联样式。原因是富文本编辑器的内容最终会存进数据库,以后在任何地方展示这段HTML,都不依赖当前页面有没有加载对应的样式表。如果转成CSS类名,万一导出后在另一个系统展示,样式文件没带过去,表格立马变成原始HTML的样子。
内联样式虽然会让HTML变得又长又啰嗦,但在富文本内容持久化这个场景下,是最稳妥的。
样式的转换可以封装一个函数,逐一处理背景色、字体、边框、对齐、宽高:
function cellStyleToInline(sheet, cell, row, col) { const style = cell?.s; if (!style) return ''; const styles = []; if (style.fill?.fgColor?.rgb) { styles.push(`background-color: #${style.fill.fgColor.rgb.slice(2)}`); } if (style.font) { if (style.font.bold) styles.push('font-weight: bold'); if (style.font.italic) styles.push('font-style: italic'); if (style.font.sz) styles.push(`font-size: ${style.font.sz}px`); if (style.font.color?.rgb) { styles.push(`color: #${style.font.color.rgb.slice(2)}`); } } if (style.alignment?.horizontal) { styles.push(`text-align: ${style.alignment.horizontal}`); } if (style.alignment?.vertical) { styles.push(`vertical-align: ${style.alignment.vertical}`); } // 边框信息比较琐碎,逐边处理 ['top', 'right', 'bottom', 'left'].forEach(side => { const border = style.border?.[side]; if (border?.style && border?.color?.rgb) { styles.push(`border-${side}: ${borderWidthMap[border.style] || '1px'} solid #${border.color.rgb.slice(2)}`); } }); return styles.join('; '); }这里有一个从Excel边框样式到CSS边框粗细的映射表,比如thin映射为1px,medium映射为2px,thick映射为3px。这个映射表不完美,但实测下来展示效果基本能对齐用户的肉眼预期。
列宽和行高也尽量转过去,列宽用sheet['!cols']数组,行高用sheet['!rows']数组,转成HTML表格的colgroup和tr的style。但有个细节:Excel的列宽单位是字符宽度,浏览器里的px做不到精确换算,只能近似。我用的是SheetJS文档里提的估算方式:px大约等于列宽乘以7,再加5个像素的padding余量。这活儿没法完美,够用就行。
4. 数据类型、公式与图片的保真
4.1 日期、数字格式的防变形处理
数据类型的保真是个大坑。Excel内部把日期存成数字序列号,比如2024年1月1日,存的是45292这样的数字。如果你不做任何处理,转出来的HTML里就会显示一串数字,用户一看就懵。
即使开了cellDates: true,在单元格对象里,日期会被解析成JavaScript的Date对象,但HTML里怎么写才能显示成用户想要的格式,还需要进一步格式化。
我的方式是对单元格的type字段做判断,再结合样式里的数字格式编号(cell.z)来格式化。Excel的数字格式代码很丰富,像yyyy-mm-dd、0.00%、#,##0.00这些,不能全支持,但常见类型可以覆盖:
| Excel格式示例 | 类型推断 | HTML输出示例 |
|---|---|---|
| yyyy-mm-dd | 日期 | 2024-01-01 |
| m/d/yy | 日期 | 1/1/24 |
| 0.00% | 百分比 | 85.00% |
| #,##0.00 | 数字千分位 | 12,345.67 |
| 0.00E+00 | 科学计数 | 1.25E+7 |
| @ | 文本 | 字符串原样 |
在SheetJS对象里,cell.t === 'd'代表日期,cell.z保存的是格式代码。我写了一个formatCellValue函数,根据cell.t和cell.z做分发,优先用SheetJS自带的SSF格式化工具,再兜底用自己的格式化逻辑。SSF是SheetJS内部的格式化引擎,可以通过XLSX.SSF.format来用。
另外有个特别容易踩的坑:身份证号、订单号这类超过15位的数字列。Excel默认用科学计数法显示,SheetJS解析后cell.t是'n',直接输出就会变成科学计数法的字符串。这种必须根据单元格的原始值做判断,如果原Excel里这一列本来是文本格式(单元格左上角有绿色小三角),cell.t会是's',字符串原样输出就没问题。但如果是被Excel强制转成了数字,那就只能靠用户上传前先把列设置成文本格式来规避。
4.2 公式:保住结果还是保住表达式
公式是“无损转存”里很难两全的部分。Excel里的公式如=SUM(A1:A10),在复制粘贴到富文本编辑器时,只会留下计算后的结果,公式本身会丢失。哪怕是用前端解析方案,SheetJS默认给出的cell.v也是计算后的结果,而cell.f才是公式字符串。
那到底该保留哪个?我当时的判断逻辑是分场景的:
- 如果这段富文本只是做展示用,比如生成报表正文、邮件内容,那就只需要计算结果。用户看到的数字应该是最终结果,而不是一坨公式字符串。
- 如果后续还有“从富文本再导出回Excel”的需求,那必须把公式保存下来,否则导出的Excel里只有静态的数值,可编辑性大打折扣。
我的做法是:生成HTML时,计算结果显示在单元格里,同时把公式存到td的自定义属性里,比如data-formula="SUM(A1:A10)"。这样既不影响展示,又保留了公式信息。后续要导出Excel时,解析HTML的tr/td结构,发现data-formula属性就直接还原成单元格公式。
不过要提醒一下,如果把公式字符串写进td的textContent里,用户会看到公式而不是结果,这是个很常见的实现错误。公式必须只放属性,不放内容。
4.3 图片:基础方案是将就,进阶方案需要换解析器
Excel里的图片分很多种。嵌入单元格里的(Excel新版支持放到单元格内)、浮动在工作表上的、还有图表图片。富文本编辑器对图片的支持是有的,但前提是图片得有URL或者base64数据。
这里有一个现实问题:SheetJS社区版不支持读取Excel里嵌入的图片。社区版的解析器只处理单元格数据、样式、合并区域这些,图片数据不在解析范围内。如果需求里必须保留图片,我试过两条路:
第一条路是换解析库,比如ExcelJS。ExcelJS能读取工作簿里的media资源,拿到图片二进制后转成base64,再作为img标签的src插入HTML。但ExcelJS的行列遍历和样式映射不如SheetJS顺手,我通常是两个库配合:SheetJS负责表格结构解析和HTML生成,ExcelJS单独抽图片,然后按坐标把图片定位到对应的单元格区域。
第二条路是后端兜底。前端上传Excel后,后端解析一次,把图片抽取出来存到对象存储,返回图片URL列表,前端再根据坐标插入HTML。这个方案稳定,但需要开发后端接口。
我当时选的是第二条路,因为项目里本来就有后端,而且图片不可能只存base64进富文本内容,数据库会爆炸。用URL方案,HTML体积小,后续加载也快,还方便做CDN。
这里必须说句实在话:图片转存并没有一个完美的纯前端方案,特别是图表、浮动图片、嵌入单元格图片混在一起时,定位逻辑会非常复杂。如果预算有限,建议先和业务方确认,图片是否真的需要从Excel同步进富文本。很多时候业务方只是习惯把图片放在Excel里,真正做报表时需要的还是数据和表格结构。
5. 实操落地:Vue 3 + SheetJS完整实现
5.1 项目准备与依赖
项目环境是Vue 3 + Vite + wangEditor 5,需要安装的依赖如下:
npm install xlsx npm install @wangeditor/editor npm install @wangeditor/editor-for-vue@nextwangEditor 5的Vue 3版本包名后要加@next,这个很容易踩版本错位的坑。装完先跑一个最小demo,确认编辑器能正常渲染,再往下接导入功能。
组件结构上,我把导入功能封装成一个独立组件ExcelImportButton,放在编辑器工具栏旁边。用户点按钮后弹出文件选择框,选完Excel文件,解析并生成HTML,把HTML直接覆盖到编辑器的内容区。
5.2 核心代码:从Excel文件到编辑器HTML
核心函数分三步:读取文件 → 解析生成HTML → 写入编辑器。
async function importExcelToEditor(file, editorRef) { // 第1步:读取并解析 const buffer = await file.arrayBuffer(); const workbook = XLSX.read(buffer, { type: 'array', cellStyles: true, cellDates: true, }); const sheet = workbook.Sheets[workbook.SheetNames[0]]; // 第2步:生成HTML const html = sheetToHTML(sheet); // 第3步:写入编辑器 editorRef.value.setHtml(html); }核心的sheetToHTML函数,逻辑是遍历所有行和列,根据mergeMap决定是否跳过被合并覆盖的单元格,根据样式映射生成内联样式,并把上一步formatCellValue的结果写入单元格内容:
function sheetToHTML(sheet) { const range = XLSX.utils.decode_range(sheet['!ref']); const mergeMap = buildMergeMap(sheet); const colWidths = sheet['!cols'] || []; const rowHeights = sheet['!rows'] || []; let html = '<table style="border-collapse: collapse; width: 100%;">'; // 列宽 html += '<colgroup>'; for (let c = range.s.c; c <= range.e.c; c++) { const width = colWidths[c]?.wpx; html += `<col style="width: ${width || 80}px;">`; } html += '</colgroup>'; // 行 for (let r = range.s.r; r <= range.e.r; r++) { const height = rowHeights[r]?.hpx; html += `<tr style="${height ? `height: ${height}px;` : ''}">`; for (let c = range.s.c; c <= range.e.c; c++) { const mergeInfo = mergeMap[`${r},${c}`]; if (mergeInfo === null) continue; // 被合并区域覆盖,跳过 const cell = sheet[XLSX.utils.encode_cell({ r, c })]; const styleStr = cellStyleToInline(sheet, cell, r, c); const value = formatCellValue(cell); let tdAttr = styleStr ? `style="${styleStr}"` : ''; if (mergeInfo?.rowspan > 1) tdAttr += ` rowspan="${mergeInfo.rowspan}"`; if (mergeInfo?.colspan > 1) tdAttr += ` colspan="${mergeInfo.colspan}"`; html += `<td ${tdAttr}>${value}</td>`; } html += '</tr>'; } html += '</table>'; return html; }这里专门处理一个边界:如果sheet['!ref']不存在,说明工作表是空的,直接返回提示信息,不要进入遍历逻辑,否则decode_range会拿不到范围然后报错。
5.3 大数据量表格的渲染优化
Excel文件动辄几百行、几十列,生成的HTML表格可能包含上万个td。直接把这么大的字符串setHtml进编辑器,浏览器会卡顿甚至假死。
我遇到过一次3000行、20列的表格,生成HTML用了200多毫秒,setHtml之后编辑器直接卡了5秒才渲染出来。这个体验肯定不行。
后来协调业务后,采用了两套策略:
第一,默认导入数据超过500行时,给用户弹一个确认框:“当前表格数据量较大,是否仅导入前500行用于展示?”如果用户只是想把表格贴进报表,一般都会同意。真要完整数据,不如直接附件上传。
第二,渲染端采用懒加载思路,富文本编辑器的内容先分成若干个子表格,比如每200行一个table,然后用Vue的异步组件配合IntersectionObserver,滚动到哪个区域才渲染哪个table。不过要实现这个,编辑器本身得支持内容插槽,wangEditor的用法比较复杂,我试过用自定义模块扩展,成本太高,最后放弃了,还是回到第一套策略,控制单个Excel的导入行数上限。
这个优化过程给我的启示是:富文本编辑器不适合做超大数据量的表格承载容器。它定位是内容编辑,不是数据分析工具。数据量超过合理范围,就应该引导用户用附件或者独立表格模块来解决。
6. 常见问题与排查技巧实录
6.1 数字全部变成科学计数法
症状:导入身份证号、订单号列,显示成一串类似1.23457E+17的数字。
排查思路:这个问题的根源在Excel本身。Excel对超过11位的数字默认使用科学计数法显示,SheetJS解析后cell.t是'n',表示这是一个数字,而不是文本。如果不做格式化,直接用数字本身的toString()就会输出科学计数法。
解决方案分两层。如果你在解析时发现原单元格的值是一个超过15位的整数,且没有小数位,那尽快用字符串原样保留:
function formatCellValue(cell) { if (cell.t === 'n' && cell.v !== undefined) { const num = cell.v; if (Number.isInteger(num) && String(num).length >= 15) { return String(num); } } // 其他类型走原有格式化逻辑 }但这个方法只对“Excel里已经是文本格式的数字”有效。如果Excel里那列已经被转成真正的数字,精度在Excel内部就已经丢失了,前端再怎么处理都救不回来。所以更靠前的办法是,在导入说明里写清楚:身份证号、订单号这类长数字列,请先在Excel里设置成文本格式,再导入。
6.2 合并单元格导入后错位
症状:表头是两行合并的,导入后第一行空了,第二行数据错到左边去了。
排查思路:这个基本是mergeMap没写对。常见的有两种错误。第一种,遍历时没有跳过被合并覆盖的单元格,导致每个单元格都渲染了td,表格整体多出一列。第二种,mergeMap记录的是“从0开始”的行列号,而HTML的tr/td是从1开始的,转换时行号列号没对齐,导致跨度错位。
解决办法:写一个自测用例,构造一个只有合并单元格的Excel文件,手动对比解析后的mergeMap和实际Excel结构。我一般在mergeMap生成后,会先console.log打印所有合并区域,肉眼核对坐标,再决定继续排查还是能放行。
6.3 导入后Excel里的图片不显示
症状:解析后图片区域是个空白块,或者整个消失。
排查思路:前面已经说过,SheetJS社区版不读图片。如果确认业务上必须带图片,我建议做两件事。第一,跟业务方确认是否真的需要图片进富文本,很多时候图片只是辅助说明,真正重要的是数据和表格结构。第二,如果确认需要,就上后端解析方案,让后端抽取图片并给出URL,前端再把img标签插到对应单元格。
在这里我特别提一句:不要试图在Excel的HTML片段里找图片。浏览器复制粘贴Excel时也会丢弃图片,这是所有富文本编辑器都绕不开的限制。
6.4 导入的表格样式全乱了
症状:表格导入后变成了一个没有颜色、没有边框、文字挤在一块的普通表格。
排查思路:样式全丢,九成是cellStyles: true没开。另外还要确认,sheet['!cols']和sheet['!rows']是否存在,如果不存在说明原Excel本来就没设置列宽行高。还有一个隐蔽问题:有些样式在Excel里是通过主题色来定义的,SheetJS解析出来的color值可能是Theme: 0开头,而不是标准的RGB。这种时候要么手动映射主题色,要么对拿不到颜色的单元格用默认样式兜底,别让整个表都崩掉。
6.5 快速定位问题速查表
| 现象 | 优先排查方向 | 解决方案 |
|---|---|---|
| 全部数字科学计数法 | 单元格类型是数字而非文本 | 长整数转字符串保留 |
| 合并单元格错位 | mergeMap坐标记录错误 | 打印merges对照排查 |
| 图片丢失 | SheetJS不解析媒体资源 | 用ExcelJS或后端抽取图片 |
| 样式全无 | cellStyles未开启 | 解析参数加cellStyles: true |
| 日期显示为43831 | cellDates未开启,或格式化漏了 | 开启cellDates并走格式化函数 |
| 大数据量卡顿 | HTML过大 | 限制行数、分段处理 |
| 公式只显示结果 | 未保留cell.f | >
|