当我们选用ElementUI作为页面开发的组件库,并打算创建一个如下的菜单表项:
如果我们采用 ElementUI 库中的 el-menu 组件来实现的话,效果会很不错,但是代码的画风可能是这样的:
<el-menu>
<el-submenu index="1">
<el-menu-item index="1-1"></el-menu-item>
<el-menu-item index="i-2"></el-menu-item>
</el-submenu>
<el-submenu index="2">
<el-menu-item index="2-1"></el-menu-item>
<el-menu-item index="2-2"></el-menu-item>
<el-submenu index="2-3">
<el-menu-item index="2-3-1"></el-menu-item>
<el-menu-item index="2-3-2"></el-menu-item>
</el-submenu>
</el-submenu>
</el-menu>
在此简化结构上,每个组件标签还要配上一堆自带属性,代码瞬间就会变得臃肿许多;
再者,随着需求的增多,导航菜单项也不断变长,不仅整个 navigator.vue 文件会变得非常的长,在需求变更时对代码进行维护也比较麻烦。
因此,就有了使用 vue-router 自动配置生成导航菜单的想法,它的原理就是:
拿到 vue-router 的路由配置项 this.$router.options.routes
,为了方便描述,我们简记为 routes
,routes
对象如下放示例代码。
routes
是一棵多叉树,而恰好我们的菜单就是一颗多叉树,每一个无 children
属性的路由项对应者一个菜单项。
因此,只需要对这个 routes
进行 DFS,我们将当前遍历到的结点记为 cur
:
当 cur
是非叶子节点时,我们给它渲染成子菜单组件 < el-submenu >
当 cur
是叶子节点时,我们就给它渲染成菜单项组件 < el-menu-item >
, 并使用 router-link
与 路由中的path响应点击事件进行组件切换
其中,
每个结点的 meta信息中的 title 作为导航项的信息,
每个结点的 path 都需要与之前的进行拼接,并传递给子节点进行拼接,叶子节点将拿到这个拼接好的路径进行导航
const routes = [
{
path: '/',
component: () => import('../views/Home'),
meta: {title: "组卷管理"},
children: [
{
path: 'hand',
component: () => import('../views/Home'),
meta: {title: "手动组卷"}
},
{
path: 'mid',
component: () => import('../views/Home'),
meta: {title: "自动组卷"}
},
]
},
{
path: '/exam',
component: () => import('../views/Home'),
meta: {title: "考试管理"},
children: [
{
path: 'mid',
component: () => import('../views/Home'),
meta: {title: "期中考试"}
},
{
path: 'final',
component: () => import('../views/Home'),
meta: {title: "期末考试"}
},
{
path: 'else',
component: () => import('../views/Home'),
meta: {title: "其他考试"},
children: [
{
path: 'unit',
component: () => import('../views/Home'),
meta: {title: "单元测验"}
},
{
path: 'daily',
component: () => import('../views/Home'),
meta: {title: "日常作业"}
},
]
},
]
}
]
那我们就开始吧。
(以下只讲具体的思路,与需要注意的点,毕竟全部把代码贴上整篇文章就太长了)
根据上述分析,我们可以创建三种角色的组件 :
(1)根菜单 < Nav-Menu >
(2)子菜单 < Sub-Menu >
(3)菜单项 < Menu-Item >
我们一个一个看:(伪代码)
根菜单:
<el-menu v-for="cur of routes">
<sub-menu v-if="cur.childen.length>0"/>
<menu-item v-else/>
</el-menu>
从根节点开始对routes深搜,叶子节点渲染成< Menu-Item >
,非叶子节点渲染成< Sub-Menu >
,并继续深搜,因此需要将当前节点传递给< Sub-Menu >
,因此:
< Sub-Menu >
需要属性:cur 表示当前节点对象、以及之前拼装好的路由路径
path.resolve(cur.path, pre_url);
因为是递归结构,故< Menu-Item >
需要的属性相同。
代码可以改成:(伪代码)
<sub-menu
v-if="cur.children.length>0"
:cur="cur"
:pre_url="resolvePath(cur.path)"
/>
<menu-item
v-else
:cur="cur"
:pre_url="resolvePath(cur.path)"
/>
再看比较简单的 < Menu-Item >
菜单项,
菜单项需要的功能就是导航,因此需要一个 < router-link > ,tag的作用是将router-link渲染成div以免显得过于突兀
<el-menu-item>
<router-link
tag="div"
:to="resolvePath(cur.path)">
{{cur.meta.title}}
</router-link>
</el-menu-item>
最后看看 子菜单组件 < Sub-Menu >
的设计吧,和同根组件相差无几
<el-submenu :index="idx">
<template slot="title">
<i class="el-icon-location"></i>
<span>{{cur.meta.title}}</span>
</template>
<div>
<template v-for="(item, num) of cur.children">
<sub-menu
v-if="item.children && item.children.length>0"
:cur="item"
:pre_url="resolvePath(cur.path)"
/>
<menu-item
v-else
:cur="item"
:pre_url="resolvePath(cur.path)"
/>
</template>
</div>
</el-submenu>
到这里基本就大功告成了,不过还有几个注意事项:
(1)递归组件时,Vue组件的 name 不能缺省
(2)v-for 每个子结点的 key 用 cur.path 就好了,毕竟路由路径是不会重复的
(3)因为使用的是ElementUI的组件嘛,所以一些必须的属性如 index 还是要配的,这个可以按照配置 pre_url 属性的方法进行拼接。
最后看看实际的成果图: