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

2023-11-05

转载
https://www.cnblogs.com/HouJiao/p/13139901.html
完整代码在码云
https://gitee.com/zhuxueling/router-menu.git
在日常开发中,项目中的菜单栏都是已经实现好了的。如果需要添加新的菜单,只需要在路由配置中新增一条路由,就可以实现菜单的添加。

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

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

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

最终的效果
在这里插入图片描述
本次实现的这个菜单栏包含有一级菜单、二级菜单和三级菜单这三种类型,基本上已经可以覆盖项目中不同的菜单需求。

后面会一步一步从易到难去实现这个菜单。
简单实现
我们都知道到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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue结合路由配置递归实现菜单栏 的相关文章

随机推荐

  • 这个 Chrome 插件,让你的 ChatGPT 不再报错

    ChatGPT的官网最近几天报错越来越频繁了 相信大家都发现了 一旦你离开页面时间比较久 再度返回跟它进行对话 就会出现如下报错 虽然这个报错信息以前也出现过 但现在的频率确实过高 对于每天需要使用 ChatGPT 处理大量任务的用户来说
  • [ kvm ] 进程的处理器亲和性和vCPU的绑定

    cpu调用进程或线程的方式 Linux内核的进程调度器根据自有的调度策略将系统中的一个进程调度到某个CPU上执行 一个进程在前一个执行时间是在cpuM上运行 而在后一个执行时间则是在cpuN上运行 这样的情况在cpu中是很可能发生的 因为l
  • 有关CSS3 3D盒子模型的一些总结

    以前就想学CSS3动画 觉得挺高级的 但后来因为一些原因 没能理解好 也没有时间 最近重新学了一波 为了帮助那些像我一样理解能力不太好的人 同时也使自己更好的理解知识点 这里做一下总结 主要是我在学习过程中遇到的一些问题 如果有写的不清晰的
  • 安防天下5、6——视频编码器技术DVS、网络录像机(NVR)技术

    视频编码器技术DVS DVS Digital vedio server 的出现 标志着视频监控系统进入了网络时代 编码器的主要功能是编码压缩及网络传输 适合应用再监控点比较分散的应用环境中 但从本质上讲 DVS还不是纯粹的网络监控设备 因为
  • Linux常用命令大全(详细版)

    目录 1 Linux管理文件和目录的命令 2 有关磁盘空间的命令 3 文件备份和压缩命令 4 有关关机和查看系统信息的命令 5 管理使用者和设立权限的命令 6 线上查询的命令 7 文件阅读的命令 8 网络操作命令 9 其他命令 详细版本 1
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全

    Bean public ColorFactoryBean colorFactoryBean return new ColorFactoryBean 创建一个spring定义的FactoryBean public class ColorFac
  • 原生js实现导航栏拖拽滑动(适用于pc端和手机端)

    先贴一张动图看看效果吧 下面把代码贴上注释都在代码边上
  • 【PyTorch】torch.utils.data.Dataset 介绍与实战

    文章目录 一 前言 二 torch utils data Dataset 是什么 1 干什么用的 2 长什么样子 三 通过继承 torch utils data Dataset 定义自己的数据集类 四 为什么要定义自己的数据集类 五 实战
  • 什么是多态?如何实现?只看这一篇就够了

    1 多态的概念 1 1 概念 多态的概念 通俗来说 就是多种形态 具体点就是去完成某个行为 当不同的对象去完成时会产生出不同的状态 2 多态的定义及实现 2 1多态的构成条件 多态是在不同继承关系的类对象 去调用同一函数 产生了不同的行为
  • 在任务管理器结束某些任务后电脑突然白屏

    1 问题描述 任务管理器 可以通过单击右键电脑底部任务栏找到 快捷键 ctrl shfit esc 一般我们可以在 任务管理器 中强制结束一些任务 尤其是电脑特别卡的时候 但是 有时候一不留神 不知道结束了什么任务 电脑突然就白屏了 2 解
  • 【算法与数据结构】530、LeetCode二叉搜索树的最小绝对差

    文章目录 一 题目 二 解法 三 完整代码 所有的LeetCode题解索引 可以看这篇文章 算法和数据结构 LeetCode题解 一 题目 二 解法 思路分析 二叉搜索树的性质是左子树的所有节点键值小于中间节点键值 右子树的所有节点键值大于
  • C语言快速入门(2)

    目录 1 常量 1 1字面常量 1 2const修饰的常变量 1 3 define定义的标识符常量 1 4枚举常量 2 字符串和转义字符 2 1字符串 2 2转义字符 3 注释 4 选择语句 5 循环语句 6 函数 7 数组 7 1数组的定
  • css实现单行,多行文本超出文本显示省略号

    1 单行文本 div width 100px 必须要设置 overflow hidden 超出部分隐藏 white space nowrap 禁止换行 强制文本在一行显示 text overflow ellipsis 溢出文本显示省略号 使
  • to load JavaHL Library解决方法

    解决方法 Window Preferences Team SVN 在SVN接口的下拉框可以看到 默认选择的是JavaHL JNI Not Available 手动更改为SVNKit Pure Java SVNKit v1 3 5 7406
  • msys2安装与配置: 在windows上使用linux工具链g++和包管理工具pacman C++开发

    文章目录 为什么用这个msys2 下载 doc 安装 很简单 初次运行 做些配置 更新 软件安装与卸载方法 安装必要的软件包 设置win环境变量 在windows terminal中使用 在vscode中使用 为什么用这个msys2 方便w
  • OSPF详解及简单配置

    动态路由协议 直连路由器之间构建邻居关系 通过收发各类数据包进行共享信息 来获取未知的路由信息 基于某种算法自动生成未知网段最优路径的解 将其加载在路由表中 判断一个网络协议的好坏 通常关注三方面 收敛速度快 占用资源少 选路佳 各类协议的
  • Aspose最新版22.8教程

    直接上代码 用vs开发工具写一段代码 word转pdf new Document this txtWordFile Text Save new pdf new PdfSaveOptions 编译 生成exe 打开dnspy 将生成的exe和
  • 王姓名字大全

    土 王 士 王 健 王雯新 王美玲 王 振 王诗雅 王博岩 王 一 王德丹 王竣臣 王资存 王 一 王宇陈 王 颖 王淑华 王骏臣 王阳菲 王孜臣 王 三 王 婷 王 樱 王 莉 王鹏哲 王玺毓 王新华 王 任 王之惠 王韬涵 王添乐 王孝
  • 超详细前端面试八股文

    今天在掘金看到一篇特别好的前端八股文 链接 连八股文都不懂还指望在前端混下去么 掘金
  • Vue结合路由配置递归实现菜单栏

    转载 https www cnblogs com HouJiao p 13139901 html 完整代码在码云 https gitee com zhuxueling router menu git 在日常开发中 项目中的菜单栏都是已经实现