Vue結(jié)合路由配置遞歸實(shí)現(xiàn)菜單欄功能

前言
在日常開(kāi)發(fā)中,項(xiàng)目中的菜單欄都是已經(jīng)實(shí)現(xiàn)好了的。如果需要添加新的菜單,只需要在路由配置中新增一條路由,就可以實(shí)現(xiàn)菜單的添加。
相信大家和我一樣,有時(shí)候會(huì)躍躍欲試自己去實(shí)現(xiàn)一個(gè)菜單欄。那今天我就將自己實(shí)現(xiàn)的菜單欄的整個(gè)思路和代碼分享給大家。
本篇文章重在總結(jié)和分享菜單欄的一個(gè)遞歸實(shí)現(xiàn)方式,代碼的優(yōu)化、菜單權(quán)限等不在本篇文章范圍之內(nèi),在文中的相關(guān)部分也會(huì)做一些提示,有個(gè)別不推薦的寫(xiě)法希望大家不要參考哦。
同時(shí)可能會(huì)存在一些細(xì)節(jié)的功能沒(méi)有處理或者沒(méi)有提及到,忘知曉。
最終的效果

本次實(shí)現(xiàn)的這個(gè)菜單欄包含有一級(jí)菜單、二級(jí)菜單和三級(jí)菜單這三種類型,基本上已經(jīng)可以覆蓋項(xiàng)目中不同的菜單需求。
后面會(huì)一步一步從易到難去實(shí)現(xiàn)這個(gè)菜單。
簡(jiǎn)單實(shí)現(xiàn)
我們都知道到element提供了 NavMenu 導(dǎo)航菜單組件,因此我們直接按照文檔將這個(gè)菜單欄做一個(gè)簡(jiǎn)單的實(shí)現(xiàn)。
基本的布局架構(gòu)圖如下:

菜單首頁(yè)-menuIndex
首先要實(shí)現(xiàn)的是菜單首頁(yè)這個(gè)組件,根據(jù)前面的布局架構(gòu)圖并且參考官方文檔,實(shí)現(xiàn)起來(lái)非常簡(jiǎn)單。
<!-- 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: '員工管理系統(tǒng)'
}
}
}
</script>
<style lang="scss">
#menu-index{
.el-header{
padding: 0px;
}
}
</style>
頂部菜單欄-topMenu
頂部菜單欄主要就是一個(gè)logo和產(chǎn)品名稱。
邏輯代碼也很簡(jiǎn)單,我直接將代碼貼上。
<!-- 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>
這段代碼中包含了父組件傳遞給子組件的兩個(gè)數(shù)據(jù)。
props: ['logoPath', 'name']
這個(gè)是父組件menuIndex傳遞給子組件topMenu的兩個(gè)數(shù)據(jù),分別是logo圖標(biāo)的路徑和產(chǎn)品名稱。
完成后的界面效果如下。

左側(cè)菜單欄-leftMenu
首先按照官方文檔實(shí)現(xiàn)一個(gè)簡(jiǎn)單的菜單欄。
<!-- 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">首頁(yè)</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">員工統(tǒng)計(jì)</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">考勤統(tǒng)計(jì)</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">工時(shí)管理</span>
</template>
<el-menu-item index="4-1">工時(shí)統(tǒng)計(jì)</el-menu-item>
<el-submenu index="4-2">
<template slot="title">工時(shí)列表</template>
<el-menu-item index="4-2-1">選項(xiàng)一</el-menu-item>
<el-menu-item index="4-2-2">選項(xiàng)二</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>
注意菜單的樣式代碼,設(shè)置了絕對(duì)定位,并且設(shè)置top、bottom使菜單高度撐滿屏幕。
此時(shí)在看下界面效果。

基本上算是實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的菜單布局。
不過(guò)在實(shí)際項(xiàng)目在設(shè)計(jì)的時(shí)候,菜單欄的內(nèi)容有可能來(lái)自后端給我們返回的數(shù)據(jù),其中包含菜單名稱、菜單圖標(biāo)以及菜單之間的層級(jí)關(guān)系。
總而言之,我們的菜單是動(dòng)態(tài)生成的,而不是像前面那種固定的寫(xiě)法。因此下面我將實(shí)現(xiàn)一個(gè)動(dòng)態(tài)生成的菜單,菜單的數(shù)據(jù)來(lái)源于我們的路由配置。
結(jié)合路由配置實(shí)現(xiàn)動(dòng)態(tài)菜單
路由配置
首先,我將項(xiàng)目的路由配置代碼貼出來(lái)。
import Vue from 'vue';
import Router from "vue-router";
// 菜單
import MenuIndex from '@/components/menu/menuIndex.vue';
// 首頁(yè)
import Index from '@/components/homePage/index.vue';
// 人員統(tǒng)計(jì)
import EmployeeStatistics from '@/components/employeeManage/employeeStatistics.vue';
import EmployeeManage from '@/components/employeeManage/employeeManage.vue'
// 考勤
// 考勤統(tǒng)計(jì)
import AttendStatistics from '@/components/attendManage/attendStatistics';
// 考勤列表
import AttendList from '@/components/attendManage/attendList.vue';
// 異常管理
import ExceptManage from '@/components/attendManage/exceptManage.vue';
// 工時(shí)
// 工時(shí)統(tǒng)計(jì)
import TimeStatistics from '@/components/timeManage/timeStatistics.vue';
// 工時(shí)列表
import TimeList from '@/components/timeManage/timeList.vue';
Vue.use(Router)
let routes = [
// 首頁(yè)(儀表盤(pán)、快速入口)
{
path: '/index',
name: 'index',
component: MenuIndex,
redirect: '/index',
meta: {
title: '首頁(yè)', // 菜單標(biāo)題
icon: 'el-icon-s-home', // 圖標(biāo)
hasSubMenu: false, // 是否包含子菜單,false 沒(méi)有子菜單;true 有子菜單
},
children:[
{
path: '/index',
component: Index
}
]
},
// 員工管理
{
path: '/employee',
name: 'employee',
component: MenuIndex,
redirect: '/employee/employeeStatistics',
meta: {
title: '員工管理', // 菜單標(biāo)題
icon: 'el-icon-user-solid', // 圖標(biāo)
hasSubMenu: true, // 是否包含子菜單
},
children: [
// 員工統(tǒng)計(jì)
{
path: 'employeeStatistics',
name: 'employeeStatistics',
meta: {
title: '員工統(tǒng)計(jì)', // 菜單標(biāo)題,
hasSubMenu: false // 是否包含子菜單
},
component: EmployeeStatistics,
},
// 員工管理(增刪改查)
{
path: 'employeeManage',
name: 'employeeManage',
meta: {
title: '員工管理', // 菜單標(biāo)題
hasSubMenu: false // 是否包含子菜單
},
component: EmployeeManage
}
]
},
// 考勤管理
{
path: '/attendManage',
name: 'attendManage',
component: MenuIndex,
redirect: '/attendManage/attendStatistics',
meta: {
title: '考勤管理', // 菜單標(biāo)題
icon: 'el-icon-s-claim', // 圖標(biāo)
hasSubMenu: true, // 是否包含子節(jié)點(diǎn),false 沒(méi)有子菜單;true 有子菜單
},
children:[
// 考勤統(tǒng)計(jì)
{
path: 'attendStatistics',
name: 'attendStatistics',
meta: {
title: '考勤統(tǒng)計(jì)', // 菜單標(biāo)題
hasSubMenu: false // 是否包含子菜單
},
component: AttendStatistics,
},
// 考勤列表
{
path: 'attendList',
name: 'attendList',
meta: {
title: '考勤列表', // 菜單標(biāo)題
hasSubMenu: false // 是否包含子菜單
},
component: AttendList,
},
// 異常管理
{
path: 'exceptManage',
name: 'exceptManage',
meta: {
title: '異常管理', // 菜單標(biāo)題
hasSubMenu: false // 是否包含子菜單
},
component: ExceptManage,
}
]
},
// 工時(shí)管理
{
path: '/timeManage',
name: 'timeManage',
component: MenuIndex,
redirect: '/timeManage/timeStatistics',
meta: {
title: '工時(shí)管理', // 菜單標(biāo)題
icon: 'el-icon-message-solid', // 圖標(biāo)
hasSubMenu: true, // 是否包含子菜單,false 沒(méi)有子菜單;true 有子菜單
},
children: [
// 工時(shí)統(tǒng)計(jì)
{
path: 'timeStatistics',
name: 'timeStatistics',
meta: {
title: '工時(shí)統(tǒng)計(jì)', // 菜單標(biāo)題
hasSubMenu: false // 是否包含子菜單
},
component: TimeStatistics
},
// 工時(shí)列表
{
path: 'timeList',
name: 'timeList',
component: TimeList,
meta: {
title: '工時(shí)列表', // 菜單標(biāo)題
hasSubMenu: true // 是否包含子菜單
},
children: [
{
path: 'options1',
meta: {
title: '選項(xiàng)一', // 菜單標(biāo)題
hasSubMenu: false // 是否包含子菜單
},
},
{
path: 'options2',
meta: {
title: '選項(xiàng)二', // 菜單標(biāo)題
hasSubMenu: false // 是否包含子菜單
},
},
]
}
]
},
];
export default new Router({
routes
})
在這段代碼的最開(kāi)始部分,我們引入了需要使用的組件,接著就對(duì)路由進(jìn)行了配置。
此處使用了直接引入組件的方式,項(xiàng)目開(kāi)發(fā)中不推薦這種寫(xiě)法,應(yīng)該使用懶加載的方式
路由配置除了最基礎(chǔ)的path、component以及children之外,還配置了一個(gè)meta數(shù)據(jù)項(xiàng)。
meta: {
title: '工時(shí)管理', // 菜單標(biāo)題
icon: 'el-icon-message-solid', // 圖標(biāo)
hasSubMenu: true, // 是否包含子節(jié)點(diǎn),false 沒(méi)有子菜單;true 有子菜單
}
meta數(shù)據(jù)包含的配置有菜單標(biāo)題(title)、圖標(biāo)的類名(icon)和是否包含子節(jié)點(diǎn)(hasSubMenu)。
根據(jù)title、icon這兩個(gè)配置項(xiàng),可以展示當(dāng)前菜單的標(biāo)題和圖標(biāo)。
hasSubMenu表示當(dāng)前的菜單項(xiàng)是否有子菜單,如果當(dāng)前菜單包含有子菜單(hasSubMenu為true),那當(dāng)前菜單對(duì)應(yīng)的標(biāo)簽元素就是el-submenu;否則當(dāng)前菜單對(duì)應(yīng)的菜單標(biāo)簽元素就是el-menu-item。
是否包含子菜單是一個(gè)非常關(guān)鍵的邏輯,我在實(shí)現(xiàn)的時(shí)候是直接將其配置到了meta.hasSubMenu這個(gè)參數(shù)里面。
根據(jù)路由實(shí)現(xiàn)多級(jí)菜單
路由配置完成后,我們就需要根據(jù)路由實(shí)現(xiàn)菜單了。
獲取路由配置
既然要根據(jù)路由配置實(shí)現(xiàn)多級(jí)菜單,那第一步就需要獲取我們的路由數(shù)據(jù)。這里我使用簡(jiǎn)單粗暴的方式去獲取路由配置數(shù)據(jù):this.$router.options.routes。
這種方式也不太適用日常的項(xiàng)目開(kāi)發(fā),因?yàn)闊o(wú)法在獲取的時(shí)候?qū)β酚勺鲞M(jìn)一步的處理,比如權(quán)限控制。
我們?cè)诮M件加載時(shí)打印一下這個(gè)數(shù)據(jù)。
// 代碼位置:src/menu/leftMenu.vue
mounted(){
console.log(this.$router.options.routes);
}
打印結(jié)果如下。

可以看到這個(gè)數(shù)據(jù)就是我們?cè)?code>router.js中配置的路由數(shù)據(jù)。
為了方便使用,我將這個(gè)數(shù)據(jù)定義到計(jì)算屬性中。
// 代碼位置:src/menu/leftMenu.vue
computed: {
routesInfo: function(){
return this.$router.options.routes;
}
}
一級(jí)菜單
首先我們來(lái)實(shí)現(xiàn)一級(jí)菜單。
主要的邏輯就是循環(huán)路由數(shù)據(jù)routesInfo,在循環(huán)的時(shí)候判斷當(dāng)前路由route是否包含子菜單,如果包含則當(dāng)前菜單使用el-submenu實(shí)現(xiàn),否則當(dāng)前菜單使用el-menu-item實(shí)現(xiàn)。
<!-- src/menu/leftMenu.vue -->
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<!-- 一級(jí)菜單 -->
<!-- 循環(huán)路由數(shù)據(jù) -->
<!-- 判斷當(dāng)前路由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>
結(jié)果:

可以看到,我們第一級(jí)菜單已經(jīng)生成了,員工管理、考勤管理、工時(shí)管理這三個(gè)菜單是有子菜單的,所以會(huì)有一個(gè)下拉按鈕。

不過(guò)目前點(diǎn)開(kāi)是沒(méi)有任何內(nèi)容的,接下來(lái)我們就來(lái)實(shí)現(xiàn)這三個(gè)菜單下的二級(jí)菜單。
二級(jí)菜單
二級(jí)菜單的實(shí)現(xiàn)和一級(jí)菜單的邏輯是相同的:循環(huán)子路由route.children,在循環(huán)的時(shí)候判斷子路由childRoute是否包含子菜單,如果包含則當(dāng)前菜單使用el-submenu實(shí)現(xiàn),否則當(dāng)前菜單使用el-menu-item實(shí)現(xiàn)。
那話不多說(shuō),直接上代碼。
<!-- src/menu/leftMenu.vue -->
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<!-- 一級(jí)菜單 -->
<!-- 循環(huán)路由數(shù)據(jù) -->
<!-- 判斷當(dāng)前路由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>
<!-- 二級(jí)菜單 -->
<!-- 循環(huán)子路由`route.children` -->
<!-- 循環(huán)的時(shí)候判斷子路由`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>
結(jié)果如下:

可以看到二級(jí)菜單成功實(shí)現(xiàn)。
三級(jí)菜單
三級(jí)菜單就不用多說(shuō)了,和一級(jí)、二級(jí)邏輯相同,這里還是直接上代碼。
<!-- src/menu/leftMenu.vue -->
<el-menu
:default-active="$route.path"
class="el-menu-vertical-demo"
:collapse="false">
<!-- 一級(jí)菜單 -->
<!-- 循環(huán)路由數(shù)據(jù) -->
<!-- 判斷當(dāng)前路由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>
<!-- 二級(jí)菜單 -->
<!-- 循環(huán)子路由`route.children` -->
<!-- 循環(huán)的時(shí)候判斷子路由`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>
<!-- 三級(jí)菜單 -->
<!-- 循環(huán)子路由`childRoute.children` -->
<!-- 循環(huán)的時(shí)候判斷子路由`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>

可以看到工時(shí)列表下的三級(jí)菜單已經(jīng)顯示了。
總結(jié)
此時(shí)我們已經(jīng)結(jié)合路由配置實(shí)現(xiàn)了這個(gè)動(dòng)態(tài)的菜單。
不過(guò)這樣的代碼在邏輯上相關(guān)于三層嵌套的for循環(huán),對(duì)應(yīng)的是我們有三層的菜單。
假如我們有四層、五層甚至更多層的菜單時(shí),那我們還得在嵌套更多層for循環(huán)。很顯然這樣的方式暴露了前面多層for循環(huán)的缺陷,所以我們就需要對(duì)這樣的寫(xiě)法進(jìn)行一個(gè)改進(jìn)。
遞歸實(shí)現(xiàn)動(dòng)態(tài)菜單
前面我們一直在說(shuō)一級(jí)、二級(jí)、三級(jí)菜單的實(shí)現(xiàn)邏輯都是相同的:循環(huán)子路由,在循環(huán)的時(shí)候判斷子路由是否包含子菜單,如果包含則當(dāng)前菜單使用el-submenu實(shí)現(xiàn),否則當(dāng)前菜單使用el-menu-item實(shí)現(xiàn)。那這樣的邏輯最適合的就是使用遞歸去實(shí)現(xiàn)。
所以我們需要將這部分共同的邏輯抽離出來(lái)作為一個(gè)獨(dú)立的組件,然后遞歸的調(diào)用這個(gè)組件。

邏輯拆分
<!-- 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>
需要注意的是,這次抽離出來(lái)的組件循環(huán)的時(shí)候直接循環(huán)的是route數(shù)據(jù),那這個(gè)route數(shù)據(jù)是什么呢。
我們先看一下前面三層循環(huán)中循環(huán)的數(shù)據(jù)源分別是什么。
為了看得更清楚,我將前面代碼中一些不相關(guān)的內(nèi)容進(jìn)行了刪減。
<!-- src/menu/leftMenu.vue --> <!-- 一級(jí)菜單 --> <el-submenu v-for="route in routesInfo" v-if="route.meta.hasSubMenu"> <!-- 二級(jí)菜單 --> <el-submenu v-for="childRoute in route.children" v-if="childRoute.meta.hasSubMenu"> <!-- 三級(jí)菜單 --> <el-submenu v-for="child in childRoute.children" v-if="child.meta.hasSubMenu"> </el-submenu> </el-submenu> </el-submenu>
從上面的代碼可以看到:
一級(jí)菜單循環(huán)的是`routeInfo`,即最初我們獲取的路由數(shù)據(jù)`this.$router.options.routes`,循環(huán)出來(lái)的每一項(xiàng)定義為`route`
二級(jí)菜單循環(huán)的是`route.children`,循環(huán)出來(lái)的每一項(xiàng)定義為`childRoute`
三級(jí)菜單循環(huán)的是`childRoute.children`,循環(huán)出來(lái)的每一項(xiàng)定義為`child`
按照這樣的邏輯,可以發(fā)現(xiàn)二級(jí)菜單、三級(jí)菜單循環(huán)的數(shù)據(jù)源都是相同的,即前一個(gè)循環(huán)結(jié)果項(xiàng)的children,而一級(jí)菜單的數(shù)據(jù)來(lái)源于this.$router.options.routes。
前面我們抽離出來(lái)的menuItem組件,循環(huán)的是route數(shù)據(jù),即不管是一層菜單還是二層、三層菜單,都是同一個(gè)數(shù)據(jù)源,因此我們需要統(tǒng)一數(shù)據(jù)源。那當(dāng)然也非常好實(shí)現(xiàn),我們?cè)谡{(diào)用組件的時(shí)候,為組件傳遞不同的值即可。

代碼實(shí)現(xiàn)
前面公共組件已經(jīng)拆分出來(lái)了,后面的代碼就非常好實(shí)現(xiàn)了。
首先是抽離出來(lái)的meunItem組件,實(shí)現(xiàn)的是邏輯判斷以及遞歸調(diào)用自身。
<!-- 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>
<!--遞歸調(diào)用組件自身 -->
<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組件,調(diào)用menuIndex組件,傳遞原始的路由數(shù)據(jù)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>
最終的結(jié)果這里就不展示了,和我們需要實(shí)現(xiàn)的結(jié)果是一致的。
功能完善
到此,我們結(jié)合路由配置實(shí)現(xiàn)了菜單欄這個(gè)功能基本上已經(jīng)完成了,不過(guò)這是一個(gè)缺乏靈魂的菜單欄,因?yàn)闆](méi)有設(shè)置菜單的跳轉(zhuǎn),我們點(diǎn)擊菜單欄還無(wú)法路由跳轉(zhuǎn)到對(duì)應(yīng)的組件,所以接下來(lái)就來(lái)實(shí)現(xiàn)這個(gè)功能。
菜單跳轉(zhuǎn)的實(shí)現(xiàn)方式有兩種,第一種是NavMenu組件提供的跳轉(zhuǎn)方式。

第二種是在菜單上添加router-link實(shí)現(xiàn)跳轉(zhuǎn)。
那本次我選擇的是第一種方式實(shí)現(xiàn)跳轉(zhuǎn),這種實(shí)現(xiàn)方式需要兩個(gè)步驟才能完成,第一步是啟用el-menu上的router;第二步是設(shè)置導(dǎo)航的index屬性。
那下面就來(lái)實(shí)現(xiàn)這兩個(gè)步驟。
啟用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>
設(shè)置導(dǎo)航的index屬性
首先我將每一個(gè)菜單標(biāo)題對(duì)應(yīng)需要設(shè)置的index屬性值列出來(lái)。
index值對(duì)應(yīng)的是每個(gè)菜單在路由中配置的path值
首頁(yè) 員工管理 員工統(tǒng)計(jì) index="/employee/employeeStatistics" 員工管理 index="/employee/employeeManage" 考勤管理 考勤統(tǒng)計(jì) index="/attendManage/attendStatistics" 考勤列表 index="/attendManage/attendList" 異常管理 index="/attendManage/exceptManage" 員工統(tǒng)計(jì) 員工統(tǒng)計(jì) index="/timeManage/timeStatistics" 員工統(tǒng)計(jì) index="/timeManage/timeList" 選項(xiàng)一 index="/timeManage/timeList/options1" 選項(xiàng)二 index="/timeManage/timeList/options2"
接著在回顧前面遞歸調(diào)用的組件,導(dǎo)航菜單的index設(shè)置的是child.path,為了看清楚child.path的值,我將其添加菜單標(biāo)題的右側(cè),讓其顯示到界面上。
<!-- 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>
<!--遞歸調(diào)用組件自身 -->
<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>
同時(shí)將菜單欄的寬度由200px設(shè)置為400px。
<!-- src/menu/menuIndex.vue --> <!-- 省略其余未修改代碼--> <el-aside width="400px"> <LeftMenu></LeftMenu> </el-aside>
然后我們看一下效果。

可以發(fā)現(xiàn),child.path的值就是當(dāng)前菜單在路由中配置path值(router.js中配置的path值)。
那么問(wèn)題就來(lái)了,前面我們整理了每一個(gè)菜單標(biāo)題對(duì)應(yīng)需要設(shè)置的index屬性值,就目前來(lái)看,現(xiàn)在設(shè)置的index值是不符合要求的。不過(guò)仔細(xì)觀察現(xiàn)在菜單設(shè)置的index值和正常值是有一點(diǎn)接近的,只是缺少了上一級(jí)菜單的path值,如果能將上一級(jí)菜單的path值和當(dāng)前菜單的path值進(jìn)行一個(gè)拼接,就能得到正確的index值了。
那這個(gè)思路實(shí)現(xiàn)的方式依然是在遞歸時(shí)將當(dāng)前菜單的path作為參數(shù)傳遞給menuItem組件。
<!-- src/menu/menuIndex.vue --> <!--遞歸調(diào)用組件自身 --> <MenuItem :route="child.children" :basepath="child.path"> </MenuItem>
將當(dāng)前菜單的path作為參數(shù)傳遞給menuItem組件之后,在下一級(jí)菜單實(shí)現(xiàn)時(shí),就能拿到上一級(jí)菜單的path值。然后組件中將basepath的值和當(dāng)前菜單的path值做一個(gè)拼接,作為當(dāng)前菜單的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 為當(dāng)前菜單的path值
// getpath: 拼接 當(dāng)前菜單的上一級(jí)菜單的path 和 當(dāng)前菜單的path
getPath: function(routePath){
return path.resolve(this.basepath, routePath);
}
}
}
</script>
再看一下界面。

我們可以看到二級(jí)菜單的index值已經(jīng)沒(méi)問(wèn)題了,但是仔細(xì)看,發(fā)現(xiàn)工時(shí)管理-工時(shí)列表下的兩個(gè)三級(jí)菜單index值還是有問(wèn)題,缺少了工時(shí)管理這個(gè)一級(jí)菜單的path。
那這個(gè)問(wèn)題是因?yàn)槲覀冊(cè)谡{(diào)用組件自身是傳遞的basepath有問(wèn)題。
<!--遞歸調(diào)用組件自身 --> <MenuItem :route="child.children" :basepath="child.path"> </MenuItem>
basepath傳遞的只是上一級(jí)菜單的path,在遞歸二級(jí)菜單時(shí),index的值是一級(jí)菜單的path值+二級(jí)菜單的path值;那當(dāng)我們遞歸三級(jí)菜單時(shí),index的值就是二級(jí)菜單的path值+三級(jí)菜單的path值,這也就是為什么工時(shí)管理-工時(shí)列表下的兩個(gè)三級(jí)菜單index值存在問(wèn)題。
所以這里的basepath值在遞歸的時(shí)候應(yīng)該是累積的,而不只是上一級(jí)菜單的path值。因此借助遞歸算法的優(yōu)勢(shì),basepath的值也需要通過(guò)getPath方法進(jìn)行處理。
<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>
<!--遞歸調(diào)用組件自身 -->
<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 為當(dāng)前菜單的path值
// getpath: 拼接 當(dāng)前菜單的上一級(jí)菜單的path 和 當(dāng)前菜單的path
getPath: function(routePath){
return path.resolve(this.basepath, routePath);
}
}
}
</script>
刪除其余用來(lái)調(diào)試的代碼
最終效果
文章的最后呢,將本次實(shí)現(xiàn)的最終效果在此展示一下。

選項(xiàng)一和選項(xiàng)二這兩個(gè)三級(jí)菜單在路由配置中沒(méi)有設(shè)置component,這兩個(gè)菜單只是為了實(shí)現(xiàn)三級(jí)菜單,在最后的結(jié)果演示中,我已經(jīng)刪除了路由中配置的這兩個(gè)三級(jí)菜單
此處在leftMenu組件中為el-menu開(kāi)啟了unique-opened
在menuIndex組件中,將左側(cè)菜單欄的寬度改為200px
總結(jié)
到此這篇關(guān)于Vue結(jié)合路由配置遞歸實(shí)現(xiàn)菜單欄功能的文章就介紹到這了,更多相關(guān)vue 路由遞歸菜單欄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue-cli構(gòu)建項(xiàng)目使用 less的方法
這篇文章主要介紹了vue-cli構(gòu)建項(xiàng)目使用 less,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-10-10
vue開(kāi)發(fā)中關(guān)于axios的封裝過(guò)程
這篇文章主要介紹了vue開(kāi)發(fā)中關(guān)于axios的封裝過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
vue+element實(shí)現(xiàn)表單校驗(yàn)功能
這篇文章主要介紹了vue+element表單校驗(yàn)功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-05-05
Vue2打包部署后可動(dòng)態(tài)修改后端接口地址的解決方法
本篇文章將介紹使用Vue2開(kāi)發(fā)前后端分離項(xiàng)目時(shí),前端打包部署后可動(dòng)態(tài)修改后端接口地址的解決方法,文中通過(guò)圖文結(jié)合的方式介紹的非常詳細(xì),需要的朋友可以參考下2024-07-07
vue element自定義表單驗(yàn)證請(qǐng)求后端接口驗(yàn)證
這篇文章主要介紹了vue element自定義表單驗(yàn)證請(qǐng)求后端接口驗(yàn)證,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
Vue+Electron打包桌面應(yīng)用(超詳細(xì)完整教程)
這篇文章主要介紹了Vue+Electron打包桌面應(yīng)用超詳細(xì)完整教程,在這大家要記住整個(gè)項(xiàng)目的json文件不能有注釋,及時(shí)沒(méi)報(bào)錯(cuò)也不行,否則運(yùn)行命令時(shí)還是有問(wèn)題,具體細(xì)節(jié)問(wèn)題參考下本文詳細(xì)講解2024-02-02
vue結(jié)合el-table實(shí)現(xiàn)表格行拖拽排序(基于sortablejs)
這篇文章主要介紹了vue結(jié)合el-table實(shí)現(xiàn)表格行拖拽排序(基于sortablejs),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01

