菜单权限的业务分析
超级管理员:首页、权限模块、商品模块
不同角色能看到的菜单是不一样的。
如何实现菜单的权限
登录时向服务器发请求,服务器会把用户相应的菜单的权限信息,返回给前端,可以根据服务器返回的数据,动态的设置路由,可以根据不同的用户展示不同的菜单。
获取用户信息时,返回的数据为上图
用户名name 用户头像avatar
routes返回的标志:不同用户应该展示哪些菜单的标记
roles用户角色信息 buttons按钮信息,按钮权限标记
在Vuex中存储这些数据。
实现
当用户获取用户信息的时候,服务器会把相应的用户拥有菜单权限的信息返回,需要用户身份对比出,当前这个用户需要展示哪些信息。
1.将路由进行拆分
常量路由:不管用户是什么角色都可以看到《登录、首页、404》
export const constantRoutes = [
// 首页
{
path: '/',
component: Layout,
redirect: '/dashboard',
children: [{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index'),
meta: { title: '首页', icon: 'dashboard' }
}]
},
····
]
异步路由:
export const asyncRoutes = [
//权限
]
任意路由
当路径出现错误的时候重定向404
anyRoutes=[]
2.在登录存储token和获取用户请求的vuex中
1、定义state中的数据
token: getToken(),
name: '',
avatar: '',
routes:[],
buttons:[],
roles:[],
// 对比之后,项目中已有的异步路由,与服务器返回的标记信息进行对比最终需要展示的路由
resultAsyncRoutes:[],
// 最终需要展示的全部路由
resultAllRoutes:[]
2、定义一个函数
在actions中使用commit向mutations传递
传递什么?传递的是已经计算出来当前用户给予的权限路由(数组)
当然我们现在还没有计算出来
commit('SET_RESULTASYNCROUTES',computedAsyncRoutes(asyncRoutes,data.routes))
需要单独写一个函数computedAsyncRoutes来计算:
asyncRoutes是从router中拿来的所有异步路由
data.routes是服务器返回的当前用户的权限路由
我们需要将两者进行对比过滤,将需要的保留下来,与常量路由和任意路由合并,从而形成一个用户所独有的权限功能数组。
// 函数对比:最终显示路由asyncRoutes动态路由数组,routes服务器返回数组
const computedAsyncRoutes=(asyncRoutes,routes)=>{
return asyncRoutes.filter((item)=>{
if(routes.indexOf(item.name)!=-1){
// 包含相应权限的
// 递归
if(item.children&&item.children.length){
item.children=computedAsyncRoutes(item.children,routes)
}
return true
}
})
}
此时我们就可以直接去mutations中了
SET_RESULTASYNCROUTES:(state,asyncRoutes)=>{
// 仅仅计算出所有的异步路由
state.resultAsyncRoutes=asyncRoutes
// 合并路由 state.resultAllRoutes=constantRoutes.concat(state.resultAsyncRoutes,anyRoutes)
// 给路由添加新的路由
router.addRoutes(state.resultAllRoutes)
// 判断是否添加动态路由
},
3.路由组件依旧不显示
不显示的原因是因为框架当时写的时候就不是根据你合并的这个路由数组遍历的,所以我们要将合并后的数组进行遍历
components->Sidebar->index.vue改一下就行
3.白屏问题
问题描述:当页面刷新时,vuex数据并不是持久存储的,所以所包含的路由权限信息就会消失,但是此时又需要展示所需要的页面(是需要请求得来的),所以出现了白屏的问题。
解决:依据全局前置路由守卫
const whiteList = ['/login']
router.beforeEach(async(to, from, next) => {
//获取token
const hasToken = getToken()
//判断token是否存在
if (hasToken) {
//如果是从login过来的,就放行
if (to.path === '/login') {
next({ path: '/' })
} else {
//如果不是从login过来的,那就是因为路由跳转或刷新出现的问题了,我们可以获取用户信息
const hasGetUserInfo = store.getters.name
//判断用户信息是否存在,如果存在就放行,说明没有刷新,只是路由跳转
if (hasGetUserInfo) {
next()
} else {
//说明用户信息丢失,需要重新获取用户信息
try {
// get user info
await store.dispatch('user/getInfo')
// hack方法 确保addRoutes已完成,注意要添加 replace: true
// 其实在路由守卫中,只有next()是放行,其他的诸如:next('/logon') 、 next(to) 或者 next({ ...to, replace: true })都不是放行,
// 而是:中断当前导航,执行新的导航
// next({ ...to, replace: true })中的replace: true只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。
next({ ...to, replace: true })
// next()
} catch (error) {
await store.dispatch('user/resetToken')
next(`/login?redirect=${to.path}`)
}
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
}
}
})
总结:
1.router.addRoutes()
用于给路由器添加新的路由
2.next({ …to, replace: true })
next({ …to, replace: true })中的replace: true只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由。
3.确保 addRoutes() 已经完成
再执行下一次beforeEach((to, from, next)
。如果守卫中没有正确的放行出口的话,会使next({ …to})进入死循环
器后退按钮,返回前一个路由。
3.确保 addRoutes() 已经完成
再执行下一次beforeEach((to, from, next)
。如果守卫中没有正确的放行出口的话,会使next({ …to})进入死循环