前一阵子做信创环境办公系统适配的时候,我遇到了一个特别典型的场景:UEditor这个老牌富文本编辑器,其他功能都正常,但只要用户从Word里复制内容粘贴进来,就各种翻车——样式跟Word里完全不像、图片传不上去、表格一粘贴就碎成一片。回头看,问题根本不是“粘贴”这一个动作,而是整条链路:浏览器剪贴板提供什么样的HTML、UEditor的过滤规则如何清洗Word垃圾代码、图片如何从base64转存成服务器文件、信创浏览器对contenteditable和样式的渲染差异,任何一个环节没梳理清楚都会出问题。
这篇文章把信创环境下UEditor的Word编辑功能这条路完整拆开来讲,先弄明白Word内容进到编辑器到底经历了哪些阶段,再逐个解决图片、表格、字体这三类高频问题,最后给出一套可以直接抄作业的改造顺序和验收清单。不管你是正在做信创适配的前端开发,还是想搞清楚UEditor粘贴原理的初学者,读完应该都能上手。
1. 信创环境不是“换台电脑”这么简单:UEditor为什么会水土不服
1.1 信创环境的技术栈变量:CPU、操作系统、浏览器三者叠加
很多人以为信创环境就是把Windows换成国产操作系统,换个浏览器再登录系统,其实远不是这么回事。信创环境通常是一整套软硬件组合:国产CPU加上基于Linux内核的操作系统,再配上定制版浏览器。对前端开发者来说,真正决定兼容性的是浏览器,而信创浏览器的内核版本往往跟主流浏览器有明显的版本差。
我遇到过几种典型情况:有的国产浏览器用Chromium内核,但版本锁定在一个比较老的节点上,可能是Chromium 70几甚至更老;有的是基于Firefox ESR定制的;还有双核浏览器,可以在兼容模式和极速模式之间切换,兼容模式实际上是模拟IE的行为。这些差异直接影响了剪贴板数据的读取方式、粘贴事件的参数结构、图片上传控件的可用性,以及CSS渲染细节。
所以在开始改UEditor之前,必须先拿到目标浏览器清单。别听“就按Chrome适配就行”这种话,亲自看一遍目标机器上浏览器的UA和内核版本,再决定后面怎么写兼容代码。
1.2 UEditor的IE时代烙印:Flash依赖与陈旧的环境判断
UEditor 1.4.3这个版本在不少老系统里是标配,但它的设计语言还停留在IE时代。里面大量使用浏览器UA判断,比如isIE、isWebkit、isGecko这样的分支。在信创浏览器上,UA可能伪装成Chrome,也可能伪装成别的内核,一旦走到了错误的分支,编辑器某些功能就会表现诡异。
更要命的是Flash依赖。UEditor早期的图片上传、截图、粘贴图片能力都跟Flash有关,代码里会去加载swf资源。信创环境下一是基本没有Flash插件可装,二是即便装上了,安全策略也会禁止它运行。所以第一件要做的就是把Flash链路全部禁掉,改用标准的XMLHttpRequest配合FormData来做上传,否则只要走到Flash分支,上传就静默失败。
还有一个容易被忽略的坑:需求方说“要Word编辑功能”,你以为是要完整复刻Word,其实不是。Word内容进入在线编辑器之后,涉及段落格式、字符样式、列表、图片、表格、公式、页眉页脚、分页符等等。页眉页脚和分页符这种依赖“整页排版模型”的东西,连续网页视图下根本表达不了。这时候一定要跟产品经理确认清楚:保内容、保表格结构、保图片、保基本样式,这四样能做到就已经可以交付了。
2. UEditor吃掉Word内容的完整链路:剪贴板、过滤规则与插件时机
2.1 Ctrl+V那一刻:浏览器把Word的HTML交给了谁
用户在Word里按下Ctrl+V之前,先经历的是从Word复制。Windows剪贴板里同一份内容会有多个表示,纯文本、HTML、图片、OLE对象都有。浏览器在contenteditable区域粘贴时,通常拿到的是text/html部分的片段。这一段HTML不是简洁的网页标记,而是带着大量Office命名空间和私有标记的“脏HTML”。
UEditor在粘贴时会拦截这个动作,然后把HTML字符串交给自己的过滤机制。这段过程如果有任何一个环节拿到的格式不对,后面全部白搭。信创浏览器里遇到过一个实际情况:某些浏览器出于安全限制,paste事件里拿不到完整的clipboardData;有的拿到了但文本编码格式跟标准稍有偏差,导致中文乱码。处理方式是给UEditor的粘贴流程打一个补丁——在beforepaste阶段优先从原生事件对象读取text/html,拿到字符串之后手动调用UEditor的insertHtml指令,让它走一遍完整的过滤链再插入编辑器。
这里有个关键认知:直接往编辑器设置innerHTML和通过过滤链插入,结果完全不同。过滤链会清掉Office私货,直接设置innerHTML则会把<![if supportMisalignedColumns]>这种抱着炸弹的标签原样放进去。
2.2 filterWord到底清洗了什么,又漏掉了什么
UEditor内部有一个专门处理Word粘贴内容的方法,常被称作filterWord。它本质上是靠一长串正则表达式对HTML字符串做清洗,目标是干掉Word粘贴内容里的常见垃圾。我大致梳理一下它做的事:
- 删除meta标签、xml命名空间、Office条件注释;
- 把
<o:p>这类段落标记去掉,只保留段落文字; - 把
v:shape、v:imagedata这类VML矢量图形节点转成img或直接移除; - 清掉
w:开头的标记和属性; - 清理
mso-前缀的内联样式,比如mso-bidi-font-size、mso-fareast-font-family; - 删除
@page块、空段落和多余的分页样式。
听着挺全,但它是字符串层面的正则清洗,不是DOM结构层面的智能处理。它不理解“这是一个合并单元格”“这是三级列表”,所以对于表格合并结构、多级列表语义、图片位置这类东西,它管不了。也就是说,filterWord只负责把东西从“Word垃圾”变成“干净但粗糙的HTML”,不保证“跟Word里看起来一致”。
这个预期调整很重要。我在适配过程中反复跟团队讲:UEditor的定位是“能编辑”,不是“保真还原”。真正保真需要另做一套文档转换服务,跟在线编辑器本身是两码事。
2.3 wordimage、pasteImage与catchremoteimage:三个插件的分工
UEditor里跟图片粘贴相关的有三个插件,很多人分不清楚:
- wordimage负责的是“Word粘贴内容的图片转存”。它监听粘贴后的内容变化,扫描编辑器里所有img标签,如果发现src是base64数据,就批量收集后提交给后台上传接口,再把src替换成服务器URL。
- pasteImage负责“从剪贴板直接粘贴一张图片”。比如用户截图之后直接Ctrl+V到编辑器,这种情况图片也在剪贴板里,但不是从Word复制过来的,处理路径不太一样。
- catchremoteimage负责“把远程URL图片抓回本地服务器”。如果Word文档里插的是外链图片,粘贴后img的src是普通http地址,想让图片跟文档一起被保存,就需要这个插件去抓取转存。
信创环境下三个插件都可能被触发,但它们默认的请求方式、鉴权方式和错误处理都太简陋了。最简单的做法是:不要完全依赖这些插件的默认行为,自己写一个统一的图片后处理函数,在所有图片进入编辑器之后接管转存逻辑。这样后端接口设计一套就够了,前端也能统一做鉴权头、超时和压缩。
3. Word图片在信创环境下的三种形态与转存改造
3.1 base64、远程URL和file协议:图片来源要区分开
从Word里复制内容过来,图片会以几种不同形态出现在HTML里。最常见的是base64的data URI,形如data:image/png;base64,xxxxx。浏览器能直接渲染,但缺点是体积大,一个大图可能几十KB甚至几MB,如果直接存在正文里,数据库和接口的负担都很重,所以必须要转存成服务器文件。
第二种是远程URL,Word里的外链图片复制出来会保留原始src。这种情况需要远程抓取,然后转存到自己的存储上。信创内网环境网络策略严格,抓外网URL可能超时,抓内网IP可能被防火墙挡,还涉及安全审计,处理起来比较麻烦。
第三种是file协议路径,常见于部分Linux系统里的国产Office套件。复制内容后产生的HTML片段里图片src可能是file:///home/user/xxx.png。浏览器出于安全策略不允许网页读取本地文件,所以这个img在编辑器里就是裂的。UEditor默认不会处理这种,要自己写扫描逻辑,发现src以file:开头就给它替换成一个占位提示,或者尝试通过后台上传接口把文件内容读出来转换成服务器URL。
3.2 终端转发上传接口:鉴权头、压缩与格式白名单
图片转存的第一步,是先让UEditor的上传请求能走通。老版本UEditor上传图片的时候会优先尝试Flash桥接,信创环境下这条路走不通,要改成XHR配合FormData。而且信创办公系统一般有统一的登录鉴权体系,上传接口要求把token放在请求头里。Flash上传走的是form提交,没法自定义请求头,这又是一个必须改造的点。
我建议后端设计一个“中转上传”接口:前端先把base64图片数据发给自己的web服务,web服务端再做二次转发到真正的文件存储服务。这样做的好处是鉴权逻辑只在自己的服务端写一次,跨域问题从一个跨到文件服务的变成两个都在自己的域名体系内,而且后端的文件安全扫描、压缩转换都能在这个环节做。
前端这块可以加一个压缩环节。Word里的大图直接传会特别慢,可以先在前端用canvas把图片缩放一下。简单的压缩函数大概长这样:
function compressImage(dataUrl, maxWidth = 1200, quality = 0.8) { return new Promise((resolve) => { const img = new Image(); img.onload = () => { const scale = Math.min(1, maxWidth / img.width); const canvas = document.createElement('canvas'); canvas.width = Math.round(img.width * scale); canvas.height = Math.round(img.height * scale); const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); resolve(canvas.toDataURL('image/jpeg', quality)); }; img.src = dataUrl; }); }有一点得注意:Word里的图片不一定都是JPEG/PNG,偶尔会遇到WMF或EMF这种矢量格式,它们会以image/x-emf或者image/x-wmf的MIME类型出现在data URI里,而浏览器不一定能渲染。这种图前端没法压缩,后端要能识别出来并且做格式转换,转成PNG再存储。如果后端没有这个能力,至少要在上传接口里拒掉这类文件,并且在编辑区给个提示,别让用户看到一片空白以为编辑器坏了。
批量图片上传也建议改成串行队列。一个Word文档复制进来有几十张图很正常,全部并发请求,服务端不一定顶得住,也容易出现部分图片成功、部分失败。串行上传虽然慢一点,但稳定可控,而且可以给每张图标记上传状态,失败的图保留一个占位样式,用户知道是图片上传失败而不是内容丢了。
3.3 远程图片抓取的内网安全策略
catchremoteimage插件默认会请求远程图片地址。这个能力在信创内网环境里要特别小心,否则编辑器本身就成了一个内网探测工具。攻击者只要往编辑器里粘贴一个带src="http://10.0.0.1:8080/xxx"的内容,触发抓取,服务端就会去请求这个内网地址,返回时间、状态码都可能是探测信息。
所以后端做远程抓取时,必须加目标地址白名单和协议限制。只允许http和https,禁止私有网段、保留网段、以及本机回环地址。如果业务上确实只需要抓取某个固定的图片服务器域名,那就直接把白名单锁死到那一个域名最高效。前端配置里也可以调整catchremoteimage插件扫描图片时的过滤条件,只处理指定白名单域名下的URL。
4. 样式与表格:信创浏览器渲染差异下最难啃的两块骨头
4.1 字体映射:没有宋体微软雅黑之后,排版为何“六亲不认”
Windows下编辑的Word文档,默认字体大概率是宋体、黑体或者微软雅黑。这些字体在信创操作系统里默认是不存在的,系统预装的可能是Noto Sans CJK、文泉驿之类的开源字体。于是Word粘贴进来的内容里那些font-family:宋体、font-family:微软雅黑的样式,在信创浏览器里没有字体可命中的时候,就会按系统默认字体显示。如果是文档里的标题用了黑体,正文用了宋体,贴进来之后两者会变成同一种无衬线字体,版式观感差别很大。
处理方案是在过滤环节维护一张字体映射表,把Windows常见字体替换成信创系统实际存在的字体。通用的映射思路像这样:
- 宋体、SimSun、NSimSun -> Noto Serif CJK SC;
- 黑体、SimHei -> Noto Sans CJK SC;
- 微软雅黑、Microsoft YaHei -> Noto Sans CJK SC;
- Calibri -> Noto Sans;
- Times New Roman -> Noto Serif。
替换的时候要小心,不要无脑把所有英文字体都换掉。如果某个段落是英文,font-family可能是Calibri,直接换成Noto也能用,但如果换成中文字体族,英文渲染效果可能反而变差。比较好的做法是保留原字体作为备选,把映射字体追加到字体族列表的后面,让浏览器自己按优先级决定。
字号方面,Word粘贴内容常用pt单位,浏览器对pt的渲染没有问题,但跟屏幕显示的习惯不太一致。如果系统主要面向屏幕阅读,可以在过滤时把pt转成px,转换系数大约是按4/3换算,12pt就是16px。如果系统以后要打印,保留pt反而更准确,这个按项目情况取舍,别两头都想要。
4.2 表格的保真方案:保留colspan/rowspan,修复宽度与边框
Word表格粘贴是另一个重灾区。先看典型的Word表格HTML长什么样:<table class="MsoTableGrid" border="1" cellspacing="0" cellpadding="0" style="border-collapse:collapse;mso-yfti-tbllook:...">,里面每个单元格还带一串mso-border-alt:solid windowtext .5pt之类的样式。
UEditor的filterWord清洗的时候,会很粗暴地把mso-前缀的样式全删掉。问题来了:Word表格的边框定义主要就靠mso-border-alt,删完之后表格边框也没了。处理完的表格看起来就是一片没有网格线的文字堆在一起,这几乎是信创环境里接到投诉最多的一个问题。
解决方案是在自定义过滤规则里对mso-border做映射转换,把mso-border-top-alt转成标准的border-top,mso-border-left-alt转成border-left,mso-border-alt转成border。同时要保证过滤白名单里保留了border、border-collapse这类基础样式属性。这块没有标准配置,需要你根据自己收集的真实Word样例去逐个补充规则,建议每改一次就打一个真实样张验证一遍。
合并单元格的问题更隐蔽。filterWord和后续的白名单过滤如果不允许colspan和rowspan属性存在,那合并的单元格到了浏览器里就会被拆开,左边的表格结构完全变形。我建议无论如何都要在属性白名单里把这两个放行,同时把>const ue = UE.getEditor('editor'); ue.ready(() => { ue.addListener('beforepaste', (type, e) => { // 优先读取原生剪贴板里的text/html const html = e?.clipboardData?.getData('text/html'); if (html) { e.preventDefault(); // 交给UEditor走一遍过滤链再插入 ue.execCommand('insertHtml', html); } }); });
这里的重点是execCommand('insertHtml')会触发UEditor内部的过滤、选中区替换、undo记录等完整流程,比自己直接设置innerHTML要可靠得多。
5.2 服务端与前端联动改造要点
前端工作做得再好,服务端不配合也白搭。UEditor的服务端接口通常是通过serverUrl加action参数来区分行为,比如uploadimage、catchremoteimage。信创环境下我建议改造为:保留这些action的入口,但内部实现切换到新的文件存储服务和鉴权方式。
如果文件服务是独立部署的,前端最好不直接跟它通信,而是让自家的web后台上传接口做中转。前端只把base64数据发给自己后台,后台校验权限之后再去调文件服务的接口。这样做的好处有三个:第一,文件服务的token永远不会暴露到浏览器端;第二,上传日志和审计都集中在自己的后台,方便追溯;第三,文件服务如果换了地址,前端不用改。
服务端还要做图片格式白名单。JPEG、PNG、GIF、WebP正常放开,WMF、EMF要么转换要么拒收。转换工具可以用服务端常用的图像处理库,但要注意转换尺寸和DPI,Word里的矢量图转成位图之后不要太小,否则打印出来会糊。
5.3 兼容性验收清单:别再用“看起来没问题”交差
信创适配这种活,靠人工肉眼“好像没问题”是不行的。我实践下来,至少要准备下面这么一张验收清单,在每台目标浏览器上都完整跑一遍才算数:
| 验收项 | 预期表现 | 关键检查点 |
|---|---|---|
| 纯文本粘贴 | 段落和换行正常,无残留样式 | 复制多段文字后检查段落间距 |
| 带格式文本粘贴 | 字体、字号、颜色、加粗保留 | 检查span内联样式是否被过滤误删 |
| 单张图片粘贴 | 图片转存成功,可编辑可删除 | 粘贴后img的src应为服务器URL而非base64 |
| 多图混排粘贴 | 图片顺序不乱,文字可编辑 | 与Word原文逐段对比 |
| 合并单元格表格 | rowspan/colspan属性保留 | 修改单元格内容后表格结构不散 |
| 大图(>2MB) | 不崩溃,可压缩后上传 | 上传后图片清晰度可接受 |
| 上传失败场景 | 有占位提示,不白屏 | 断网状态下粘贴图片,编辑器仍可继续操作 |
| 字体映射 | 中文显示正常,不出现口口 | 检查标题和正文的字体渲染 |
| 打印输出 | 表格不溢出,分页合理 | 导出PDF后逐页检查 |
这张表做完,基本能覆盖掉信创环境里最常见的用户投诉。我还会建议测试团队把“从Windows Chrome粘贴”和“从信创浏览器粘贴”两条路径分开记录,因为两个环境下剪贴板提供的HTML结构经常会不一样,有些问题在Windows上是复现不出来的。
6. 折腾完这轮适配,我留下的几点大实话
做UEditor的信创适配做了几轮之后,我的整体感觉是:这个地方拼的不是什么高深技术,而是你有没有耐心把整条链路的每一个小环节都抠明白。
第一,别追求100%保留Word排版。信创环境、Office兼容软件、老编辑器三者之间,本来就不存在一个统一的排版模型。跟业务方达成共识,保内容、保表格结构、保图片、保基本样式,做到这四样已经能应付绝大多数办公场景。分页符、页眉页脚、域这些,能做降级处理就做降级处理,不要在编辑器里硬造一个不存在的能力。
第二,一定要建一个真实Word测试样张库。平时收集那些格式各异的文档——带公文字体的、带复杂表格账单的、带大量图片图注的、带公式的、带多级列表的。每次改完过滤规则,就拿这批样张全量回归一遍。这个样本库的价值远远超过花在它上面的时间。我自己吃过亏,某个版本的过滤规则改完之后单测全过,结果拿了一篇真实公文样张一贴,正文里一小段字全变成了红色,排查了半天才发现是过滤规则里一个颜色属性没放进白名单。
第三,如果项目允许,趁早考虑用更现代的开源编辑器替代UEditor。信创适配只是表象,真正的矛盾是UEditor这个项目很多年没有实质维护了。但如果你跟我一样,接手的是一个已经深度依赖UEditor的老系统,那上面的思路就是性价比最高的路线——自己fork一个版本,锁在私有源里,把行业里积累的这些补丁都打进去,比反复跟那个老旧版本做斗争要省心得多。
最后分享一个很实用的兜底方案:信创浏览器对本地文件读取限制很严,但用户就是想把docx里的整段内容粘进编辑器。这种时候别硬修浏览器兼容,给个提示让用户把Word另存为HTML文件,再通过UEditor自带的“从Word粘贴”弹窗导入。很多用户对HTML文件不熟悉,但这个兜底方案比继续和浏览器差异死磕要快得多,而且在信创环境里实测下来很稳定。