根据用户权限动态生成菜单路由
2019-04-29
思路:
我这里使用了脚手架vue-element-admin,其菜单是sidebar组件根据路由(router.options.routes)自动生成的,所以菜单和路由的控制就变成了仅路由的控制。
首先路由要保留一些基础路由,如首页、404等,动态路由数据由后端根据用户权限返回,然后由前端转换成vue可用的路由,然后使用router.addRoutes()动态添加路由即可。
另外值得一说的是router.addRoutes()可以动态添加路由,却不会改变router.options.routes的值,需要自己手动往里面添加路由。
这波骚操作要在路由导航router.beforeEach()中完成。
重点:
路由的JSON数据结构
[
{
"path": '/pms',
"component": 'Layout',
"redirect": '/pms/application',
"name": 'PMS',
"meta": { "title": 'PMS', "icon": 'example' },
"children": [
{
"path": 'resource',
"name": 'Resource',
"component": '/pms/resource/index.vue',
"meta": { "title": '资源管理', "icon": 'table' }
},
{
"path": 'application',
"name": 'Application',
"component": '/pms/application/index.vue',
"meta": { "title": '应用管理', "icon": 'table' }
},
{
"path": 'group',
"name": 'Group',
"component":'/pms/organization/index.vue',
"meta": { "title": '组织管理', "icon": 'table' }
},
{
"path": 'permission',
"name": 'Permission',
"component": '/pms/permission/index.vue',
"meta": { "title": '权限管理', "icon": 'table' }
}
]
}
]
将JSON转换成vue路由对象的方法
export function rFormat(routers) {
// 简单检查是否是可以处理的数据
if (!(routers instanceof Array)) {
return false
}
// 处理后的容器
const fmRouters = []
routers.forEach(router => {
const path = router.path
const component = router.component
const name = router.name
const hidden = router.hidden
let children = router.children
const meta = router.meta
// 如果有子组件,递归处理
if (children && children instanceof Array) {
children = rFormat(children)
}
const fmRouter = {
path: path,
component(resolve) {
// 拼出相对路径,由于component无法识别变量
// 利用Webpack 的 Code-Splitting 功能
if (router.component === 'Layout') {
// Layout作为特殊组件处理,当然后端也可以写成'/layout/Layout.vue
require(['@/views/layout/Layout.vue'], resolve)
} else {
// '@/views'要拼接进去,组件直接返回'@/views/xxxx/xxx.vue',然后require([compoent],resolve)会报错
require(['@/views' + component], resolve)
}
},
name: name,
hidden: hidden,
children: children,
meta: meta
}
fmRouters.push(fmRouter)
})
return fmRouters
}
保留的基础路由
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
export const constantRouterMap = [
{ path: '/login', component: () => import('@/views/login/index'), hidden: true },
{ path: '/404', component: () => import('@/views/404'), hidden: true },
{
path: '/',
component: Layout,
redirect: '/dashboard',
name: 'Dashboard',
hidden: true,
children: [{
path: 'dashboard',
component: () => import('@/views/dashboard/index')
}]
}
]
export default new Router({
mode: 'history', // 后端支持可开
scrollBehavior: () => ({ y: 0 }),
routes: constantRouterMap
})
路由导航守卫
逻辑:
导航守卫中要做的事就是判断用户是否登录,没登录就去登录,登录完就去获取用户信息。获取用户信息这一步就会拿到用户的信息(姓名,角色,权限[url/element/菜单])。所以“拿到路由json数据,转换成vue可用路由,添加到当前路由”这波操作就放到登录成功后获取用户信息这里完成即可。
# 下面就是判断是否登录成功的逻辑,重点在这个store.dispatch('GetInfo'),就是获取用户信息的动作。
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo').then(() => { // 拉取用户信息
next({ ...to, replace: true })
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
} else {
next()
NProgress.done()
}
} else if (to.query.code) {
// 没有获取到本地token,从url获取code
const code = to.query.code
store.dispatch('Login', code).then(() => {
if (store.getters.roles.length === 0) {
store.dispatch('GetInfo', { code }).then(() => {
}).catch((err) => {
store.dispatch('FedLogOut').then(() => {
Message.error(err || 'Verification failed, please login again')
next({ path: '/' })
})
})
next({ ...to, replace: true })
} else {
next()
NProgress.done()
}
})
} else {
// 没有登录状态,需要跳转到sso
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
const redirect = (url, asLink = true) => asLink ? (window.location.href = url) : window.location.replace(url)
redirect(process.env.SSO_URL + '&type=login')
NProgress.done()
}
}
})
GetInfo这个action具体的实现
actions: {
// 获取用户信息
GetInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const data = response.data
// 拿到后端返回的路由json数据
const rout = data.routers
// 转换成vue可用的路由
const routers = rFormat(rout)
// 动态添加路由,404要放到最后,不然刷新会404
router.addRoutes(routers.concat([{ path: '*', redirect: '/404', hidden: true }]))
// 往router.option.routes里添加路由
routers.forEach((item, index) => {
router.options.routes[index] = item
})
if (data.roles && data.roles.length > 0) { // 验证返回的roles是否是一个非空数组
commit('SET_ROLES', data.roles)
} else {
// reject('getInfo: roles must be a non-null array !')
}
commit('SET_MENUROUTERS', routers)
commit('SET_NAME', data.name)
commit('SET_AVATAR', data.avatar)
localStorage.setItem('element_perms', JSON.stringify(data.element_perms))
// const a = JSON.parse(localStorage.getItem('element_perms'))
resolve(response)
}).catch(error => {
reject(error)
})
})
}
效果
用户权限不同,登陆后的菜单和路由也不同
管理员
普通用户