使用 md-editor-v3 开发自定义 Markdown 编辑器组件
在日常项目中,开发一个功能丰富的 Markdown 编辑器对于提升用户体验和编辑效率非常重要。本文将介绍如何使用 md-editor-v3 来实现一个带有自定义工具栏和功能的 Markdown 编辑器组件。
Github: https://github.com/imzbf/md-editor-v3
一、项目结构和依赖
本示例使用 Vue 3 和 TypeScript 进行开发。核心依赖包括 md-editor-v3、@element-plus/icons-vue、@vavt/v3-extension 等。
yarn add md-editor-v3
使用现有的语言和主题等,如日语
yarn add @vavt/cm-extension
使用toolbar的现有组件,例如将内容导出为PDF
yarn add @vavt/v3-extension
二、代码实现详解
- 基础模板结构
我们在模板中使用了 MdEditor 组件,并定义了一些自定义工具栏(toolbars中的自定义工具使用数字代替,从0开始)和弹窗。
<template>
<div w-full>
<MdEditor
v-model="editorText"
:footers="['markdownTotal', '=', 0, 'scrollSwitch']"
:theme="theme"
:toolbars="toolbars"
@on-save="onSave"
@on-change="handleChange"
@on-upload-img="onUploadImg"
>
<template #defToolbars>
<ExportPDF :model-value="editorText" @on-progress="onProgress" @on-success="onSuccess" />
<!-- 自定义工具栏NormalToolbar,不需要可删除-->
<NormalToolbar title="mark" @on-click="customHandler">
<template #trigger>
<el-icon>
<VideoCameraFilled />
</el-icon>
</template>
</NormalToolbar>
<!-- 自定义工具栏ModalToolbar,不需要可删除-->
<ModalToolbar
:visible="visible"
height="200px"
modal-title="测试"
title="video"
width="400px"
@on-close="closeModal"
@on-adjust="adjustModal"
@on-click="openModal"
>
<template #trigger>
<el-icon>
<List />
</el-icon>
</template>
</ModalToolbar>
</template>
<template #defFooters>
<NormalFooterToolbar>{{ parseTime(new Date()) }}</NormalFooterToolbar>
</template>
</MdEditor>
</div>
</template>
2. 脚本逻辑
在 <script setup> 部分,我们定义了组件的核心逻辑和交互方法。
typescript
复制代码
<script lang="ts" setup>
import { defineEmits, defineProps, ref, watch } from 'vue';
import { MdEditor, ModalToolbar, NormalFooterToolbar, NormalToolbar, Themes, ToolbarNames } from 'md-editor-v3';
import { upload } from '@/utils/request';
import { List, VideoCameraFilled } from '@element-plus/icons-vue';
import { ExportPDF } from '@vavt/v3-extension';
import modal from '@/plugins/modal';
import { parseTime } from '@/utils/ruoyi';
// 定义传入参数和事件
const emit = defineEmits(['update:modelValue']);
const props = defineProps({
modelValue: {
type: String,
default: ''
}
});
// 工具栏配置
const toolbars = ref<ToolbarNames[]>([
'revoke', 'next', '-', 'bold', 'underline', 'italic', 'strikeThrough', '-', 'title', 'sub',
'sup', 'quote', 'unorderedList', 'orderedList', 'task', 'codeRow', 'code', '-', 'link',
'image', 'table', 'mermaid', 'katex', '-', 0, 1, 2, '=', 'save', 'prettier', 'pageFullscreen',
'catalog', 'preview', 'previewOnly', 'htmlPreview', 'github'
]);
const editorText = ref(props.modelValue);
const visible = ref(false);
// 主题切换
const isDark = useDark({ storageKey: 'useDarkKey', valueDark: 'dark', valueLight: 'light' });
const theme = ref<Themes>(isDark.value ? 'dark' : 'light');
// 监听主题变化
watch(isDark, () => {
theme.value = isDark.value ? 'dark' : 'light';
});
// 监听传入的值变化
watch(() => props.modelValue, (newValue) => {
editorText.value = newValue;
});
// 更新父组件的 v-model
watch(editorText, (newValue) => {
emit('update:modelValue', newValue);
});
// 保存操作
const onSave = (value: string) => {
console.log(value);
};
// 文本改变操作
const handleChange = (text: string) => {
editorText.value = text;
};
// 自定义工具栏方法
const customHandler = () => {
alert('自定义菜单');
};
// 弹窗调整方法
const adjustModal = () => {
visible.value = false;
};
// 弹窗开启方法
const openModal = () => {
visible.value = true;
};
// 关闭弹窗
const closeModal = () => {
visible.value = false;
};
// 导出 PDF 进度条
const onProgress = (progress: number) => {
console.log(progress);
};
// PDF 导出成功
const onSuccess = () => {
modal.msgSuccess('PDF导出成功!');
};
// 图片上传
const onUploadImg = async (files: File[], callback: (url: { alt: string; title: string; url: any }[]) => void) => {
const res = await Promise.all(
files.map((file) => {
return new Promise((resolve, reject) => {
const form = new FormData();
form.append('file', file);
upload(form)
.then((res) => resolve(res))
.catch((error) => reject(error));
});
})
);
callback(
res.map((item: any) => ({
url: item.data.url,
alt: item.data.fileName,
title: item.data.fileName
}))
);
};
</script>
三、功能实现
-
自定义工具栏和弹窗
我们通过 和 组件定义了自定义工具栏和弹窗,用于扩展 Markdown 编辑器的功能,例如添加视频插入按钮和自定义弹窗。 -
图片上传
实现了 onUploadImg 方法,支持异步上传图片,并通过回调返回图片的 URL、alt 和 title 属性。 -
主题切换
使用了 useDark 实现编辑器的暗黑模式切换,用户体验更加灵活。 -
PDF 导出
使用 @vavt/v3-extension 提供的 ExportPDF 组件实现导出 PDF 功能,并监听导出进度和完成情况。
四、只读模式
<template>
<div class="yk-md-editor base-bg-box">
<MdCatalog v-if="showCatalog" :editor-id="id" :scroll-element="scrollElement" :theme="theme" />
<MdPreview :editor-id="id" :model-value="modelValue" :theme="theme" />
</div>
</template>
<script setup>
import { MdCatalog, MdPreview } from 'md-editor-v3';
import 'md-editor-v3/lib/preview.css';
const props = defineProps({
modelValue: {
type: String,
default: ''
},
showCatalog: {
type: Boolean,
default: true
}
});
// 是否暗黑模式
const isDark = useDark({
storageKey: 'useDarkKey',
valueDark: 'dark',
valueLight: 'light'
});
const theme = ref(isDark.value ? 'dark' : 'light');
// 匹配菜单颜色
watch(isDark, () => {
theme.value = isDark.value ? 'dark' : 'light';
});
const id = 'preview-only';
const scrollElement = document.documentElement;
</script>
<style lang="scss" scoped>
@import '@/assets/styles/variables.module.scss';
.yk-md-editor {
border-radius: 5px;
overflow: hidden;
margin: 12px 0;
}
.md-editor {
--md-bk-color: $base-bg-box;
}
</style>
五、总结
通过上述步骤,我们实现了一个功能强大、易于扩展的 Markdown 编辑器组件。希望这篇文章能帮助到你在项目中构建类似的编辑器。
转载自: https://web.yujky.cn/app/article/article-edit/index/1855647538840924161
具体效果可登录我的网站体验
https://web.yujky.cn/
租户:体验租户
用户名:cxks
密码: cxks123