记一次使用vue-markdown在vue中解析markdown格式文件,并自动生成目录大纲

本文介绍了如何在网页中使用Vue-Markdown库来渲染Markdown文档,并自动生成目录大纲,以便用户方便地浏览和跳转。主要步骤包括安装vue-markdown、在模板中渲染文件、提取H1-H5标题生成目录结构以及实现节点点击导航。
摘要由CSDN通过智能技术生成

先上效果图

如图所示,在网页中,能直接解析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>

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值