vue3实现markdown预览和编辑

Markdown作为一种轻量级标记语言,已经成为开发者编写文档的首选工具之一。在Vue3项目中集成Markdown编辑和预览功能可以极大地提升内容管理体验。本文将介绍如何使用Vditor这一强大的开源Markdown编辑器在Vue3项目中实现这一功能。

一、Vditor简介

Vditor是一款浏览器端的Markdown编辑器,支持所见即所得(WYSIWYG)、即时渲染(IR)和分屏预览模式。它具有以下特点:

二、安装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", 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值