先上效果图
如图所示,在网页中,能直接解析markdown文档,并且生成目录大纲,也支持点击目录标题跳转到对应栏目中,下面就来讲讲是如何实现此功能的。
1、下载vue-markdown
yarn add vue-markdown
2、在页面中渲染markdown文件
<template>
<div>
<VueMarkdown
class="api-content"
:source="markdownContent"
id="content"
/>
</div>
</template>
<script>
import VueMarkdown from "vue-markdown";
export default {
components: {
VueMarkdown,
},
data() {
return {
markdownContent: "",
};
},
mounted() {
this.loadMarkdownFile();
},
methods: {
async loadMarkdownFile() {
try {
// api.md文件存放在根目录下的public文件夹中
const response = await fetch("/api.md");
const markdownText = await response.text();
this.markdownContent = markdownText;
} catch (error) {
console.error("Failed to load the Markdown file:", error);
}
},
},
};
</script>
此时,打开浏览器查看,页面中已经正常渲染markdown文件了。
3、生成目录大纲
现在,我们需要有目录大纲方便我们查看文档。我的思路是,首先拿到markdown文件的html结构,然后找到所有H1-H5的标题标签,并给这些标签设置id,生成一个新数组,通过这个数组生成目录结构,说干就干。
//html部分
<div class="api-tree" id="tree">
<el-tree
:data="tree"
:default-expand-all="true"
@node-click="handleNodeClick"
></el-tree>
</div>
// js部分
catalogTree() {
const content = document.getElementById("content").children;
var arr = [];
let currentHightestLevel;
let parentId;
let index = 0;
for (let i = 0; i < content.length; i++) {
let header = content[i].localName;
if (/\b[h][0-9]\b/.test(header)) {
let ele = $(content[i]);
let name = ele.text();
// 设置id
ele.attr("id", i);
let id = i;
if (index === 0 || header <= currentHightestLevel) {
currentHightestLevel = header;
parentId = id;
}
arr.push({
id: id,
label: name,
parentId: parentId == id ? "0" : parentId,
});
index++;
}
}
const tree = [];
arr.forEach((item) => {
if (item.parentId === "0") {
tree.push(this.convertArrayToTree(arr, item));
}
});
this.tree = tree;
},
convertArrayToTree(arr, node) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].parentId === node.id) {
const res = this.convertArrayToTree(arr, arr[i]);
if (node.children) {
node.children.push(res);
} else {
node.children = [res];
}
}
}
return node;
},
handleNodeClick(data) {
let anchorElement = document.getElementById(data.id);
if (anchorElement) {
anchorElement.scrollIntoView({
behavior: "smooth",
block: "end",
});
}
},
4、大功告成,最后,附上全部代码,带css样式
<template>
<div class="page-api" id="myElement">
<div class="api-tree" id="tree">
<el-tree
:data="tree"
:default-expand-all="true"
@node-click="handleNodeClick"
></el-tree>
</div>
<VueMarkdown
class="api-content"
:source="markdownContent"
id="content"
/>
</div>
</template>
<script>
import VueMarkdown from "vue-markdown";
import $ from "jquery";
export default {
components: {
VueMarkdown,
},
data() {
return {
markdownContent: "",
tree: [],
};
},
mounted() {
this.loadMarkdownFile();
},
methods: {
async loadMarkdownFile() {
try {
const response = await fetch("/api.md");
const markdownText = await response.text();
this.markdownContent = markdownText;
this.$nextTick(() => {
this.catalogTree();
});
} catch (error) {
console.error("Failed to load the Markdown file:", error);
}
},
catalogTree() {
const content = document.getElementById("content").children;
var arr = [];
let currentHightestLevel;
let parentId;
let index = 0;
for (let i = 0; i < content.length; i++) {
let header = content[i].localName;
if (/\b[h][0-9]\b/.test(header)) {
let ele = $(content[i]);
let name = ele.text();
// 设置id
ele.attr("id", i);
// let id = ele.children("a").attr("id");
let id = i;
if (index === 0 || header <= currentHightestLevel) {
currentHightestLevel = header;
parentId = id;
}
arr.push({
id: id,
label: name,
parentId: parentId == id ? "0" : parentId,
});
index++;
}
}
const tree = [];
arr.forEach((item) => {
if (item.parentId === "0") {
tree.push(this.convertArrayToTree(arr, item));
}
});
this.tree = tree;
},
convertArrayToTree(arr, node) {
for (let i = 0; i < arr.length; i++) {
if (arr[i].parentId === node.id) {
const res = this.convertArrayToTree(arr, arr[i]);
if (node.children) {
node.children.push(res);
} else {
node.children = [res];
}
}
}
return node;
},
handleNodeClick(data) {
let anchorElement = document.getElementById(data.id);
let scrollPosition = anchorElement.offsetTop - 20;
let myElement = document.getElementById("myElement");
myElement.scrollTo({
left: 0,
top: scrollPosition,
behavior: "smooth",
});
// if (anchorElement) {
// anchorElement.scrollIntoView({
// behavior: "smooth",
// block: "end",
// });
// }
},
},
};
</script>
<style lang="scss">
.page-api {
display: flex;
height: 100%;
overflow-y: scroll;
.api-tree {
position: fixed;
left: 20px;
top: 120px;
width: 200px;
height: calc(100% - 160px);
overflow-y: scroll;
z-index: 99;
.el-tree {
background: none;
color: #fff;
.el-tree-node:focus > .el-tree-node__content,
.el-tree-node__content:hover {
background: none;
color: rgb(24, 144, 255);
}
}
}
.api-content {
flex: 1;
margin-left: 220px;
padding: 0 30px;
color: rgba(255, 255, 255, 0.75);
h3 {
margin-left: 25px;
}
code {
border-radius: 2px;
color: #e96900;
margin: 0 2px;
padding: 3px 5px;
white-space: pre-wrap;
}
table {
border-collapse: collapse;
border-spacing: 0;
th,
td {
border: 1px solid #ddd;
padding: 6px 13px;
margin: 0;
}
}
pre {
background: rgba(0, 0, 0, 0.7);
padding: 20px 30px;
}
}
}
</style>