Vue结合路由配置递归实现菜单栏
作者:小土豆biubiubiu
博客园:https://www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/2436173500265335
微信公众号:不知名宝藏程序媛(关注"不知名宝藏程序媛"免费领取前端电子书籍。文章公众号首发,关注公众号第一时间获取最新文章。)
作者文章的内容均来源于自己的实践,如果觉得有帮助到你的话,可以点赞❤️给个鼓励或留下宝贵意见
前言
在日常开发中,项目中的菜单栏都是已经实现好了的。如果需要添加新的菜单,只需要在路由配置
中新增一条路由,就可以实现菜单的添加。
相信大家和我一样,有时候会跃跃欲试自己去实现一个菜单栏。那今天我就将自己实现的菜单栏的整个思路和代码分享给大家。
本篇文章重在总结和分享菜单栏的一个
递归实现方式
,代码的优化
、菜单权限
等不在本篇文章范围之内,在文中的相关部分也会做一些提示,有个别不推荐的写法希望大家不要参考哦。同时可能会存在一些细节的功能没有处理或者没有提及到,忘知晓。
最终的效果
本次实现的这个菜单栏包含有一级菜单
、二级菜单
和三级菜单
这三种类型,基本上已经可以覆盖项目中不同的菜单需求。
后面会一步一步从易到难去实现这个菜单。
简单实现
我们都知道到element
提供了 NavMenu
导航菜单组件,因此我们直接按照文档将这个菜单栏做一个简单的实现。
基本的布局架构图如下:
菜单首页-menuIndex
首先要实现的是菜单首页
这个组件,根据前面的布局架构图并且参考官方文档,实现起来非常简单。
<!-- src/menu/menuIndex.vue -->
<template>
<div id="menu-index">
<el-container>
<el-header>
<TopMenu :logoPath="logoPath" :name="name"></TopMenu>
</el-header>
<el-container id="left-container">
<el-aside width="200px">
<LeftMenu></LeftMenu>
</el-aside>
<el-main>
<router-view/>
</el-main>
</el-container>
</el-container>
</div>
</template>
<script>
import LeftMenu from './leftMenu';
import TopMenu from './topMenu';
export default {
name: 'MenuIndex',
components: {LeftMenu, TopMenu},
data() {
return {
logoPath: require("../../assets/images/logo1.png"),
name: '员工管理系统'
}
}
}
</script>
<style lang="scss">
#menu-index{
.el-header{
padding: 0px;
}
}
</style>
顶部菜单栏-topMenu
顶部菜单栏主要就是一个logo
和产品名称
。
逻辑代码也很简单,我直接将代码贴上。
<!-- src/menu/leftMenu.vue -->
<template>
<div id="top-menu">
<img class="logo" :src="logoPath" />
<p class="name">{{name}}</p>
</div>
</template>
<script>
export default {
name: 'topMenu',
props: ['logoPath', 'name']
}
</script>
<style lang="scss" scoped>
$topMenuWidth: 80px;
$logoWidth: 50px;
$bg-color: #409EFF;
$name-color: #fff;
$name-size: 18px;
#top-menu{
height: $topMenuWidth;
text-align: left;
background-color: $bg-color;
padding: 20px 20px 0px 20px;
.logo {
width: $logoWidth;
display: inline-block;
}
.name{
display: inline-block;
vertical-align: bottom;
color: $name-color;
font-size: $name-size;
}
}
</style>
这段代码中包含了父组件
传递给子组件
的两个数据。
props: ['logoPath', 'name']
这个是父组件menuIndex
传递给子组件topMenu
的两个数据,分别是logo图标的路径
和产品名称
。
完成后的界面效果如下。
左侧菜单栏-leftMenu
首先按照官方文档实现一个简单的菜单栏。
<!-- src/menu/leftMenu.vue -->
<template>
<div id="left-menu">
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<el-menu-item index="1">
<i class="el-icon-s-home"></i>
<span slot="title">首页</span>
</el-menu-item>
<el-submenu index="2">
<template slot="title">
<i class="el-icon-user-solid"></i>
<span slot="title">员工管理</span>
</template>
<el-menu-item index="2-1">员工统计</el-menu-item>
<el-menu-item index="2-2">员工管理</el-menu-item>
</el-submenu>
<el-submenu index="3">
<template slot="title">
<i class="el-icon-s-claim"></i>
<span slot="title">考勤管理</span>
</template>
<el-menu-item index="3-1">考勤统计</el-menu-item>
<el-menu-item index="3-2">考勤列表</el-menu-item>
<el-menu-item index="3-2">异常管理</el-menu-item>
</el-submenu>
<el-submenu index="4">
<template slot="title">
<i class="el-icon-location"></i>
<span slot="title">工时管理</span>
</template>
<el-menu-item index="4-1">工时统计</el-menu-item>
<el-submenu index="4-2">
<template slot="title">工时列表</template>
<el-menu-item index="4-2-1">选项一</el-menu-item>
<el-menu-item index="4-2-2">选项二</el-menu-item>
</el-submenu>
</el-submenu>
</el-menu>
</div>
</template>
<script>
export default {
name: 'LeftMenu'
}
</script>
<style lang="scss">
// 使左边的菜单外层的元素高度充满屏幕
#left-container{
position: absolute;
top: 100px;
bottom: 0px;
// 使菜单高度充满屏幕
#left-menu, .el-menu-vertical-demo{
height: 100%;
}
}
</style>
注意菜单的样式代码,设置了
绝对定位
,并且设置top
、bottom
使菜单高度撑满屏幕。
此时在看下界面效果。
基本上算是实现了一个简单的菜单布局。
不过在实际项目在设计的时候,菜单栏的内容有可能来自后端给我们返回的数据,其中包含菜单名称
、菜单图标
以及菜单之间的层级关系
。
总而言之,我们的菜单是动态生成的,而不是像前面那种固定的写法。因此下面我将实现一个动态生成的菜单,菜单的数据来源于我们的路由配置
。
结合路由配置实现动态菜单
路由配置
首先,我将项目的路由配置代码贴出来。
import Vue from 'vue';
import Router from "vue-router";
// 菜单
import MenuIndex from '@/components/menu/menuIndex.vue';
// 首页
import Index from '@/components/homePage/index.vue';
// 人员统计
import EmployeeStatistics from '@/components/employeeManage/employeeStatistics.vue';
import EmployeeManage from '@/components/employeeManage/employeeManage.vue'
// 考勤
// 考勤统计
import AttendStatistics from '@/components/attendManage/attendStatistics';
// 考勤列表
import AttendList from '@/components/attendManage/attendList.vue';
// 异常管理
import ExceptManage from '@/components/attendManage/exceptManage.vue';
// 工时
// 工时统计
import TimeStatistics from '@/components/timeManage/timeStatistics.vue';
// 工时列表
import TimeList from '@/components/timeManage/timeList.vue';
Vue.use(Router)
let routes = [
// 首页(仪表盘、快速入口)
{
path: '/index',
name: 'index',
component: MenuIndex,
redirect: '/index',
meta: {
title: '首页', // 菜单标题
icon: 'el-icon-s-home', // 图标
hasSubMenu: false, // 是否包含子菜单,false 没有子菜单;true 有子菜单
},
children:[
{
path: '/index',
component: Index
}
]
},
// 员工管理
{
path: '/employee',
name: 'employee',
component: MenuIndex,
redirect: '/employee/employeeStatistics',
meta: {
title: '员工管理', // 菜单标题
icon: 'el-icon-user-solid', // 图标
hasSubMenu: true, // 是否包含子菜单
},
children: [
// 员工统计
{
path: 'employeeStatistics',
name: 'employeeStatistics',
meta: {
title: '员工统计', // 菜单标题,
hasSubMenu: false // 是否包含子菜单
},
component: EmployeeStatistics,
},
// 员工管理(增删改查)
{
path: 'employeeManage',
name: 'employeeManage',
meta: {
title: '员工管理', // 菜单标题
hasSubMenu: false // 是否包含子菜单
},
component: EmployeeManage
}
]
},
// 考勤管理
{
path: '/attendManage',
name: 'attendManage',
component: MenuIndex,
redirect: '/attendManage/attendStatistics',
meta: {
title: '考勤管理', // 菜单标题
icon: 'el-icon-s-claim', // 图标
hasSubMenu: true, // 是否包含子节点,false 没有子菜单;true 有子菜单
},
children:[
// 考勤统计
{
path: 'attendStatistics',
name: 'attendStatistics',
meta: {
title: '考勤统计', // 菜单标题
hasSubMenu: false // 是否包含子菜单
},
component: AttendStatistics,
},
// 考勤列表
{
path: 'attendList',
name: 'attendList',
meta: {
title: '考勤列表', // 菜单标题
hasSubMenu: false // 是否包含子菜单
},
component: AttendList,
},
// 异常管理
{
path: 'exceptManage',
name: 'exceptManage',
meta: {
title: '异常管理', // 菜单标题
hasSubMenu: false // 是否包含子菜单
},
component: ExceptManage,
}
]
},
// 工时管理
{
path: '/timeManage',
name: 'timeManage',
component: MenuIndex,
redirect: '/timeManage/timeStatistics',
meta: {
title: '工时管理', // 菜单标题
icon: 'el-icon-message-solid', // 图标
hasSubMenu: true, // 是否包含子菜单,false 没有子菜单;true 有子菜单
},
children: [
// 工时统计
{
path: 'timeStatistics',
name: 'timeStatistics',
meta: {
title: '工时统计', // 菜单标题
hasSubMenu: false // 是否包含子菜单
},
component: TimeStatistics
},
// 工时列表
{
path: 'timeList',
name: 'timeList',
component: TimeList,
meta: {
title: '工时列表', // 菜单标题
hasSubMenu: true // 是否包含子菜单
},
children: [
{
path: 'options1',
meta: {
title: '选项一', // 菜单标题
hasSubMenu: false // 是否包含子菜单
},
},
{
path: 'options2',
meta: {
title: '选项二', // 菜单标题
hasSubMenu: false // 是否包含子菜单
},
},
]
}
]
},
];
export default new Router({
routes
})
在这段代码的最开始部分,我们引入了需要使用的组件,接着就对路由进行了配置。
此处使用了直接引入组件的方式,项目开发中
不推荐
这种写法,应该使用懒加载
的方式
路由配置除了最基础的path
、component
以及children
之外,还配置了一个meta
数据项。
meta: {
title: '工时管理', // 菜单标题
icon: 'el-icon-message-solid', // 图标
hasSubMenu: true, // 是否包含子节点,false 没有子菜单;true 有子菜单
}
meta
数据包含的配置有菜单标题
(title
)、图标的类名
(icon
)和是否包含子节点
(hasSubMenu
)。
根据title
、icon
这两个配置项,可以展示当前菜单的标题
和图标
。
hasSubMenu
表示当前的菜单项是否有子菜单,如果当前菜单包含有子菜单(hasSubMenu
为true
),那当前菜单对应的标签元素就是el-submenu
;否则当前菜单对应的菜单标签元素就是el-menu-item
。
是否包含子菜单是一个非常关键的逻辑,我在实现的时候是直接将其配置到了
meta.hasSubMenu
这个参数里面。
根据路由实现多级菜单
路由配置完成后,我们就需要根据路由实现菜单了。
获取路由配置
既然要根据路由配置实现多级菜单,那第一步就需要获取我们的路由数据。这里我使用简单粗暴的方式去获取路由配置数据:this.$router.options.routes
。
这种方式也不太适用日常的项目开发,因为无法在获取的时候对路由做进一步的处理,比如
权限控制
。
我们在组件加载时打印一下这个数据。
// 代码位置:src/menu/leftMenu.vue
mounted(){
console.log(this.$router.options.routes);
}
打印结果如下。
可以看到这个数据就是我们在router.js
中配置的路由数据。
为了方便使用,我将这个数据定义到计算属性中。
// 代码位置:src/menu/leftMenu.vue
computed: {
routesInfo: function(){
return this.$router.options.routes;
}
}
一级菜单
首先我们来实现一级菜单
。
主要的逻辑就是循环路由数据routesInfo
,在循环的时候判断当前路由route
是否包含子菜单,如果包含则当前菜单使用el-submenu
实现,否则当前菜单使用el-menu-item
实现。
<!-- src/menu/leftMenu.vue -->
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<!-- 一级菜单 -->
<!-- 循环路由数据 -->
<!-- 判断当前路由route是否包含子菜单 -->
<el-submenu
v-for="route in routesInfo"
v-if="route.meta.hasSubMenu"
:index="route.path">
<template slot="title">
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</template>
</el-submenu>
<el-menu-item :index="route.path" v-else>
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</el-menu-item>
</el-menu>
结果:
可以看到,我们第一级菜单已经生成了,员工管理
、考勤管理
、工时管理
这三个菜单是有子菜单的,所以会有一个下拉按钮。
不过目前点开是没有任何内容的,接下来我们就来实现这三个菜单下的二级菜单
。
二级菜单
二级菜单
的实现和一级菜单
的逻辑是相同的:循环子路由route.children
,在循环的时候判断子路由childRoute
是否包含子菜单,如果包含则当前菜单使用el-submenu
实现,否则当前菜单使用el-menu-item
实现。
那话不多说,直接上代码。
<!-- src/menu/leftMenu.vue -->
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<!-- 一级菜单 -->
<!-- 循环路由数据 -->
<!-- 判断当前路由route是否包含子菜单 -->
<el-submenu
v-for="route in routesInfo"
v-if="route.meta.hasSubMenu"
:index="route.path">
<template slot="title">
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</template>
<!-- 二级菜单 -->
<!-- 循环子路由`route.children` -->
<!-- 循环的时候判断子路由`childRoute`是否包含子菜单 -->
<el-submenu
v-for="childRoute in route.children"
v-if="childRoute.meta.hasSubMenu"
:index="childRoute.path">
<template slot="title">
<i :class="childRoute.meta.icon"></i>
<span slot="title">{{childRoute.meta.title}}</span>
</template>
</el-submenu>
<el-menu-item :index="childRoute.path" v-else>
<i :class="childRoute.meta.icon"></i>
<span slot="title">{{childRoute.meta.title}}</span>
</el-menu-item>
</el-submenu>
<el-menu-item :index="route.path" v-else>
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</el-menu-item>
</el-menu>
结果如下:
可以看到二级菜单
成功实现。
三级菜单
三级菜单
就不用多说了,和一级
、二级
逻辑相同,这里还是直接上代码。
<!-- src/menu/leftMenu.vue -->
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<!-- 一级菜单 -->
<!-- 循环路由数据 -->
<!-- 判断当前路由route是否包含子菜单 -->
<el-submenu
v-for="route in routesInfo"
v-if="route.meta.hasSubMenu"
:index="route.path">
<template slot="title">
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</template>
<!-- 二级菜单 -->
<!-- 循环子路由`route.children` -->
<!-- 循环的时候判断子路由`childRoute`是否包含子菜单 -->
<el-submenu
v-for="childRoute in route.children"
v-if="childRoute.meta.hasSubMenu"
:index="childRoute.path">
<template slot="title">
<i :class="childRoute.meta.icon"></i>
<span slot="title">{{childRoute.meta.title}}</span>
</template>
<!-- 三级菜单 -->
<!-- 循环子路由`childRoute.children` -->
<!-- 循环的时候判断子路由`child`是否包含子菜单 -->
<el-submenu
v-for="child in childRoute.children"
v-if="child.meta.hasSubMenu"
:index="child.path">
<template slot="title">
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}}</span>
</template>
</el-submenu>
<el-menu-item :index="child.path" v-else>
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}}</span>
</el-menu-item>
</el-submenu>
<el-menu-item :index="childRoute.path" v-else>
<i :class="childRoute.meta.icon"></i>
<span slot="title">{{childRoute.meta.title}}</span>
</el-menu-item>
</el-submenu>
<el-menu-item :index="route.path" v-else>
<i :class="route.meta.icon"></i>
<span slot="title">{{route.meta.title}}</span>
</el-menu-item>
</el-menu>
可以看到工时列表
下的三级菜单
已经显示了。
总结
此时我们已经结合路由配置
实现了这个动态的菜单。
不过这样的代码在逻辑上相关于三层嵌套
的for
循环,对应的是我们有三层的菜单。
假如我们有四层
、五层
甚至更多层的菜单时,那我们还得在嵌套更多层for
循环。很显然这样的方式暴露了前面多层for
循环的缺陷,所以我们就需要对这样的写法进行一个改进。
递归实现动态菜单
前面我们一直在说一级
、二级
、三级
菜单的实现逻辑都是相同的:循环子路由,在循环的时候判断子路由是否包含子菜单,如果包含则当前菜单使用el-submenu
实现,否则当前菜单使用el-menu-item
实现。那这样的逻辑最适合的就是使用递归
去实现。
所以我们需要将这部分共同的逻辑抽离出来作为一个独立的组件,然后递归的调用这个组件。
逻辑拆分
<!-- src/menu/menuItem.vue -->
<template>
<div>
<el-submenu
v-for="child in route"
v-if="child.meta.hasSubMenu"
:index="child.path">
<template slot="title">
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}}</span>
</template>
</el-submenu>
<el-menu-item :index="child.path" v-else>
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}}</span>
</el-menu-item>
</div>
</template>
<script>
export default {
name: 'MenuItem',
props: ['route']
}
</script>
需要注意的是,这次抽离出来的组件循环的时候直接循环的是route
数据,那这个route
数据是什么呢。
我们先看一下前面三层循环中循环的数据源分别是什么。
为了看得更清楚,我将前面代码中一些不相关的内容进行了删减。
<!-- src/menu/leftMenu.vue -->
<!-- 一级菜单 -->
<el-submenu
v-for="route in routesInfo"
v-if="route.meta.hasSubMenu">
<!-- 二级菜单 -->
<el-submenu
v-for="childRoute in route.children"
v-if="childRoute.meta.hasSubMenu">
<!-- 三级菜单 -->
<el-submenu
v-for="child in childRoute.children"
v-if="child.meta.hasSubMenu">
</el-submenu>
</el-submenu>
</el-submenu>
从上面的代码可以看到:
一级菜单循环的是`routeInfo`,即最初我们获取的路由数据`this.$router.options.routes`,循环出来的每一项定义为`route`
二级菜单循环的是`route.children`,循环出来的每一项定义为`childRoute`
三级菜单循环的是`childRoute.children`,循环出来的每一项定义为`child`
按照这样的逻辑,可以发现二级菜单
、三级菜单
循环的数据源都是相同的,即前一个循环结果项的children
,而一级菜单的数据来源于this.$router.options.routes
。
前面我们抽离出来的menuItem
组件,循环的是route
数据,即不管是一层菜单
还是二层
、三层菜单
,都是同一个数据源,因此我们需要统一数据源。那当然也非常好实现,我们在调用组件的时候,为组件传递不同的值即可。
代码实现
前面公共组件已经拆分出来了,后面的代码就非常好实现了。
首先是抽离出来的meunItem
组件,实现的是逻辑判断
以及递归调用自身
。
<!-- src/menu/menuItem.vue -->
<template>
<div>
<el-submenu
v-for="child in route"
v-if="child.meta.hasSubMenu"
:index="child.path">
<template slot="title">
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}}</span>
</template>
<!--递归调用组件自身 -->
<MenuItem :route="child.children"></MenuItem>
</el-submenu>
<el-menu-item :index="child.path" v-else>
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}}</span>
</el-menu-item>
</div>
</template>
<script>
export default {
name: 'MenuItem',
props: ['route']
}
</script>
接着是leftMenu
组件,调用menuIndex
组件,传递原始的路由数据routesInfo
。
<!-- src/menu/leftMenu.vue -->
<template>
<div id="left-menu">
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<MenuItem :route="routesInfo"></MenuItem>
</el-menu>
</div>
</template>
<script>
import MenuItem from './menuItem'
export default {
name: 'LeftMenu',
components: { MenuItem }
}
</script>
<style lang="scss">
// 使左边的菜单外层的元素高度充满屏幕
#left-container{
position: absolute;
top: 100px;
bottom: 0px;
// 使菜单高度充满屏幕
#left-menu, .el-menu-vertical-demo{
height: 100%;
}
}
</style>
最终的结果这里就不展示了,和我们需要实现的结果是一致的。
功能完善
到此,我们结合路由配置实现了菜单栏
这个功能基本上已经完成了,不过这是一个缺乏灵魂的菜单栏,因为没有设置菜单的跳转,我们点击菜单栏还无法路由跳转到对应的组件
,所以接下来就来实现这个功能。
菜单跳转的实现方式有两种,第一种是NavMenu
组件提供的跳转方式。
第二种是在菜单上添加router-link
实现跳转。
那本次我选择的是第一种方式实现跳转,这种实现方式需要两个步骤才能完成,第一步是启用el-menu
上的router
;第二步是设置导航的index
属性。
那下面就来实现这两个步骤。
启用el-menu上的router
<!-- src/menu/leftMenu.vue -->
<!-- 省略其余未修改代码-->
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
router
:collapse="false">
<MenuItem :route="routesInfo">
</MenuItem>
</el-menu>
设置导航的index属性
首先我将每一个菜单标题对应需要设置的index
属性值列出来。
index
值对应的是每个菜单在路由中配置的path
值
首页
员工管理
员工统计 index="/employee/employeeStatistics"
员工管理 index="/employee/employeeManage"
考勤管理
考勤统计 index="/attendManage/attendStatistics"
考勤列表 index="/attendManage/attendList"
异常管理 index="/attendManage/exceptManage"
员工统计
员工统计 index="/timeManage/timeStatistics"
员工统计 index="/timeManage/timeList"
选项一 index="/timeManage/timeList/options1"
选项二 index="/timeManage/timeList/options2"
接着在回顾前面递归调用的组件,导航菜单的index
设置的是child.path
,为了看清楚child.path
的值,我将其添加菜单标题的右侧,让其显示到界面上。
<!-- src/menu/menuItem.vue -->
<!-- 省略其余未修改代码-->
<el-submenu
v-for="child in route"
v-if="child.meta.hasSubMenu"
:index="child.path">
<template slot="title">
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}} | {{child.path}}</span>
</template>
<!--递归调用组件自身 -->
<MenuItem :route="child.children"></MenuItem>
</el-submenu>
<el-menu-item :index="child.path" v-else>
<i :class="child.meta.icon"></i>
<span slot="title">{{child.meta.title}} | {{child.path}}</span>
</el-menu-item>
同时将菜单栏的宽度由200px
设置为400px
。
<!-- src/menu/menuIndex.vue -->
<!-- 省略其余未修改代码-->
<el-aside width="400px">
<LeftMenu></LeftMenu>
</el-aside>
然后我们看一下效果。
可以发现,child.path
的值就是当前菜单在路由中配置path
值(router.js
中配置的path
值)。
那么问题就来了,前面我们整理了每一个菜单标题对应需要设置的index
属性值,就目前来看,现在设置的index
值是不符合要求的。不过仔细观察现在菜单设置的index
值和正常值是有一点接近的,只是缺少了上一级菜单的path
值,如果能将上一级菜单
的path
值和当前菜单的path
值进行一个拼接,就能得到正确的index
值了。
那这个思路实现的方式依然是在递归时将当前菜单的path
作为参数传递给menuItem
组件。
<!-- src/menu/menuIndex.vue -->
<!--递归调用组件自身 -->
<MenuItem
:route="child.children"
:basepath="child.path">
</MenuItem>
将当前菜单的path
作为参数传递给menuItem
组件之后,在下一级菜单实现时,就能拿到上一级菜单的path
值。然后组件中将basepath
的值和当前菜单的path
值做一个拼接,作为当前菜单的index
值。
<!-- src/menu/menuIndex.vue -->
<el-menu-item :index="getPath(child.path)" v-else>
</el-menu-item>
<script>
import path from 'path'
export default {
name: 'MenuItem',
props: ['route','basepath'],
data(){
return {
}
},
methods :{
// routepath 为当前菜单的path值
// getpath: 拼接 当前菜单的上一级菜单的path 和 当前菜单的path
getPath: function(routePath){
return path.resolve(this.basepath, routePath);
}
}
}
</script>
再看一下界面。
我们可以看到二级菜单的index
值已经没问题了,但是仔细看,发现工时管理
-工时列表
下的两个三级菜单index
值还是有问题,缺少了工时管理
这个一级菜单的path
。
那这个问题是因为我们在调用组件自身是传递的basepath
有问题。
<!--递归调用组件自身 -->
<MenuItem
:route="child.children"
:basepath="child.path">
</MenuItem>
basepath
传递的只是上一级菜单的path
,在递归二级菜单
时,index
的值是一级菜单的path值
+二级菜单的path值
;那当我们递归三级菜单
时,index
的值就是二级菜单的path值
+三级菜单的path值
,这也就是为什么工时管理-工时列表
下的两个三级菜单index
值存在问题。
所以这里的basepath
值在递归的时候应该是累积
的,而不只是上一级菜单的path
值。因此借助递归算法的优势,basepath
的值也需要通过getPath
方法进行处理。
<MenuItem
:route="child.children"
:basepath="getPath(child.path)">
</MenuItem>
最终完整的代码如下。
<!-- src/menu/menuIndex.vue -->
<template>
<div>
<el-submenu
v-for="child in route"
v-if="child.meta.hasSubMenu"
:key="child.path"
:index="getPath(child.path)">
<template slot="title">
<i :class="child.meta.icon"></i>
<span slot="title">
{{child.meta.title}}
</span>
</template>
<!--递归调用组件自身 -->
<MenuItem
:route="child.children"
:basepath="getPath(child.path)">
</MenuItem>
</el-submenu>
<el-menu-item :index="getPath(child.path)" v-else>
<i :class="child.meta.icon"></i>
<span slot="title">
{{child.meta.title}}
</span>
</el-menu-item>
</div>
</template>
<script>
import path from 'path'
export default {
name: 'MenuItem',
props: ['route','basepath'],
data(){
return {
}
},
methods :{
// routepath 为当前菜单的path值
// getpath: 拼接 当前菜单的上一级菜单的path 和 当前菜单的path
getPath: function(routePath){
return path.resolve(this.basepath, routePath);
}
}
}
</script>
删除其余用来调试的代码
最终效果
文章的最后呢,将本次实现的最终效果在此展示一下。
选项一
和选项二
这两个三级菜单在路由配置中没有设置component
,这两个菜单只是为了实现三级菜单,在最后的结果演示中,我已经删除了路由中配置的这两个三级菜单此处在
leftMenu
组件中为el-menu
开启了unique-opened
在
menuIndex
组件中,将左侧菜单栏的宽度改为200px
作者:小土豆biubiubiu
博客园:https://www.cnblogs.com/HouJiao/
掘金:https://juejin.im/user/2436173500265335
微信公众号:不知名宝藏程序媛(关注"不知名宝藏程序媛"免费领取前端电子书籍。文章公众号首发,关注公众号第一时间获取最新文章。)
作者文章的内容均来源于自己的实践,如果觉得有帮助到你的话,可以点赞❤️给个鼓励或留下宝贵意见