vue實現el-menu與el-tabs聯動,通過點擊menu菜單來動態添加tabs選項頁


先上效果圖

 達成這樣的效果,其實根本在於,通過開啟el-menu的route屬性,點擊menu菜單后會進行路徑的跳轉,但是你跳轉的路由地址必須寫為根組件的子路由地址,否則會直接進行整個頁面的跳轉,失去了這樣的效果。

然后看一下el-tabs,這里放一下elementUI的官方文檔

 子元素都是可以通過for遍歷循環出來的,所以到了這里,基本的思路應該就是通過watch來監聽$route,根據跳轉的路徑,在遍歷tabs的數組里面進行判斷,來動態的增加或者進行路由跳轉。

同時,這里面有多個組件之間的通信,我們可以使用vuex,在這里做個最基本的應用。

下面開始上代碼。

首先,我的頁面結構布局是 左側的菜單,右側的頭部和主體內容,直接使用element的Container布局容器

<el-container>

  <el-aside width="200px">Aside</el-aside>

  <el-container>

    <el-header>Header</el-header>

     <el-main>Main</el-main>

   </el-container>

</el-container>

先來看看左側的Menu的代碼:

<template>
    <div class="menu">
        <el-menu
            :default-active="String(activeNav)"
            class="el-menu-vertical-demo"
            @open="handleOpen"
            @close="handleClose"
            :collapse="getToggle"
            :collapse-transition="false"
            unique-opened
            router
        >
            <div class="menu_logo">
                <img :src="logoImg" alt="" />
                <span v-show="!getToggle">{{ menuTitle }}</span>
            </div>
            <fragment v-for="item in menuList" :key="item.key">
                <!-- 一級菜單 -->
                <el-submenu
                    v-if="item.children && item.children.length"
                    :index="item.path"
                    :key="item.key"
                >
                    <template slot="title"
                        ><i class="icon iconfont" v-html="item.icon"></i
                        ><span>{{ item.title }}</span></template
                    >

                    <!-- 二級菜單 -->
                    <template v-for="itemChild in item.children">
                        <el-submenu
                            v-if="
                                itemChild.children && itemChild.children.length
                            "
                            :index="itemChild.path"
                            :key="itemChild.key"
                        >
                            <template slot="title"
                                ><i
                                    class="icon iconfont"
                                    v-html="itemChild.icon"
                                ></i
                                ><span>{{ itemChild.title }}</span></template
                            >

                            <!-- 三級菜單 -->
                            <el-menu-item
                                v-for="itemChild_Child in itemChild.children"
                                :index="itemChild_Child.path"
                                :key="itemChild_Child.key"
                            >
                                <i
                                    class="icon iconfont"
                                    v-html="itemChild_Child.icon"
                                ></i
                                ><span slot="title">{{
                                    itemChild_Child.title
                                }}</span></el-menu-item
                            >
                        </el-submenu>

                        <el-menu-item
                            v-else
                            :index="itemChild.path"
                            :key="itemChild.key"
                            ><i
                                class="icon iconfont"
                                v-html="itemChild.icon"
                            ></i
                            ><span slot="title">{{
                                itemChild.title
                            }}</span></el-menu-item
                        >
                    </template>
                </el-submenu>

                <el-menu-item v-else :index="item.path" :key="item.key"
                    ><i class="icon iconfont" v-html="item.icon"></i
                    ><span slot="title">{{ item.title }}</span></el-menu-item
                >
            </fragment>
        </el-menu>
    </div>
</template>

<script>
import sunLogo from '@/assets/image/sun-logo.png';

import { mapGetters, mapState } from 'vuex';

export default {
    name: 'Menu',
    props: {
        menuTitle: {
            type: String,
            default: () => {
                return '';
            }
        }
    },
    data() {
        return {
            logoImg: sunLogo
        };
    },
    created() {},
    mounted() {},
    computed: {
        activeNav() {
            // 獲取當前激活的導航
            return this.$route.path;
        },
        ...mapGetters(['getToggle']),
        ...mapState(['menuList'])
    },
    methods: {
        handleOpen(key, keyPath) {},
        handleClose(key, keyPath) {}
    }
};
</script>

<style lang="less" scoped>
@import '~@/assets/css/common.less';

.menu {
    ::v-deep .el-menu {
        border-right: none;
        background-color: transparent;

        .menu_logo {
            text-align: center;
            padding-top: 1.5rem;
            margin-bottom: 1.5rem;
            img {
                width: 2.83rem;
                height: 2.83rem;
                vertical-align: middle;
                margin-right: 0.5rem;
            }
            span {
                font-size: 1.5rem;
                font-family: PingFangSC-Semibold, PingFang SC;
                font-weight: 600;
                color: #ffffff;
                height: 2.08rem;
                line-height: 2.08rem;
            }
        }

        .el-submenu__title,
        .el-menu-item {
            font-size: 1.17rem;
            height: 4rem;
            line-height: 4rem;
            color: @theme-white;
            transition: none;
            .icon {
                margin-right: 0.33rem;
                color: @theme-white;
            }
        }
        .el-submenu__title i {
            color: @theme-white;
        }
        .el-submenu .el-menu-item {
            height: 40px;
            line-height: 40px;
        }
        .el-menu-item.is-active {
            background-color: @theme-white;
            color: @theme-text-color-primary;
            border-top-left-radius: 25px;
            border-bottom-left-radius: 25px;
            position: relative;

            .icon {
                color: @theme-text-color-primary !important;
            }

            &::before {
                content: '';
                width: 0;
                height: 0;
                border: 4px solid transparent;
                border-bottom-color: @theme-white;
                border-right-color: @theme-white;
                position: absolute;
                top: -8px;
                right: 0;
            }
            &::after {
                content: '';
                width: 0;
                height: 0;
                border: 4px solid transparent;
                border-top-color: @theme-white;
                border-right-color: @theme-white;
                position: absolute;
                bottom: -8px;
                right: 0;
            }
        }
        .el-menu--inline {
            padding-left: 1.27rem;
        }
        .el-menu-item:focus,
        .el-menu-item:hover,
        .el-submenu__title:hover {
            background-color: @theme-white;
            border-top-left-radius: 25px;
            border-bottom-left-radius: 25px;
            color: @theme-text-color-primary;
            position: relative;
            .icon {
                color: @theme-text-color-primary !important;
            }
            i {
                color: @theme-text-color-primary !important;
            }
            &::before {
                content: '';
                width: 0;
                height: 0;
                border: 4px solid transparent;
                border-bottom-color: @theme-white;
                border-right-color: @theme-white;
                position: absolute;
                top: -8px;
                right: 0;
            }
            &::after {
                content: '';
                width: 0;
                height: 0;
                border: 4px solid transparent;
                border-top-color: @theme-white;
                border-right-color: @theme-white;
                position: absolute;
                bottom: -8px;
                right: 0;
            }
        }
    }
}
</style>
 
其中,fragment標簽是用來替代div使折疊菜單生效的,可以使用 npm i vue-fragment -D 下載包后,在main.js里面  import Fragment from 'vue-fragment'  來使用。
可以看到,是啟用了route屬性的,而 :default-active="String(activeNav)"  則是來根據跳轉的菜單來進行當前選中的激活。
這個 getToggle 則是用來收縮菜單的, 也是放在了 vuex 里面,可以先暫時不考慮這個。 
這個 menuList 我則是放在了vuex里面,里面的數據為:
menuList: [
            {
                key: 0,
                title: '首頁',
                icon: '&#xe603;',
                path: '/index'
            },
            {
                key: 1,
                title: '人員管理',
                icon: '&#xe789;',
                path: '/person_manage'
            },
            {
                key: 2,
                title: '事物管理',
                icon: '&#xe7c1;',
                path: 'drug_case_manage',
                children: [
                    {
                        key: '2-1',
                        title: '物件管理',
                        icon: '&#xe7c8;',
                        path: '/case_manage'
                    },
                    {
                        key: '2-2',
                        title: '物件研判',
                        icon: '&#xe7c9;',
                        path: '/person_involved'
                    }
                ]
            },
            {
                key: 3,
                title: '一鍵搜',
                icon: '&#xe79d;',
                path: '/search'
            }
        ]
其中的icon是我本地使用的,這個可以不用管,接下來看下router.js里面的路由配置。
這是我router文件夾下面的路由配置
    {
        path: '/',
        redirect: '/home'
    },
    {
        path: '/home',
        name: 'Home',
        component: () => import(/* webpackChunkName: "home" */ '@/views/Home'),
        redirect: '/guide',
        children: [
            {
                // 引導頁
                path: '/guide',
                name: 'Guide',
                meta: {
                    name: 'guide',
                    title: '歡迎使用',
                    comp: 'guide'
                },
                component: () => import('@/views/Guide/guide')
            },
            {
                // 首頁
                path: '/index',
                name: 'Index',
                meta: {
                    name: 'index',
                    title: '首頁',
                    comp: 'Index'
                },
                component: () => import('@/views/index/Index')
            },
            {
                path: '/search',
                name: 'Search',
                component: () => import('@/views/OneClickSearch/GoSearch.vue')
            },
            {
                // 人員管理
                path: '/person_manage',
                name: 'PersonManage',
                meta: {
                    name: 'PersonManage',
                    title: '人員管理',
                    comp: 'manage'
                },
                component: () => import('@/views/PersonManage/manage')
            },
            {
                path: '/case_manage',
                name: 'CaseManage',
                meta: {
                    name: 'CaseManage',
                    title: '物品管理',
                    comp: 'CaseManage'
                },
                component: () => import('@/views/CaseManage/manage')
            },
            {
                path: '/person_involved',
                name: 'involved',
                meta: {
                    name: 'involved',
                    title: '物件研判',
                    comp: 'involved'
                },
                component: () => import('@/views/CaseManage/involved')
            },
            {
                path: '/search',
                name: '一鍵搜',
                meta: {
                    name: '一鍵搜',
                    title: '一鍵搜',
                    comp: 'Search'
                },
                component: () => import('@/views/OneClickSearch/GoSearch')
            }
        ]
    }
其中meta是自定義的屬性值,可以在監聽 $route 時,監聽到定義的屬性值。
將這些頁面設為home根路徑下面的子路徑,並將home頁面重定向到歡迎頁,這樣打開時直接顯示的就是歡迎頁面。
接下來就是tabs組件了,主要的邏輯功能都在這一部分。
先上代碼:
<template>
    <div>
        <el-tabs
            v-model="activeIndex"
            type="card"
            @tab-remove="removeTab"
            @tab-click="clickTab"
        >
            <el-tab-pane
                :key="item.name"
                v-for="item in openTab"
                :label="item.title"
                :name="item.name"
                :closable="item.closable"
            >
                <router-view></router-view>
            </el-tab-pane>
        </el-tabs>
    </div>
</template>
 
其中的 activeIndex 就是tabs當前選中的,我們默認為歡迎頁面,然后通過循環 openTab 這個數組來進行動態的添加或刪除,而這個 closable 屬性則是可以手動設置為是否可以關掉tabs的選項卡,我們在里面放一個 router-view 來進行子頁面的跳轉。
data 里面的則是:
data() {
        return {
            activeIndex: '/guide',
            openTab: [
                {
                    title: '引導頁',
                    name: '/guide',
                    closable: false
                }
            ]
        };
    }
這是初始數據,默認設置歡迎頁,然后重點就是通過watch來監聽 $route ,代碼如下:
    watch: {
        $route(to, form) {
            var flag = false;
            // 當前頁面菜單已打開
            for (let i = 0; i < this.openTab.length; i++) {
                if (to.path == this.openTab[i].name) {
                    this.activeIndex = this.openTab[i].name;
                    flag = true;
                    break;
                }
            }
            // 打開新的頁面
            if (!flag) {
                let obj = {
                    title: to.meta.title,
                    name: to.path,
                    closable: true
                };
                this.activeIndex = to.path;
                this.openTab.push(obj);
            }
        }
    }
我們加個自定義的 flag 屬性來進行往 openTab 這個數組里面push數據的判斷依據,同時,通過methods里面的 clickTab 方法來進行tabs頁面的切換
clickTab(tab) {
            this.activeIndex = tab.paneName;
            this.$router.push({ path: this.activeIndex });
}
接下來是 removeTab 方法,關閉打開的tabs標簽頁
removeTab(target) {
            // 刪除的是當前選中的頁面
            if (this.activeIndex === target) {
                this.openTab.forEach((item, index) => {
                    if (item.name == target) {
                        let nextTab = item[index + 1] || item[index - 1];
                        if (nextTab) {
                            this.activeIndex = nextTab.name;
                        }
                    }
                });
            }
            var i = 0;
            this.openTab.forEach((item, index) => {
                if (item.name == target) {
                    // eslint-disable-next-line no-return-assign
                    return (i = index);
                }
            });
            this.openTab.splice(i, 1);

            // 更新路由
            this.$router.push({ path: this.openTab[this.openTab.length - 1].name });
}
到這里,基本的頁面跳轉以及切換就差不多了,但是我們還需要再補充一下,當我們打開幾個頁面時,進行刷新,就會發現重置了,這時需要在 mounted 里面添加如下代碼:
mounted() {
        // 監聽頁面加載前
        window.addEventListener('beforeunload', e => {
            sessionStorage.setItem(
                'openTab',
                JSON.stringify({
                    openTab: this.openTab.filter(
                        item => item.name != '/guide'
                    ),
                    openTabPath: this.openTab.filter(
                        item => item.name !== '/guide'
                    ),
                    currActiveTabs: this.activeIndex
                })
            );
        });
}
同時,在created里面進行讀取本地 sessionStorage 來進行判斷:
created() {
        const sessionTab = JSON.parse(window.sessionStorage.getItem('openTab')) || '';
        if (sessionTab) {
            if (sessionTab.openTab.length != 0 && sessionTab.openTabPath.length != 0) {
                for (let i = 0; i < sessionTab.openTab.length; i++) {
                    this.openTab.push({
                        title: sessionTab.openTab[i].title,
                        name: sessionTab.openTab[i].name,
                        closable: true
                    });
                }
                this.activeIndex = sessionTab.currActiveTabs;
                this.$router.push({ path: this.activeIndex });
            }
        }
    }
這樣,在打開幾個頁面后,刷新頁面之前,保留已打開的頁面,刷新之后,還是顯示之前打開過的頁面,至此,基本的流程已經結束。
我把這些功能都單獨拿出去了,所以就貼上了基本的代碼,主要的邏輯流程就是這些,其中可能會有些問題,我們還是需要自己來一一解決。
框架完整代碼可以在碼雲上面去看    https://gitee.com/chenkai777/vue_demo
 
 
 
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM