ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Visio流程图在TinyMCE中失真?BOM系统图片清晰度排查与解决实践

Visio流程图在TinyMCE中失真?BOM系统图片清晰度排查与解决实践 上个月在给机械设计BOM系统做升级时被一个看似不起眼的小问题卡了两天工程师从Visio里画好的流程图粘到TinyMCE富文本编辑器里再保存到BOM系统的工艺备注字段出来全是糊的。不光是图片模糊有时候箭头被拉歪、文字区块错位甚至整张图变成一片空白。群里老师傅直接甩了一句“这系统还不如我在Word里粘贴得清楚。”后来我把Visio、TinyMCE、BOM系统这三层都翻了一遍才彻底弄明白问题出在哪也把这条链路完整修好了。这篇东西就是想把这段排查和改造经验写清楚给正在做类似BOM、ERP或者文档管理系统集成的朋友一个能直接落地的参考。1. Visio流程图在TinyMCE里失真的根因不是像素不够这么简单1.1 一次BOM备注里的“糊图”现象我们系统里的机械设计BOM每个物料节点可以挂一份工艺流程图比如“轴类零件加工流程”“装配过程检验流程”。这些图基本都出自Visio工程师习惯画完之后直接CtrlC复制再切到浏览器里CtrlV粘贴进TinyMCE。表面上看粘贴完编辑器里确实显示了一张流程图颜色、线条都还在所以绝大多数人根本不会多想直接点保存。问题在做BOM审核时集中爆发有人的流程图在提交之后变成了一坨模糊的像素块字看不清连线变成“毛边”有人的图在编辑界面看着是正常的等审批流里走一圈再打开尺寸被莫名放大了一倍还有更离谱的粘贴进来当时正常第二天再打开那条BOM记录图片裂了浏览器显示一个小红叉。这些现象的根源并不是Visio画图质量差也不是TinyMCE本身存不了图片而是“从Visio复制到浏览器粘贴”这条路径里经过了多种格式转换每一步都可能丢东西。1.2 剪贴板里装的不是一张图而是一堆格式很多人以为在Visio里按CtrlC剪贴板里就是一张PNG或者JPEG。实际上Office系列软件复制对象时会同时往剪贴板里塞很多种格式包括原始Visio对象OLE对象EMF/WMF矢量图元文件DIB位图PNG位图HTML片段里面可能引用VML或内嵌图片当你把它粘贴到Word里Word会优先选择OLE对象或者EMF所以得到的依然是矢量效果当你把它粘贴到浏览器里的TinyMCE时浏览器和编辑器插件会从剪贴板里挑一种自己能处理的格式。Chrome等现代浏览器通常会把图片格式转为PNG但问题是这个PNG的分辨率可能只有96 DPI。Visio在复制时生成的PNG更多是为了“快速预览”用的不是为印刷或者高清屏幕准备的一旦在BOM系统里被CSS拉伸或者审批流里做了缩略图放大糊掉是必然的。这里还要特别提一下DIB格式。如果TinyMCE的粘贴插件在某种情况下拿不到干净的PNG而选择了DIB那存出来的图经常是带黑底、透明通道丢失的放在BOM系统白色背景下一眼就能看出来。1.3 真正要命的是VML和EMF这两个“历史遗留物”Visio往剪贴板里塞的HTML片段是带VMLVector Markup Language的。VML是微软在IE时代主推的矢量标记语言在今天的Chrome、EdgeChromium版里已经不是原生支持的东西了。TinyMCE的历史版本里PowerPaste插件会试图解析这种HTML片段把里面的VML转成图片或者直接保留VML标签。一旦转换失败编辑器里就会出现一堆莫名其妙的v:group、v:shape、o:OLEObject之类的标签保存到后端再读出来浏览器不认图片自然就裂了。EMF则是另一种情况。Visio复制时生成的EMF是矢量格式理论上放大不会失真但绝大多数网页和TinyMCE不直接在img标签里渲染EMF。如果你用images_upload_handler把剪贴板里的数据直接当图片上传后端拿到的可能是一个无法被前端识别的EMF文件硬要用img srcxxx.emf去展示结果就是空白或者浏览器弹下载框。总结一句失真的核心不是“图片被压缩了”这么一个简单原因而是Visio剪贴板的多格式输出与TinyMCE的HTML解析机制之间发生了错配。2. TinyMCE默认行为为什么扛不住Visio流程图2.1 Paste插件到底在处理什么TinyMCE默认有一个paste插件它负责把用户粘贴进来的内容转换成编辑器可用的HTML。对于文本它处理起来非常轻车熟路对于图片逻辑就复杂了。当你在TinyMCE里粘贴图片时paste插件会经历一个类似这样的判断读取剪贴板的text/html内容从里面提取图片相关的标签或二进制数据根据配置决定是把图片保留为Base64、转成Blob上传还是丢掉最终生成一段HTML插入编辑器。粘贴Visio流程图时剪贴板里那堆HTML/VML片段会被第一个读到。TinyMCE默认的PowerPaste模式会尝试把VML转成图片但它的转换器主要针对简单的Office文本对于Visio这种复杂的图形组合转换出来的往往是一个分辨率很低的预览图而且位置信息大概率会错乱。很多开发者遇到问题后第一反应是“那我直接把paste_data_images设置成true让它把图片当作独立图片粘贴不就行了”。这个方向是对的但还不够。因为Visio粘贴过来的“图片”经常不是一张干净的位图而是一段混合了VML和图片的HTML你的处理逻辑必须针对这种混合内容做专门清洗。2.2 位图和矢量图在粘贴时的不同命运如果粘贴的是普通截图工具截出来的PNGTinyMCE处理起来很轻松识别到剪贴板里有PNG数据直接转成Base64或者上传完事。流程图这种场景的问题在于Visio里的图形元素在剪贴板里是“矢量描述”和“位图预览”同时存在的。编辑器更倾向保留HTML描述但HTML描述里是过时的VML最终表现就是个别浏览器自动把VML渲染成一个低分辨率的图片某些版本的TinyMCE会把VML原样塞进content导致后端拿到一堆垃圾标签图片在编辑器里看起来有一层灰蒙蒙的背景因为VML转成的位图带了默认底色。换句话说位图粘贴时是“图”Visio粘贴时是“图一堆描述信息”TinyMCE需要从这堆描述里“拆”出一张能用的图而拆出来的结果完全取决于Visio给了什么。Visio给的预览PNG是96 DPI的那拆出来就是96 DPI的放到BOM系统的高分屏上就是糊的。2.3 BOM系统场景的特殊约束机械设计BOM系统跟普通内容管理系统还不一样它对流程图有几个额外的约束流程图的审批链路很长工艺人员绘制的流程图要经过校对、审核、批准每一级都可能打开附件预览图片在不同缩放级别下都必须保持清晰可读。图纸信息密度高方框里的文字、箭头上的标注、线条之间的间距都很小一个普通流程图里可能塞了几十个文本标签分辨率不够会直接导致文字无法辨认。后端存储与回显链路多样BOM数据可能在Web端查看、在打印模板里渲染、在移动端审批应用里回显。同一张图片要适应这些场景靠一张96 DPI的小位图是撑不住的。所以我后来在方案设计时给自己定了一个原则不能让Visio的剪贴板格式来决定我们的存储质量必须把图片格式的控制权从前端粘贴动作里夺回来。3. 源头治理Visio导出这一步就要做对3.1 高分辨率PNG导出分辨率设到多少才够如果团队暂时不打算上SVG最低限度也应该用PNG导出。Visio里不要再用“复制”功能了改成“文件 - 导出 - 更改文件类型 - PNG”。导出时会让你填分辨率设置。根据我的经验机械BOM里的流程图至少要做到200 DPI以上推荐300 DPI。以一张A4横向画布为例300 DPI导出的PNG分辨率大概是3508×2480像素一张图大约13 MB能保证在BOM系统页面里无论放大到150%还是缩略显示文字都清楚。导出时记得看一下“大小”选项。Visio默认导出的是当前画布大小如果你以前把画布拖得特别大或者页面上有很多边距空隙导出的PNG里会有一大片空白。我一般会在Visio里先用“设计 - 大小 - 适应绘图”把画布裁剪到内容刚好放下的范围再导出。这一步虽然简单但能明显减小图片体积也能避免BOM系统回显时出现大块留白。3.2 直接存SVG机械流程图最稳的矢量格式我最终在系统里选了SVG作为流程图的主流存储格式。原因很直接Visio从2013版本开始就支持“另存为SVG”导出结果保留了矢量信息文字变不变形、线条清不清晰都不再受分辨率限制。Visio导出SVG有两种常见做法“文件 - 另存为 - 选择SVG格式”这种方式适合单张流程图将多张图放在一个Visio文件的不同页里用“另存为”时没法一次性导出多页需要用到Visio自带的“导出”功能或者VBA批量处理。对于BOM系统里大量节点都要上传流程图的情况我建议给工艺人员做一个简单的VBA宏把当前Visio文件里的每一页都导出成独立的SVG文件命名规则用图号或者物料编码这样能省掉大量手工操作。还有一个细节必须注意Visio导出的SVG默认尺寸是跟随画布的但其中的文本字体不一定能嵌入。如果BOM系统部署的服务器或者客户端没有安装对应的字体SVG里的文字会被替换成其他字体可能造成排版变化。我在项目里为了规避这个问题要求工艺流程图统一使用宋体或微软雅黑这两种字体在公司内网机器上基本都是标配。3.3 画布、缩放、字体统一避免“到编辑器里就变形”Visio图在BOM系统里变形的另一个隐藏原因是原始画布设置不统一。同一个流程图中有人把方框画得很大、字很小有人一页里塞了20个节点有人把整个图缩到50%再画导致每个图形对象的实际坐标和尺寸五花八门。我在给工艺部门做培训时提了三条硬性规范统一纸张方向横向流程图用横向画布纵向流程图用纵向画布不要一张图里横竖混排。统一字体大小框内文字至少10号标注文字至少9号保证导出成位图时不会挤成一团。画图时保持100%缩放只在100%视图下编辑避免在缩小状态下误判文字清晰度。这三条做到位Visio导出图片后到了TinyMCE里比例和视觉感受基本不会再突变。别小看这些“非技术”规范我后面做实测对比时发现光是把字体统一了流程图在BOM回显里的可读性就能提高一个档次。4. TinyMCE端改造粘贴、上传、后处理三段式拦截4.1 基础配置允许图片粘贴并自动上传Visio那边源头治理好了接下来是TinyMCE这边的工程改造。第一件事是把TinyMCE的图片粘贴能力打开并且让图片自动上传到服务器的文件存储而不是默认塞一个巨长的Base64字符串进BOM字段。初始化配置里核心的几个参数如下tinymce.init({ selector: #bomDescription, plugins: paste image lists link code, toolbar: undo redo | blocks | bold italic | bullist numlist | link image | code, paste_data_images: true, automatic_uploads: true, images_upload_url: /api/bom/upload-image, images_upload_base_path: /uploads, images_upload_handler: function (blobInfo, success, failure) { // 自定义上传逻辑后面会详细写 }, paste_postprocess: function (pluginApi, editor, data) { // 清洗Visio粘贴带过来的脏HTML } });paste_data_images: true是允许粘贴画板里的位图数据automatic_uploads: true是让TinyMCE拿到图片数据后自动调用上传接口images_upload_handler是自定义上传行为的入口。这里要提醒一句只开前两个还不够因为Visio粘贴过来的HTML片段里那张图片可能会被TinyMCE识别成“内嵌图片”但它的分辨率就是Visio给的那个低质量预览图。所以还需要在paste_postprocess里做清洗和替换。4.2 paste_postprocess把剪贴板里的“脏内容”洗成干净的imgpaste_postprocess是一个在粘贴内容被插入编辑器之前执行的钩子。我的处理思路是把粘贴内容里的VML标签全部删掉找到里面的img标签检查图片的宽高和来源如果是Visio带来的低分辨率预览图干脆移除并提示用户改用上传按钮上传高清PNG/SVG。代码可以这样写paste_postprocess: function (pluginApi, editor, data) { let content data.content; // 1. 移除VML和OLE相关标签 content content.replace(/v:[^][\s\S]*?\/v:[^]/gi, ); content content.replace(/o:[^][\s\S]*?\/o:[^]/gi, ); content content.replace(/v:[^]\//gi, ); content content.replace(/o:[^]\//gi, ); // 2. 遍历所有img标签 const tempDiv document.createElement(div); tempDiv.innerHTML content; const imgs tempDiv.querySelectorAll(img); imgs.forEach(img { const width parseInt(img.getAttribute(width), 10); const height parseInt(img.getAttribute(height), 10); // 3. 如果图片宽度明显小于800像素或者src是base64且体积很小视为低质量预览图 if (img.src.startsWith(data:image) img.src.length 50000) { img.remove(); editor.notificationManager.open({ text: 检测到Visio粘贴图质量过低请使用“插入图片”按钮上传高清PNG或SVG。, type: warning }); } }); data.content tempDiv.innerHTML; }这套逻辑在项目里实测下来能挡掉至少一半因为直接CtrlV导致的质量问题。但注意它不能百分之百识别所有脏数据比如有些Visio版本粘贴出来的预览图Base64超过50KB但实际分辨率依然不高。所以不要过分依赖后处理最重要的还是引导用户走上传通道。4.3 自定义上传让工程师用文件选择器而不是CtrlV后处理只能起到拦截和提醒作用真正可靠的做法是在编辑器里把“插入图片”这个按钮用好。我给TinyMCE配置了自定义images_upload_handler让用户选择的PNG/SVG文件直接走上传接口返回URL之后插入编辑器。images_upload_handler: function (blobInfo, success, failure) { const formData new FormData(); formData.append(file, blobInfo.blob(), blobInfo.filename()); fetch(/api/bom/upload-image, { method: POST, body: formData, headers: { X-CSRF-Token: window.csrfToken } }) .then(response response.json()) .then(res { if (res.code 0) { success(res.data.url); } else { failure(res.message); } }) .catch(err { failure(err.message || upload failed); }); }后端接口我统一接收PNG和SVG两种格式。PNG用于生成缩略图SVG用于高清展示。工程师在浏览器端看到的是img src/uploads/xxx.svg但文件上传这块并不复杂真正复杂的是后端怎么处理SVG的安全性和尺寸校验。5. BOM系统里的完整落地代码与交互设计5.1 交互流程从Visio导出到BOM保存一共四步为了让工艺人员不再依赖CtrlV这种不稳定的方式我把整个交互流程重新设计成四步工程师在Visio里把流程图另存为SVG或高分辨率PNG在BOM系统的TinyMCE编辑器里点击“插入图片”按钮选择本地文件前端上传文件到BOM附件服务拿到URL后插入编辑器编辑器里显示图片预览工程师填写必要的流程说明文字保存BOM节点信息。这套流程虽然比“复制粘贴”多了一步选文件的操作但换来的是图片稳定清晰。培训成本也不算高关键是得给工程师讲清楚为什么不能直接复制。5.2 前端代码TinyMCE初始化、图片上传、尺寸校验我贴一下当时实际使用的完整前端代码包含图片尺寸校验和格式校验function initBomEditor() { tinymce.init({ selector: #bomEditor, height: 480, menubar: false, plugins: paste image lists link code autosave, toolbar: undo redo | blocks | bold italic bullist numlist | link image | code, paste_data_images: true, automatic_uploads: true, images_upload_handler: function (blobInfo, success, failure) { const file blobInfo.blob(); // 校验格式 const allowedTypes [image/png, image/svgxml]; if (!allowedTypes.includes(file.type)) { failure(仅支持PNG或SVG格式的流程图); return; } // 校验大小10MB以内 if (file.size 10 * 1024 * 1024) { failure(流程图大小不能超过10MB); return; } // SVG不做像素校验PNG检查最小宽度 if (file.type image/png) { const reader new FileReader(); reader.onload function (e) { const img new Image(); img.onload function () { if (img.width 1200) { failure(PNG图片宽度太小请用Visio导出至少200 DPI的PNG); return; } uploadBomImage(file).then(res { success(res.url); }).catch(err { failure(err.message); }); }; img.src e.target.result; }; reader.readAsDataURL(file); } else { uploadBomImage(file).then(res { success(res.url); }).catch(err { failure(err.message); }); } } }); } function uploadBomImage(file) { const formData new FormData(); formData.append(file, file); return fetch(/api/bom/upload-image, { method: POST, body: formData, headers: { X-CSRF-Token: getCsrfToken() } }).then(res res.json()).then(res { if (res.code ! 0) { throw new Error(res.message || 上传失败); } return res.data; }); }注意这里我在PNG上传前做了宽度校验要求至少1200像素宽。这个值不是拍脑袋定的而是根据BOM系统内容区大约800像素宽的渲染宽度和2倍图的清晰度要求算出来的800×1.5或2正好落在1200到1600之间。如果前端不加这道校验后面审核人员在125%缩放的显示器上打开BOM详情时图片边缘会出现肉眼可见的锯齿。5.3 服务端处理校验图片尺寸与SVG安全性服务端我用的Java Spring Boot但思路是通用的。上传接口主要做三件事格式识别、SVG安全清洗、PNG尺寸复核。对于PNG我用ImageIO读取流后检查实际像素尺寸防止前端被绕过、直接POST一个小文件上来。实际上后端必须把校验再执行一遍因为接口是公开的不能只依赖前端。对于SVG重点不是尺寸而是安全性。Visio导出的SVG是干净的XML但用户也可能上传一个从网上下载的、内嵌了JavaScript脚本的SVG直接通过浏览器展示会有XSS风险。所以我写了这么一段清洗逻辑public String sanitizeSvg(String svgContent) { // 解析XML DocumentBuilderFactory factory DocumentBuilderFactory.newInstance(); factory.setNamespaceAware(true); factory.setFeature(http://apache.org/xml/features/disallow-doctype-decl, true); DocumentBuilder builder factory.newDocumentBuilder(); Document doc builder.parse(new ByteArrayInputStream(svgContent.getBytes(StandardCharsets.UTF_8))); // 移除script、foreignObject、事件属性 NodeList scriptNodes doc.getElementsByTagName(script); while (scriptNodes.getLength() 0) { Node node scriptNodes.item(0); node.getParentNode().removeChild(node); } // 遍历所有元素移除on开头的属性 removeEventAttributes(doc.getDocumentElement()); Transformer transformer TransformerFactory.newInstance().newTransformer(); StringWriter writer new StringWriter(); transformer.transform(new DOMSource(doc), new StreamResult(writer)); return writer.toString(); }你可能觉得BOM系统是内网系统不用太担心XSS。但内网不等于绝对安全Visio文件本身经常在企业微信、邮件里传来传去万一某个流程图是外部供应商发来的里面被塞了脚本你也不知道。清洗这一步花不了多少时间但能避免一个长期隐患。6. 实测对比与三个典型坑6.1 三种贴图方式的效果对照表改造完成之后我让工艺部的同事用了两周同时对三种贴图方式做了对比测试结果如下操作方式编辑器内显示BOM审批回显打印模板渲染文件大小推荐度直接CtrlV粘贴当时正常细看有锯齿模糊文字发虚放大后边缘明显锯齿50KB左右不推荐从Visio导出PNG后上传清晰尺寸可控清晰放大到150%依然可读清晰13MB推荐从Visio导出SVG后上传极致清晰任意缩放无损清晰矢量化渲染打印效果最好100500KB最推荐第二行和第三行之间最终我们BOM系统选择了SVG作为主格式PNG作为兜底兼容格式。原因是有些老旧的浏览器或第三方审批组件对SVG支持得不好这时候如果系统里已经有同名的PNG文件就自动切换成PNG展示。6.2 坑一Base64超长导致BOM保存接口超时第一次改造时我没有启用automatic_uploads觉得让用户粘图时把图片转成Base64存进BOM字段里最简单一个字段搞定不用额外建附件表。结果上线第一天就出问题一张Visio导出的PNG高分辨率图可能有2MB转成Base64之后约2.7MB几个节点一起保存BOM接口瞬间超时。后来我把方案改成“图片单独上传到附件服务BOM节点字段只保存图片URL或一个JSON数组”。这样BOM主表的字段始终很小保存动作不受图片大小影响。如果你也在做类似系统一定要尽早把图片和业务数据分开存别让它们混在同一个字段里。6.3 坑二Visio导出SVG里带着危险标签测试SVG上传时我发现Visio导出的一些文件里带了foreignObject标签里面包了一层HTML。这种标签在浏览器里是可以解析HTML的存在脚本注入风险。而且Visio还可能在SVG里加入office:shape这种扩展属性虽然浏览器会忽略但也会让HTML渲染出现怪异的空白。我最终的清洗规则是除svg、g、path、rect、text、tspan、defs、line、polyline、polygon等常规绘图元素外其余一律过滤掉所有on*事件属性全部删除。这样处理之后SVG还能保持Visio导出的矢量效果但危险面大幅缩小。6.4 坑三透明背景在深色主题下看不见Visio画流程图时默认是白色背景但导出的PNG如果背景是透明的放到BOM系统暗色主题下展示时那些黑色线条就会和深色背景融为一体根本看不清。我们的解法是在上传PNG时用服务端程序把透明通道检查一遍如果发现整体透明度很高就自动填充白色背景。SVG同样处理在根节点svg上强制加一个stylebackground-color: #ffffff这样不管前端主题怎么切换流程图本身始终有白底不会“隐身”。这个细节是在一次夜班值班时被发现的当时审批人员用的是深色主题的工单系统打开的每张流程图几乎都是黑乎乎的。从那之后所有上传流程图统一压白底再也没出过这个幺蛾子。最后再分享一个小习惯整个项目做完我最深的一个体会是Visio流程图这类内容一旦进了BOM系统它就不是一张“图”那么简单了而是一份要反复流转、审核、打印、归档的工程数据。对工程数据来说稳定和清晰永远比方便更重要。所以哪怕多一步导出操作我也更倾向于让源头交付出高分辨率PNG或SVG而不是赌TinyMCE能把剪贴板里的混合格式处理得完美。如果你也在做类似集成我建议先把你系统里真实使用的浏览器版本和Visio版本固定下来做一轮全链路粘贴实测看看剪贴板里到底带出了什么格式再决定是走纯PNG方案还是SVG方案。这比直接抄配置更靠谱。另外一个很实用的小操作给工艺人员做一页A4纸的“Visio导出流程图检查卡”上面写清楚导出步骤、命名规范和最容易踩的三个坑贴在工位旁边比发十页操作手册管用得多。
返回列表