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

前言

在日常开发中,项目中的菜单栏都是已经实现好了的。如果需要添加新的菜单,只需要在路由配置中新增一条路由,就可以实现菜单的添加。

相信大家和我一样,有时候会跃跃欲试自己去实现一个菜单栏。那今天我就将自己实现的菜单栏的整个思路和代码分享给大家。

本篇文章重在总结和分享菜单栏的一个递归实现方式代码的优化菜单权限等不在本篇文章范围之内,在文中的相关部分也会做一些提示,有个别不推荐的写法希望大家不要参考哦。

同时可能会存在一些细节的功能没有处理或者没有提及到,忘知晓。

最终的效果

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

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

简单实现

我们都知道到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>

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

此时在看下界面效果。

基本上算是实现了一个简单的菜单布局。

不过在实际项目在设计的时候,菜单栏的内容有可能来自后端给我们返回的数据,其中包含菜单名称菜单图标以及菜单之间的层级关系

总而言之,我们的菜单是动态生成的,而不是像前面那种固定的写法。因此下面我将实现一个动态生成的菜单,菜单的数据来源于我们的路由配置

结合路由配置实现动态菜单

路由配置

首先,我将项目的路由配置代码贴出来。

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
})

在这段代码的最开始部分,我们引入了需要使用的组件,接着就对路由进行了配置。

此处使用了直接引入组件的方式,项目开发中不推荐这种写法,应该使用懒加载的方式

路由配置除了最基础的pathcomponent以及children之外,还配置了一个meta数据项。

meta: {
 title: '工时管理', // 菜单标题
 icon: 'el-icon-message-solid', // 图标
 hasSubMenu: true, // 是否包含子节点,false 没有子菜单;true 有子菜单
}

meta数据包含的配置有菜单标题(title)、图标的类名(icon)和是否包含子节点(hasSubMenu)。

根据titleicon这两个配置项,可以展示当前菜单的标题图标

hasSubMenu表示当前的菜单项是否有子菜单,如果当前菜单包含有子菜单(hasSubMenutrue),那当前菜单对应的标签元素就是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

总结

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

(0)

相关推荐

  • Vue-路由导航菜单栏的高亮设置方法

    如下所示: 默认情况下,路由的导航菜单,会自动给当前菜单添加router-link-exact-active 和router-link-active 类. 我们可以通过 linkActiveClass 来修改 router-link-active 这个类名, 在路由规则配置中添加配置项linkActiveClass: 'mui-active',通过'mui-active'来自定义控制菜单栏切换样式 以上这篇Vue-路由导航菜单栏的高亮设置方法就是小编分享给大家的全部内容了,希望能给大家一个参考,

  • Vue实现侧边菜单栏手风琴效果实例代码

    效果图如下所示: <template> <div class="asideBox"> <aside> <ul class="asideMenu"> <li v-for="(item,index) in menuList"> <div class="oneMenu" @click="showToggle(item,index)"> <

  • vue2.0使用v-for循环制作多级嵌套菜单栏

    使用v-for循环生成一个多级嵌套菜单栏,只要你学会了这个方法,几乎所有的菜单栏都可以实现了. 方法 <div class="level-one" v-if="obj.level == 1" v-for="obj in bar1"><a>{{obj.title}}</a> <div class="level-two" v-if="obj1.parentId == obj.id

  • 基于vue.js实现侧边菜单栏

    侧边菜单栏应该是很多项目里必不可少的 自己手写了一个 下面是效果图 下面就说一下实现的过程 还是比较简单的 首先导入一下需要的文件 <link rel="stylesheet" type="text/css" href="bootstrap/css/bootstrap.min.css" rel="external nofollow" > <link rel="stylesheet" typ

  • Vue 菜单栏点击切换单个class(高亮)的方法

    步骤: 遍历对象(goods)获取菜单栏每一项的对象(item)和下标(index) 添加点击事件toggle(),传入下标参数:@click="fn1();fn2()" 动态切换classname::class="{'active':index ==checkindex }"> (class赋予对应下标值的DOM) ps:该方法直接切换class,不需要手动添加清除其他非动态DOM的class html <ul> <li v-for=&qu

  • vue如何实现自定义底部菜单栏

    最近vue不是特别火,自己想写一个vue 的底部菜单栏,然后试着开始写,起来还是听痛苦的,但是还是写出来,这个过程重查询了一些资料和看了一些视频. 1 写好界面 这是我写好的四个界面 2 在router.js重定义路由 在一级路由下面定义自己tabbr的子路由. routes: [ { path: '/', name: 'index', component:()=>import('./views/index'), //懒加载引入,路由 children:[ {path:'',redirect:'

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

    前言 在日常开发中,项目中的菜单栏都是已经实现好了的.如果需要添加新的菜单,只需要在路由配置中新增一条路由,就可以实现菜单的添加. 相信大家和我一样,有时候会跃跃欲试自己去实现一个菜单栏.那今天我就将自己实现的菜单栏的整个思路和代码分享给大家. 本篇文章重在总结和分享菜单栏的一个递归实现方式,代码的优化.菜单权限等不在本篇文章范围之内,在文中的相关部分也会做一些提示,有个别不推荐的写法希望大家不要参考哦. 同时可能会存在一些细节的功能没有处理或者没有提及到,忘知晓. 最终的效果 本次实现的这个菜

  • vue动态路由配置及路由传参的方式

    动态路由: 当我们很多个页面或者组件都要被很多次重复利用的时候,我们的路由都指向同一个组件,这时候从不同组件进入一个"共用"的组件,并且还要传参数,渲染不同的数据 这就要用到动态路由跟路由传参了! 首先我们来了解下router-link这个组件: 简单来说,它是个导航器,利用to属性导航到目标组件,并且在渲染的时候会自动生成一个a标签,当然官方也有说明,加个tag标签属性就可以渲染不同的标签,可以浏览器端查看到 并且当一个导航器被激活的时候,会自动加上一个css的激活样式,可以全局在路

  • vue获取路由详细内容信息方法实例

    目录 前言: 路由(router)的信息: 获取路由的所有信息 获取路由中每个信息的单个值 获取路由中需要显示的值 补充:vue $router和$route的区别 总结: 前言: vue 中路由(router)的功能就是:把 url 与 应用中的对应的组件进行关联,通过不同的 url 访问不同的组件.但是如果我们想要获取路由中的信息改如何做呢,今天我就给大家详细讲解一下如何获取路由的详细信息. 路由(router)的信息: routes: [ { path: '/', redirect:'lo

  • 详解如何写出一个利于扩展的vue路由配置

    前言 从历往经验来看,开发一个新项目,往往在刚开始部署项目,到项目的正式交付,以及交付后的后续维护,功能增强等过程,都需要对项目的一些已有结构和逻辑进行调整. 因此,如果有些内容刚建项目时不考虑好未来的可扩展性,后续调整会很麻烦. 这里先来说,在vue项目中,如何写路由配置,更利于未来可扩展. vue-router的基本配置 为了方便新学者的阅读与理解.先来看一下最基本的路由是如何配置的 // 0. 导入Vue和VueRouter脚本,如果使用模块化机制编程,要调用 Vue.use(VueRou

  • ant design vue导航菜单与路由配置操作

    此功能包含: 1.根据动态路由自动展开与自动选择对应路由所在页面菜单 2.只展开一个子菜单 3.兄弟组件控制菜单与路由 <a-menu :openKeys="openKeys" :selectedKeys="selectedKeys" mode="inline" theme="dark" :inlineCollapsed="$store.state.isCollapse" @click='select

  • vue实现路由切换改变title功能

    由于vue项目通常是单页应用,因此在入口文件index.html只有一个title,单页所展示的若干页面只是随着路由的切换而在同一个index.html上不同的渲染而已,因此此时的title属性是不会随着页面的切换而变更的 那么想实现路由切换title变换可以通过vue-router的导航守卫来实现,最简单的的目录结构可如下所示 ├── index.html ├── main.js ├── api │ └── ... # 抽取出API请求 ├── common │ └── constants.j

  • vue.js路由mode配置之去掉url上默认的#方法

    比如 : http://localhost:8080/#/login 路由中间默认带有 # 如果需要去掉#,只需将mode的默认值'hash'改为'history'即可. router.js : import Router from 'vue-router' import routers from './routers' export default () => { return new Router({ routers, mode: 'history' // 加上这个配置项,url默认的 #

  • vue 开发之路由配置方法详解

    本文实例讲述了vue 开发之路由配置方法.分享给大家供大家参考,具体如下: 概要 用 Vue.js + vue-router 创建单页应用,是非常简单的.使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 vue-router 添加进来,我们需要做的是,将组件(components)映射到路由(routes),然后告诉 vue-router 在哪里渲染它们. 实现代码 1.在main.js 中引入 router.3 import router from './router/i

  • Nginx配置 location模块实现路由(反向代理、重定向)功能

    目录 前置文章: 匹配规则 proxy_pass 代理转发 root 与 index root 与 alias location 执行过程 rewrite 重定向 前置文章: Linux(CentOS7) 下 Nginx1.15.8 安装步骤 Nginx 的配置文件 nginx.conf 我们访问一个网址,服务器返回对应的资源.那么一个网址是如何对应一个资源的呢?用 Nginx 可以很好地帮我们实现路由功能,我们所有需要做的就是配置好 location 模块. 语法规则 location [=|

  • Vue路由配置方法详细介绍

    目录 手动配置Vue-router环境 组件内部跳转路由与传参useRouter,useRoute 手动配置Vue-router环境 1.下载包: npm i vue-router --save或者 npm i vue-router --S 或者用cdn引入 2.创建路由的js文件(路由.子路由.重定向.开启history模式) createRouter.createWebHistory //路由文件 import { createRouter, createWebHistory } from

随机推荐