一、动态路由的分析
- 动态路由的生成逻辑,如下所示:
- 从
@router
读取asyncRoutes
和constantRoutes
,获取用户角色roles
,判断roles
是否包含admin
。 - 如果
roles
是包含admin
,将过滤后的asyncRoutes
保存到vuex
中。 - 如果
roles
不包含admin
,那么遍历routes
,判断是否具有路由访问权限。如果不具备,继续遍历routes
。如果具备,判断路由是否包含children
。如果包含children
,遍历children
,过滤children
,更新tmp.children
,再传入判断路由是否包含children
。如果不包含children
,将路由存入res
,将过滤后的asyncRoutes
保存到vuex
中。 - 将过滤后的
asyncRoutes
保存到vuex
中后,asyncRoutes
和constantRoutes
进行合并。
二、动态路由的实现
- 生成动态路由的关键方法是
premission.js
中的generateRoutes
方法,代码如下所示:
const actions = {
// 生成动态路由的关键方法
generateRoutes({
commit }, roles) {
// 返回 Promise 对象
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
// 如果角色中包含 admin,则直接跳过判断,直接将 asyncRoutes 全部返回
accessedRoutes = asyncRoutes || []
} else {
// 如果角色中不包含 admin,则调用 filterAsyncRoutes 过滤路由
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
// 将路由保存到 vuex 中
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
mutations
中的SET_ROUTES
,代码如下所示:
const mutations = {
SET_ROUTES: (state, routes) => {
// 将 routes 保存到 state 中的 addRoutes
state.addRoutes = routes
// 将 routes 集成到 src/router/index.js 中的 constantRoutes 中
state.routes = constantRoutes.concat(routes)
}
}
- 路由过滤的方法
filterAsyncRoutes
,代码如下所示:
/**
* @param routes 异步加载的路由
* @param roles 用户的角色,数组形式
*/
// 路由过滤
export function filterAsyncRoutes(routes, roles) {
const res = []
// 遍历全部的路由
routes.forEach(route => {
// 对路由进行浅拷贝,注意 children 不会拷贝,因为不需要对 children 进行判断,所有可以直接使用
const tmp = {
...route }
// 检查用户角色是否具备访问路由的权限
if (hasPermission(roles, tmp)) {
// 当路由具备访问的权限时,判断路由是否具备 children 属性
if (tmp.children) {
// 当路由包含 children 时,对 children 迭代调用 filterAsyncRoutes 方法
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
// 当路由具有访问权限时,将 tmp 保存到 res 中
res.push(tmp)
}
})
return res
}
- 检查权限的方法
hasPermission
,代码如下所示:
// 检查权限的方法
function hasPermission(roles, route) {
// 检查路由是否包含 meta 和 meta.roles 属性
if (route.meta && route.meta.roles) {
// 判断 route.meta.roles 中是否包含用户角色 roles 中的任何一个权限,如果包含则返回 true
return roles.some(role => route.meta.roles.includes(role))
} else {
// 如果路由没有 meta 或 meta.roles 属性,则视为该路由不需要进行权限控制,所有用户对该路由可访问
return true
}
}
- 完整的核心代码,如下所示:
premission.js
import {
asyncRoutes, constantRoutes } from '@/router'
// 检查权限的方法
function hasPermission(roles, route) {
// 检查路由是否包含 meta 和 meta.roles 属性
if (route.meta && route.meta.roles) {
// 判断 route.meta.roles 中是否包含用户角色 roles 中的任何一个权限,如果包含则返回 true
return roles.some(role => route.meta.roles.includes(role))
} else {
// 如果路由没有 meta 或 meta.roles 属性,则视为该路由不需要进行权限控制,所有用户对该路由可访问
return true
}
}
// 路由过滤
export function filterAsyncRoutes(routes, roles) {
const res = []
// 遍历全部的路由
routes.forEach(route => {
// 对路由进行浅拷贝,注意 children 不会拷贝,因为不需要对 children 进行判断,所有可以直接使用
const tmp = {
...route }
// 检查用户角色是否具备访问路由的权限
if (hasPermission(roles, tmp)) {
// 当路由具备访问的权限时,判断路由是否具备 children 属性
if (tmp.children) {
// 当路由包含 children 时,对 children 迭代调用 filterAsyncRoutes 方法
tmp.children = filterAsyncRoutes(tmp.children, roles)
}
// 当路由具有访问权限时,将 tmp 保存到 res 中
res.push(tmp)
}
})
return res
}
const state = {
routes: [],
addRoutes: []
}
const mutations = {
SET_ROUTES: (state, routes) => {
// 将 routes 保存到 state 中的 addRoutes
state.addRoutes = routes
// 将 routes 集成到 src/router/index.js 中的 constantRoutes 中
state.routes = constantRoutes.concat(routes)
}
}
const actions = {
// 生成动态路由的关键方法
generateRoutes({
commit }, roles) {
// 返回 Promise 对象
return new Promise(resolve => {
let accessedRoutes
if (roles.includes('admin')) {
// 如果角色中包含 admin,则直接跳过判断,直接将 asyncRoutes 全部返回
accessedRoutes = asyncRoutes || []
} else {
// 如果角色中不包含 admin,则调用 filterAsyncRoutes 过滤路由
accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
}
// 将路由保存到 vuex 中
commit('SET_ROUTES', accessedRoutes)
resolve(accessedRoutes)
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
user.js
import {
login, logout, getInfo } from '@/api/user'
import {
getToken, setToken, removeToken } from '@/utils/auth'
import router, {
resetRouter } from '@/router'
const state = {
token: getToken(),
name: '',
avatar: '',
introduction: '',
roles: []
}
const mutations = {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_INTRODUCTION: (state, introduction) => {
state.introduction = introduction
},
SET_NAME: (state, name) => {
state.name = name
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_ROLES: (state, roles) => {
state.roles = roles
}
}
const actions = {
login({
commit }, userInfo) {
const {
username, password } = userInfo
return new Promise((resolve, reject) => {
login({
username: username.trim(), password: password }).then(response => {
const {
data } = response
commit('SET_TOKEN', data.token)
setToken(data.token)
resolve()
}).catch(error => {
reject(error)
})
})
},
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
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)
})
})
},
logout({
commit, state, dispatch }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resetRouter()
dispatch('tagsView/delAllViews', null, {
root: true })
resolve()
}).catch(error => {
reject(error)
})
})
},
resetToken({
commit }) {
return new Promise(resolve => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
removeToken()
resolve()
})
},
async changeRoles({
commit, dispatch }, role) {
const token = role + '-token'
commit('SET_TOKEN', token)
setToken(token)
const {
roles } = await dispatch('getInfo')
resetRouter()
const accessRoutes = await dispatch('permission/generateRoutes', roles, {
root: true })
router.addRoutes(accessRoutes)
dispatch('tagsView/delAllViews', null, {
root: true })
}
}
export default {
namespaced: true,
state,
mutations,
actions
}