附cherry-markDown官网及api使用示例
官网:GitHub - Tencent/cherry-markdown: ✨ A Markdown Editor
api:Cherry Markdown API
考虑到复用性,我在插件的基础上做了二次封装,步骤如下:
1.下载 npm install cherry-markdown --save
2..先在html中定义一个markDown容器,设置id及样式
<template>
<div v-loading="loading" element-loading-text="文件上传中...">
<div @click.prevent.stop>
<div :id="mdId" :style="{ height: height + 'px' }"></div>
</div>
</div>
</template>
3.在js中引入markDown
import Cherry from "cherry-markdown";
import "cherry-markdown/dist/cherry-markdown.min.css";
4.定义需要使用的变量并初始化markDown
(一部分是从父组件传过来的,loading是在上传图片/视频/附件的时候使用)
const props = defineProps({
height: {
type: Number,
default: 600,
},
modelValue: {
type: String,
default: "",
},
knwlgId: {
type: String,
default: "",
},
mdId: {
type: String,
default: "markdown-container",
},
});
const emits = defineEmits(["update:modelValue", "setHtml"]);
const cherrInstance = ref(null);
const loading = ref(false);
onMounted(() => {
//初始化markDown
initCherryMD();
});
5.初始化markDown
toolbars.toolbar内的togglePreview就是预览按钮
设置默认模式:editor.defaultModel
// defaultModel 编辑器初始化后的默认模式,一共有三种模式:1、双栏编辑预览模式;2、纯编辑模式;3、预览模式
// edit&preview: 双栏编辑预览模式
// editOnly: 纯编辑模式(没有预览,可通过toolbar切换成双栏或预览模式)
// previewOnly: 预览模式(没有编辑框,toolbar只显示“返回编辑”按钮,可通过toolbar切换成编辑模式)
// defaultModel: 'edit&preview',
const initCherryMD = (value, config) => {
cherrInstance.value = new Cherry({
id: props.mdId,
value: props.modelValue,
fileUpload: fileUpload,
emoji: {
useUnicode: true,
},
header: {
anchorStyle: "autonumber",
},
editor: {
defaultModel: "editOnly",
},
toolbars: {
theme: "light",
toolbar: [
"bold",
"italic",
"underline",
"strikethrough",
"|",
"color",
"header",
"|",
"list",
"image",
{
insert: [
"audio",
"video",
"link",
"hr",
"br",
"code",
"formula",
"toc",
"table",
"line-table",
"bar-table",
"pdf",
"word",
],
},
"graph",
"settings",
// "switchModel",
"togglePreview",
],
bubble: [
"bold",
"italic",
"underline",
"strikethrough",
"sub",
"sup",
"|",
"size",
"color",
],
float: [
"h1",
"h2",
"h3",
"|",
"checklist",
"quote",
"quickTable",
"code",
],
customMenu: [],
},
callback: {
afterChange: afterChange,
beforeImageMounted: beforeImageMounted,
},
});
};
6.定义上传图片、获取数据的方法(这里可以实际需求做判断)
// 上传通用接口未实现audioVideo
const fileUpload = (file, callback) => {
if (file.size / 1024 / 1024 > 200) {
return proxy.$modal.msgError("请上传200M以内的图片!");
}
if (!file.type.includes("image")) {
return proxy.$modal.msgError("仅支持上传图片!");
}
const formData = new FormData();
formData.append("file", file);
console.log(file, "file");
loading.value = true;
uploadImg(props.knwlgId, formData)
.then((res) => {
loading.value = false;
callback(
import.meta.env.VITE_APP_BASE_API +
"/ekms/images/v1/preview/" +
res.data.imgId
);
})
.catch(() => {
loading.value = false;
});
};
// 变更事件回调
const afterChange = (e) => {
emits("setHtml", getCherryContent(), getCherryHtml());
};
// 获取渲染后html内容
const getCherryHtml = () => {
const result = cherrInstance.value.getHtml();
// console.log(result, "get");
return result;
};
// 获取markdown内容
const getCherryContent = () => {
const result = cherrInstance.value.getMarkdown();
return result;
};
// 设置markdown内容
const setCherryContent = (val) => {
cherrInstance.value.setMarkdown(val, 1);
};
// 图片加载回调
const beforeImageMounted = (e, src) => {
return { [e]: src };
};
defineExpose({
getCherryHtml,
setCherryContent,
});
使用该组件:文章来源:https://www.toymoban.com/news/detail-654208.html
<CherryMD
ref="MDRef"
v-model="mdContent"
:knwlgId="artDetails.pkId"
@setHtml="getContent"
/>
const mdContent = ref("");
//设置默认值
mdContent.value = res.data.content;
nextTick(() => {
proxy.$refs.MDRef.setCherryContent(res.data.content || "");
});
// 获取文章结构信息
const getContent = (content, html) => {
mdHtml.value = html;
mdContent.value = content;
changeArticle();
};
完整代码:文章来源地址https://www.toymoban.com/news/detail-654208.html
<template>
<div>
<div
id="vditor"
class="vditor"
:class="{ 'vditor-hidden': showPreview }"
></div>
</div>
</template>
<script setup>
import Vditor from "vditor";
import "vditor/dist/index.css";
import { getToken } from "@/utils/auth";
const { proxy } = getCurrentInstance();
const props = defineProps({
height: {
type: [Number, String],
default: "inherit",
},
modelValue: {
type: String,
default: "",
},
knwlgId: {
type: String,
default: "",
},
showPreview: {
type: Boolean,
default: false,
},
});
watch(
() => props.showPreview,
(newValue, oldValue) => {
let previewDom =
contentEditor.value.vditor.toolbar.elements.preview.firstChild;
let isPreview = previewDom.className.indexOf("vditor-menu--current") > -1;
emits("update:showPreview", isPreview ? false : true);
previewDom.click();
}
);
const emits = defineEmits([
"update:modelValue",
"update:showPreview",
"setHtml",
]);
const contentEditor = ref(null);
onMounted(() => {
contentEditor.value = new Vditor("vditor", {
height: props.height,
mode: "wysiwyg", //所见即所得(wysiwyg)、即时渲染(ir)、分屏预览(sv)
value: props.modelValue,
cdn: import.meta.env.VITE_APP_VDITOR_API,
toolbarConfig: {
pin: true,
// hide: true,
},
// outline: {
// enable: true, //展示大纲,position默认left
// },
cache: {
enable: false, // 是否使用 localStorage 进行缓存
},
preview: {
mode: "both", //显示模式
delay: 10,
actions: [],
theme: {
path: `${import.meta.env.VITE_APP_VDITOR_API}/dist/css/content-theme`,
},
},
toolbar: [
"emoji",
"headings",
"bold",
"italic",
"strike",
"link",
"|",
"list",
"ordered-list",
"check",
"outdent",
"indent",
"|",
"quote",
"line",
"code",
"inline-code",
"insert-before",
"insert-after",
"|",
{
//自定义上传
hotkey: "",
name: "upload",
tip: "上传图片",
className: "right",
},
"table",
"|",
"undo",
"redo",
"|",
"code-theme",
"content-theme",
"preview",
{
hotkey: "⇧⌘S",
name: "clearAll",
tipPosition: "n",
tip: "清空内容",
className: "right",
icon: '<svg t="1696926237451" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2371" width="180" height="180"><path d="M512 838.858c10.89 0 19.732-9.158 19.732-20.43v-490.275c0-11.273-8.842-20.43-19.732-20.43s-19.755 9.157-19.755 20.43v490.275c0 11.272 8.842 20.43 19.755 20.43M629.877 838.813c10.935 0.428 20.138-8.37 20.475-19.688l28.665-489.69c0.427-11.272-8.077-20.745-18.99-21.195-10.935-0.405-20.137 8.415-20.475 19.688l-28.665 489.713c-0.405 11.317 8.1 20.767 18.99 21.172M848.038 185.142h-197.708v-81.653c0-22.545-17.685-40.882-39.51-40.882h-197.64c-21.87 0-39.532 18.338-39.532 40.882v81.653h-197.685c-10.913 0-19.755 9.158-19.755 20.475 0 11.272 8.843 20.407 19.755 20.407h39.577l39.488 653.67c6.367 44.73 35.415 81.72 79.065 81.72h355.793c43.65 0 71.573-37.44 79.088-81.72l39.488-653.67h39.578c10.867 0 19.755-9.135 19.755-20.408 0-11.317-8.888-20.475-19.755-20.475M413.157 103.49h197.64v81.653h-197.64v-81.653zM729.418 879.695c-2.655 21.555-17.73 40.86-39.533 40.86h-355.793c-21.87 0-36.54-19.057-39.532-40.86l-39.532-653.67h513.945l-39.555 653.67zM394.145 838.858c10.89-0.473 19.373-9.9 18.99-21.195l-29.070-489.712c-0.427-11.273-9.585-20.070-20.475-19.665-10.913 0.428-19.463 9.9-19.013 21.173l29.093 489.712c0.36 11.295 9.54 20.070 20.475 19.688z" p-id="2372"></path></svg>',
click() {
contentEditor.value.setValue("");
},
},
],
after: () => {
emits("setHtml", getValue(), getHTML());
contentEditor.value.vditor.toolbar.elements.preview.firstChild.style.display =
"none";
},
input: () => {
// 变更事件回调
emits("setHtml", getValue(), getHTML());
},
// 上传图片
upload: {
accept: "image/*",
fieldName: "file",
url:
import.meta.env.VITE_APP_BASE_API +
`/ekms/images/v1/upload?knwlgId=${props.knwlgId}`,
headers: {
Authorization: "Bearer " + getToken(),
},
filename(name) {
return name
.replace(/[^(a-zA-Z0-9\u4e00-\u9fa5\.)]/g, "")
.replace(/[\?\\/:|<>\*\[\]\(\)\$%\{\}@~]/g, "")
.replace("/\\s/g", "");
},
linkToImgUrl:
import.meta.env.VITE_APP_BASE_API +
`/ekms/images/v1/upload?knwlgId=${props.knwlgId}`,
max: 20 * 1024 * 1024,
multiple: false,
withCredentials: false,
validate(files) {
const isLt2M = files[0].size / 1024 / 1024 < 20;
if (!isLt2M) {
proxy.$modal.msgError("上传图片大小不能超过 20MB!");
}
if (!files[0].type.includes("image")) {
return proxy.$modal.msgError("仅支持上传图片!");
}
},
//粘贴图片回显处理,如果有图片加了防盗链,则让后台代理替换成自己的图片
linkToImgFormat(responseText) {
let res = JSON.parse(responseText);
let end = JSON.stringify({
msg: "",
code: 0,
data: {
originalURL:
import.meta.env.VITE_APP_BASE_API +
"/ekms/images/v1/preview/" +
res.data.imgId, //图片原始地址记录到本地文件中,可以防止跨站点调用。
url:
import.meta.env.VITE_APP_BASE_API +
"/ekms/images/v1/preview/" +
res.data.imgId, //图片链接记录到本地文件中,可以防止跨站点调用。
},
});
return end;
},
format(files, responseText) {
var res = JSON.parse(responseText);
//图片回显
nextTick(() => {
emits("setHtml", getValue(), getHTML());
});
return JSON.stringify({
msg: "",
code: 0,
data: {
errFiles: [],
succMap: {
[res.data.imgPath]:
import.meta.env.VITE_APP_BASE_API +
"/ekms/images/v1/preview/" +
res.data.imgId,
},
},
});
},
},
});
});
function getValue() {
return contentEditor.value.getValue(); //获取 Markdown 内容
}
function getHTML() {
let dom = document.querySelector(
".vditor-content .vditor-wysiwyg .vditor-reset"
)?.innerHTML;
return dom;
}
function setValue(value) {
return contentEditor.value.setValue(value); //设置 Markdown 内容
}
defineExpose({
getHTML,
setValue,
});
</script>
<style lang="scss" scoped>
// :deep(.vditor) {
// padding-top: 36px;
// }
:deep(.vditor-toolbar--pin) {
// position: fixed;
// top: 236px;
padding-left: calc(50% - 340px);
}
:deep(.vditor-wysiwyg) {
a {
color: #4285f4;
outline: 0;
text-decoration: none;
}
}
:deep(.vditor-hidden) {
height: inherit !important;
border: none !important;
.vditor-toolbar,
.vditor-wysiwyg {
display: none !important;
}
.vditor-preview {
display: block !important;
overflow-x: hidden !important;
background: #fff;
border: none !important;
}
.vditor-reset {
padding: 20px;
margin: 0 !important;
max-width: fit-content !important;
background: #fff;
a {
color: #006eff;
}
}
}
:deep(.vditor-wysiwyg .vditor-reset) {
padding: 10px 35px !important;
}
</style>
到了这里,关于vue3中使用cherry-markDown步骤的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!