uni-app多端可用的Markdown编辑组件,带实时预览和代码高亮

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:直接放进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.jshighlight.js 做代码高亮——这些在 H5 上跑得飞起,但在小程序里直接报错:document is not definedMutationObserver is not supportedPrism.highlightElement is not a function。我最早试过强行引入 mavon-editor,结果在微信开发者工具里编译直接失败;换用 vue-markdownhighlight.js,H5 正常,小程序里代码块全变纯文本,连 <pre><code> 标签都渲染不出来。根本原因在于:小程序没有 DOM 全局对象,不支持原生 MutationObserver,且 highlight.js 的浏览器版依赖 windowdocument 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-apprequire 加载)。

这个判断不是拍脑袋来的。我做过实测:在微信小程序中,textareaconfirm-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 机制在多端环境下存在两个致命问题:

  1. 代码块语言标识丢失:原版 markedjs 的处理是生成 <pre><code class="language-js">,但小程序 rich-text 会过滤掉 class 属性,导致高亮脚本找不到目标元素;
  2. 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-textline-height 默认是 1.5,而 App WebView 里可能是 1.2;更麻烦的是,小程序不支持 :not() 伪类,highlight.js.hljs-comment 在小程序里根本不会生效。

ly-markdownmarkdown.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)或上下布局(小程序),左侧是编辑区(textareadiv[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.vueprops 定义里,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),需要手动改两处:

  1. script 标签里加 setup() { const props = defineProps(['value']); const emit = defineEmits(['input']); return { props, emit }; }
  2. 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”,而是“先占位、再替换”。当你点击工具栏图片按钮,组件会:

  1. 调用 uni.chooseImage() 选择图片;
  2. 生成一个临时占位符:![上传中...](data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)
  3. 把这个占位符插入到光标位置;
  4. 在后台发起 uni.uploadFile() 上传;
  5. 上传成功后,用返回的真实 URL 替换占位符中的 data:image/gif... 部分。

这个流程的关键在于第 2 步:用 base64 的 1x1 透明 gif 作为占位图。为什么不用 ![loading](/static/loading.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...);
  • 把整个占位符字符串替换成 ![描述](真实URL)
  • 触发 @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.jsApp.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> 但没 classmarked 解析未加 data-languagely-markdown.vuecomputed.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.vuemounted() 钩子里加一段降级逻辑:

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'),直接插入纯文本。

但这里有三个限制必须知道:

  1. 微信小程序不支持 event.clipboardData:小程序里 paste 事件的 event 对象没有 clipboardData 字段。解决方案是,在小程序端改用 uni.getClipboardData() API,在 @focus 时主动获取剪贴板内容;
  2. 跨域图片会被过滤:从网页复制带图片的内容,<img src="https://xxx.com/1.jpg"> 在粘贴时会被 markedsanitize 过滤掉。ly-markdown 的对策是,在粘贴后扫描 HTML,把 <img> 标签替换成 ![alt](src) 语法,再交给 marked 解析;
  3. 表格粘贴不支持:目前 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 执行环境差异导致的。排查顺序如下:

  1. 检查 marked 是否被压缩混淆:App 打包时可能把 marked 的函数名压缩成 a()b(),导致 renderer.code() 无法调用。解决方案:在 vue.config.js 里加 configureWebpack: { optimization: { minimize: false } } 关闭压缩,或把 marked 加入 externals
  2. 确认 highlight.js 语言包路径:App 里 require('./languages/js.js') 的相对路径可能解析错误。解决方案:改用绝对路径 require('@/components/ly-markdown/languages/js.js')
  3. 检查 v-html 的 XSS 过滤:App WebView 默认开启 domStorageEnabled: true,但某些版本会拦截 v-html 渲染。解决方案:在 App.vueonLaunch 里加 uni.setStorageSync('disableXSSFilter', true)(需原生层配合);
  4. 字体加载失败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.vueconsole.log 全部删掉——不是怕影响性能,而是避免在用户手机上弹出调试信息。这套组件不是玩具,它是我在三个上线项目里反复打磨出来的交付物,每一行代码背后都有一个线上 bug 的教训。你现在拿去用,省下的不是几小时开发时间,而是少踩几十个坑的试错成本。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:直接放进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,无强制第三方依赖。适合做文章发布、笔记记录、后台富文本录入等场景,嵌入现有页面只需一行标签引用。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值