给 Vue 页面加个 Markdown 编辑器:ME.js 的接入、图片粘贴与音视频
给后台加一个文章编辑框,常见的需求其实没那么复杂:能排版,能贴图,内容最好还能以 Markdown 存下来。
ME.js 做的就是这件事。它不是左边写语法、右边看预览的双栏编辑器,而是在排好版的内容上直接编辑,再把结果序列化成 Markdown。项目同时提供原生 JavaScript、Vue 3 和 React 18 三种接入文件。下面以 Vue 版为主,把接入方式和几个容易踩的点说清楚。12
先说明核对范围:本文对照了演示页和仓库提交 7f25980e99f97c746b86293f1a5bf28de159e41b,并运行了 Vue 版解析器及上传 URL 拼接的代码级测试;不是完整的浏览器兼容性测试。文中配图均为说明示意,不是产品截图。
框架示例已经备好,不用先搭一套工程
仓库的 src 目录里有三个入口:1
| 页面用什么 | 加载哪个文件 | 内容如何绑定 |
|---|---|---|
| 原生 JavaScript | me-editor.js |
ME.init() 创建实例 |
| Vue 3 | me-vue.js |
v-model |
| React 18 | me-react.js |
value 和 onChange |
每个文件已经包含编辑器核心,不需要先加载原生版,再叠加 Vue 或 React 适配层。项目也提供对应的演示页面,可以直接照着改。1

这里的"开箱即用",指的是不用为了试一个编辑框,先准备打包器和脚手架。Vue、React 本身还是要加载。当前 Vue 文件使用全局 Vue 和字符串模板,下面也按这个方式接入,不把它写成一个尚未核实的 npm 包用法。2
把下面的代码保存成 HTML,通过本地静态服务打开,即可开始接入验证。示例固定了编辑器的源码版本,避免 main 分支变化后,文章里的行为跟着变。
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ME Vue 编辑器示例</title>
</head>
<body>
<div id="app">
<me-editor
v-model="content"
:options="editorOptions"
></me-editor>
<h3>当前 Markdown</h3>
<pre>{{ content }}</pre>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/gh/juicy696/markdown-editor@7f25980e99f97c746b86293f1a5bf28de159e41b/src/me-vue.js"></script>
<script>
Vue.createApp({
data() {
return {
content: '# 开始写作\n\n这里是一段 **加粗文字**。',
editorOptions: {
title: '文章编辑器',
placeholder: '写点什么......',
uploadUrl: null,
showViewCode: true,
showCopy: true,
showAudio: true,
showVideo: true,
showStatusBar: true,
maxLength: 0
}
};
}
}).use(MEVue).mount('#app');
</script>
</body>
</html>
标题放在 options.title 里。虽然演示文档里也出现了直接传 title 的写法,但当前组件声明的 props 只有 modelValue 和 options,内部导航标题读取的是 opts.title。2
在已有 Vue 工程中接入时,还要留意它当前是全局脚本,不是标准的单文件组件示例。不要直接把上面的代码理解成 import MeEditor from '某个包' 就能用。
图片直接粘贴,后端没准备好也能先写
图片粘贴是这个编辑器很实用的一点。当前 Vue 实现会检查剪贴板里的图片项,再根据 uploadUrl 选择处理方式:2
- 没有配置上传地址:读取图片,转成 Base64 Data URL 后插入。
- 配了上传地址:先上传,再把响应里的图片 URL 插入正文。
- 上传过程抛出错误:提示上传失败,回退到 Base64。
也就是说,试用时可以先不搭图床;正式用到文章后台,再把上传接口接上。
上传接口需要做什么
前端发送的是 POST 请求,正文为 multipart/form-data,图片字段名是 file。后端返回 JSON,其中需要有 url 字段。2
json
{
"url": "https://static.example.com/uploads/example.png"
}
下面是配置形状,域名需要替换成你自己的服务,不是可以直接使用的公共接口:
js
editorOptions: {
title: '文章编辑器',
uploadUrl: 'https://api.example.com/upload'
}
建议上传地址和响应中的图片地址都使用完整的 HTTPS URL。当前实现遇到相对图片路径时,会用 new URL(uploadUrl).origin 拼接;如果 uploadUrl 也只写成 /upload,这个构造会失败,最后落到 Base64 回退。我对这段函数做了模拟响应测试,确认了这个分支。2
另外,文档列出了 pasteImage: 'auto' | 'base64' | 'upload',但当前 Vue 版的粘贴处理函数并没有读取这个选项,真正决定分支的是有没有 uploadUrl。因此,不能一边配置上传地址,一边指望 pasteImage: 'base64' 强制走内嵌。12
Base64 适合临时记录和小图,不适合在长文章里大量堆图。它把图片内容放进正文,保存、传输和后续编辑都要一起处理这段数据。当前状态栏又是按序列化后的 Markdown 字符串计数,一张 Base64 图片就可能让"字符数"明显上涨。2
如果业务要求"上传失败就不准提交",还需要自己补失败状态和提交校验,不能把默认回退当成上传成功。
仓库里带了一个上传服务
项目提供 server.py,基于 Python 标准库,可以作为本地联调的上传接收端:13
bash
python server.py
# 默认端口:1688
python server.py 9000
# 换一个端口
接口是 POST /upload,上传后的文件通过 /uploads/ 路径访问。3
这能省掉验证图片上传时临时写接口的工夫,但它不是拿来直接暴露到公网的生产图床。项目 README 也明确提醒了这一点。正式部署仍要处理鉴权、文件类型和大小校验、访问控制,以及自己的存储策略。1
音频、视频有入口,但保存后要再检查一次
工具栏提供音频和视频插入入口,填入资源 URL 后,编辑区插入浏览器原生的 <audio>、<video> 元素,并带上 controls。这两个按钮分别由 showAudio 和 showVideo 控制。2
它做的是"插入已有媒体地址",不是内置录音、视频剪辑或转码。资源是否能播,也要看地址是否可访问、媒体编码和浏览器支持情况。
序列化时,音视频会以 HTML 标签的形式留在 Markdown 字符串里,例如:2
html
<audio src="https://example.com/demo.mp3" controls></audio>
<video src="https://example.com/demo.mp4" controls></video>

这里必须提一个当前版本的限制:能插入,不代表保存后再打开就能原样恢复。
我调用了当前 Vue 版的 MEVue.MdParser.parse(),把上面这样的音视频标签重新传进去,得到的是被转义的 HTML 文本,而不是播放器。这是解析器层面的测试结果,不是对所有浏览器播放效果的判断。2
所以,准备拿它做带媒体的内容后台时,至少要走完一次"插入 → 保存 → 重新打开"。如果要补 HTML 回读,需要同时考虑标签、属性和 URL 的白名单过滤,不建议直接把任意用户内容塞进 innerHTML。
几个写起来顺手的小功能
常规排版之外,当前 Vue 实现里还有这些细节:2
表格可以填行列数插入。 不必先手写一排竖线,再去调整表头。
支持任务列表。 勾选状态可以序列化为 Markdown 的 - [ ] 和 - [x]。
图片可以点选尺寸。 选项包括原始尺寸、100%、75%、50% 和 25%。这里调整的是页面显示尺寸,不是压缩原始图片文件。带尺寸的图片会序列化成 HTML <img>,同样需要注意前面说的 HTML 回读限制。
有查看 Markdown 和复制入口。 写的时候用可视化界面,需要保存或搬运内容时,可以查看底层文本。
有清除格式按钮。 选中内容后,可以去掉格式,不用一项项取消。
工具栏会处理横向空间不足。 放不下的按钮会收进更多菜单。这是源码中实现的溢出处理,不等于已经验证所有手机浏览器的编辑体验。
这些功能放在一起,更适合简单文章、项目说明、内部文档之类的编辑场景。至于协同编辑、修订记录、公式、复杂 Markdown 方言,本文没有验证,也不把它们列成卖点。
配置不用多,但这几个含义要分清
Vue 版常用配置可以先记住这些:2
| 配置 | 用途 | 注意点 |
|---|---|---|
title |
编辑器顶部标题 | 放进 options |
placeholder |
空内容提示 | 不会成为正文 |
uploadUrl |
粘贴图片上传地址 | 不配时走 Base64 |
showViewCode / showCopy |
源码查看、复制按钮 | 默认开启 |
showAudio / showVideo |
音视频按钮 | 不需要时关闭 |
showStatusBar |
底部字符统计 | 默认开启 |
maxLength |
超长变红提醒 | 不是输入拦截,也不是提交校验 |
iconsMode / iconsLocalPath |
图标加载方式 | 本地图标资源要自己准备 |
maxLength 很容易看名字就误会。当前逻辑是在计数超过阈值时改变状态栏样式,没有截断输入。业务上需要限制正文长度,仍然要在提交时校验,而且要先决定是统计可见文字,还是包含 Markdown 标记的字符串。2
图标默认通过 Google Fonts 加载 Material Icons。内网或网络受限的环境可以换成本地 CSS:2
js
editorOptions: {
iconsMode: 'local',
iconsLocalPath: '/assets/material-icons.css'
}
这个配置只是切换资源路径,不会自动下载字体文件。对应 CSS 及其引用的字体,需要自行部署。
还有一个接入细节:当前 options 在组件初始化时合并,没有单独监听后续变化。需要切换整套配置时,要自己处理组件重建,或修改组件以支持配置更新。2
Vue 版先用 v-model,不要直接照搬原生版 API
对于读取和替换内容,Vue 版最直接的入口就是绑定的数据:
js
// 读取当前已同步的 Markdown
const markdown = this.content;
// 从接口拿到文章后,替换绑定值
this.content = '# 新文章\n\n正文';
普通输入目前有约 400ms 的防抖同步。接正式保存按钮时,要测试"刚打完字马上保存"的情况,避免保存到尚未更新的值。2
演示文档还列了通过组件 ref 调用 setMarkdown()、getMarkdown()、importFromElement()、download() 的例子。但当前 Vue 组件的方法列表中没有这些方法;原生 JavaScript 版才有对应实现。这里以源码为准,不把文档里的写法直接放进可用示例。24
如果需要程序化导出,原生版提供 Markdown、HTML 和混合格式相关接口;如果用 Vue 版,建议先围绕 v-model 接自己的保存流程,再验证需要的导出格式。4
什么情况下值得用
如果你需要的是一个能很快放进页面的编辑框,要求基本排版、图片粘贴和 Markdown 存储,ME.js 的接入方式值得一看。三个框架入口、已有的演示模板和简单的图片上传约定,省掉了不少起步工作。
但"能开箱跑起来"和"可以不做验证就上线"是两回事。音视频与带样式内容的回读、上传失败后的处理、最后一次输入的保存时机,这几项都应该放进接入检查表。
我的建议是先用一篇真实文章试:放两张图、一张表格,再加一段音频,保存后重新打开。这个流程通过了,再去补上传鉴权和业务校验,比单看工具栏上有多少按钮更有用。
项目与核对来源
- 在线体验:juicy696.github.io/markdown-ed...
- 项目仓库:github.com/juicy696/ma...
- 1 README:github.com/juicy696/ma...
- 2 Vue 版实现:github.com/juicy696/ma...
- 3 上传接收端:github.com/juicy696/ma...
- 4 原生版实现:github.com/juicy696/ma...