vue3实现markdown预览和编辑

news2025/4/6 9:11:38

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>

使用

 <Vditor
            v-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(`![图片描述](${url})`);
      })
      .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", 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2329115.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高并发秒杀系统接入层如何设计

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

C++异常处理 throw try catch

C 异常处理概述 C 异常处理机制提供了一种在程序运行时捕获错误或异常情况的方式。异常处理的目的是使得程序在遇到错误时能够优雅地终止或恢复&#xff0c;并防止程序出现崩溃。C 使用 try, throw, 和 catch 关键字来实现异常处理。 异常处理的基本结构&#xff1a; throw: …

纯css实现环形进度条

需要在中实现一个定制化的环形进度条&#xff0c;最终效果如图&#xff1a; 使用代码 <divclass"circular-progress":style"{--progress: nextProgress,--color: endSliderColor,--size: isFull ? 60rpx : 90rpx,}"><div class"inner-conte…

0基础 | 硬件 | 电源系统 一

降压电路LDO 几乎所有LDO都是基于此拓扑结构 图 拓扑结构 LDO属于线性电源&#xff0c;通过控制开关管的导通程度实现稳压&#xff0c;输出纹波小&#xff0c;无开关噪声 线性电源&#xff0c;IoutIin&#xff0c;发热功率P电压差△U*电流I&#xff0c;转换效率Vo/Vi LDO不适…

获取KUKA机器人诊断文件KRCdiag的方法

有时候在进行售后问题时需要获取KUKA机器人的诊断文件KRCdiag&#xff0c;通过以下方法可以获取KUKA机器人的诊断文件KRCdiag&#xff1a; 1、将U盘插到控制柜内的任意一个USB接口&#xff1b; 2、依次点【主菜单】—【文件】—【存档】—【USB&#xff08;控制柜&#xff09…

一周学会Pandas2 Python数据处理与分析-NumPy数据类型

锋哥原创的Pandas2 Python数据处理与分析 视频教程&#xff1a; 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili NumPy 提供了丰富的数据类型&#xff08;dtypes&#xff09;&#xff0c;主要用于高效数值计算。以下是 NumPy 的主要…

Redis核心机制-缓存、分布式锁

目录 缓存 缓存更新策略 定期生成 实时生成 缓存问题 缓存预热&#xff08;Cache preheating&#xff09; 缓存穿透&#xff08;Cache penetration&#xff09; 缓存雪崩&#xff08;Cache avalanche&#xff09; 缓存击穿&#xff08;Cache breakdown&#xff09; 分…

如何在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&#xff0c;很少会将所有的内容都放在一个文件中。FastAPI 提供了一个方便的工具&#xff0c;可以在保持所有灵活性的同时构建你的应用程序。假设你的文件结构如下&#xff1a; . ├── app # 「app」是一个 Python 包…

【算法竞赛】状态压缩型背包问题经典应用(蓝桥杯2019A4分糖果)

在蓝桥杯中遇到的这道题&#xff0c;看上去比较普通&#xff0c;但其实蕴含了很巧妙的“状态压缩 背包”的思想&#xff0c;本文将从零到一&#xff0c;详细解析这个问题。 目录 一、题目 二、思路分析&#xff1a;状态压缩 最小覆盖 1. 本质&#xff1a;最小集合覆盖问题…

常微分方程 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文件 创建网站&#xff0c;根目录到public 在浏览器中打开网页 修改文件目录名称 在public目录中的。htaccess中填写下面代码 <IfModule mod_rewrite.c >…

开源 LLM 应用开发平台 Dify 全栈部署指南(Docker Compose 方案)

开源 LLM 应用开发平台 Dify 全栈部署指南&#xff08;Docker Compose 方案&#xff09; 一、部署环境要求与前置检查 1.1 硬件最低配置 组件要求CPU双核及以上内存4GB 及以上磁盘空间20GB 可用空间 1.2 系统兼容性验证 ✅ 官方支持系统&#xff1a; Ubuntu 20.04/22.04 L…

BN 层的作用, 为什么有这个作用?

BN 层&#xff08;Batch Normalization&#xff09;——这是深度神经网络中非常重要的一环&#xff0c;它大大改善了网络的训练速度、稳定性和收敛效果。 &#x1f9e0; 一句话理解 BN 层的作用&#xff1a; Batch Normalization&#xff08;批归一化&#xff09;通过标准化每一…

金仓数据库KCM认证考试介绍【2025年4月更新】

KCM&#xff08;金仓认证大师&#xff09;认证是金仓KES数据库的顶级认证&#xff0c;学员需通过前置KCA、KCP认证才能考KCM认证。 KCM培训考试一般1-2个月一次&#xff0c;KCM报名费原价为1.8万&#xff0c;当前优惠价格是1万&#xff08;趋势是&#xff1a;费用越来越高&…

如何通过句块训练法(Chunks)提升英语口语

真正说一口流利英语的人&#xff0c;并不是会造句的人&#xff0c;而是擅长“调取句块”的人。下面我们从原理、方法、场景、资源几个维度展开&#xff0c;告诉你怎么用“句块训练法&#xff08;Chunks&#xff09;”快速提升英语口语&#xff1a; 一、什么是“句块”&#xff…

[ctfshow web入门]burpsuite的下载与使用

下载 吾爱破解网站工具区下载burpsuite https://www.52pojie.cn/thread-1544866-1-1.html 本博客仅转载下载链接&#xff0c;下载后请按照说明进行学习使用 打开 配置 burpsuite配置 burpsuite代理设置添加127.0.0.1:8080 浏览器配置 如果是谷歌浏览器&#xff0c;打开win…

vscode集成deepseek实现辅助编程(银河麒麟系统)【详细自用版】

针对开发者用户&#xff0c;可在Visual Studio Code中接入DeepSeek&#xff0c;实现辅助编程。 可参考我往期文章在银河麒麟系统环境下部署DeepSeek&#xff1a;基于银河麒麟桌面&&服务器操作系统的 DeepSeek本地化部署方法【详细自用版】 一、前期准备 &#xff08…

elementui的默认样式修改

今天用element ui &#xff0c;做了个消息提示&#xff0c;发现提示的位置总是在上面&#xff0c;如图&#xff1a; 可是我想让提示的位置到下面来&#xff0c;该怎么办&#xff1f; 最后还是看了官方的api 原来有个自定义样式属性 customClass 设置下就好了 js代码 css代码 效…

基于STM32的智能门禁系统设计与实现

一、项目背景与功能概述 在物联网技术快速发展的今天&#xff0c;传统门锁正在向智能化方向演进。本系统基于STM32F103C8T6微控制器&#xff0c;整合多种外设模块&#xff0c;实现了一个具备以下核心功能的智能门禁系统&#xff1a; 密码输入与验证&#xff08;4x3矩阵键盘&a…