vue+element 后台管理侧边导航栏

15,315 阅读3分钟

背景

新公司之前的项目还没做前后台分离,利用没项目的这段时间,先搞一个后台管理系统,熟悉熟悉业务,项目是基于vue+elementUI,虽然接触过一段时间vue,但是没干过后台管理这种活啊,在此记录一下在做的过程中学习到的一些知识点,方便日后查阅。

效果

实现


1.页面区域划分

想要的效果

点击左侧选项,右侧RightContent.vue中显示不同的页面

Home.vue

<template>
    <div class="home">
        <Header></Header>
        <div class="main">
            <LeftMenu></LeftMenu>
            <RightContent></RightContent>
        </div>
    </div>
</template>
import LeftMenu from '@/components/admin/common/LeftMenu.vue';
import RightContent from '@/components/admin/common/RightContent.vue';
import Header from '@/components/admin/common/Header.vue';

export default {
    name: 'home',
    components: {
        LeftMenu,
        RightContent,
        Header,
    },
    data() {
        return {
        };
    },
    created() {
        // 拿到默认的路由对象,保存全局变量
        this.initTabList();
    },
    computed: {
        // 当前所在模块
        menuModule() {
            return this.$store.state.menuModule;
        },
        // 标签栈
        tabList() {
            return this.$store.state.tabList;
        },
    },
    methods: {
        /**
        *@description: 保存默认的当前模块的首页的路由对象
        *@param{}
        *@return:
        */
        initTabList() {
            // console.log(this.$route.path);
            // 获取匹配到的路由对象
            const firstRoute = this.$router.options.routes.filter(item => item.name === this.menuModule)[0];
            // 循环该路由对象的children
            const { redirect } = firstRoute;
            if (firstRoute && redirect) {
                const saveRoute = firstRoute.children.filter(item => item.path === redirect)[0];
                if (saveRoute) {
                    this.tabList.push(saveRoute);
                    this.$store.commit('tabList', this.tabList);
                }
            }
        },

    },
};
</script>

2.路由实现

路由这里要单独说一下,因为之前使用路由的时候基本没遇到过这种路由之间有多层嵌套的,我需要的效果是点击侧边栏后右侧内容区域就要变,所以想到了页面变化可以通过嵌套路由来解决,在RightContent.vue内部使用router-view匹配路由即可。

RightContent.vue中router-view对应的路由思路

    /index --->首页
    /menu  --->功能
       /menu/page1 -->功能1
       /menu/page2 --->功能2

RightContent.vue

<template>
    <div class="right-content">
        <div class="content-item">
            <el-tabs v-model="activeTabsName" type="card" closable @tab-remove="removeTab"  @tab-click="tabClick">
                <el-tab-pane
                    v-for="(item) in tabList"
                    :key="item.path"
                    :label="item.name"
                    :name="item.name"
                >
                    // 在这里匹配左侧的路由
                    <router-view></router-view>
                </el-tab-pane>
            </el-tabs>
        </div>
    </div>
</template>

<script>
export default {
    name: 'RightContent',
    components: {
    },
    computed: {
        // 标签路由栈
        tabList() {
            return this.$store.state.tabList;
        },
        // 当前被激活的tab标签
        activeTabsName: {
            get() {
                return this.$store.state.activeTabsName;
            },
            set(val) {
                this.$store.commit('activeTabsName', val);
            },
        },
        // 当前标签的下标
        activeIndex() {
            let temIndex = null;
            this.tabList.forEach((item, index) => {
                if (item.name === this.activeTabsName) {
                    temIndex = index;
                }
            });
            return temIndex;
        },
    },
    data() {
        return {
        };
    },
    methods: {
        /**
        *@description:移除标签的方法
        *@param{String} 要删除的标签
        *@return: null
        */
        removeTab(targetName) {
            const tabs = this.tabList;
            let activeName = this.activeTabsName;
            if (activeName === targetName) {
                tabs.forEach((tab, index) => {
                    if (tab.name === targetName) {
                        const nextTab = tabs[index + 1] || tabs[index - 1];
                        if (nextTab) {
                            activeName = nextTab.name;
                        }
                    }
                });
                this.$store.commit('activeTabsName', activeName);
                this.$store.commit('tabList', tabs.filter(tab => tab.name !== targetName));
                this.tabClick();
            } else {
                this.$store.commit('tabList', tabs.filter(tab => tab.name !== targetName));
            }
        },

        /**
        *@description: 标签页点击方法
        *@param{}
        *@return:
        */
        tabClick() {
            // 当前路由和被选中的路由不相等的时候触发
            const { path } = this.tabList[this.activeIndex];
            if (this.$route.path !== path) this.$router.push(path);
        },
    },
};
</script>

router.js


import Vue from 'vue';
import Router from 'vue-router';
import Index from '@/components/admin/home/Index.vue';
import TableLevel from '@/components/admin/home/TableLevel.vue';
import Home from './views/Home.vue';
import Alarm from './views/Alarm.vue';
import Patch from './views/Patch.vue';

Vue.use(Router);
const router = new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    routes: [
        {
            path: '/',
            name: 'home',
            component: Home,
            showMenu: true,
            redirect: '/index',
            meta: {
                icon: 'el-icon-location',
            },
            children: [
                {
                    path: '/index',
                    name: '首页',
                    component: Index,
                    showMenu: true,
                    meta: {
                        icon: 'el-icon-s-grid',
                    },
                },
                {
                    path: '/funmenu',
                    name: '功能菜单',
                    component: Patch,
                    showMenu: true,
                    meta: {
                        icon: 'el-icon-remove',
                    },
                    children: [
                        {
                            path: '/funmenu/table',
                            name: '表格组件',
                            showMenu: true,
                            meta: {
                                icon: 'el-icon-s-marketing',
                            },
                            component: Patch,
                            children: [
                                {
                                    path: '/funmenu/table/level',
                                    name: '多级表头',
                                    component: TableLevel,
                                    showMenu: true,
                                    meta: {
                                        icon: 'el-icon-s-data',
                                    },
                                },
                            ],
                        },
                        {
                            path: '/funmenu/pop',
                            name: '弹窗组件',
                            showMenu: true,
                            component: Alarm,
                            meta: {
                                icon: 'el-icon-bell',
                            },
                        },
                    ],
                },
            ],
        },
    ],
});

export default router;

3.导航栏实现

左侧导航栏使用 element 的NavMenu导航菜单组件模板,直接复制代码,因为当菜单栏有层级嵌套的时候,里面应该是一个循环组件,所以这里将导航栏拆分为了两个组件,LeftMenu.vue和MenuNav.vue

1.LeftMenu.vue

 <template>
    <div class="left-menu" :class="{fold: isCollapse===true}">
        <div class="menu-content">
            <el-menu  :default-active="$route.path" class="el-menu-vertical-demo"
                      @open="handleOpen"
                      @close="handleClose"
                      :collapse="isCollapse"
                      background-color="#545c64"
                      @select="select"
                      text-color="#fff"
                      active-text-color="#ffd04b"
                      ref="leftNavigation">
                <div class="menu-toggle" @click.prevent="control">
                    <i class="el-icon-s-fold" v-show="!isCollapse" title="收起"></i>
                    <i class="el-icon-s-unfold" v-show="isCollapse" title="展开"></i>
                </div>
                <template v-for="(item,index)  in $router.options.routes">
                    <template v-if="item.name === menuModule">
                        <MenuNav :key="index" :menuData="item.children"></MenuNav>
                    </template>
                </template>
            </el-menu>
        </div>
    </div>
</template>

<script>
import MenuNav from '@/components/admin/common/MenuNav.vue';
import debounce from '@/common/debounce';

export default {
    name: 'leftMenu',
    data() {
        return {
            de: null,
        };
    },
    computed: {
        // 侧边导航栏是否打开
        isCollapse() {
            return this.$store.state.isCollapse;
        },
        // 标签路由栈
        tabList() {
            return this.$store.state.tabList;
        },
        // 当前被激活的tab标签
        activeTabsName() {
            return this.$store.state.activeTabsName;
        },
        // 当前所在的模块
        menuModule() {
            return this.$store.state.menuModule;
        },
    },
    components: {
        MenuNav,
    },
    mounted() {
        // FIXME 暂时注释
        // this.initLeftMenu();
        // 初始化展示首页
        this.select(this.$route.path);
    },
    methods: {
        // 判断要打开的父级路由
        initLeftMenu() {
            // 获取所有的路由对象,循环子菜单,分级成菜单树展示
            // 获取当前路由
            let elSubmenu = null;
            let needOpenSubmenu = false;
            const curRoute = this.$route.path;
            const allRoutes = this.$router.options.routes;
            for (let i = 0; i < allRoutes.length; i += 1) {
                const { children } = allRoutes[i];
                // 如果有子路由
                if (children) {
                    // 循环子路由 如果子路由和当前路由相等的直接退出循环
                    for (let j = 0; i < children.length; j += 1) {
                        // console.log('进来了');
                        if (children[j].path === curRoute) break;
                        // 如果该菜单下还有子菜单
                        if (children[j].children) {
                            const grandChild = children[j].children;
                            for (let z = 0; z < grandChild[z].length; z += 1) {
                                if (grandChild[z].path === curRoute) {
                                    elSubmenu = j;
                                    needOpenSubmenu = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            if (this.$refs.leftNavigation && needOpenSubmenu) {
                this.$refs.leftNavigation.open(elSubmenu); // 打开子菜单
            }
        },

        /**
        *@description: FIXME选中路由跳转
        *@param{}
        *@return:
        */
        select(index) {
            // 路由不相等的时候push(解决路由相等时报错问题)
            if (this.$route.path !== index) this.$router.push(index);
            // 当前选中的标签被选中
            this.$store.commit('activeTabsName', this.$route.name);
            // 判断之前路由栈里没有此路由对象,否则不入栈
            if (this.tabList.every(item => item.path !== this.$route.path)) {
                this.tabList.push(this.$route);
                this.$store.commit('tabList', this.tabList);
            }
        },

        /**
        *@description: FIXME打开菜单
        */
        handleOpen() {
            // console.log('打开菜单');
        },

        /**
        *@description: FIXME关闭菜单
        */
        handleClose() {
            // console.log('菜单关闭');
        },
        /**
        *@description: 执行控制方法的时候加入节流或防抖
        *@param{}
        *@return:
        */
        control() {
            debounce('leftmenu', () => {
                this.$store.commit('isCollapse', !this.isCollapse);
            }, 300, true);
        },
    },
};
</script>

2.MenuNav.vue(内部循环组件)

<template>
    <fragment>
        <template v-for="(itemss,indexss) in menuData">
            <!-- 一级菜单 -->
            <el-submenu :index="indexss+''"  :key="indexss"  v-if="itemss.showMenu && itemss.children">
                <!-- 一级菜单标题 -->
                <template slot="title">
                    <i :class="itemss.meta.icon"></i>
                    <span slot="title">{{itemss.name}}</span>
                </template>
                <template v-for="(term,indext) in itemss.children">
                    <!-- 二级菜单没有子路由的时候 -->
                    <el-menu-item
                        :key="term.path"
                        :index="term.path+''"
                        v-if="term.showMenu && !term.children"
                        :class="$route.path===term.path?'is-active':''">
                        <i :class="term.meta.icon"></i>
                        <span slot="title">{{term.name}}</span>
                    </el-menu-item>
                    <!-- 二级菜单有子路由的时候,递归循环自身 -->
                    <MenuNav v-else :key="indext" :menuData="[...term]"></MenuNav>
                </template>
            </el-submenu>
            <el-menu-item
                :key="indexss"
                :index="itemss.path+''"
                v-if="itemss.showMenu && !itemss.children"
                :class="$route.path===itemss.path?'is-active':''">
                <i :class="itemss.meta.icon"></i>
                <span slot="title">{{itemss.name}}</span>
            </el-menu-item>
        </template>
    </fragment>
</template>

<script>
export default {
    name: 'MenuNav',
    props: ['menuData'],
    components: {},
    data() {
        return {
        };
    },
    computed: {},
    watch: {
    },
    created() {},
    mounted() {
        // console.log(this.menuData);
    },
    destroyed() {},
    methods: {},
};
</script>

项目git地址(持续更新中~)

花间提壶王大厨

ps:直接复制上面代码应该是不能运行的,最好从git上拉代码运行一下,避免有些部分缺胳膊少腿的