天天看點

Vue結合路由配置遞歸實作菜單欄

Vue結合路由配置遞歸實作菜單欄

在日常開發中,項目中的菜單欄都是已經實作好了的。如果需要添加新的菜單,隻需要在`路由配置`中新增一條路由,就可以實作菜單的添加。

相信大家和我一樣,有時候會躍躍欲試自己去實作一個菜單欄。那今天我就将自己實作的菜單欄的整個思路和代碼分享給大家。

Vue結合路由配置遞歸實作菜單欄

作者:小洋芋biubiubiu

部落格園:https://www.cnblogs.com/HouJiao/

掘金:https://juejin.im/user/58c61b4361ff4b005d9e894d

微信公衆号:洋芋媽的碎碎念(掃碼關注,一起吸貓,一起聽故事,一起學習前端技術)

作者文章的内容均來源于自己的實踐,如果覺得有幫助到你的話,可以點贊給個鼓勵或留下寶貴意見

前言

在日常開發中,項目中的菜單欄都是已經實作好了的。如果需要添加新的菜單,隻需要在

路由配置

中新增一條路由,就可以實作菜單的添加。

相信大家和我一樣,有時候會躍躍欲試自己去實作一個菜單欄。那今天我就将自己實作的菜單欄的整個思路和代碼分享給大家。

本篇文章重在總結和分享菜單欄的一個

遞歸實作方式

代碼的優化

菜單權限

等不在本篇文章範圍之内,在文中的相關部分也會做一些提示,有個别不推薦的寫法希望大家不要參考哦。

同時可能會存在一些細節的功能沒有處理或者沒有提及到,忘知曉。

最終的效果

本次實作的這個菜單欄包含有

一級菜單

二級菜單

三級菜單

這三種類型,基本上已經可以覆寫項目中不同的菜單需求。

後面會一步一步從易到難去實作這個菜單。

簡單實作

我們都知道到

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/58c61b4361ff4b005d9e894d

微信公衆号

洋芋媽的碎碎念

微信公衆号的初衷是記錄自己和身邊的一些故事,同時會不定期更新一些技術文章

歡迎大家掃碼關注,一起吸貓,一起聽故事,一起學習前端技術

作者寄語

小小總結,歡迎大家指導~

vue