Vue结合路由配置递归实现菜单栏功能

本次实现的这个菜单栏包含有一级菜单二级菜单三级菜单这三种类型,基本上已经可以覆盖项目中不同的菜单需求。

后面会一步一步从易到难去实现这个菜单。

简单实现

我们都知道到element提供了 NavMenu 导航菜单组件,因此我们直接按照文档将这个菜单栏做一个简单的实现。

基本的布局架构图如下:

左侧菜单栏-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>

注意菜单的样式代码,设置了绝对定位,并且设置topbottom使菜单高度撑满屏幕。

此时在看下界面效果。

可以看到这个数据就是我们在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>

结果如下:

可以看到工时列表下的三级菜单已经显示了。

总结

此时我们已经结合路由配置实现了这个动态的菜单。

不过这样的代码在逻辑上相关于三层嵌套for循环,对应的是我们有三层的菜单。

假如我们有四层五层甚至更多层的菜单时,那我们还得在嵌套更多层for循环。很显然这样的方式暴露了前面多层for循环的缺陷,所以我们就需要对这样的写法进行一个改进。

递归实现动态菜单

前面我们一直在说一级二级三级菜单的实现逻辑都是相同的:循环子路由,在循环的时候判断子路由是否包含子菜单,如果包含则当前菜单使用el-submenu实现,否则当前菜单使用el-menu-item实现。那这样的逻辑最适合的就是使用递归去实现。

所以我们需要将这部分共同的逻辑抽离出来作为一个独立的组件,然后递归的调用这个组件。

代码实现

前面公共组件已经拆分出来了,后面的代码就非常好实现了。

首先是抽离出来的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组件提供的跳转方式。

可以发现,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>

再看一下界面。

选项一选项二这两个三级菜单在路由配置中没有设置component,这两个菜单只是为了实现三级菜单,在最后的结果演示中,我已经删除了路由中配置的这两个三级菜单

此处在leftMenu组件中为el-menu开启了unique-opened

menuIndex组件中,将左侧菜单栏的宽度改为200px

总结

到此这篇关于Vue结合路由配置递归实现菜单栏功能的文章就介绍到这了,更多相关vue 路由递归菜单栏内容请搜索来客网以前的文章或继续浏览下面的相关文章希望大家以后多多支持来客网!