Vue角色菜单权限

550 阅读3分钟

背景简介

如上图,我们在平时的工作当中,例如做一个系统,一般都在左侧有对应的菜单选项,也都会写一个关于右侧导航栏的配置文件,通常我们是有什么菜单就在文件中写什么,以后如果有新增的菜单,就在旧的基础上重新添加新的菜单项,这样的方案固然是可行的,但是也有他的缺点,那就是死板,不灵活,那么我们怎么样能够写出一个优雅的系统菜单项呢?

一般的左侧菜单写法

对于一个系统的菜单模块,我们不难写出的如下的菜单代码,以vue + element技术栈为例

        <el-menu
        active-text-color="#FA8E00"
        :default-active="$route.path"
        @select="selectMenu"
        >
        <el-submenu index="/xxxx">
            <template slot="title">
                <span>一级菜单1</span>
            </template>
            <el-menu-item index="/xxxx">
                二级菜单1-1
            </el-menu-item>
        </el-submenu>
        <el-submenu index="/xxxx">
            <template slot="title">
                <span>一级菜单2</span>
            </template>
            <el-menu-item index="/xxxx">
                二级菜单2-1
            </el-menu-item>
                <el-submenu index="/xxxx">
            ...
        </el-submenu>
    </el-menu>

这种方案当然是可行的,但是如果当一个系统越来越大、左侧菜单越来越多,系统成为一个巨石系统的时候,这么写的话,不难想象代码会又长又臭,感觉很不优雅,如果在此基础上再加上角色菜单权限逻辑,不同的角色有不同的菜单功能,那么代码将会十分冗余

如何让菜单文件更优雅

方法介绍

这里先拓展性介绍vue-router中的两个方法:

addRoutes

用法:router.addRoutes(routes: Array);

理解:使用vue-router现成的addRoutes方法,将户路由注入到现有的vue-router实例中去,以实现用户路由

通过请求服务端获取当前用户路由配置,编码为 vue-router 所支持的基本格式(具体如何编码取决于前后端协商好的数据格式),通过调用 this.$router.addRoutes 方法将编码好的用户路由注入到现有的 vue-router 实例中去,以实现用户路由,但是本文不需要用到这个方法。

beforeEach(导航守卫):

用法:

router.beforeEach((to, from, next) => {
  // ...
})
接受三个参数(具体细节看文档)
to: Route: 即将要进入的目标 路由对象

from: Route: 当前导航正要离开的路由

next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

理解:通过跳转路由的方式守卫导航,当一个导航触发时,全局前置守卫按照创建顺序调用。

通过请求服务端获取当前用户路由配置,通过注册 router.beforeEach 钩子对路由的每次跳转进行管理,每次跳转都进行检查,如果目标路由不存再于后段传来的基本路由和当前用户的用户路由中,就取消跳转,转为跳转错误页。

思路

总的来说,需要和后端一起协商出一种对双方有利的方案,都需要后端协同给前端传输相关的数据结构,包括显示左侧菜单的menuList,以及跳转页面所需要的routerList。

menuList传输树状数据结构,利用传输的树状结构遍历生成前端需要的右侧菜单,总体思路如下,列出了两种方式的优缺点以及前后端需要做的工作。

routerList传递用户的路由页面权限,根据导航守卫来判断是否能进入页面

后端需要传递的用户信息

需要后端返回menuList和routerList的数据结构如下

menuList:

menuList = [
            {
                id: 'routerpage1',
                title: '路由页1',
                index: '/routerpage1',
                icon: 'icon',
                children: [
                    {
                        id: 'routerpage2',
                        title: '路由页2',
                        index: '/routerpage2',
                        icon: null,
                        children: []
                    }, {
                        id: 'routerpage3',
                        title: '路由页3',
                        index: '/routerpage3',
                        icon: null,
                        children: []
                    }, {
                        id: 'routerpage4',
                        title: '路由页4',
                        index: '/routerpage4',
                        icon: null,
                        children: []
                    }
                ]
            },
        ];

routerList:

routerList:[routerpage1, routerpage2, routerpage3, routerpage4]

前端需要做的配置

main.js里写导航守卫

    router.beforeEach((to, from, next) => {
        //routerList就是从接口获取,当前用户拥有哪些能访问到的路由列表,它是一个扁平化的数据结构
        const {routerList} = store.state;
        if (routerList.includes(to.name)) {
            return next();
        }
        //errorPage是定义的一个,当用户没有权限时,会进入的一个提示“用户无权限”的页面
        if (to.name === 'errorPage') {
            return next();
        }
        return next({name: 'errorPage', replace: true});
    });

这里在main.js中就是根据用户信息,用户具有那些路由页面的权限,在跳转的时候就判断是否有权限,有权限的话就进入路由,否则就进入报错的errorPage页面

菜单页

菜单页的话,就需要根据用户信息动态生成所需要的菜单,这里需要涉及一些知识点,Vue里面有个叫做函数式组件的概念,参考文档地址:cn.vuejs.org/v2/guide/re…

我们需要写两个组件,具体的代码实现如下:

menu.vue:

<template>
    <div :class="['menu', isCollapse ?'menu--collapse' : '']">
        <div class="logo"></div>
        <el-menu
            ref="menu"
            background-color="#00b3a4"
            text-color="rgba(255, 255, 255, 0.65)"
            active-text-color="#FFF"
            menu-trigger="click"
            :unique-opened="true"
            :default-active="$route.path"
            :collapse="isCollapse"
            :default-openeds="openeds"
            @select="selectMenu"
        >
            <div class="menu-list">
                <div v-for="item in menuList" :key="item.id" class="menu-item">
                    <el-menu-item v-if="item.children.length === 0" :index="item.index">
                        <i v-if="item.icon" class="font_family" :class="item.icon"></i>
                        <span slot="title" class="menu-title">{{item.title}}</span>
                    </el-menu-item>
                    <subMenu v-else :item-data="item" />
                </div>
            </div>
        </el-menu>
        <div class="collapse-btn" @click="onClickCollapseBtn">
            <i class="font_family icon-shouqi"></i>
        </div>
    </div>
</template>

这里的需要写一个菜单列表循环,根据用户所具有哪些menuList来遍历生成左侧菜单,​onClickCollapseBtn这个方法是点击底部的导航收起按钮进行的操作,需要自己去写,selectMenu方法是点击菜单需要进行的一些操作,根据业务场景自己写,这个方法不是必须的。但是,这个组件生成的只是一级的菜单,也就是没有子菜单的部分,生成子菜单需要下面这个组件,这里就用到了“函数式组件”的概念,上代码。

submenu.vue

<template functional>
    <el-submenu :index="props.itemData.index" popper-class="sidebar-submenu-popper">
        <template slot="title">
            <i v-if="props.itemData.icon" class="font_family" :class="props.itemData.icon"></i>
            <span>{{props.itemData.title}}</span>
        </template>
        <div v-for="item in props.itemData.children" :key="item.id">
            <el-menu-item v-if="item.children.length===0" :index="`${props.itemData.index + item.index}`">
                <span slot="title">{{item.title}}</span>
            </el-menu-item>
            <sub-menu v-else :item-data="item" />
        </div>
    </el-submenu>
</template>
<script>
export default {
    name: 'Submenu',
    props: {
        itemData: {
            type: Object,
            default: () => ({})
        }
    }
};
</script>

这个组件就可以生成所有的子菜单,如果子菜单还有子菜单,则递归调用本组件,有了这两个组件,就可以生成所有的右侧菜单,以后如果要有新菜单,也不用再去写了

结语

以上就是如何利用Vu进行角色菜单配置,思路不算复杂,能够一劳永逸,希望能够帮助到大家