☰
UEditor公式插件落地:图片与LaTeX源码双存及回显二次编辑
2026/10/23 21:10:23 网站建设 项目流程

简介:面向需要在后台编辑器中录入数学公式的Web开发者,这是一套已整合UEditor与kityformula-plugin公式插件的可直接运行源码包,解决富文本编辑器无法插入、编辑与渲染数学公式的问题,适合教务系统、题库、在线考试、理科课程内容管理等场景做二次开发。压缩包共747个文件,约3.43MB,其中85个js文件承载编辑器核心逻辑与插件脚本,26个html为示例与功能演示页,26个css配合图片素材完成工具栏、弹窗与公式面板的样式;539个png与46个gif多为编辑器图标、按钮和公式预览所需图像,另含woff、eot、ttf等字体文件保证公式符号正常显示。目前已有1463人学习下载。包内保留了完整的编辑器目录结构与插件接入示例,读者可直接对照演示页跑通“打开公式面板—书写公式—回显到编辑区”的流程,也可按需裁剪语言包与皮肤,快速嵌入自有项目。

1. 公式插件版 UEditor 编辑器:从“能输入”到“能交付”的四个硬指标

题库后台里最怕的不是公式难打,而是改一个根号要重新截图。带公式插件的 UEditor 编辑器解决的就是这个事:公式在弹窗里可视化编辑,确定后插入正文,保存的是图片加源码,回显还能二次编辑。它适合试卷录入、培训资料、题目解析这类需要频繁改公式的编辑器场景;不适合只想贴一张静态公式图、后面不再改的轻量页面。判断这份资源能不能用,我一般看四个硬指标:公式按钮能不能稳定弹出、插入后图片能不能落盘、提交后源码会不会被过滤、回显时能不能再次打开编辑。四个里缺一个,后面都会变成返工。

2. 拆开公式插件:UEditor 的 formula 目录、数据流与三种渲染路线

2.1 formula 插件目录里到底有什么

拿到资源包后,先别急着改配置,先把ueditor/plugins/formula目录翻一遍。常见结构是插件入口formula.js、弹窗页formula.html、公式编辑内核子目录、图标和样式文件。formula.js负责向编辑器注册按钮和命令,formula.html是 iframe 弹窗里真正加载的页面,内核子目录负责公式的可视化编辑和图片导出。很多人只把ueditor.all.min.js拷进项目,漏掉plugins/formula,结果工具栏上有按钮,点下去没反应。

文件/目录作用常见改动点
plugins/formula/formula.js注册formula按钮、命令和弹窗回调插入图片时补>// plugins/formula/formula.js 内,拼 img 时补>// 初始化编辑器时显式打开 formula 按钮 var editor = UE.getEditor('editor', { UEDITOR_HOME_URL: '/static/ueditor/', serverUrl: '/api/ueditor/upload', toolbars: [[ 'source', 'bold', 'italic', 'underline', 'forecolor', 'fontsize', 'justifyleft', 'justifycenter', 'insertorderedlist', 'insertunorderedlist', 'formula', 'insertimage', 'link', 'undo', 'redo' ]], initialFrameHeight: 420, zIndex: 1200, lang: 'zh-cn' });

逻辑说明:UEDITOR_HOME_URL末尾带斜杠,指向静态资源根目录,公式弹窗会按这个路径去拼plugins/formula/formula.html;serverUrl是上传接口,公式图片如果走服务端保存,必须和它对齐;toolbars是二维数组,formula放在第二行也能显示,但不要和source混在同一组导致换行错位;zIndex在后台弹层多的项目里要调高,否则公式弹窗被遮住,看起来像“点了没反应”。参数没有万能值,initialFrameHeight按后台布局调,zIndex比页面最大弹层高 100 到 200 就够。

3. 把编辑器跑起来:从静态资源到 formula 插件启用的可复现配置

3.1 最小启动页:静态目录、UEDITOR_HOME_URL 与语言包

先别接后端,拿一个空 HTML 把编辑器跑起来。资源包里的ueditor目录整体放到静态资源路径下,不要只拷ueditor.all.min.js。最小页面按顺序引ueditor.config.js和ueditor.all.min.js,再放一个容器。路径错了,控制台会先报语言包 404,再报插件 404,最后按钮全灰,看起来像资源坏了。

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8" /> <title>formula editor demo</title> <script src="/static/ueditor/ueditor.config.js"></script> <script src="/static/ueditor/ueditor.all.min.js"></script> </head> <body> <script id="editor" type="text/plain" style="width:100%;height:420px;"></script> <script> var editor = UE.getEditor('editor', { UEDITOR_HOME_URL: '/static/ueditor/', initialFrameHeight: 420 }); </script> </body> </html>

逻辑说明:script标签的id="editor"是编辑器容器,type="text/plain"不能省,否则里面的 HTML 会被浏览器先解析一遍;UEDITOR_HOME_URL指向ueditor目录,不是ueditor.all.min.js文件;语言包默认在lang/zh-cn/zh-cn.js,路径不对时编辑器能出来一部分,但按钮提示和弹窗文案会缺。参数上,initialFrameHeight只影响编辑区,不影响公式弹窗,弹窗尺寸在formula.html或插件配置里改。

3.2 打开 formula 按钮:toolbars 配置与实例初始化

最小页面能跑后,再把formula加进工具栏。很多资源包的ueditor.config.js里工具栏是长数组,实例初始化又写了一遍短数组,结果公式按钮被覆盖掉。排查时在控制台执行editor.getOpt('toolbars'),看最终生效的是哪一组。下面这段配置把公式按钮和图片按钮分开,避免样式串位。

var editor = UE.getEditor('editor', { UEDITOR_HOME_URL: '/static/ueditor/', serverUrl: '/api/ueditor/upload', toolbars: [[ 'bold', 'italic', 'underline', 'forecolor', 'fontsize', 'justifyleft', 'justifycenter', 'justifyright', 'insertorderedlist', 'insertunorderedlist', 'formula', 'insertimage', 'link', 'unlink', 'undo', 'redo' ]], elementPathEnabled: false, wordCount: false, initialFrameHeight: 420 });

逻辑说明:formula是插件注册的按钮名,写错成formulaBtn或math都不会显示;elementPathEnabled和wordCount关掉是为了后台布局紧凑,不影响公式功能;serverUrl指向统一上传入口,公式图片如果由插件直接走图片上传,会复用这个地址。参数上,toolbars里的按钮顺序就是显示顺序,公式按钮建议放在insertimage前面,录入时手指移动距离短。改完刷新页面,点公式按钮,Network 里应该能看到formula.html和内核 JS 的请求。

3.3 公式弹窗 404/空白:用 Network 面板定位 iframe 路径

公式弹窗空白是最高频的翻车点。先开浏览器 Network,点一次公式按钮,过滤formula,看formula.html是不是 404。常见原因是UEDITOR_HOME_URL写成相对路径,后台路由带了一层前缀,比如页面在/admin/question/edit,相对路径拼出来变成/admin/question/plugins/formula/formula.html。改成以/开头的绝对路径,或者用构建变量注入站点根路径。第二个原因是 Nginx 的X-Frame-Options: DENY,iframe 被浏览器拒绝,控制台会有Refused to display字样。

location /static/ueditor/ { alias /var/www/app/static/ueditor/; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; location ~* \.(js|css|html|png|svg)$ { expires 7d; } }

逻辑说明:alias指向磁盘目录,末尾斜杠要和location对应,少一个斜杠会拼出/static/ueditorplugins/...;X-Frame-Options用SAMEORIGIN,不要用DENY,否则同源弹窗也加载不了;静态资源缓存 7 天,升级插件时记得改文件名或加版本参数,否则用户拿到旧formula.js。参数上,expires按发布频率调,后台项目一周发一次就够;如果公式弹窗和主站不同源,不要试图用document.domain硬解,直接把ueditor目录放到同源静态服务下,省掉跨域通信的玄学问题。

3.4 上传接口约定:返回 state、url 与字段对齐

公式图片走服务端保存时,上传接口要按 UEditor 的约定返回 JSON。最常见错误是返回{"code":0,"data":{"url":"..."}},编辑器只认state字段,拿不到就弹“上传失败”。下面是一个最小 Flask 接口,字段名和编辑器配置对齐。

from flask import Flask, request, jsonify import os, uuid app = Flask(__name__) UPLOAD_DIR = '/var/www/app/uploads/formula' ALLOWED = {'.png', '.jpg', '.jpeg', '.svg'} @app.post('/api/ueditor/upload') def ueditor_upload(): f = request.files.get('upfile') # 对应 imageFieldName if not f: return jsonify(state='ERROR', message='no file') ext = os.path.splitext(f.filename)[1].lower() if ext not in ALLOWED: return jsonify(state='ERROR', message='bad type') name = f'{uuid.uuid4().hex}{ext}' path = os.path.join(UPLOAD_DIR, name) f.save(path) return jsonify( state='SUCCESS', url=f'/uploads/formula/{name}', title=name, original=f.filename, type=ext, size=os.path.getsize(path) )

逻辑说明:request.files.get('upfile')的字段名要和编辑器配置里的imageFieldName一致,默认常见是upfile,资源包改过就跟着改;state必须是大写SUCCESS,小写或OK都会被当成失败;url要返回浏览器可访问的路径,不要返回磁盘绝对路径;size是字节数,编辑器用来做前端提示。参数上,ALLOWED按公式图片实际格式收窄,公式一般只出 PNG 或 SVG,别放开到gif;UPLOAD_DIR要可写,Nginx 或 Flask 用户权限不对时会保存失败但接口仍返回 500,日志里才看得到。

4. 公式的存取与回显:后端过滤、图片落地与 LaTeX 转 MathML 的边界

4.1 提交内容白名单:img 的 src、data-formula 与 _src

编辑器提交的 HTML 到后端,第一道坎是富文本过滤。很多项目用默认白名单,img只留src、alt、title,>import bleach ALLOWED_TAGS = ['p', 'br', 'strong', 'em', 'u', 'ol', 'ul', 'li', 'img', 'span', 'sub', 'sup', 'table', 'tbody', 'tr', 'td'] ALLOWED_ATTRS = { 'img': ['src', 'alt', 'title', 'width', 'height', 'style', 'data-formula', '_src'], 'span': ['style'], 'p': ['style'], '*': ['class'] } ALLOWED_PROTOCOLS = ['http', 'https', 'data'] def clean_content(html: str) -> str: return bleach.clean( html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS, protocols=ALLOWED_PROTOCOLS, strip=True )

逻辑说明:>import base64, re, uuid, os IMG_DATA_RE = re.compile( r'<img\b[^>]*\bsrc="data:image/(png|jpeg|jpg);base64,([^"]+)"[^>]*>', re.I ) def replace_base64_images(html: str, upload_dir: str, url_prefix: str) -> str: def _repl(m): ext = 'png' if m.group(1).lower() == 'png' else 'jpg' raw = base64.b64decode(m.group(2)) name = f'{uuid.uuid4().hex}.{ext}' path = os.path.join(upload_dir, name) with open(path, 'wb') as f: f.write(raw) old = m.group(0) return old.replace( m.group(0)[m.group(0).find('src="'):m.group(0).find('"', m.group(0).find('src="')) + 1], f'src="{url_prefix}/{name}"' ) return IMG_DATA_RE.sub(_repl, html)

逻辑说明:正则只匹配src是 base64 的img,不会动普通 URL 图片;替换时保留img上的>// 展示页把公式图替换成可缩放渲染结果 document.querySelectorAll('.content img[data-formula]').forEach(function (img) { var latex = decodeURIComponent(img.getAttribute('data-formula') || ''); if (!latex) return; var box = document.createElement('span'); box.className = 'formula-render'; try { window.renderFormula(latex, box, { displayMode: true, throwOnError: false }); img.replaceWith(box); } catch (e) { // 渲染失败保留原图,别让整页崩 console.warn('formula render failed', e); } });

逻辑说明:decodeURIComponent只解一次,和插入时的encodeURIComponent对应;throwOnError: false让不支持的公式退化为红色文本而不是整页报错;渲染失败保留原图,保证老数据还能看。参数上,displayMode控制行内还是块级,题库解析里的独立公式用块级,题干里的行内公式用行内;渲染器版本要和编辑器内核版本做一次命令覆盖对照,至少把分式、根式、上下标、矩阵、希腊字母跑一遍,别等用户反馈才发现某个命令不支持。

5. 排查与避坑:公式插件在真实项目里最常见的五类翻车

5.1 点公式按钮没反应,弹窗也不出来

现象:工具栏上公式按钮是亮的,点下去没有任何弹窗,控制台也没有明显报错。原因通常有三个:plugins/formula/formula.js没被加载、toolbars被实例配置覆盖、formula.html路径 404 被浏览器静默吞掉。解决顺序是先看 Network 里有没有formula.js和formula.html请求,再看editor.getOpt('toolbars')的最终值,最后检查UEDITOR_HOME_URL是否以/开头。弹窗被zIndex压住也会像没反应,把zIndex调到页面最大弹层之上再试。

5.2 公式图片提交后变成裂图

现象:编辑器里公式正常,保存后重新打开,公式位置是破图图标。原因多半是后端过滤器把src删了,只留下_src,或者把data:image协议拦了。解决是把img的src、># 只对编辑器保存接口放宽请求体,不要全局放开 location = /api/ueditor/upload { client_max_body_size 8m; proxy_pass http://127.0.0.1:8000; }

逻辑说明:location =精确匹配上传接口,避免把全站请求体限制都放大;8m是经验值,公式图片正常单张几百 KB,留出批量提交余量;proxy_pass指向实际后端。参数上,如果前端已经压缩到 2MB 以内,这里设 8MB 足够;再大就要检查是不是把整页 base64 都塞进了一个请求。

5.4 回显后公式不能二次编辑,点开是空白弹窗

现象:正文里公式图还在,双击或点编辑按钮,弹窗里没有原公式。原因是只存了图片,没有存>@media print { .content img[data-formula] { max-width: 100% !important; height: auto !important; image-rendering: -webkit-optimize-contrast; } .content .formula-render { break-inside: avoid; } }

逻辑说明:max-width:100%防止长公式撑出纸张,height:auto保持比例;image-rendering对低分辨率图只能缓解,根治还是导出高分辨率图;break-inside: avoid避免公式被分页截断。参数上,打印样式里不要用zoom放大图片,放大只会更糊。

6. 进阶验证:用一组回归用例把公式编辑器压到可交付

公式编辑器的验收不能只点一次分式。我一般准备一组最小回归用例,覆盖简单分式、根式、上下标、希腊字母、矩阵、长公式、复制粘贴、二次编辑、打印九项。每项记录三件事:编辑器内是否正常、保存后回显是否正常、再次编辑能否带出源码。下面这张表可以直接照着跑。

用例检查点失败时先看
简单分式插入后图片清晰,>// 回显页校验:公式图与源码必须成对 var imgs = Array.from(document.querySelectorAll('.content img[data-formula]')); var bad = imgs.filter(function (img) { var src = img.getAttribute('src') || ''; var latex = img.getAttribute('data-formula') || ''; var okSrc = /^https?:\/\//.test(src) || src.indexOf('/uploads/') === 0; var okLatex = false; try { okLatex = decodeURIComponent(latex).length > 0; } catch (e) { okLatex = false; } return !okSrc || !okLatex; }); console.log('formula total=', imgs.length, 'bad=', bad.length);

逻辑说明:okSrc只认绝对 URL 或/uploads/开头的路径,data:开头说明还没落盘;okLatex解码后长度大于 0,空字符串说明源码丢了;bad数量大于 0 就别上线,先查过滤器和上传接口。参数上,如果项目图片走 CDN,okSrc的正则要加上 CDN 域名;如果历史数据里有相对路径,先做一次批量修正再跑校验。

从那以后我每次接编辑器需求,都先把“公式源码 + 图片地址”双存写进验收单,再谈工具栏美化。公式插件能不能用,不看弹窗多漂亮,看的是保存、回显、再编辑这条链路有没有断。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询