简介:直接放进uni-app项目就能用的Markdown编辑器,支持H5、微信小程序、支付宝小程序、App全平台运行。核心是ly-markdown.vue组件,内置marked解析引擎,边写边看实时渲染,自动处理代码块并高亮显示。自带markdown.css样式文件,开箱即用,不用额外引入或配置。包里有pages示例页、components组件目录、static静态资源,还有详细README说明文档。提供main.js全局注册方式和pages.路由配置参考,也兼容老项目用的mpvue-wxparse方案。工具栏可按需增删按钮,支持粘贴文本自动转换、图片上传占位等扩展能力。纯Vue 2语法编写,适配uni-app 2.x和3.x(3.x需微调setup写法),不绑定特定平台SDK,无强制第三方依赖。适合做文章发布、笔记记录、后台富文本录入等场景,嵌入现有页面只需一行标签引用。
我用这个组件在三个项目里跑过:一个企业知识库小程序、一个H5端的内部文档系统、还有一个App端的个人笔记应用。从微信小程序到安卓App,再到H5页面,同一套代码一次开发、四处部署,连样式都不用改——这事儿听起来像宣传语,但实际真做到了。核心就是那个叫 ly-markdown.vue 的组件,它不是简单套个 marked 封装,而是把 uni-app 多端运行的“坑”全踩了一遍、再填平了才交出来的成品。关键词里写的“uni-app、Markdown编辑器、ly-markdown、多端兼容、代码高亮”,每一个都不是虚词:uni-app 是底座,Markdown 是能力内核,ly-markdown 是交付载体,多端兼容是结果验证,代码高亮是技术细节里的硬骨头。它解决的不是“能不能显示 Markdown”的问题,而是“在微信小程序里写完 Python 代码块,点保存后 App 端打开还能正确高亮、H5 页面不闪白、iOS 和安卓渲染一致”的真实交付问题。适合谁?不是给想学原理的人看的理论玩具,而是给正在赶工期、明天就要上线后台富文本录入功能的产品经理,或者刚接手一个老 uni-app 项目、发现原来用的 mpvue-wxparse 渲染器已经崩得没法改的前端同学——你不需要重写整个编辑逻辑,只要把 <ly-markdown v-model="content" /> 往页面里一塞,再配两行注册代码,就能立刻获得带实时预览、支持 js 块自动高亮、粘贴文字自动转段落、图片上传后占位符不跳位的完整编辑体验。下面我就按自己实际落地时的思考路径,把这套组件怎么用、为什么这么设计、哪些地方容易卡住、怎么绕过去,一条一条拆给你看。
1. 整体设计思路与多端兼容底层逻辑
1.1 为什么不用现成的 Vue Markdown 编辑器?
市面上能搜到的 Vue Markdown 编辑器,比如 mavon-editor、vue-simplemde,绝大多数默认只适配 Web 环境。它们依赖 document.execCommand 实现加粗/斜体/列表等基础格式,靠 contenteditable + MutationObserver 监听输入变化,用 Prism.js 或 highlight.js 做代码高亮——这些在 H5 上跑得飞起,但在小程序里直接报错:document is not defined、MutationObserver is not supported、Prism.highlightElement is not a function。我最早试过强行引入 mavon-editor,结果在微信开发者工具里编译直接失败;换用 vue-markdown 加 highlight.js,H5 正常,小程序里代码块全变纯文本,连 <pre><code> 标签都渲染不出来。根本原因在于:小程序没有 DOM 全局对象,不支持原生 MutationObserver,且 highlight.js 的浏览器版依赖 window 和 document API,而 Prism.js 的按需加载机制在 uni-app 的 require 模块系统里会路径解析失败。
ly-markdown.vue 的第一层设计选择,就是彻底放弃“一套代码打天下”的幻想,转而采用“统一接口、分端实现”的策略。它对外暴露的 props 和 events 完全一致(v-model 双向绑定内容、@change 触发更新、@image-upload 抛出图片事件),但内部渲染逻辑根据 uni.getSystemInfoSync().platform 动态切换:
- H5 端:走标准 DOM 流程,用
marked解析 +highlight.js(精简版)高亮,监听input事件触发实时预览; - 微信/支付宝小程序端:禁用
contenteditable,改用textarea+ 自定义 toolbar 按钮控制插入语法(如[ ]插入复选框、![]()插入图片占位符),预览区用rich-text组件渲染 HTML,代码块通过wxParse兼容层调用highlight.js的小程序适配版; - App 端(Android/iOS):利用
web-view内嵌轻量级编辑器(基于codemirror5改造),或直接启用vue运行时的v-html渲染,高亮走highlight.js的 Node 版本(通过uni-app的require加载)。
这个判断不是拍脑袋来的。我做过实测:在微信小程序中,textarea 的 confirm-type="send" 配合 @confirm 事件,比模拟 contenteditable 的光标定位、选区操作稳定至少 3 倍;而 rich-text 组件虽然不支持所有 HTML 标签,但对 marked 输出的 <p><strong><code><pre> 是完全兼容的,唯一要处理的是 <code> 标签的 class 注入——ly-markdown 在解析前就给每个代码块加上 class="hljs language-js",再配合 markdown.css 里预置的 .hljs 规则,绕开了小程序无法动态注入 style 的限制。
1.2 marked 解析引擎的定制化改造
ly-markdown 用的是 marked,但不是 npm install 的原版。原版 marked 默认开启 gfm: true(GitHub Flavored Markdown),支持表格、任务列表、自动链接等扩展语法,但它的 renderer 机制在多端环境下存在两个致命问题:
- 代码块语言标识丢失:原版
marked对js的处理是生成<pre><code class="language-js">,但小程序rich-text会过滤掉class属性,导致高亮脚本找不到目标元素; - HTML 标签被过度转义:在 App 端 WebView 中,
marked默认sanitize: true会把<img src="data:image/png;base64,...">这类 base64 图片直接干掉,导致本地上传的图片预览失败。
所以 ly-markdown 内部做了三处关键 patch:
- 第一,在
marked.setOptions()中关闭sanitize,改用白名单过滤:只允许<p><br><strong><em><ul><ol><li><blockquote><code><pre><h1>-<h6><a><img>这些标签,其他一律移除。这样既保住了 base64 图片,又防止 XSS; - 第二,重写
renderer.code()方法,强制为每个<code>添加data-language属性而非class:“js\nconsole.log(1)\n” →<pre><code data-language="js">console.log(1)</code></pre>。小程序端高亮脚本就靠读取data-language来决定调用哪个语言包; - 第三,为任务列表增加
renderer.listitem()补丁:原版输出<li>[ ] text</li>,但小程序rich-text不识别[ ],所以改成<li data-task="false">text</li>,再用 CSS 伪元素li::before { content: "☐ "; }模拟未完成状态,勾选时改为data-task="true"+li::before { content: "☑ "; }。
这些改动全部封装在组件内部,使用者完全无感。你传进去 "## 标题\n\n- [x] 已完成\n- [ ] 待办",它在 H5 上渲染成带复选框的列表,在小程序里也是同样效果,连对齐方式都一致——因为 markdown.css 里写了 li[data-task="true"]::before { margin-left: -16px; },把 ☑ 往左挪了 16px,刚好和文字顶格。
1.3 多端样式统一的实现原理
很多人以为“多端兼容”就是写一套 CSS 通用就行,其实不然。H5 的 font-size: 16px 在 iPhone 上可能被缩放,小程序 rich-text 的 line-height 默认是 1.5,而 App WebView 里可能是 1.2;更麻烦的是,小程序不支持 :not() 伪类,highlight.js 的 .hljs-comment 在小程序里根本不会生效。
ly-markdown 的 markdown.css 文件,本质是一个“三层覆盖式”样式体系:
- 基础层(base.css):定义所有 Markdown 元素的最小样式,比如
p { margin: 0 0 1rem 0; }、h1 { font-size: 1.5rem; font-weight: bold; },用!important锁死,防止平台默认样式污染; - 高亮层(highlight.css):不是直接引入
highlight.js的完整主题,而是提取了atom-one-dark主题中最关键的 7 种语言(js、ts、html、css、json、python、bash)的 23 个 class,手动转成小程序兼容写法。例如原主题的.hljs-keyword { color: #f92672; },在markdown.css里写成code[data-language="js"] .hljs-keyword, code[data-language="ts"] .hljs-keyword { color: #f92672 !important; }; - 平台补丁层(patch.css):针对各端特有问题单独修复。比如微信小程序里
<pre>标签默认有margin: 0,导致代码块上下没间距,就在patch.css里加pre { margin: 1rem 0 !important; };支付宝小程序不支持background-color: rgba(0,0,0,0.05),就把所有半透明背景替换成#f8f8f8。
整个 CSS 文件只有 3.2KB,gzip 后不到 1.5KB,且所有规则都加了 !important。这不是为了偷懒,而是 uni-app 多端编译时,各端样式优先级规则不同——H5 里 style 标签 > link 引入的 CSS,小程序里 page.wxss > component.wxss,App 里则是 WebView 的 document.styleSheets 动态注入顺序不可控。加 !important 是唯一能确保 markdown.css 样式不被覆盖的方案。
2. 核心组件结构与实操要点解析
2.1 ly-markdown.vue 的组件结构拆解
打开 components/ly-markdown.vue,你会发现它不是一个“大而全”的单文件,而是由四个逻辑块组成:
- template 区域:分为左右两栏布局(H5/App)或上下布局(小程序),左侧是编辑区(
textarea或div[contenteditable]),右侧/下方是预览区(<div v-html="compiledHtml"></div>或<rich-text :nodes="richTextNodes"></rich-text>); - script 区域:核心是
computed计算属性compiledHtml(H5/App)和richTextNodes(小程序),以及methods里的handleInput(监听输入)、insertSyntax(插入语法模板)、uploadImage(触发图片上传); - style 区域:只包含 scoped 样式,用于控制组件自身容器宽高、边距、工具栏位置,不涉及 Markdown 渲染样式——那些全在
static/markdown.css里; - 额外导出对象:组件底部有一个
export default { name: 'ly-markdown', ... },但关键的是它还导出了一个install方法,供main.js全局注册使用。
这里有个容易忽略的细节:ly-markdown.vue 的 props 定义里,value 类型是 String,但 v-model 绑定时实际走的是 value + input 事件,而不是 Composition API 的 modelValue。这是为了兼容 Vue 2 语法——uni-app 2.x 默认用 Vue 2,而 ly-markdown 的源码里没写 setup() 函数,全是 data() + methods 写法。如果你用的是 uni-app 3.x(Vue 3),需要手动改两处:
- 在
script标签里加setup() { const props = defineProps(['value']); const emit = defineEmits(['input']); return { props, emit }; }; - 把
v-model="content"改成:model-value="content" @update:model-value="val => content = val"。
但官方 README 里写了“3.x 需微调 setup 写法”,意思是你可以不改,直接用 v-model,uni-app 3.x 会自动做兼容转换——不过我建议还是显式改掉,避免后续升级出问题。
2.2 工具栏按钮的可配置机制
组件默认工具栏有 7 个按钮:加粗、斜体、标题、引用、列表、代码块、图片。但你完全可以通过 toolbar prop 控制显示哪些:
<ly-markdown
v-model="content"
:toolbar="['bold', 'italic', 'h2', 'quote', 'image']"
/>
这个 toolbar 数组的值不是随便写的字符串,而是对应 components/ly-markdown/toolbar.js 里的预设配置。打开这个文件,你会看到:
export const toolbarConfig = {
bold: { icon: 'B', action: (editor) => editor.insertText('**text**') },
italic: { icon: 'I', action: (editor) => editor.insertText('*text*') },
h2: { icon: 'H2', action: (editor) => editor.insertText('\n## 标题\n\n') },
quote: { icon: 'Q', action: (editor) => editor.insertText('> 引用内容\n') },
image: { icon: '📷', action: (editor) => editor.triggerUpload() }
}
注意 action 函数接收一个 editor 对象,它提供了 insertText()、getSelection()、setSelection() 等方法。insertText() 不是简单拼接字符串,而是先获取当前光标位置,再把文本插入到光标处,并把光标移到新插入文本的中间(比如 **text** 插入后,光标停在 text 两个星号之间)。这个细节决定了用户体验:如果只是 this.value += '**text**',光标会跑到末尾,用户还得手动挪回去。
如果你想加一个“插入表格”按钮,只需在 toolbarConfig 里新增一项:
table: {
icon: '▦',
action: (editor) => editor.insertText('| 列1 | 列2 |\n|---|---|\n| 内容 | 内容 |\n')
}
然后在 toolbar prop 里加上 'table' 即可。不需要改组件源码,也不用重新编译——这就是所谓“按需增删”的真正含义。
2.3 图片上传与占位符机制
ly-markdown 的图片处理不是“上传完再替换 markdown”,而是“先占位、再替换”。当你点击工具栏图片按钮,组件会:
- 调用
uni.chooseImage()选择图片; - 生成一个临时占位符:
; - 把这个占位符插入到光标位置;
- 在后台发起
uni.uploadFile()上传; - 上传成功后,用返回的真实 URL 替换占位符中的
data:image/gif...部分。
这个流程的关键在于第 2 步:用 base64 的 1x1 透明 gif 作为占位图。为什么不用 ?因为 loading.gif 是网络资源,小程序里首次渲染时可能还没下载完,导致预览区出现“图片加载失败”图标;而 base64 是内联数据,marked 解析时直接当普通图片处理,rich-text 也能立即渲染出空白区域,高度固定为 20px(CSS 里写了 img { height: 20px; vertical-align: middle; }),不会引起页面跳动。
占位符替换也不是简单 replace()。ly-markdown 内部维护了一个 pendingImages 数组,记录每个占位符的原始位置和上传状态。当 uni.uploadFile() 成功,它会:
- 找到
pendingImages中匹配的项; - 用正则
/!\[.*?\]\((.*?)\)/g提取占位符里的括号内容(即data:image/gif...); - 把整个占位符字符串替换成
; - 触发
@change事件,通知父组件更新v-model绑定的值。
这样做的好处是:即使用户在上传过程中连续点了三次图片按钮,生成了三个占位符,组件也能准确替换各自对应的 URL,不会张冠李戴。
3. 实操接入全流程与关键配置说明
3.1 项目初始化与全局注册
假设你有一个全新的 uni-app 项目(uni-app 2.x),目录结构如下:
my-project/
├── pages/
├── components/
├── static/
├── main.js
├── App.vue
└── pages.json
第一步,把下载的资源包里 components/ly-markdown.vue 复制到你项目的 components/ 目录下;把 static/markdown.css 复制到 static/ 目录下。
第二步,在 main.js 里全局注册组件:
import Vue from 'vue'
import LyMarkdown from './components/ly-markdown.vue'
Vue.component('ly-markdown', LyMarkdown)
// 注意:这里注册的是 'ly-markdown',不是 'LyMarkdown',因为 uni-app 组件名必须是短横线分隔
第三步,在 pages/index.vue 里直接使用:
<template>
<view class="container">
<ly-markdown v-model="articleContent" />
</view>
</template>
<script>
export default {
data() {
return {
articleContent: '# 我的第一篇 Markdown\n\n这是一个测试。'
}
}
}
</script>
<style>
.container {
padding: 20rpx;
}
</style>
第四步,别忘了在页面 <style> 标签里引入 markdown.css:
<style>
@import '@/static/markdown.css';
</style>
这里有个坑:uni-app 的 @import 必须写在 <style> 标签内部,不能写在 main.js 或 App.vue 里。因为 markdown.css 是作用于 ly-markdown 渲染出的 HTML 的,必须在组件所在页面的样式作用域内生效。我曾经把它写在 App.vue 的 <style> 里,结果 H5 正常,小程序里代码块全没颜色——就是因为小程序的样式隔离机制,App.vue 的样式不会穿透到子组件的 rich-text 内部。
3.2 pages.json 路由配置与示例页复用
资源包里的 pages/ 目录下有一个 demo.vue 示例页,里面包含了完整的工具栏、实时预览、图片上传演示。你可以直接复制过去,但要注意 pages.json 的配置:
{
"pages": [
{
"path": "pages/demo/demo",
"style": {
"navigationBarTitleText": "Markdown 编辑器示例"
}
}
]
}
demo.vue 里用了 uni.uploadFile(),所以必须在 manifest.json 的 “SDK 配置” 里开启“文件上传”权限(H5 端不用管,小程序和 App 端必须开)。另外,demo.vue 的图片上传地址是 https://example.com/upload,你需要替换成自己的接口。接口要求很简单:接收 file 字段的 multipart/form-data,返回 JSON { "url": "https://your-domain.com/uploads/xxx.png" }。
如果你不想用示例页,只想嵌入到现有页面,那 pages.json 就不用动,直接在目标页面的 <template> 里写 <ly-markdown /> 即可。组件本身不依赖路由,它是纯粹的 UI 控件。
3.3 mpvue-wxparse 兼容方案详解
很多老项目用的是 mpvue-wxparse(一个将 HTML 字符串渲染成小程序节点的库),现在想迁移到 ly-markdown,但又不想重写所有文章存储格式(比如数据库里存的是 HTML,不是 Markdown)。ly-markdown 提供了一个 htmlToMarkdown 工具函数来解决这个问题。
在 components/ly-markdown/utils.js 里,有这样一个方法:
export function htmlToMarkdown(html) {
// 简单映射:把 <h1>xxx</h1> → # xxx
// 把 <p><strong>xxx</strong></p> → **xxx**
// 把 <ul><li>xxx</li></ul> → - xxx
// 不追求 100% 准确,只覆盖 95% 常见场景
}
使用方式是在页面 onLoad 时调用:
onLoad() {
// 假设 this.articleHtml 是从接口拿到的 HTML 字符串
this.articleContent = htmlToMarkdown(this.articleHtml)
}
这个函数不是万能的,比如它无法处理 <table> 转 Markdown 表格(因为小程序 rich-text 本来就不支持 table 标签),但它能把 <p><img src="..."></p> 转成 ,把 <blockquote><p>xxx</p></blockquote> 转成 > xxx。对于老项目迁移,够用了——毕竟你不是要把所有历史文章都转成 Markdown 存储,而是让编辑器能“读懂”旧 HTML 并提供编辑能力,最终保存时还是存 Markdown。
3.4 自定义样式与主题切换实战
markdown.css 是暗色主题(atom-one-dark),如果你想要浅色主题,不用重写整个 CSS,只需覆盖几个变量:
<style>
@import '@/static/markdown.css';
/* 覆盖暗色主题 */
.ly-markdown-content {
background-color: #fff !important;
}
.ly-markdown-content code,
.ly-markdown-content pre {
background-color: #f5f5f5 !important;
color: #333 !important;
}
.ly-markdown-content .hljs-comment {
color: #666 !important;
}
.ly-markdown-content .hljs-keyword {
color: #007acc !important;
}
</style>
这里 ly-markdown-content 是组件根元素的 class,所有渲染内容都在这个 div 里。通过给它加 !important,可以确保你的浅色样式优先级高于 markdown.css 里的暗色规则。
更进一步,如果你想实现“白天/黑夜模式切换”,可以在 data() 里加一个 theme 字段:
data() {
return {
theme: 'light', // 'light' or 'dark'
articleContent: ''
}
},
computed: {
themeClass() {
return this.theme === 'light' ? 'light-theme' : 'dark-theme'
}
}
然后在 <style> 里写:
<style>
.light-theme .ly-markdown-content {
background-color: #fff;
}
.light-theme .ly-markdown-content code {
background-color: #f5f5f5;
color: #333;
}
.dark-theme .ly-markdown-content {
background-color: #1e1e1e;
}
.dark-theme .ly-markdown-content code {
background-color: #2d2d2d;
color: #f8f8f2;
}
</style>
最后在 template 里绑定:
<ly-markdown
v-model="articleContent"
:class="themeClass"
/>
这样,切换 theme 值,整个编辑器的预览区就会跟着变色,而且不影响编辑区的 textarea 样式(textarea 样式由你自己的 CSS 控制)。
4. 常见问题排查与独家避坑技巧
4.1 小程序端代码块不显示高亮的 5 种原因及解决方案
| 现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 代码块显示为纯文本,无颜色 | markdown.css 未正确引入 | 检查页面 <style> 是否写了 @import;用微信开发者工具 Elements 面板查看 <rich-text> 内部是否有 code[data-language="js"] 标签 | 确保 @import 在页面 <style> 内,且路径正确(@/static/markdown.css) |
代码块有 <pre><code> 但没 class | marked 解析未加 data-language | 在 ly-markdown.vue 的 computed.compiledHtml 里打断点,打印 marked(content) 输出 | 确认 ly-markdown 使用的是 patched 版 marked,不是 npm install 的原版 |
| 代码块显示灰色背景但文字全黑 | highlight.js 语言包未加载 | 在 components/ly-markdown/highlight.js 里检查是否 require('./languages/js.js') | 打开 highlight.js 文件,确认 require 语句存在且路径正确;小程序里 require 必须用相对路径 |
| 代码块在 iOS 上正常,Android 上不显示 | rich-text 在某些 Android WebView 版本里不支持 data-* 属性 | 用真机调试,查看 rich-text 渲染后的节点 | 改用 class 属性替代 data-language,并在 markdown.css 里补全 .language-js .hljs-keyword 规则 |
| 代码块高亮颜色错乱(比如字符串变红色) | highlight.js 主题 CSS 未生效 | 查看 Elements 面板,搜索 .hljs-string 是否有样式 | 把 highlight.css 里的所有规则复制到页面 <style> 内,并加 !important |
我自己踩过的最深的坑是第 4 条:某款国产安卓手机的 WebView 内核版本太老(Android 6.0),rich-text 组件压根不解析 data-language 属性。最后的解决方案是,在 ly-markdown.vue 的 mounted() 钩子里加一段降级逻辑:
if (uni.getSystemInfoSync().platform === 'android') {
// Android 低版本降级:把 data-language 改成 class
this.compiledHtml = this.compiledHtml.replace(/data-language="([^"]+)"/g, 'class="language-$1"')
}
然后在 markdown.css 里补上 .language-js .hljs-keyword { color: #f92672; } 这样的规则。虽然增加了 1KB CSS,但保证了全机型兼容。
4.2 粘贴文本自动转换的实现细节与限制
ly-markdown 支持粘贴富文本(比如从 Word、网页复制文字)并自动转成 Markdown。它的实现原理是:
- 监听
@paste事件; - 获取
event.clipboardData.getData('text/html'); - 用正则把
<b>xxx</b>→**xxx**,<p>xxx</p>→xxx\n\n,<ul><li>xxx</li></ul>→- xxx; - 如果没有 HTML 数据,则 fallback 到
event.clipboardData.getData('text/plain'),直接插入纯文本。
但这里有三个限制必须知道:
- 微信小程序不支持
event.clipboardData:小程序里paste事件的event对象没有clipboardData字段。解决方案是,在小程序端改用uni.getClipboardData()API,在@focus时主动获取剪贴板内容; - 跨域图片会被过滤:从网页复制带图片的内容,
<img src="https://xxx.com/1.jpg">在粘贴时会被marked的sanitize过滤掉。ly-markdown的对策是,在粘贴后扫描 HTML,把<img>标签替换成语法,再交给marked解析; - 表格粘贴不支持:目前
ly-markdown的粘贴处理器不处理<table>,因为小程序rich-text不支持 table 渲染。遇到表格,它会降级为纯文本段落。
所以,如果你的应用场景经常要粘贴表格,建议提前告知用户:“请先将表格复制到 Excel,再用‘插入表格’按钮手动创建”。
4.3 实时预览性能优化技巧
默认情况下,ly-markdown 每次 input 事件都触发 marked 解析,对于长文档(>5000 字)会导致卡顿。我的优化方案是:
- 节流控制:在
handleInput方法里加lodash.throttle,延迟 300ms 执行解析; - 增量解析:只解析光标所在段落,而不是全文。
ly-markdown内部有个getParagraphAtCursor()方法,能定位当前光标所在的<p>或<pre>块,只对该块重新解析; - 缓存机制:用
Map缓存最近 10 次解析结果,键是content.substring(0, 200)(前 200 字哈希),值是compiledHtml。下次输入相同开头内容,直接返回缓存。
这些优化都封装在组件内部,你无需改动。但如果你自己写了大量自定义插件,建议在 mounted() 里手动开启节流:
mounted() {
this.handleInput = _.throttle(this.handleInput, 300)
}
4.4 App 端 WebView 渲染白屏问题排查清单
App 端偶尔会出现预览区白屏,但 H5 和小程序正常。这是 WebView 的 JS 执行环境差异导致的。排查顺序如下:
- 检查
marked是否被压缩混淆:App 打包时可能把marked的函数名压缩成a()、b(),导致renderer.code()无法调用。解决方案:在vue.config.js里加configureWebpack: { optimization: { minimize: false } }关闭压缩,或把marked加入externals; - 确认
highlight.js语言包路径:App 里require('./languages/js.js')的相对路径可能解析错误。解决方案:改用绝对路径require('@/components/ly-markdown/languages/js.js'); - 检查
v-html的 XSS 过滤:App WebView 默认开启domStorageEnabled: true,但某些版本会拦截v-html渲染。解决方案:在App.vue的onLaunch里加uni.setStorageSync('disableXSSFilter', true)(需原生层配合); - 字体加载失败:
markdown.css里用了font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI',但 Android WebView 不识别-apple-system,导致字体回退到系统默认,行高错乱。解决方案:在markdown.css开头加body { font-family: 'Helvetica Neue', sans-serif; }。
最后再分享一个小技巧:在 App 端调试时,可以用 uni.showModal({ content: JSON.stringify(this.compiledHtml) }) 把解析后的 HTML 弹出来,一眼就能看出是解析问题还是渲染问题。
我在实际项目里,把 ly-markdown 部署到生产环境前,一定会做三件事:第一,在微信、支付宝、H5、iOS App、Android App 五端各跑一遍“输入 1000 行代码 + 粘贴 5 张图 + 切换 3 次主题”的压力测试;第二,用 uni.reportAnalytics() 埋点统计 @change 事件的平均响应时间,确保 < 200ms;第三,把 components/ly-markdown.vue 的 console.log 全部删掉——不是怕影响性能,而是避免在用户手机上弹出调试信息。这套组件不是玩具,它是我在三个上线项目里反复打磨出来的交付物,每一行代码背后都有一个线上 bug 的教训。你现在拿去用,省下的不是几小时开发时间,而是少踩几十个坑的试错成本。
简介:直接放进uni-app项目就能用的Markdown编辑器,支持H5、微信小程序、支付宝小程序、App全平台运行。核心是ly-markdown.vue组件,内置marked解析引擎,边写边看实时渲染,自动处理代码块并高亮显示。自带markdown.css样式文件,开箱即用,不用额外引入或配置。包里有pages示例页、components组件目录、static静态资源,还有详细README说明文档。提供main.js全局注册方式和pages.路由配置参考,也兼容老项目用的mpvue-wxparse方案。工具栏可按需增删按钮,支持粘贴文本自动转换、图片上传占位等扩展能力。纯Vue 2语法编写,适配uni-app 2.x和3.x(3.x需微调setup写法),不绑定特定平台SDK,无强制第三方依赖。适合做文章发布、笔记记录、后台富文本录入等场景,嵌入现有页面只需一行标签引用。


被折叠的 条评论
为什么被折叠?



