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

来源:https://www.cnblogs.com/HouJiao/archive/2020/06/16/13139901.html
-Advertisement-
Play Games

在日常開發中,項目中的菜單欄都是已經實現好了的。如果需要添加新的菜單,只需要在`路由配置`中新增一條路由,就可以實現菜單的添加。 相信大家和我一樣,有時候會躍躍欲試自己去實現一個菜單欄。那今天我就將自己實現的菜單欄的整個思路和代碼分享給大家。 ...


作者:小土豆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>

註意菜單的樣式代碼,設置了絕對定位,並且設置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

關於

作者

小土豆biubiubiu

一個努力學習的前端小菜鳥,知識是無限的。堅信只要不停下學習的腳步,總能到達自己期望的地方

同時還是一個喜歡小貓咪的人,家裡有一隻美短小母貓,名叫土豆

博客園

https://www.cnblogs.com/HouJiao/

掘金

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

微信公眾號

土豆媽的碎碎念

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

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

作者寄語

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • npm install 裝包時提示Error EACCES permission denied解決辦法 ...
  • 1 <div class="pie" style="width:500px;height:500px;"></div> 2 3 <div class="line" style="width:500px;height:500px;"></div> 4 5 <script type="text/java ...
  • layui 是什麼? 是一個ui庫 UI設計(或稱界面設計)是指對軟體的人機交互、操作邏輯、界面美觀的整體設計。UI設計分為實體UI和虛擬UI,互聯網常用的UI設計是虛擬UI,UI即User Interface(用戶界面)的簡稱。 大致內容 觀察layui文件內的內部結構 ├─css //css目錄 ...
  • 前端早早聊大會,前端成長的新起點,與掘金聯合舉辦。 本文 是前端早早聊的第 45 位講師 ,也是第六屆 - Serverless 專場,來自閑魚前端團隊的丹俠的分享 - 講稿簡要整理版(完整版含演示請看錄播視頻和 PPT): 概述 今天給大家分享一下閑魚如何在現有產品中落地 FaaS,希望我們的實踐 ...
  • readyState document.readyState 返回當前文檔的狀態 屬性如下: uninitialized 還未開始載入 loading 載入中 interactive 已載入,文檔與用戶可以開始交互 complete 載入完成 DOMContentLoaded 當 DOMConten ...
  • 幾天前一個小伙伴問我 Object.getOwnPropertyNames() 是乾什麼用的 平時還真沒有使用到這個方法,一時不知如何回答 從方法名稱來分析,應該是返回的是對象自身屬性名組成的數組 那和 Object.keys() 方法不就一樣了嗎 感覺事情並不這麼簡單,於是我仔細看了一下這幾種遍歷 ...
  • 在平時的開發過程中,父子 / 兄弟組件間的通信是肯定會遇到的啦,所以這裡總結了 6 種 Vue 組件的通信props / $e$emit / Vuex$attrs / $listeners $parent / $children 與 ref provide / inject 前言 如上圖所示,A/B ...
  • 只要接觸過前端,都會指導web前端的知識主要由三部分組成:分別為靜態html,樣式css,動態javascript(簡稱js)這三大部分組成。其三部分組成的一個體系的複雜程度不亞於其他一門技術的複雜程度。當然對於跟我一樣厲害的那些web前端來說那就是小菜一碟,但是很多人都只學了錶面,基礎部分,很多重 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...