定义路由
- 使用constantRoutes进行初始化router
constantRoutes中定义了无需权限控制的路由(下文中以[通用路由表]代指),其中path '/',必须放在通用路由表中:
## 文件src/router/index.js
constantRoutes = [
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [
{
path: 'dashboard',
component: () => import('@/views/dashboard/index'),
name: 'Dashboard',
meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
}
]
},
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
{
path: '/profile',
component: Layout,
redirect: '/profile/index',
hidden: true,
children: [
{
path: 'index',
component: () => import('@/views/profile/index'),
name: 'Profile',
meta: { title: 'Profile', icon: 'user', noCache: true }
}
]
},
{
path: '/401',
component: () => import('@/views/error-page/401'),
hidden: true
},
{
path: '/404',
component: () => import('@/views/error-page/404'),
hidden: true
}
]
export const asyncRoutes = [{
path: '/permission',
component: Layout,
redirect: '/permission/page',
alwaysShow: true, // will always show the root menu
name: 'Permission',
meta: {
title: 'Permission',
icon: 'lock',
roles: ['admin', 'editor'] // you can set roles in root nav
},
children: [
{
path: 'page',
component: () => import('@/views/permission/page'),
name: 'PagePermission',
meta: {
title: 'Page Permission',
roles: ['admin'] // or you can only set roles in sub nav
}
},
{
path: 'directive',
component: () => import('@/views/permission/directive'),
name: 'DirectivePermission',
meta: {
title: 'Directive Permission'
// if do not set roles, means: this page does not require permission
}
},
{
path: 'role',
component: () => import('@/views/permission/role'),
name: 'RolePermission',
meta: {
title: 'Role Permission',
roles: ['admin']
}
}
]
},
{
path: '/icon',
component: Layout,
children: [
{
path: 'index',
component: () => import('@/views/icons/index'),
name: 'Icons',
meta: { title: 'Icons', icon: 'icon', noCache: true }
}
]
]
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
routes: constantRoutes
})
const router = createRouter()
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465 用以在登出或切换角色时清空路由表以便下次动态新增路由表
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
export default router
全局前置守卫
- 全局前置守卫中检查store中是否已经有角色列表,如果没有角色列表,则进行登录拉取角色信息,并拉取带角色标记的路由asyncRoutes(下文中以[异步路由表]代指),结合当前登录的角色过滤出当前角色可访问的路由accessedRoutes(下文中以[未加入通用路由表的可访问路由表]代指),然后与通用路由表constantRoutes合并,生成最终可访问路由表routes(下文中以[可访问路由表]代指)存入store中。如果已经有角色列表,则放行。 然后通过这个可访问路由表数据渲染出可访问菜单列表。
# src/permission.js
router.beforeEach(async(to, from, next) => {
...
const hasRoles = store.getters.roles && store.getters.roles.length > 0
if (hasRoles) {
next()
} else {
try {
// 获取用户信息得到角色列表 类似于: ['admin'] 或['developer','editor']
const { roles } = await store.dispatch('user/getInfo')
// 生成可访问路由表
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// 动态加入可访问路由表
router.addRoutes(accessRoutes)
// hack method to ensure that addRoutes is complete
// set the replace: true, so the navigation will not leave a history record
next({ ...to, replace: true })
} catch (error) {
// remove token and go to login page to re-login
await store.dispatch('user/resetToken')
next(`/login?redirect=${to.path}`)
}
}
...
}
# src/store/modules/permission.js
//本例中,异步路由表直接由前端配置,从@/router/index.js中获取
import { asyncRoutes, constantRoutes } from '@/router'
//权限判断
function hasPermission(roles, route) {
if (route.meta && route.meta.roles) {
return roles.some(role => route.meta.roles.includes(role))
} else {
return true
}
}
//路由表过滤
export function filterAsyncRoutes(routes, roles) {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(roles, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
//合并通用路由表
const mutations = {
SET_ROUTES: (state, routes) => {
state.addRoutes = routes
state.routes = constantRoutes.concat(routes)
}
}
//拉取异步路由表
const actions = {
generateRoutes({ commit }, roles) {
return new Promise(resolve => {
//api.getRoutes().then(response => {
// const { data } = response
let accessedRoutes
// let asyncRoutes = data
if (roles.includes('admin')) {
accessedRoutes = asyncRoutes || []
} else {
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
// })
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
登出和角色切换
在登出和角色切换时的一个action是重置router,以便重新载入新的router数据
#src/store/modules/user.js
...
const actions = {
...
// get user info
getInfo({ commit, state }) {
return new Promise((resolve, reject) => {
getInfo(state.token).then(response => {
const { data } = response
if (!data) {
reject('Verification failed, please Login again.')
}
const { roles, name, avatar, introduction } = data
// roles must be a non-empty array
if (!roles || roles.length <= 0) {
reject('getInfo: roles must be a non-null array!')
}
commit('SET_ROLES', roles)
commit('SET_NAME', name)
commit('SET_AVATAR', avatar)
commit('SET_INTRODUCTION', introduction)
resolve(data)
}).catch(error => {
reject(error)
})
})
},
// user logout
logout({ commit, state, dispatch }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
//重置路由
resetRouter()
// reset visited views and cached views
// to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
dispatch('tagsView/delAllViews', null, { root: true })
resolve()
}).catch(error => {
reject(error)
})
})
},
// remove token
resetToken({ commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
})
},
// dynamically modify permissions
changeRoles({ commit, dispatch }, role) {
return new Promise(async resolve => {
const token = role + '-token'
commit('SET_TOKEN', token)
setToken(token)
const { roles } = await dispatch('getInfo')
//重置路由
resetRouter()
// generate accessible routes map based on roles
const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
// dynamically add accessible routes
router.addRoutes(accessRoutes)
// reset visited views and cached views
dispatch('tagsView/delAllViews', null, { root: true })
resolve()
})
}
}
...
按钮的权限控制
- 按钮操作(即组件)的权限控制,在每个组件加入v-if="checkPermission(['admin'])" 指令,由checkPermission工具函数计算组件对当前角色列表是否可见。checkPermission内容如下
#src/utils/permission.js
import store from '@/store'
/**
* @param {Array} value
* @returns {Boolean}
* @example see @/views/permission/directive.vue
*/
export default function checkPermission(value) {
if (value && value instanceof Array && value.length > 0) {
const roles = store.getters && store.getters.roles
const permissionRoles = value
const hasPermission = roles.some(role => {
return permissionRoles.includes(role)
})
if (!hasPermission) {
return false
}
return true
} else {
console.error(`need roles! Like v-permission="['admin','editor']"`)
return false
}
}
参考