Vue + ElementUI 手撸后台管理网站基本框架(二)权限控制


  • 接口权限控制
    页面权限控制
    编写路由表
    页面访问权限
    页面元素操作权限
    路由控制完整流程图
    NEXT登录及系统菜单加载

后台管理系统中,权限控制一般分为两个部分。一个是接口访问的权限控制,一个是页面的权限控制。本章将讲述这两种权限控制如何实现。本章内容较多,请耐心阅读。

接口权限控制


相比于页面权限控制,接口权限控制简单很多,实现也很简单,所以写在最前。

接口权限控制说白了就是对用户的校验。正常来说,在用户登录时服务器应给前台返回一个token,以后前台每次调用接口时都需要带上这个token,服务端获取到这个token后进行比对,如果通过则可以访问。

我们通过对axios进行简单的设置即可达到这种要求

import axios from 'axios'
// 将token记录到vuex及cookie中,这部分将在后面章节讲述
import store from '../store'

// 超时设置
const service = axios.create({
    timeout: 5000
})
// baseURL
// axios.defaults.baseURL = 'https://api.github.com';

// http request 拦截器
// 每次请求都为http头增加Authorization字段,其内容为token
service.interceptors.request.use(
    config => {
        if (store.state.user.token) {
            config.headers.Authorization = `token ${store.state.user.token}`;
        }
        return config
    },
    err => {
        return Promise.reject(err)
    }
);
export default service
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

将以上代码写到单独文件中,直接将在入口文件main.js中注册到Vue原型上即可。这样在.vue文件中直接使用this.$axios即可。当然你也可以不这么做,在每个需要用到axios的文件中单独引入也是可以的。

import axios from './util/ajax'
...
Vue.prototype.$axios = axios
 
 
  • 1
  • 2
  • 3

页面权限控制


在后台管理系统中,第一感觉最难最繁琐的可能就是权限控制了。在这里我们先简单梳理一下权限都有哪些方面。

页面权限主要分为两个部分

  • 页面从根本上能否被访问
  • 页面中的某些元素是否可以操作

页面能否被访问一般都是在登录后,直接获取该用户能访问的页面列表;或者返回该用户所在的权限组进行判断,但实际中权限组菜单也应该是在系统中可配置的,所以这种权限组追根究底还是和第一种情况一致。

页面元素是否可以操作一般都是在进入页面前再进行鉴权一次。这里一种方式是通过请求后台获取详细权限,或者是在上次获取页面列表的数据中直接带着该页面详细权限

根据以上分析,我们总结一下大致流程: 
登录成功 ——> 获取用户能访问的页面列表(权限列表) ——> 根据返回数据生成菜单 ——> 点击菜单——> 进入页面前 ——> 读取权限列表中的该页面详细权限,判断元素操作权限 ——> 进入页面

在对流程梳理完成后我们开始进行详细的编写。


编写路由表

在上文中我们提到权限页面,但在实际中我们肯定有不需要权限,可直接访问的页面,比如登录页面、错误页面、维护页面等等。我们将这些不需要权限校验的页面写在默认路由中。同时建议把需要权限校验的页面写在另一个变量或者文件中以做区分,这样无论项目怎么变化你的代码改动量都会很少。

默认路由表,不需要权限

import Vue from 'vue'
import VueRouter from 'vue-router'
...

const routes = [
    {
        path: '/login',
        component: r => require.ensure([], () => r(require('../page/login/login')), 'login')
    },
    {
        path: '/defaultLayout',
        component: r => require.ensure([], () => r(require('../page/layout/layout')), 'layout'),
        children: [{
            path: '/home',
            component: r => require.ensure([], () => r(require('../page/home/home')), 'home'),
        }]
    },
    {
        path: '/error',
        component: r => require.ensure([], () => r(require('../page/error/error')), 'error'),
        children: [
            {
                path: '/error/401',
                component: r => require.ensure([], () => r(require('../page/error/401')), 'error')
            },
            {
                path: '/error/403',
                component: r => require.ensure([], () => r(require('../page/error/403')), 'error')
            },
            {
                path: '/error/404',
                component: r => require.ensure([], () => r(require('../page/error/404')), 'error')
            }
        ]
    }
]
// 注册路由
const router = new VueRouter({
    // mode: 'history',
    routes: routes
});
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

异步路由表,需要权限

const asyncRouter = [
    {
        path: '/asyncRouter',
        component: r => require.ensure([], () => r(require('../page/layout/layout')), 'layout'),
        children: []
    },
    {
        path: '/table',
        component: r => require.ensure([], () => r(require('../page/table/table')), 'table')
    },
    {
        path: '/form',
        component: r => require.ensure([], () => r(require('../page/form/form')), 'form'),
    }
 ]
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

以上为默认路由和异步路由的编写示例。其中针对component字段进行懒加载及分块处理,提升首屏加载速度的同时,也可以手动控制让某些页面合并到一个单独的js文件中,而不是每个页面都是一个js。

完整解释请参考官方文档:vue-router懒加载

某些细心的同学可能发现了,在异步路由表中,第一项貌似是无用数据,但其实并非如此,在下文中会涉及到,所以请不用着急

页面访问权限

还记得我们在上文说过的基本流程吗?我们先实现最核心的部分:获取权限列表——>根据返回数据生成菜单 原理上其实就是在路由跳转前获取权限列表,然后根据列表数据进行匹配,将匹配到的结果添加到原来的router对象中

假设由服务器返回的权限列表数据如下,我们通过mockjs编写类似的数据

var data = [
    {
        path: '/home',
        name: '首页'
    },
    {
        name: '系统组件',
        child: [
            {
                name: '介绍',
                path: '/components'
            },
            {
                name: '功能类',
                child: [
                    {
                        path: '/components/permission',
                        name: '详细鉴权'
                    },
                    {
                        path: '/components/pageTable',
                        name: '表格分页'
                    }
                ]
            }
        ]
    }
]    
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

编写导航钩子

// router为vue-router路由对象
router.beforeEach((to, from, next) => {
    // ajax获取权限列表函数,这里省略该函数详细内容
    getPermission().then(res => {
        // 匹配权限列表生成完整的路由
        routerMatch(res, asyncRouter).then(res => {
            // 将匹配到的新路由添加到现在的router对象中
            router.addRoutes(res)
            // 跳转到对应页面
            next(to.path)
        })
    })
})

/**
 * 根据异步路由表中的path字段进行匹配,生成需要添加的路由对象
 * @param {array} permission 权限列表(菜单列表)
 * @param {array} asyncRouter 异步路由对象
 */
function routerMatch(permission, asyncRouter){
    return new Promise((resolve) => {
        // 这里需要获取完整的已经编译好的router对象,不可为空数组,也不能用类router的对象。因为当程序运行到这里时,vue-router已经解析完毕
        const routers = asyncRouter[0]
        // 创建路由
        function createRouter(permission){
            // 根据路径匹配到的router对象添加到routers中即可
            // 因permission数据格式不一定相同,所以不写详细逻辑了
        }
        createRouter(permission)
        resolve([routers])
    })
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

完成了核心功能后,我们需要对这个钩子进行完善。当前代码是页面每次调用时都请求一次权限列表,而且随着页面跳转变多,router对象也会越来越大,增加的东西也越来越多,而且还有BUG。还有就是我们的登录页面、维护页面、错误页面是不需要权限校验的。我们需要对核心部分外围增加条件判断。这里直接上代码,逻辑都在代码中。

router.beforeEach((to, from, next) => {  
    // 判断用户是否登录
    if (Cookies.get('token')) {
        // 如果当前处于登录状态,并且跳转地址为login,则自动跳回系统首页
        // 这种情况出现在手动修改地址栏地址时
        if (to.path === '/login') {
            router.replace('/home')
        } else {
            // 页面跳转前先判断是否存在权限列表,如果存在则直接跳转,如果没有则请求一次
            if (store.state.permission.list.length === 0) {
                // 获取权限列表,如果失败则跳回登录页重新登录
                store.dispatch('permission/getPermission').then(res => {
                    // 匹配并生成需要添加的路由对象
                    routerMatch(res, asyncRouter).then(res => {
                        router.addRoutes(res)
                        next(to.path)
                    })
                }).catch(() => {
                    store.dispatch('user/logout').then(() => {
                        router.replace('/login')
                    })
                })
            } else {
                // 如果跳转页面存在于路由中则进入,否则跳转到404
                // 因为可以通过改变url值进行访问,所以必须有该判断
                if(to.matched.length){
                    next()
                } else{
                    router.replace('/error/404')
                }
            }
        }
    } else {
        // 如果是免登陆的页面则直接进入,否则跳转到登录页面
        if (whiteList.indexOf(to.path) >= 0) {
            console.log('该页面无需登录即可访问')
            next()
        } else {
            console.log('请重新登录')
            router.replace('/login')
        }
    }
})
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

到这里我们已经完成了对页面访问的权限控制。接下来讲解页面元素操作权限。

页面元素操作权限

无论你的页面元素权限是在请求页面访问权限时的附属数据还是在进入前再次请求,其实现原理都是一样的。以第一种情况为例,我们把之前的返回数据修改一下

var data = [
    {
        path: '/home',
        name: '首页'
    },
    {
        name: '系统组件',
        child: [
            {
                name: '介绍',
                path: '/components',
                // 为介绍页面增加查看按钮权限
                permission: ['view']
            },
            {
                name: '功能类',
                child: [
                    {
                        path: '/components/permission',
                        name: '详细鉴权'
                    },
                    {
                        path: '/components/pageTable',
                        name: '表格',
                        // 为表格页面增加导出、编辑权限
                        permission: ['outport', 'edit']
                    }
                ]
            }
        ]
    }
]    
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

如果页面详细数据类似这种形式,和页面访问权限数据一同返回,那么只需要在之前的routerMatch中进行判断,当然你也需要提前修改好默认路由表和异步路由表。

const asyncRouter = [
    {
        path: '/asyncRouter',
        component: r => require.ensure([], () => r(require('../page/layout/layout')), 'layout'),
        children: []
    },
    {
        path: '/table',
        // 为每个路由页面增加meta字段。在routerMatch函数中将匹配到的详细权限字段赋值到这里。这样在每个页面的route对象中就会得到这个字段。
        meta: {
            permission: []
        },
        component: r => require.ensure([], () => r(require('../page/table/table')), 'table')
    },
    ...
 ]
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

接下来我们需要编写一个vue的插件,对页面中需要进行鉴权的元素进行判断,比如类似这样的:

// 如果它有outport权限则显示
<el-button v-if="hasPermission('outport')">导出</el-button>
 
 
  • 1
  • 2

我们可以直接写在每个页面的methods中,当然更好的方法是利用mixin直接混合到每个页面。利用mixin之前请确保你的方法是唯一的,因为这个方法会混合到任何vue组件中,不仅仅是你自己的,也包括第三方组件。

const hasPermission = {
    install (Vue, options){
        Vue.mixin({
            methods:{
                hasPermission(data){
                    let permissionList = this.$route.meta.permission
                    if(permissionList && permissionList.length && permissionList.includes(data)){
                        return true
                    }
                    return false
                }
            }
        })
    }
}

export default hasPermission
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

至此为止,后台管理网站的权限控制流程就已经完全结束了,在最后我们再看一下完整的权限控制流程图吧。

路由控制完整流程图

开始进入登录页面登录成功?是否已经请求过权限菜单?根据数据生成完整路由点击菜单,进入页面前获取该页面详细权限,变更页面权限元素进入页面获取权限菜单列表获取成功?yesnoyesnoyesno

NEXT——登录及系统菜单加载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值