# 一、动态路由生成
# 方案一(前期)
前端主控(直接根据角色处理动态路由)
- 缺点:路由表与角色分配不能很好的进行动态处理
核心
利用 后端发来的 menus 数组 与 划分的动态路由的 name 标识 进行一一比对。
# 1、路由划分
- router / index.js
// 划分
// 动态路由asyncRoutes
// 静态路由constantRoutes
- router / modules
# 对动态路径进行模块化处理
# power.js
# teaching.js
# other.js
# 2、动态路由筛选
- store / modules / permission.js
// 利用递归算法、角色条件筛选并生成动态路由
// generateRoutes()
// filterAsyncRoutes()
// hasPermission()
// 并存储
// 当前用户的动态路由addRoutes
// 当前用户的全部路由routes
# 3、路由守卫生成
- @ / permission.js
// 根据
// ① 登录时后端返回的用户角色信息role(参数1)
// ② router / index.js 中划分的asyncRoutes(参数2)
// 动态生成路由
import Layout from '@/layout'
export default {
path: '/power',
component: Layout, // 保持Layout架子渲染
alwaysShow: true, // 当children只有一个时,显示根路由
meta: { title: '权限管理', icon: 'form', roles: ['super-admin', 'root'] },
children: [
{
path: 'userControl',
name: 'userControl',
component: () => import('@/views/power/userControl'),
meta: { title: '用户管理', icon: 'user', roles: ['super-admin', 'root'] }
},
{
path: 'roleControl',
name: 'roleControl',
component: () => import('@/views/power/roleControl'),
meta: {
title: '角色管理',
icon: 'nested',
roles: ['super-admin', 'root']
}
},
{
path: 'menuControl',
name: 'menuControl',
component: () => import('@/views/power/menuControl'),
meta: { title: '菜单管理', icon: 'example', roles: ['super-admin'] }
}
]
}
# 方案二(后期)
后端主控(根据 RBAC 权限从数据表中生成路由表供前端使用)
- 优点:登录时后端以知道用户角色信息,使用路由的 name 代替 role 进行动态路由生成
{
"data": {
"id": 43,
"username": "李大庄",
"email": "[email protected]",
……
"token": "Bearer eyJhbGciOiJI……",
// 用户角色
"role": ["super-admin"]
},
// 菜单权限
"menus": ["userControl","roleControl","menuControl",……],
// 按钮权限(自由度比较高,前后端商量好)
// "rights": ["view", "edit", "add", "delete"]
}
- @ / permission.js
// 进行逻辑处理、数据转换、动态添加(难点🤔)
# 1、路由划分
- router / index.js
// 划分
// 动态路由asyncRoutes
// 静态路由constantRoutes
- router / modules
# 对动态路径进行模块化处理
# power.js
# teaching.js
# other.js
# 2、动态路由筛选
- store / modules / permission.js
import { asyncRoutes, constantRoutes } from '@/router'
export default {
namespaced: true,
state: {
routes: constantRoutes // 路由菜单
},
mutations: {
setRoutes(state, filterRoutes) {
state.routes = [...constantRoutes, ...filterRoutes]
},
removeRoutes(state) {
state.routes = constantRoutes
}
},
actions: {
filterRoutes(context, menus) {
// filter返回的是一个通过测试的数组
const filterRoutes = asyncRoutes.filter((route) =>
// 使用some对每一个route的name属性进行检查
menus.some((name_item) => name_item.includes(route.children[0].name))
)
// 添加路由到路由配置中
router.addRoutes([
...filterRoutes,
// 解决刷新后404问题🤔
{ path: '*', redirect: '/404', hidden: true }
])
// 动态路由合并
context.commit('setRoutes', filterRoutes)
}
}
}
- @ / layout / components / Sidebar / index.vue
菜单生成:数据获取从 router 改为使用 vuex 中的数据
export default {
computed: {
routes() {
// return this.$router.options.routes
// 解决菜单显示异常的问题🤔
return this.$store.state.permission.routes
}
}
}
- @ / router / index.js
// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // reset router
}
- @ / store / modules / user.js
路由生成:退出登录时 重置路由
import { resetRouter } from '@/router'
// 退出登录
logOutActions({ commit }) {
commit('RESET_STATE')
// 重置token
commit('REMOVE_TOKEN')
// 重置路由
resetRouter()
}
思考
1、为何动态添加好用, 但是左侧导航不出现?
网页刚打开, 左侧导航已经使用路由对象规则数组, 动态权限是靠网络请求异步回来添加的
而this.$router.options.routes
是静态的,想要它能够丢失再重新生成则可以使用 Vuex 来存储路由数据
2、退出登录时,要做哪些事情?
清除本地的 token, 清除 vuex 里 token, 重置路由规则对象的数组, 然后重新跳转回到登录页
# 3、路由守卫生成
- @ / permission.js
// 前置路由守卫
router.beforeEach(async (to, from, next) => {
// 显示进度条效果
NProgress.start()
// 获取到 token
const token = store.getters.token
// 如果存在 token
if (token) {
if (to.path === '/login') next('/') // 如果存在 token,访问的是登录页面,直接跳转到主页
else {
if (!store.getters.name) {
const menus = await store.dispatch('user/getUserInfoActions')
// 根据menu过滤出 用户可访问的动态路由,并对路由进行合并
const filterRoutes = await store.dispatch('permission/filterRoutes', menus)
// 解决菜单显示异常的问题
// router.options.routes = [...constantRoutes, ...filterRoutes]
// 解决刷新后白屏问题🤔
next({
path: to.path, // next()重定向:保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true // 重进一次, 不保留重复历史
})
}
next() // 如果存在 token,访问的是其他页面,直接放行
}
} else {
if (whiteList.includes(to.path)) next() // 如果不存在 token,访问的是白名单内容,直接放行
else next('/login') // 没有 token,且不是白名单页面,跳转到登录页面
}
})
# 方案三(后端路由主控)
后端主控(根据 RABC 权限从数据表中生成路由表供前端使用)
缺点:
- 路由的 path 和 component 名称未知,不利于前端开发
- 路由表要经过 前端二次处理
- 前端的 tree 数据处理成本高
路由表示例
后端路由表经过前端处理的最终结构(当然:数据是动态的、并且要动态的添加到 vue router 中)。
- 路由表
import Layout from '@/layout'
export default {
path: '/power',
component: Layout, // 保持Layout架子渲染
alwaysShow: true, // 当children只有一个时,显示根路由
meta: { title: '权限管理', icon: 'form', roles: ['super-admin', 'root'] },
children: [
{
path: 'userControl',
name: 'userControl',
component: () => import('@/views/power/userControl'),
meta: { title: '用户管理', icon: 'user', roles: ['super-admin', 'root'] }
},
{
path: 'roleControl',
name: 'roleControl',
component: () => import('@/views/power/roleControl'),
meta: {
title: '角色管理',
icon: 'nested',
roles: ['super-admin', 'root']
}
},
{
path: 'menuControl',
name: 'menuControl',
component: () => import('@/views/power/menuControl'),
meta: { title: '菜单管理', icon: 'example', roles: ['super-admin'] }
}
]
}
# 二、动态侧边栏
基于 element ui 的 NavMenu 导航菜单进行升级改造。
- @ / layout / SideBar / SidbarItem.vue
// 进行递归组件改造
// 其子组件:AppLink、Item
# 三、权限管理与 api 接口
# 1、角色表
- 用户管理页面 -- 更改角色
- 角色管理页面 -- 数据铺设
# 2、权限表
- 菜单管理页面 -- 数据铺设
- 菜单管理页面 -- 编辑菜单
# 4、角色权限管理表(路由表)
角色管理页面 -- 分配权限
- 更改指定角色的权限
# 四、其他
# 1、树形结构数据
应用场景:侧边栏、菜单管理……
- @ / utils / index.js
/**
* 把扁平结构的数据, 转成树形控件
* @param {*} list
* @param {*} rootValue
* @returns
*/
export function translateListToTree(list, rootValue) {
// list: 整个数组, rootValue本次要查找的目标id -> 此函数为了找到rootValue目标id的下属们
const treeData = [] // 装下属对象的
list.forEach((item) => {
if (item.pid === rootValue) {
// 当前对象pid符合, 继续递归调用查找它的下属
const children = transTree(list, item.id) // 返回item对象下属数组
if (children.length) {
item.children = children // 为item添加children属性保存下属数组
}
treeData.push(item) // 把当前对象保存到数组里, 继续遍历
}
})
return treeData // 遍历结束, rootValue的id对应下属们收集成功, 返回给上一次递归调用children, 加到父级对象的children属性下
}
- 使用示例
async getPermissionList() {
// 后端传来的扁平数据
console.log(await getPermissionList())
// 前端处理后的树形数据
this.list = translateListToTree(await getPermissionList(), '0')
console.log(this.list)
}
分析
pid
:表示的是改数据的节点状态
- 若 pid: 0 代表为改数据为根节点
- 若
pid
等于某个数据的id
,代表两者成父子节点关系
# 2、按钮权限
- directives.js
import Vue from 'vue'
Vue.directive('imgerror', {
// 元素放到页面上类似于组件mouted
inserted(el, binding) {
if (!el.src) {
el.src = binding.value
}
el.onerror = function () {
this.src = binding.value
}
},
// 当模板发生改变,触发这个方法
update(el, binding) {
console.log('update', el.src)
if (!el.src) {
el.src = binding.value
}
}
})
# 3、api 权限
← 动态路由