vue3实现markdown预览和编辑
Markdown作为一种轻量级标记语言,已经成为开发者编写文档的首选工具之一。在Vue3项目中集成Markdown编辑和预览功能可以极大地提升内容管理体验。本文将介绍如何使用Vditor这一强大的开源Markdown编辑器在Vue3项目中实现这一功能。
一、Vditor简介
Vditor是一款浏览器端的Markdown编辑器,支持所见即所得(WYSIWYG)、即时渲染(IR)和分屏预览模式。它具有以下特点:
-
支持三种编辑模式:WYSIWYG、IR和SV
-
内置流程图、甘特图、时序图等图表支持
-
数学公式、音视频、代码高亮等丰富功能
-
高度可定制化的主题和工具栏
-
官网Vditor - 一款浏览器端的 Markdown 编辑器,支持所见即所得(富文本)、即时渲染(类似 Typora)和分屏预览模式 (b3log.org)
二、安装Vditor
在项目中安装Vditor:
npm install vditor --save
三、实现预览
Vdior中提供了专门的预览方法来针对不需要编辑仅需要展示markdown文档的场景

下面是我封装的一个预览组件
<script setup lang="ts">
import { ref, onMounted, watch } from "vue";
import VditorPreview from "vditor/dist/method.min";/*** Markdown预览组件* @component*/
const props = defineProps({/*** Markdown内容*/content: {type: String,required: true},/*** 预览选项*/options: {type: Object,default: () => ({})}
});/*** 预览容器引用*/
const previewRef = ref<HTMLDivElement | null>(null);/*** 默认预览配置*/
const defaultOptions = {cdn: "https://ld246.com/js/lib/vditor",mode: "dark",anchor: 1,hljs: {lineNumber: true,style: "github"},math: {inlineDigit: true,macros: {}},theme: {current: "dark"},lazyLoadImage: "//unpkg.com/vditor/dist/images/img-loading.svg"
};/*** 合并配置项*/
const mergedOptions = {...defaultOptions,...props.options
};/*** 渲染Markdown内容*/
const renderMarkdown = () => {if (previewRef.value) {VditorPreview.preview(previewRef.value, props.content, mergedOptions);}
};/*** 组件挂载完成后渲染Markdown*/
onMounted(() => {renderMarkdown();// 渲染其他特殊语法VditorPreview.mermaidRender(document);VditorPreview.codeRender(document);VditorPreview.mathRender(document);VditorPreview.abcRender(document);VditorPreview.chartRender(document);VditorPreview.mindmapRender(document);VditorPreview.graphvizRender(document);
});/*** 监听内容变化重新渲染*/
watch(() => props.content,() => {renderMarkdown();}
);
</script><template><div class="vditor-preview-container"><div ref="previewRef" class="vditor-preview" /></div>
</template><style scoped>
.vditor-preview-container {width: 100%;
}.vditor-preview {box-sizing: border-box;padding: 16px;color: #ccc;background-color: #1a1a1a;border-radius: 8px;
}:deep(.vditor-reset) {background: #1a1a1a;
}:deep(.vditor-reset h1),
:deep(.vditor-reset h2),
:deep(.vditor-reset h3),
:deep(.vditor-reset h4),
:deep(.vditor-reset h5),
:deep(.vditor-reset h6) {/* color: #06ad7e; */margin-top: 1.5em;margin-bottom: 0.5em;font-weight: 600;border-bottom: none;
}:deep(.vditor-reset h3) {padding-bottom: 0.3em;font-size: 1.3em;
}:deep(.vditor-reset strong) {font-weight: 600;color: #fff;
}
</style>
使用:
<script setup lang="ts">
import { ref } from "vue";
import VditorPreview from "@/components/markdown/VditorPreview.vue";
const chiefComplaint = ref("");
</script>
<template><VditorPreview:content="chiefComplaint":options="{mode: 'dark',theme: {current: 'dark'}}"/>
</template>
四、实现编辑
编辑组件按照Vditor文档来使用Vditor编辑器即可
<script setup lang="ts">
import "vditor/dist/index.css";
import Vditor from "vditor";
import { useIntervalFn } from "@vueuse/core";
import { onMounted, ref, watch, toRaw, onUnmounted } from "vue";const emit = defineEmits(["update:modelValue","after","focus","blur","esc","ctrlEnter","select"
]);const props = defineProps({options: {type: Object,default() {return {};}},modelValue: {type: String,default: ""},isDark:{type: Boolean,default: true},
});const editor = ref<Vditor | null>(null);
const markdownRef = ref<HTMLElement | null>(null);onMounted(() => {editor.value = new Vditor(markdownRef.value as HTMLElement, {...props.options,value: props.modelValue,cache: {enable: false},fullscreen: {index: 10000},toolbar: ["headings","bold","italic","strike","|","line","quote","list","ordered-list","|","check","insert-after","|","insert-before","undo","redo","link","|","table","br","fullscreen"],cdn: "https://ld246.com/js/lib/vditor",after() {emit("after", toRaw(editor.value));},input(value: string) {emit("update:modelValue", value);},focus(value: string) {emit("focus", value);},blur(value: string) {emit("blur", value);},esc(value: string) {emit("esc", value);},ctrlEnter(value: string) {emit("ctrlEnter", value);},select(value: string) {emit("select", value);}});
});watch(() => props.modelValue,newVal => {if (newVal !== editor.value?.getValue()) {editor.value?.setValue(newVal);}}
);watch(() => props.isDark,newVal => {const { pause } = useIntervalFn(() => {if (editor.value.vditor) {newVal? editor.value.setTheme("dark", "dark", "rose-pine"): editor.value.setTheme("classic", "light", "github");pause();}}, 20);}
);onUnmounted(() => {const editorInstance = editor.value;if (!editorInstance) return;try {editorInstance?.destroy?.();} catch (error) {console.log(error);}
});
</script><template><div ref="markdownRef" />
</template>
使用
<Vditorv-model="chiefComplaintContent":options="{mode: 'ir',outline: { enable: false, position: 'right' },toolbarConfig: {pin: true}}"/>
五、高级功能实现
这些都是options中的配置项,如果需要使用,直接将其加入options对象中即可
1. 自定义工具栏
Vditor允许完全自定义工具栏配置。以下是一个精简版的工具栏配置:
toolbar: ['headings','bold','italic','strike','|','list','ordered-list','check','|','quote','code','inline-code','|','upload','|','undo','redo','|','fullscreen',
],
2. 图片上传处理
upload: {accept: 'image/*',handler(files) {// 这里实现上传逻辑const file = files[0];const formData = new FormData();formData.append('file', file);// 示例:使用axios上传axios.post('/api/upload', formData).then(response => {const url = response.data.url;editor.value.insertValue(``);}).catch(error => {console.error('上传失败:', error);});return false; // 阻止默认上传行为},
},
六、常见问题解决
1. 自定义样式
我采用的方式是给父容器加一个样式,然后不使用scope写css解决
<style lang="scss">
/* 自定义抽屉样式 */
.report-drawer .el-drawer__header {padding: 8px 20px !important;margin-bottom: 0 !important;font-size: 16px !important;color: #fff !important;border-bottom: 1px solid #383838 !important;
}.report-drawer .vditor-ir {overflow-x: hidden !important;
}.report-drawer .vditorv .ditor-toolbar {overflow-x: auto;
}.report-drawer .vditor--dark .vditor--fullscreen .ditor-toolbar {overflow-y: auto;
}.report-drawer .vditor--dark .vditor--fullscreen .ditor {height: 100% !important;
}/* Vditor的预览区域样式 */
.report-drawer .vditor-preview {overflow-x: hidden !important;}.report-drawer .vditor-ir pre.vditor-reset {max-height: 100vh !important;overflow-y: auto;padding: 0 60px !important;
}/* 按钮样式 */
.el-button {border-radius: 4px !important;
}
</style>
需要注意的是预览区域的样式和选择的模式有关,如果我设置的是ir,那么我预览区域的样式是.vditor-ir pre.vditor-reset
2.中文提示
Vditor的默认提示是英文的,可以设置中文:
new Vditor(editorContainer.value, {lang: 'zh_CN',// ...其他配置
});
3.cdn配置
在使用时可能会碰到css文件和js文件无法加载的情况,这是因为Vditor默认的cdn地址失效,需要在options中传入最新的可用cdn地址,目前可用的是https://ld246.com/js/lib/vditor
cdn: "https://ld246.com/js/lib/vditor",
相关文章:
vue3实现markdown预览和编辑
Markdown作为一种轻量级标记语言,已经成为开发者编写文档的首选工具之一。在Vue3项目中集成Markdown编辑和预览功能可以极大地提升内容管理体验。本文将介绍如何使用Vditor这一强大的开源Markdown编辑器在Vue3项目中实现这一功能。 一、Vditor简介 Vditor是一款浏…...
高并发秒杀系统接入层如何设计
博主介绍:✌全网粉丝5W,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验…...
C++异常处理 throw try catch
C 异常处理概述 C 异常处理机制提供了一种在程序运行时捕获错误或异常情况的方式。异常处理的目的是使得程序在遇到错误时能够优雅地终止或恢复,并防止程序出现崩溃。C 使用 try, throw, 和 catch 关键字来实现异常处理。 异常处理的基本结构: throw: …...
纯css实现环形进度条
需要在中实现一个定制化的环形进度条,最终效果如图: 使用代码 <divclass"circular-progress":style"{--progress: nextProgress,--color: endSliderColor,--size: isFull ? 60rpx : 90rpx,}"><div class"inner-conte…...
0基础 | 硬件 | 电源系统 一
降压电路LDO 几乎所有LDO都是基于此拓扑结构 图 拓扑结构 LDO属于线性电源,通过控制开关管的导通程度实现稳压,输出纹波小,无开关噪声 线性电源,IoutIin,发热功率P电压差△U*电流I,转换效率Vo/Vi LDO不适…...
详解 MySQL 索引的最左前缀匹配原则
MySQL 的最左前缀匹配原则主要是针对复合索引(也称为联合索引)而言的。其核心思想是:只有查询条件中包含索引最左侧(第一列)开始的连续一段列,才能让 MySQL 有效地利用该索引。 一、 复合索引的结构 复合…...
蓝桥杯算法题1
前言 双指针 唯一的雪花 Unique Snowflakes #include<iostream> #include<unordered_map> using namespace std; //这道题的意思就是在一个数组找一个最大的1区间的长度,这个区间里面没有重复数据 //如果暴力解法,每次求出以a[i]开头的满…...
【愚公系列】《高效使用DeepSeek》053-工艺参数调优
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...
Cortex-M系列MCU的位带操作
Cortex-M系列位带操作详解 位带(Bit-Banding)是Cortex-M3/M4等处理器提供的一种硬件特性,允许通过别名地址对内存或外设寄存器中的单个位进行原子读-改-写操作,无需禁用中断或使用互斥锁。以下是位带操作的完整指南: …...
大坑!GaussDB数据库批量插入数据变只读
大坑!GaussDB数据库批量插入数据变只读 GaussDB插入数据时变只读df和du为什么不一致GaussDB磁盘空间使用阈值GaussDB变只读怎么办正确删除表的姿势GaussDB插入数据时变只读 涉及的数据库版本为:GaussDB Kernel 505.1.0 build da28c417。 GuassDB TPCC灌数报错DML失败,数据…...
35信号和槽_信号槽小结
Qt 信号槽 1.信号槽是啥~~ 尤其是和 Linux 中的信号进行了对比(三要素) 1) 信号源 2) 信号的类型 3)信号的处理方式 2.信号槽 使用 connect 3.如何查阅文档. 一个控件,内置了哪些信号,信号都是何时触发 一…...
获取KUKA机器人诊断文件KRCdiag的方法
有时候在进行售后问题时需要获取KUKA机器人的诊断文件KRCdiag,通过以下方法可以获取KUKA机器人的诊断文件KRCdiag: 1、将U盘插到控制柜内的任意一个USB接口; 2、依次点【主菜单】—【文件】—【存档】—【USB(控制柜)…...
一周学会Pandas2 Python数据处理与分析-NumPy数据类型
锋哥原创的Pandas2 Python数据处理与分析 视频教程: 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili NumPy 提供了丰富的数据类型(dtypes),主要用于高效数值计算。以下是 NumPy 的主要…...
Redis核心机制-缓存、分布式锁
目录 缓存 缓存更新策略 定期生成 实时生成 缓存问题 缓存预热(Cache preheating) 缓存穿透(Cache penetration) 缓存雪崩(Cache avalanche) 缓存击穿(Cache breakdown) 分…...
Three.js 系列专题 1:入门与基础
什么是 Three.js? Three.js 是一个基于 WebGL 的 JavaScript 库,它简化了 3D 图形编程,让开发者无需深入了解底层 WebGL API 就能创建复杂的 3D 场景。它广泛应用于网页游戏、可视化、虚拟现实等领域。 学习目标 理解 Three.js 的核心组件:场景(Scene)、相机(Camera)…...
[C++面试] 如何在特定内存位置上分配内存、构造对象
new面试-高阶题(可以主动讲给面试官),适用于内存池、高性能场景或需要精确控制内存布局的编程需求。 一、核心方法:placement new placement new 是C中一种特殊形式的new运算符,允许在预先分配好的内存地址上构造对象…...
针对Ansible执行脚本时报错“可执行文件格式错误”,以下是详细的解决步骤和示例
针对Ansible执行脚本时报错“可执行文件格式错误”,以下是详细的解决步骤和示例: 目录 一、错误原因分析二、解决方案1. 检查并添加可执行权限2. 修复Shebang行3. 转换文件格式(Windows → Unix)4. 检查脚本内容兼容性5. 显式指定…...
如何在Ubuntu上安装Dify
如何在Ubuntu上安装Dify 如何在Ubuntu上安装docker 使用apt安装 # Add Dockers official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg…...
Python FastApi(13):APIRouter
如果你正在开发一个应用程序或 Web API,很少会将所有的内容都放在一个文件中。FastAPI 提供了一个方便的工具,可以在保持所有灵活性的同时构建你的应用程序。假设你的文件结构如下: . ├── app # 「app」是一个 Python 包…...
Harmony OS“一多” 详解:基于窗口变化的断点自适应实现
一、一多开发核心概念(18N模式) 目标:一次开发多端部署 解决的问题: 1、界面级一多:适配不同屏幕尺寸 2、功能级一多:设备功能兼容性处理(CanIUser) 3、工…...
【算法竞赛】状态压缩型背包问题经典应用(蓝桥杯2019A4分糖果)
在蓝桥杯中遇到的这道题,看上去比较普通,但其实蕴含了很巧妙的“状态压缩 背包”的思想,本文将从零到一,详细解析这个问题。 目录 一、题目 二、思路分析:状态压缩 最小覆盖 1. 本质:最小集合覆盖问题…...
kali——masscan
目录 前言 使用方法 前言 Masscan 是一款快速的端口扫描工具,在 Kali Linux 系统中常被用于网络安全评估和渗透测试。 使用方法 对单个IP进行端口扫描: masscan -p11-65535 192.168.238.131 扫描指定端口: masscan -p80,22 192.168.238.131…...
常微分方程 1
slow down and take your time 定积分应用回顾常微分方程的概述一阶微分方程可分离变量齐次方程三阶线性微分方程 一阶线性微分方程不定积分的被积分函数出现了绝对值梳理微分方程的基本概念题型 1 分离变量题型 2 齐次方程5.4 题型 3 一阶线性微分方程知识点5.55.6 尾声 定积分…...
Web前端页面搭建
1.在D盘中创建www文件 cmd进入窗口命令windowsR 切换盘符d: 进入创建的文件夹 在文件夹里安装tp框架 在PS中打开tp文件 创建网站,根目录到public 在浏览器中打开网页 修改文件目录名称 在public目录中的。htaccess中填写下面代码 <IfModule mod_rewrite.c >…...
开源 LLM 应用开发平台 Dify 全栈部署指南(Docker Compose 方案)
开源 LLM 应用开发平台 Dify 全栈部署指南(Docker Compose 方案) 一、部署环境要求与前置检查 1.1 硬件最低配置 组件要求CPU双核及以上内存4GB 及以上磁盘空间20GB 可用空间 1.2 系统兼容性验证 ✅ 官方支持系统: Ubuntu 20.04/22.04 L…...
BN 层的作用, 为什么有这个作用?
BN 层(Batch Normalization)——这是深度神经网络中非常重要的一环,它大大改善了网络的训练速度、稳定性和收敛效果。 🧠 一句话理解 BN 层的作用: Batch Normalization(批归一化)通过标准化每一…...
JavaScript 中常见的鼠标事件及应用
JavaScript 中常见的鼠标事件及应用 在 JavaScript 中,鼠标事件是用户与网页进行交互的重要方式,通过监听这些事件,开发者可以实现各种交互效果,如点击、悬停、拖动等。 在 JavaScript 中,鼠标事件类型多样࿰…...
【nginx】Nginx的功能特性及常用功能
目录 1.核心功能特性1.1 高并发处理能力1.2 反向代理与负载均衡1.3 静态资源服务1.4 缓存加速1.5 SSL/TLS支持1.6 动态模块扩展1.7 流媒体服务1.8 高可用性 2.常用功能场景2.1 反向代理与负载均衡2.2 静态资源服务2.3 缓存加速2.4 HTTPS支持2.5 API网关2.6 微服务网关 3.优势总…...
make_01_Program_01_makefile .SECONDARY .dirstamp 是什么功能
在 Makefile 中,.SECONDARY 和 .dirstamp 与 GNU Make 处理文件和目标的方式有关。让我们分别解释这两个部分,以及它们结合在一起时的功能。 .SECONDARY 功能:.SECONDARY 是一个特殊的伪目标,用于告诉 make 保留所有中间目标文件…...
金仓数据库KCM认证考试介绍【2025年4月更新】
KCM(金仓认证大师)认证是金仓KES数据库的顶级认证,学员需通过前置KCA、KCP认证才能考KCM认证。 KCM培训考试一般1-2个月一次,KCM报名费原价为1.8万,当前优惠价格是1万(趋势是:费用越来越高&…...
