前言
前端控制权限分为两部分,菜单页面 与 按钮。
菜单权限
一个是可见的菜单页面 :左侧dom节点,另外一个是- 一个是可访问的菜单页面 :系统当中路由这一块。
路由权限
接口返回菜单表数据格式如下:
[ { 'name': '首页', //页面名 'code': 'index', 'icon': 'menu', //图标 'path': '/list', 'orders': 1, 'is_hide': 0, 'reserve': {}, 'components': [], //这个存放按钮权限code 的信息
'children': [ //子路由
{
'name': '页面1',
'code': 'page1',
'icon': 'menu',
'path': '/list/page1',
'orders': 2,
'is_hide': 0,
'components': [ //这个存放按钮权限code 的信息
{
'code': 'api/add', //按钮权限会用到
'name': '新增',
'orders': 1,
'reserve': {}
},
{
'code': 'api/update',
'name': '修改',
'orders': 1,
'reserve': {}
}
....
],
'reserve': {
componentName: 'Page1'
}
},
{
'name': '页面2',
'code': 'page2',
'icon': 'menu',
'path': '/list/page2',
'orders': 1,
'is_hide': 0,
'components': [
{
'code': 'api/query',
'name': '菜单查询',
'orders': 1,
'reserve': {}
}
],
'reserve': {
componentName: 'Page2'
}
}
]
}
]
方案一
简单来说前端页面保存全部的路由配置,登录后获取用户的权限,然后过滤得到有权限的路由配置,动态添加到路由中。
router.config.js
/**
* 基础路由 白名单
* @type { *[] }
*/
export const constantRouterMap = [
{
path: '/login',
component: Layout,
redirect: '/user/login',
hidden: true,
children: [
{
path: 'login',
name: 'login',
component: () => import(/* webpackChunkName: "user" */ '@/views/Login')
}
]
},
/**
* 异常页
*/
{
path: '/404',
name: '404',
component: () => import(/* webpackChunkName: "fail" */ '@/views/Exception/404')
}
...
]
export const routerMap = [
{
path: '/',
name: 'index',
redirect: '/main',
component: BasicLayout,
meta: { title: '首页' },
children: [
{
path: '/ac',
name: 'Main',
component: () => import('@/views/Main'),
meta: { title: '活动管理', keepAlive: true, icon: 'project' },
children: [
{
path: '/ac/ac_config',
name: 'ActivityConfiguration',
component: () => import('@/views/Main/ActivityConfiguration'),
meta: { title: '活动配置管理', keepAlive: true }
}
...
]
}
....
]
}
]
登录之后请求获取菜单接口,获取有权限的菜单,收集路由code之后跳转到首页。router.js每次在路由跳转之前做过滤, 使用router.addRoute动态添加到路由中。
/**
* 收集路由code
* 登录接口成功之后调用 将routerCodeArr存起来
*/
collectRouterCode (data) {
for (const i of data) {
if (i.code) {
this.routerCodeArr[i.code] = {}
if (i.components && i.components.length) {
for (const j of i.components) {
this.routerCodeArr[i.code][j.code] = true
}
}
}
if (i.children && i.children.length) {
this.collectRouterCode(i.children)
}
}
},
routerCodeArr:key是有权限的页面code,里面装的按钮权限
{
"index":{},
"page1": {
"api/add":true,
"api/delete": true,
},
"page2":{
"api/query":true
}
....
}
permission.js
// 路由白名单
const whiteList = ['login', '403', '404', '500']
router.beforeEach((to, from, next) => {
NProgress.start()
if (routerCodeArr && routerCodeArr) { //登录成功才有这个routerCodeArr
if (store.state.login.addRouters.length === 0) {
// 动态添加可访问路由表
filterRouter(routerMap)
//动态添加路由!
router.addRoutes(routerMap)
store.dispatch('set_add_routers', routerMap)
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
// hack方法 确保addRoutes已完成
next({ ...to, replace: true })
} else {
// 跳转到目的路由
next({ path: redirect })
}
} else {
// 在免登录白名单,直接进入
if (whiteList.includes(to.name)) {
next()
} else {
next({ path: '/user/login', query: { redirect: to.fullPath } })
NProgress.done()
}
}
}
})
router.afterEach(() => {
NProgress.done()
})
filterRouter 方法做过滤
/**
* 过滤路由
*/
function filterRouter (data) {
const accessedRouters = data.filter(route => {
if (routerCodeArr[route.name]) {
route.meta['auth'] = routerCodeArr[route.name]
if (route.children && route.children.length) {
route.children = filterAsyncRouter(route.children)
}
return true
}
return false
})
return accessedRouters
}
但是随着项目越来越大,会导致这个静态的路由配置也越来越长,不好维护。
方案二
简单来说就是根据后端返回的菜单列表,自己生成路由配置。
router.config.js 配置白名单
const constantRouter = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/404',
component: () => import(/* webpackChunkName: "fail" */ '@/views/error/404')
}
]
permission.js
router.beforeEach((to, from, next) => {
NProgress.start()
const hasToken = store.getters.token //表明已登录
/* has token */
if (hasToken) {
if (to.path === '/login') {
next()
NProgress.done()
} else {
if (store.getters.routers.length === 0) {
//生成路由配置
store.dispatch('GenerateRoutes')
.then((routers) => {
//动态添加路由
router.addRoutes(routers)
const redirect = decodeURIComponent(from.query.redirect || to.path)
if (to.path === redirect) {
next({ ...to, replace: true })
}
}).catch((err) => {
console.log(err)
notification.error({
message: '错误',
description: '请求菜单信息失败,请重试'
})
store.dispatch('Logout')
})
}
next()
}
} else {
if (whiteList.includes(to.path)) {
next()
} else {
next({ path: '/login', query: { redirect: to.fullPath } })
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
GenerateRoutes 方法生成路由配置
/**
* 生成路由
*/
GenerateRoutes ({ commit }) {
return new Promise((resolve, reject) => {
dynamicRouter().then(({ routers, menus }) => {
commit('SET_ROUTERS', routers)
resolve(routers)
}).catch(error => {
reject(error)
})
})
}
dynamicRouter 获取路由菜单信息
/**
* 获取路由菜单信息
*/
export default () => {
return new Promise((resolve, reject) => {
const routers = generator(menus)
routers.unshift(indexRouter)
asyncRouter[0].children = routers
resolve({
routers: asyncRouter,
menus: res.data.menus
})
})
}
generator 生成路由表
/**
* 递归生成层级路由表
*/
const generator = routerMap => {
return routerMap
.map(item => {
const currentRouter = {
path: item.path,
name: item.reserve.componentName,
meta: {
title: item.name,
code: item.code,
icon: item.icon,
permissions: item.components.map(item => item.code), //收集路由code
hidden: !!item.is_hide,
...item.reserve
},
component: ''
}
if (item.children && item.children.length > 0) {
currentRouter.component = BlankLayout
currentRouter.children = generator(item.children)
} else {
currentRouter.component = () => import(`@/views${item.path}/`)
}
return currentRouter
})
.filter(item => {
return !(!item || item === '')
})
}
按钮权限
按钮权限就是控制每个页面上面的按钮的显示。没有权限就不显示此按钮。
接口返回的菜单列表包含了每个页面拥有的按钮权限,收集完按钮权限code之后就通过指令来控制显示。
directive 指令定义
*/
import router from '@/router'
import Vue from 'vue'
const auth = Vue.directive('action', {
inserted: function (el, binding, vnode) {
const { value } = binding
const curRouter = router.app.$route
if (curRouter.meta.permissions.length && curRouter.meta.permissions.indexOf(value) > -1) {
} else {
el.parentNode & el.parentNode.removeChild(el) || (el.style.display = 'none')
}
}
})
export default auth
v-auth 使用
<a-button v-action="'api/add'">新增</a-button>