目录
在permission.js中获取action的返回值并过滤
上文说到,角色分配权限
,以及提到权限两个应用
动态生成左侧菜单 以及按钮级控制
权限应用-动态生成左侧菜单-整体分析
分析
登录成功(页面跳转),进入导航守卫:
- 获取个人权限信息
- 生成可以访问的动态路由
示例
权限应用-动态生成左侧菜单-addRoutes方法
vue-router对象中的addRoutes,用它来动态添加路由配置
因为用户访问到的页面(路由配置)必须是动态的,所有要先掌握下可以动态添加路由地址的api
addRoutes基本使用
router.addRoutes([路由配置对象])
或者:
this.$router.addRoutes([路由配置对象])
示例
1.在views下创建页面
<template><h1>abc</h1></template>
2.通过代码添加路由配置
// 按钮
<button @click="hAddRoute">addRoute</button>// 回调
hAddRoute() {
this.$router.addRoutes([{
path: '/abc',
component: () => import('@/views/abc'),
}])
},
点击了按钮之后,就可以在地址中访问/abc了
在代码中书写
1.在router/index.js中的路由配置中删除动态路由的部分
const createRouter = () => new Router({
// mode: 'history', // require service support
scrollBehavior: () => ({ y: 0 }),
// routes: constantRoutes
// 合并动态和静态的路由 , ...asyncRoutes
- routes: [...constantRoutes, ...asyncRoutes]
+ routes: [...constantRoutes]
})
2.在permission.js中引入,并使用addRoutes动态添加
把之前在router中直接静态写死的动态路由表改造成通过addRoutes
方法调用添加的形式
// 引入所有的动态路由表(未经过筛选)
+ import router, { asyncRoutes } from '@/router'
const whiteList = ['/login', '/404']
router.beforeEach(async(to, from, next) => {
// 开启进度条
NProgress.start()
// 获取本地token 全局getter
const token = store.getters.token
if (token) {
// 有token
if (to.path === '/login') {
next('/')
} else {
if (!store.getters.userId) {
await store.dispatch('user/getUserInfo')
// 改写成动态添加的方式
+ router.addRoutes(asyncRoutes)
}
next()
}
} else {
// 没有token
if (whiteList.includes(to.path)) {
next()
} else {
next('/login')
}
}
// 结束进度条
NProgress.done()
})
效果
1.左侧的菜单只剩下静态的首页了
2.浏览器手动输入某一个动态路由地址,依旧是可用的,这证明了已经把动态路由添加到我们的路由系统了
权限应用-动态生成左侧菜单-改写路由信息保存位置
分析
当前的菜单渲染(src\layout\components\Sidebar\index.vue)使用的数据:this.$router.options.routes
这个数据是固定,我们通过addRoutes添加的路由表只存在内存中,并不会改变this.$router.options.routes
如果我们希望在调用addRoutes方法之后,要路由数据立刻反映到菜单中,我们需要想一个额外的方法,vue开发中,哪个技术可以保证响应式特性还可以动态修改? vuex
因此在vuex中保存菜单数据
定义vuex管理菜单数据
1.补充模块,在src/store/modules下补充menu.js
- 定义数据menuList
- 修改数据的方法setMenuList
// 导入静态路由
import { constantRoutes } from '@/router'
export default {
namespaced: true,
state: {
// 先以静态路由作为菜单数据的初始值
menuList: [...constantRoutes]
},
mutations: {
setMenuList(state, asyncRoutes) {
// 将动态路由和静态路由组合起来
state.menuList = [...constantRoutes, ...asyncRoutes]
}
}
}
当然,要在src/store/index.js中注册这个模块
+ import menu from './modules/menu'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
settings,
user,
+ menu
},
getters
})
2.提交setMenuList生成完整的菜单数据
修改src/permission.js中的代码
if (!store.getters.userId) {
await store.dispatch('user/getUserInfo')
// 动态添加可以访问的路由设置
router.addRoutes(asyncRoutes)
// 根据用户实际能访问几个页面来决定从整体8个路由设置
// 中,过滤中出来几个,然后保存到vuex中
+ store.commit('menu/setMenuList', asyncRoutes)
}
3.菜单生成部分改写使用vuex中的数据
在src\layout\components\Sidebar\index.vue文件中,修改
routes() {
// 拿到的是一个完整的包含了静态路由和动态路由的数据结构
// return this.$router.options.routes
return this.$store.state.menu.menuList
}
权限应用-使用权限数据做过滤处理
上一步我们实现了
- 把动态路由通过addRoutes动态添加到了路由系统里
- 把动态路由保存到vuex的menu中
但是我们没有和权限数据做搭配,接下来我们通过接口返回的权限数据对动态菜单(8个页面)做过滤处理,以确定完成菜单与用户权限相关。
过滤的思路
过滤使用name作为标识,对照下标检查路由name是否一致
后端的接口约定如下:
- 页面名字: 员工 标识: employees
- 页面名字: 权限 标识: permissions
- 页面名字: 组织架构 标识: departments
- 页面名字: 设置 标识: settings
- 页面名字: 工资 标识: salarys
- 页面名字: 审核 标识: approvals
- 页面名字: 考勤 标识: attendances
- 页面名字: 社保 标识: social_securitys
从actions中返回菜单项
用户能访问哪些页面是通过actions获取到的,只需要从action中返回即可。
修改 store/modules/user.js
,补充return语句。
// 用来获取用户信息的action
async getUserInfo(context) {
// 1. ajax获取基本信息,包含用户id
const rs = await getUserInfoApi()
console.log('用来获取用户信息的,', rs)
// 2. 根据用户id(rs.data.userId)再发请求,获取详情(包含头像)
const info = await getUserDetailById(rs.data.userId)
console.log('获取详情', info.data)
// 把上边获取的两份合并在一起,保存到vuex中
context.commit('setUserInfo', { ...info.data, ...rs.data })
// 当前用户可以看到的菜单 res.data.roles.menus
+ return rs.data.roles.menus
},
在permission.js中获取action的返回值并过滤
在src/permission.js
中
if (!store.getters.userId) {
// 有token,要去的不是login,就直接放行
// 进一步获取用户信息
// 发ajax---派发action来做
+ const menus = await store.dispatch('user/getUserInfo')
console.log('当前用户能访问的页面', menus) // ['salarys', 'settings']
console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
// 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面
+ const filterRoutes = asyncRoutes.filter(route => {
+ const routeName = route.children[0].name
+ return menus.includes(routeName)
+ })
// 一定要在进入主页之前去获取用户信息
// addRoutes用来动态添加路由配置
// 只有在这里设置了补充了路由配置,才可能去访问页面
// 它们不会出现左侧
+ router.addRoutes(filterRoutes)
// 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
// 生成左侧菜单时,也应该去vuex中拿
+ store.commit('menu/setMenuList', filterRoutes)
}
小结
- 语法: 从actions中获取返回值
asyncRoutes.filter
刷新页面时的bug修复
问题
(1)如果我们刷新浏览器,会发现跳到了404页面
(2)对于addRoute添加的路由,在刷新时会白屏
原因
刷新浏览器,会发现跳到了404页面
现在我们的路由设置中的404页处在中间位置而不是所有路由的末尾了。
解决
把404页改到路由配置的最末尾就可以了
实现代码
1.从route/index.js中的静态路由中删除path:'*'这一项
// 不需要特殊的权限控制就可以访问的页面
export const constantRoutes = [
{
path: '/login',
component: () => import('@/views/login/index'),
hidden: true
},
// 404 page must be placed at the end !!!
- { path: '*', redirect: '/404', hidden: true }
]
2.在permission.js中补充在最后
// if(没有userInfo) {
if (!store.getters.userId) {
// 有token,要去的不是login,就直接放行
// 进一步获取用户信息
// 发ajax---派发action来做
const menus = await store.dispatch('user/getUserInfo')
console.log('当前用户能访问的页面', menus)
console.log('当前系统功能中提供的所有的动态路由页面是', asyncRoutes)
// 根据本用户实际的权限menus去 asyncRoutes 中做过滤,选出本用户能访问的页面
const filterRoutes = asyncRoutes.filter(route => {
const routeName = route.children[0].name
return menus.includes(routeName)
})
// 一定要在进入主页之前去获取用户信息
// 把404加到最后一条
+ filterRoutes.push( // 404 page must be placed at the end !!!
{ path: '*', redirect: '/404', hidden: true })
// addRoutes用来动态添加路由配置
// 只有在这里设置了补充了路由配置,才可能去访问页面
// 它们不会出现左侧
router.addRoutes(filterRoutes)
// 把它们保存在vuex中,在src\layout\components\Sidebar\index.vue
// 生成左侧菜单时,也应该去vuex中拿
store.commit('menu/setMenuList', filterRoutes)
}
解决刷新出现的白屏bug
if (!store.getters.userId) {
// 省略其他...
// 解决刷新出现的白屏bug
next({
...to, // next({ ...to })的目的,是保证路由添加完了再进入页面 (可以理解为重进一次)
replace: true // 重进一次, 不保留重复历史
})
} else {
next()
}
退出登录时重置路由
问题
退出后,再次登陆,发现菜单异常 (控制台有输出说路由重复);
原因
路由设置是通过router.addRoutes(filterRoutes)
来添加的,退出时,并没有清空,再次登陆,又加了一次,所以有重复。
需要将路由权限重置 (恢复默认) 将来登录后再次追加才可以,不然的话,就会重复添加
解决
我们的router/index.js文件,发现一个重置路由方法
// 重置路由
export function resetRouter() {
const newRouter = createRouter()
router.matcher = newRouter.matcher // 重新设置路由的可匹配路径
}
这个方法就是将路由重新实例化,相当于换了一个新的路由,之前加的路由
就不存在了,需要在登出的时候, 调用一下即可
在store/modules/user.js
import { resetRouter } from '@/router'
// 退出的action操作
logout(context) {
// 1. 移除vuex个人信息
context.commit('removeUserInfo')
// 2. 移除token信息
context.commit('removeToken')
// 3. 重置路由
resetRouter()
}