自定义富文本编辑器组件:集成 Vditor
概述
本节介绍如何将 Vditor 封装为 Vue 组件。Vditor 是一款浏览器端 Markdown 编辑器,支持三种编辑模式(即时渲染、分屏预览、所见即所得)。封装过程中需要注意样式导入、实例初始化、内容获取与设置、以及组件销毁时的资源释放。
Vditor 集成方式
安装依赖
npm install vditor
bash
样式导入
// Vditor 样式有两种导入方式:
// 方式一:导入源码样式(需要 less 支持)
import 'vditor/src/assets/scss/index.scss'
// 方式二:导入编译后的 CSS(推荐,无需额外安装 less)
import 'vditor/dist/index.css'
typescript
注意:Vditor 源码样式使用 less 编写。如果项目中使用的是 Sass/SCSS,建议直接导入编译后的
vditor/dist/index.css,避免安装额外的 less 依赖。
Vue 组件封装
完整组件实现
<!-- components/editor/Vditor.vue -->
<template>
<div ref="editorRef" :id="editorId" />
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
import Vditor from 'vditor'
import 'vditor/dist/index.css'
interface VditorProps {
modelValue?: string
options?: IOptions
height?: number | string
mode?: 'ir' | 'sv' | 'wysiwyg'
placeholder?: string
}
interface VditorEmits {
(e: 'update:modelValue', value: string): void
(e: 'ready', vditor: Vditor): void
}
const props = withDefaults(defineProps<VditorProps>(), {
modelValue: '',
height: 500,
mode: 'ir',
placeholder: '请输入内容...'
})
const emit = defineEmits<VditorEmits>()
const editorRef = ref<HTMLElement>()
const editorId = `vditor-${Date.now()}`
let vditorInstance: Vditor | null = null
onMounted(() => {
initEditor()
})
onUnmounted(() => {
vditorInstance?.destroy()
vditorInstance = null
})
function initEditor() {
if (!editorRef.value) return
vditorInstance = new Vditor(editorId, {
height: props.height,
mode: props.mode,
placeholder: props.placeholder,
cache: { enable: false },
toolbar: [
'headings', 'bold', 'italic', 'strike', '|',
'list', 'ordered-list', 'check', '|',
'quote', 'code', 'inline-code', '|',
'link', 'upload', 'table', '|',
'undo', 'redo', '|',
'fullscreen', 'preview'
],
preview: {
markdown: { toc: true }
},
// 内容变更回调
input: (value: string) => {
emit('update:modelValue', value)
},
after: () => {
// 初始化完成后设置初始内容
if (props.modelValue) {
vditorInstance?.setValue(props.modelValue)
}
emit('ready', vditorInstance!)
},
...props.options
})
}
// 监听外部 modelValue 变化
watch(
() => props.modelValue,
(newVal) => {
if (vditorInstance && newVal !== vditorInstance.getValue()) {
// 第二个参数 true = clearStack,清空历史堆栈
vditorInstance.setValue(newVal, true)
}
}
)
// 暴露实例方法
defineExpose({
getInstance: () => vditorInstance,
getValue: () => vditorInstance?.getValue(),
getHTML: () => vditorInstance?.getHTML(),
setValue: (val: string) => vditorInstance?.setValue(val),
focus: () => vditorInstance?.focus(),
destroy: () => {
vditorInstance?.destroy()
vditorInstance = null
}
})
</script>
vue
使用方式
<template>
<VditorEditor
v-model="content"
:height="500"
mode="ir"
@ready="onEditorReady"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import VditorEditor from '@/components/editor/Vditor.vue'
const content = ref('# Hello Vditor\n\n这是一个 Markdown 编辑器')
function onEditorReady() {
console.log('Editor is ready')
}
</script>
vue
Vditor 三种编辑模式
| 模式 | 值 | 说明 |
|---|---|---|
| 即时渲染 | ir | 类似 Typora,输入时实时渲染(推荐) |
| 分屏预览 | sv | 左侧 Markdown,右侧预览 |
| 所见即所得 | wysiwyg | 传统富文本编辑器体验 |
setValue 参数说明
// setValue(value, clearStack?)
vditor.setValue('# New Content', true)
// clearStack 参数:
// true → 清空历史堆栈(撤销/重做记录),适合从外部重置内容
// false → 保留历史堆栈,内容变更可撤销
typescript
Vditor 常用 API
| API | 说明 |
|---|---|
getValue() | 获取 Markdown 内容 |
getHTML() | 获取渲染后的 HTML |
setValue(value, clearStack?) | 设置内容 |
insertValue(value) | 在光标处插入内容 |
focus() | 聚焦编辑器 |
blur() | 失焦编辑器 |
disabled() | 禁用编辑器 |
enable() | 启用编辑器 |
destroy() | 销毁实例,释放资源 |
常见问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 样式不显示 | CSS 未导入 | import 'vditor/dist/index.css' |
| 初始化后内容为空 | after 回调中设置 | 在 after 回调中调用 setValue |
| 设置值后无法撤销 | clearStack 为 true | 需保留撤销记录时传 false |
| 组件切换后白屏 | 实例未销毁 | onUnmounted 中调用 destroy() |
| less 编译错误 | 项目使用 Sass | 直接导入 dist/index.css |
实践要点
- 推荐导入编译后的 CSS(
vditor/dist/index.css),避免 less 编译问题 setValue的第二个参数clearStack设为true可清空历史堆栈,避免外部更新产生无意义的撤销记录- 组件销毁时必须调用
vditor.destroy()释放资源 - 通过
defineExpose暴露 Vditor 实例方法,支持父组件直接操作编辑器 - watch
modelValue时需比较当前内容,避免循环更新
↑