Vue结合Element-Plus封装递归组件实现目录示例
时间:2022-06-13 10:13:37|栏目:vue|点击: 次
前言
在写我的个人博客网站,用MarkDownIt将md解析成html时,我一直在想,怎么才能实现官方文档他们那些可折叠的目录结构呢?我有那么多标题(h1...h5),而且有的文章是只有h2或者h3的,难道我要在目录组件里面一个个v-if来渲染这些标题达到目录的效果嘛?这个问题在我某一天看vue文档的时候得到了解决。如下。
没错,递归组件可以解决我这个困惑,递归无非就是自己调用自己,我们编写好合理的组件渲染逻辑之后,在组件内部自己调用自己,这就是递归组件,接下来请看我的解决步骤吧!
用正则匹配出所有的h标签并且保存在数组中
//这里的str是用MarkdownIt解析生成的html字符串 const menu = [...str.matchAll(/<h.*>.*</h.>/g)]
效果如下
封装函数,将数组中的内容变成父子结构
//我的每个菜单的类型 class menuItem { title: string children?: menuItem[] type: number //type为1表示是h1标签 index: number //index表示是type对应的第几个h标签 constructor( title: string, type: number, index: number, children: menuItem[] = [] ) { this.title = title this.children = children this.type = type this.index = index } } export default function (menu: any[]): any[] { //保存所有h min标签 const arr: menuItem[] = [] const arrIndex: number[] = new Array(7).fill(0) // 用于保存前一个层的结点。例如我当前遍历的是type=3的item,那么我需要知道它所属于哪个type=2的item // 如果有就添加到它的children中,如果没有就添加到pre[3]中 const pre = new Array(7).fill(null) //记录h min是哪个标签(例如h1) let minType = null for (const item of menu) { const content = item[0] const type = parseInt(content[2]) const title = content.split(/<\/?h.>/)[1] const menuitem = new menuItem(title, type, arrIndex[type]++) //判断当前type-1项有没有内容,有的话就加入到前一个种类的children中去 if (pre[type - 1]) { pre[type - 1].children.push(menuitem) } //重置当前type的项 pre[type] = menuitem minType = minType ?? type //如果是最小的h标签,就插入 if (type === minType) { arr.push(menuitem) } } return arr }
转换的arr结果如下,可以看到,数组中保存了两个顶层目录,children保存了内部的子目录。
封装递归组件fold-item(在使用之前不要忘了导入自己哦)
<script lang="ts" setup> import foldItem from './foldItem.vue' //导入自己 defineProps({ item: { type: Object, default: () => ({}) } }) </script> <template> <!-- 如果有孩子,就渲染成sub-menu(折叠item)--> <template v-if="item.children.length"> <el-sub-menu :index="item.title"> <template #title> <div class="title" v-html="item.title"></div> </template> <fold-item v-for="i in item.children" :key="i.title" :item="i" ></fold-item> </el-sub-menu> </template> <!-- 否则就渲染成menu-item--> <template v-else> <el-menu-item :index="item.title" @click="scrollToItem(item)"> <template #title> <div class="title" v-html="item.title"></div> </template> </el-menu-item> </template> </template> <style lang="less" scoped> .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } ::v-deep.el-menu-item { width: 220px; line-height: 30px; height: 30px; } ::v-deep.el-sub-menu { width: 220px; } ::v-deep .el-sub-menu__title { height: 30px; line-height: 30px; } </style>
在foldMenu中使用递归组件
<script lang="ts" setup> import foldItem from './foldItem.vue' defineProps({ menu: { type: Array, default: () => [] } }) </script> <template> <div class="menu-title">目录</div> <el-menu> <!-- 使用递归组件 --> <fold-item v-for="item in menu" :key="item.title" :item="item"></fold-item> </el-menu> </template> <style lang="less" scoped> ::v-deep.el-menu { border: none; } .menu-title { text-align: center; margin-bottom: 10px; } </style>
使用效果
更复杂的目录结构也能胜任