Vue生成Markdown目录(TOC)

先看效果

在这里插入图片描述

组件结构

在这里插入图片描述

第一步 创建工具类

工具类名称随意,我的叫toc.js,如果你的改了引用的时候注意改下

let targetContent = ''

// 获取所有的标题元素
function getElements() {
    const parser = new DOMParser();
    const htmlDocument = parser.parseFromString(targetContent, 'text/html');
    const elements = htmlDocument.querySelectorAll("h1,h2,h3,h4,h5,h6")
    return elements;
}
// 获取父级的标题
function getMinSize() {
    const elements = [...getElements()]
    let arr = []
    elements.forEach(item => {
        arr.push(parseInt(item.nodeName.replace('H', '')))
    })
    return Math.min(...arr)
}
//获取所有的父级节点
function getFatherNode() {
    let minSize = getMinSize()
    const elements = Array.from(getElements());
    const arr = []
    elements.forEach((item, index) => {
        let level = parseInt(item.nodeName.replace('H', ''))
        if (level == minSize) {
            let obj = { 'title': item.innerText, 'level': level, 'eleIndex': index }
            arr.push(obj)
        }
    })
    return arr
}
// 返回生成的toc数据结构
export function randerMarkDownToc(data) {
    targetContent = data
    const elements = getElements()
    let fatherElements = getFatherNode()
    let tocArr = []
    fatherElements.forEach(item => {
        let obj = { ...item, 'children': getChildrenArr(elements, item.eleIndex, item.level) }
        tocArr.push(obj)
    })
    return tocArr
}
// 获取子级toc
function getChildrenArr(targetArr, startIndex, targetLevel) {
    const childrenArr = []
    for (let i = startIndex + 1; i < targetArr.length; i++) {
        let level = parseInt(targetArr[i].nodeName.replace('H', ''))
        if (targetLevel + 1 === level && (childrenArr.length === 0 || i - childrenArr[childrenArr.length - 1].eleIndex == 1)) {
            if (i - startIndex > 1 && childrenArr.length == 0) {
                return childrenArr
            }
			// 这里用的递归 俗称套娃
            let obj = { 'title': targetArr[i].innerText, 'level': level, 'eleIndex': i, 'children': getChildrenArr(targetArr, i, level) }
            childrenArr.push(obj)
        }
    }
    return childrenArr
}

第二步 创建组件

创建父级组件 组件名称:MarkdownContainer.vue

<template>
  <div id="markdown-toc" :class="!isFix ? 'markdown-toc-move' : ''">
    <el-card class="box-card">
      <div slot="header" class="clearfix">
        <span>目录</span>
      </div>
      <ul>
        <li v-for="item in tocArr" :key="item.title">
          <a @click.prevent="toLink(item.title)">{{ item.title }}</a>
          <MarkdownTocItem :tocArr="item.children" />
        </li>
      </ul>
    </el-card>
  </div>
</template>

<script>
import MarkdownTocItem from "./MarkdownTocItem.vue";

export default {
  name: "MarkdownContainer",
  components: { MarkdownTocItem },
  props: ["tocArr"],
  data() {
    return {
      isFix: true,
    };
  },
  methods: {
    toLink(remark) {
      console.log("father");
      const element = document.getElementById(remark);
      const ofsHeight = element.offsetTop;
      window.scrollTo({
        top: ofsHeight - 110,
        behavior: "smooth",
      });
    },
    listenMove() {
      let backImg = document.getElementById("back-image");
      const rect = backImg.getBoundingClientRect();
      const windowHeight = window.innerHeight;

      if (windowHeight - rect.bottom <= 755) {
        this.isFix = true;
      } else {     
        this.isFix = false;
      }
    },
  },
  mounted() {
    window.addEventListener("scroll", this.listenMove);
  },
  beforeDestroy() {
    window.removeEventListener("scroll", this.listenMove);
  },
};
</script>

<style lang="scss" scoped>
.markdown-toc-move {
  top: 29% !important;
}

#markdown-toc {
  position: fixed;
  top: 430px;
  right: 16px;
  width: 233px;
  height: 300px;
  background-color: #f0f2f5;
  margin: 0px !important;
  overflow-y: scroll;

  &::-webkit-scrollbar {
    display: none; /* 隐藏滚动条(适用于 Chrome、Safari 等 WebKit 类浏览器)*/
  }

  ::v-deep .el-card{
    height: 100% !important;
  }

  ::v-deep .el-card__header {
    padding: 10px 0;
    position: relative;
    top: 0;
    right: 0;
  }

  ::v-deep .elel-card__body {
    padding: 5px !important;
    height: 300px;
  }

  .clearfix {
    text-align: center;
    span {
      width: 100%;
      font-size: 1.1rem;
      font-weight: bold;
    }
  }

  ul {
    li {
      list-style: none;
      cursor: pointer;

      a {
        color: green;
        text-decoration: none;
        display: block;
        width: 100%;
        height: 100%;

        &:hover {
          color: orange;
        }
      }
    }
  }
}

@media screen and(max-width: 480px) {
  #markdown-toc {
    display: none;
  }
}
</style>

创建子组件,用于递归toc, 组件名称:MarkdownTocItem.vue

<template>
  <div class="markdown-toc-item">
    <ul>
      <li v-for="item in tocArr" :key="item.title">
        <a @click.prevent="toLink(item.title)">{{ item.title }}</a>
        <MarkdownTocItem :tocArr="item.children" />
      </li>
    </ul>
  </div>
</template>
  
<script>
import MarkdownTocItem from "./MarkdownTocItem.vue";

export default {
  name: "MarkdownTocItem",
  components: { MarkdownTocItem },
  props: ["tocArr"],
  methods: {
    toLink(remark) {
      const element = document.getElementById(remark);
      const ofsHeight = element.offsetTop;
      window.scrollTo({
        top: ofsHeight - 110,
        behavior: "smooth",
      });
    },
  },
};
</script>
  
<style lang="scss" scoped>
ul {
  padding-left: 8px;

  li {
    list-style: none;
    cursor: pointer;
    a {
      color: green;
      text-decoration: none;
      display: block;
      width: 100%;
      height: 100%;

      &:hover {
        color:orange
      }
    }
  }
}
</style>
  

第三步 引用并使用组件 注意这里 要自己看着改 我只是提供了一些关键代码

import { randerMarkDownToc } from '@/utils/toc'
// 记得替换路径
import MarkdownContainer from '@/components/MarkDownTop/MarkdownContainer'

// 使用组件
<MarkdownContainer :tocArr="tocData" />

export default ({
	// 注册组件
	components: { MarkdownContainer },
	data(){
		return {
			tocData:null
		}
	},
	methods: {
		getMarkDownContent(){
			getArticle().then(resp => {
				// 获取toc数据
				this.tocData = randerMarkDownToc(resp.data.article.content)
			})
		}
	}
	
})
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值