安装 npm i @kangc/v-md-editor -S@1.7.11
将需要展示的内容赋值给 markdown 变量即可
<template>
<div class="v-md-editor-container">
<div class="v-md-editor-nav">
<ul>
<li v-for="(heading, index) in headings" :key="index" @click="handleNavClick(index)">{{ heading.textContent }}
</li>
</ul>
</div>
<div class="v-md-editor-wrapper">
<v-md-preview ref="editor" :text="markdown" @scroll="$refs.previewContainer.scrollTop = $event.target.scrollTop" />
</div>
</div>
</template>
<script>
import Vue from "vue";;
import vuepressTheme from "@kangc/v-md-editor/lib/theme/vuepress.js";
import "@kangc/v-md-editor/lib/theme/style/vuepress.css";
import VMdPreview from "@kangc/v-md-editor/lib/preview";
VMdPreview.use(vuepressTheme, {});
Vue.use(VMdPreview);
export default {
data() {
return {
markdown: '',
headings: [],
};
},
mounted() {
// 监听滚动事件
this.$refs.editor.$el.addEventListener('scroll', this.handleScroll);
// 初始化所有的 Markdown Titles
this.initHeadings();
},
beforeDestroy() {
// 销毁组件时取消监听滚动事件
this.$refs.editor.$el.removeEventListener('scroll', this.handleScroll);
},
methods: {
// 获取markdown preview中的所有h1~h6元素
initHeadings() {
const previewContainer = this.$refs.editor.$el;
this.headings = previewContainer.querySelectorAll('h1, h2, h3, h4, h5, h6');
},
// 处理导航栏点击事件
handleNavClick(index) {
// 获取对应元素
const targetElement = this.headings[index];
// 将目标元素滚动到视图顶部
targetElement.scrollIntoView({ behavior: 'smooth' });
// 设置高亮样式
this.setActiveNav(index);
},
// 监听滚动事件并处理高亮逻辑
handleScroll(e) {
const scrollTop = e.target.scrollTop;
// 遍历所有markdown preview中的h1~h6元素,确定高亮元素
let activeIndex = -1;
for (let i = 0; i < this.headings.length; i++) {
const element = this.headings[i];
if (element.offsetTop <= scrollTop) {
activeIndex = i;
} else {
break;
}
}
// 更新高亮样式
this.setActiveNav(activeIndex);
},
// 更新导航栏的高亮样式
setActiveNav(index) {
// 先移除之前选中的样式
const prevActiveItem = this.$el.querySelector('.active');
if (prevActiveItem) {
prevActiveItem.classList.remove('active');
}
// 再为目标元素添加选中样式
const currentItem = this.$el.querySelectorAll('.v-md-editor-nav li')[index];
if (currentItem) {
currentItem.classList.add('active');
}
},
},
};
</script>
<style>
.v-md-editor-container {
display: flex;
height: 100%;
}
.v-md-editor-nav {
width: 200px;
border-right: 1px solid rgba(0, 0, 0, 0.1);
height: 100%;
overflow-y: scroll;
}
.v-md-editor-nav ul {
margin: 0;
padding: 20px;
}
.v-md-editor-nav li {
list-style: none;
font-size: 14px;
line-height: 24px;
cursor: pointer;
transition: background-color 0.2s ease-in-out;
}
.v-md-editor-nav li:hover {
background-color: #f5f5f5;
}
.v-md-editor-nav li.active {
background-color: #e9e9e9;
font-weight: bold;
}
.v-md-editor-wrapper {
flex: 1;
display: flex;
flex-direction: column;
}
.v-md-editor-preview {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.v-md-editor-preview h1 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-size: 2em;
}
.v-md-editor-preview h2 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-size: 1.5em;
}
.v-md-editor-preview h3 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-size: 1.17em;
}
.v-md-editor-preview h4 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-size: 1em;
}
.v-md-editor-preview h5 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-size: 0.83em;
}
.v-md-editor-preview h6 {
margin-top: 1.5em;
margin-bottom: 0.5em;
font-size: 0.67em;
}
.v-md-editor-preview p {
margin-top: 1.5em;
margin-bottom: 1.5em;
line-height: 1.75em;
color: rgba(0, 0, 0, 0.87);
}
.v-md-editor-preview ol,
.v-md-editor-preview ul {
margin-top: 1.5em;
margin-bottom: 1.5em;
margin-left: 2em;
padding: 0;
list-style-position: outside;
line-height: 1.75em;
}
.v-md-editor-preview li {
margin-bottom: 0.5em;
line-height: 1.75em;
color: rgba(0, 0, 0, 0.87);
}
.v-md-editor-preview code {
background-color: #f5f5f5;
padding: 0.1em 0.3em;
border-radius: 0.2em;
font-size: 85%;
}
.v-md-editor-preview pre {
margin: 1.5em 0;
font-size: 14px;
font-family: Consolas, Menlo, monospace;
background-color: #f5f5f5;
border-radius: 6px;
padding: 10px 20px;
overflow: auto;
}
</style>
例子