Skip to content

Markdown 编辑器

带工具栏、分栏预览与表情面板的编辑器。

渲染部分直接复用 SrMarkdown,不是另写一套。 因此编辑时看到的预览与最终渲染结果完全一致——不会出现 「编辑器里好好的,上线后样式变了」。

基础用法

v-model 绑定 Markdown 源文本:

编辑器验证

左写右看,改动实时同步。

支持 粗体斜体行内代码,以及列表:

  • 第一项
  • 第二项

引用块也可以。

44
vue
<script setup>
import { ref } from 'vue'

const draft = ref('# 标题\n\n正文内容。')
</script>

<template>
  <SrMarkdownEditor v-model="draft" />
</template>

三种视图

view 控制初始视图,默认 split(左编辑、右预览)。

44

view="edit":只显示编辑区

编辑器验证

左写右看,改动实时同步。

支持 粗体斜体行内代码,以及列表:

  • 第一项
  • 第二项

引用块也可以。

44

view="preview":只显示预览

vue
<SrMarkdownEditor v-model="draft" view="edit" />
<SrMarkdownEditor v-model="draft" view="split" />
<SrMarkdownEditor v-model="draft" view="preview" />

工具栏上也有视图切换按钮,用户可自行切换。

工具栏与字数统计

两者默认都开,可以单独关掉:

编辑器验证

左写右看,改动实时同步。

支持 粗体斜体行内代码,以及列表:

  • 第一项
  • 第二项

引用块也可以。

44

toolbar="false":只留一个纯文本框

编辑器验证

左写右看,改动实时同步。

支持 粗体斜体行内代码,以及列表:

  • 第一项
  • 第二项

引用块也可以。

show-count="false":不显示右下角字数

vue
<SrMarkdownEditor v-model="draft" :toolbar="false" />
<SrMarkdownEditor v-model="draft" :show-count="false" />

字数是怎么数的

字数由 countText 统计,它会先剔除语法符号再计数: #**、列表符号、代码块都不会算进去。

图片整段不计(它是资源不是文字),链接只算可读的那部分文字。

不这么做的话,用户看到的数字会比自己数出来的大——而人对字数的直觉判断 是「我能读到的字」,不是「源文本的字符数」。

表情

不传 emoji-packages 时用内置的字符表情包。传了就用你自己的:

vue
<SrMarkdownEditor v-model="draft" :emoji-packages="myPackages" />

面板与预览必须用同一份数据

组件会把 emojiPackages 同时交给表情面板与预览渲染器。

这一条不能拆开传:面板里插入了某个表情、而渲染用的表里没有它, 预览区就会显示成一串 [名称] 纯文本——用户看到自己刚点的表情变成了乱码。

所以组件内部只接收一份数据、分发给两处,从源头避免这个偏差。

插入的是标记文本,不是表情本身

点击表情插入的是 [微笑] 这样的标记,而不是直接插入 🙂

好处是表情包可以整体替换:字符换成图片、换一整套画风, 历史内容里存的标记一个字都不用改,重新渲染时按新表查出来即可。

代价是渲染端必须认识这套标记——这正是 emojiPackages 要传两处的原因。

行数、上限与禁用

预览区——左边写,这里实时看效果

0 / 100

rows="4" + maxlength="100":超过 100 字时计数变红

这段内容不可编辑。

9

disabled:整体禁用

vue
<SrMarkdownEditor v-model="draft" :rows="4" :maxlength="100" />
<SrMarkdownEditor v-model="draft" disabled />

maxlength0(默认)表示不限。

maxlength 只提示,不阻止输入

超限时计数变红,但不会截断用户的输入。这是有意的:

写到一半被打断、或者粘贴一段稍长的内容被砍掉尾巴,比「看到红色计数」糟糕得多。 是否放行由业务判断——提交时校验、或只做提醒,都能基于这个状态自行决定。

若确实需要硬截断,在 v-modelupdate:modelValue 里自己裁。

Props

名称类型默认值说明
modelValuestring''Markdown 源文本,配合 v-model
view'edit' | 'split' | 'preview''split'初始视图
rowsnumber12编辑区行数
maxlengthnumber0最大字数,0 为不限
placeholderstring'写点什么…支持 Markdown 语法'占位提示
disabledbooleanfalse是否禁用
toolbarbooleantrue是否显示工具栏
showCountbooleantrue是否显示字数统计
size'sm' | 'md' | 'lg''md'预览区字号
emojiPackagesSrEmojiPackage[][]表情包数据,缺省用内置的

事件

名称参数说明
update:modelValue(value: string)内容变化

无障碍

已经做到的:

  • 工具栏按钮都是原生 <button> 且带 aria-label——加粗、插入链接等取 action.title,表情面板与视图切换分别是「表情」「视图切换」。 读屏软件念出的是用途,不是图标名
  • 编辑区是原生 <textarea>,输入法与读屏软件的交互由浏览器保证
  • placeholder 提供输入提示
  • 主动设了 spellcheck="false"——Markdown 里全是语法符号, 开着拼写检查会满屏红波浪线

尚未做的(需要时请自行补):

缺口影响
字数统计没有 aria-live输入时字数变化不会被朗读,读屏用户感知不到
编辑区没有 aria-label、也没有关联的 <label>用途只靠 placeholder 传达,而 placeholder 在输入后即消失

放进正式表单时,建议在组件外补一个可见的 <label>: 光靠 placeholder 传达字段名,对方填到一半就看不到它了。

相关

自用组件库 · 源码分发 · 不发布 npm