一、获取token 1.根据登录获取token账号token,请求登录接口,后端返回token,前端保存本地和vuex
二、请求角色菜单列表
2.根据token获取账号所对应的角色和菜单列表,请求角色菜单接口,后端返回角色类型和菜单列表,前端保存vuex,这种方法一般都放在vuex的action里,store.dispatch('getUserInfo'),方便其他地方调用
三、路由处理
3.前端根据后端返回的菜单数组进行循环,同时使用vue-router的addRouter方法,把每一项放入路由表里,再拼接上初始的路由(例如登录页的路由等) 菜单路由的处理思路 3.1 一般会定义两个路由,一个是初始的路由,用来存放登录页,404等,即不需要权限,每个用户都可以进入的页面; 3.2 一般会对后端返回的路由再做一层处理,例如加一些属性,例如菜单对象的每一项一般后端会返回一个type来让你区分,比如type为1时代表是菜单,type为2时代表是页面,type为3是代表是按钮,那么前端在处理是需要判断,例如
const RouteView = {
render() {
return h(resolveComponent('router-view'))
},
}
// webpack读取../views下所有vue文件的方法
const modules = require.context('../views', true, /.vue$/);
const permissionCodeList = []
async function handleMenuList(menuList){
recursionHandleRoute(menuList)
return Promise.resolve({totalMenuList:menuList,permissionCodeList})
}
// 递归的处理路由信息
function recursionHandleRoute(menu){
// 先对菜单进行排序
menu = menu.sort((a,b)=>a.sort-b.sort)
menu.forEach(item=>{
// 把所有的权限code都放进一个数组里,然后存到vuex里,方便写自定义指令的时候读取
permissionCodeList.push(item.code)
if([1,2].includes(item.type)){
item.routerInfo = {
children:item.children,
path:item.url,
name:item.name,
code:item.code,
key:item.key,
component:menu.type === 1 ?RouteView:modules[`../views${item.url}.vue`],
meta:{
name:item.name,
key:item.key,
title:item.name,
icon:item.icon,
patentId:item.parentId,
//...其他你想加的属性
},
// ...其他你想加的属性
}
}
// 如果还有children属性,就递归的再处理一遍
if(item.children?.length){
recursionHandleRoute(item.children)
}
if(item.routerInfo){
Object.assign(item,item.routerInfo)
// 这个对象只是做一层处理,用完可以删掉了
delete item.routerInfo
}
})
}
//3.3 处理完菜单后,把两个路由拼接起来,[...初始路由,...菜单路由]
export const constantRoute = [
{
path: '/login',
name: 'login',
component: () => import('@/views/login.vue'),
},
]
const asyncRouter = handleMenuList(menuList)
// 总路由
const totalRoute = [...constantRoute,...asyncRouter.totalMenuList]
// 把最后经过处理的所有路由信息用vuex存起来
store.commit('SET_MENULIST',totalRoute)
// 把所有的权限code用vuex存起来
store.commit('SET_PERMISSION',asyncRouter.permissionCodeList)
// 3.4 处理完所有的路由后添加到路由表里
totalRoute.forEach(item=>{
router.addRoute(item)
})
按钮权限的处理思路
上面路由处理完毕后我们可以在store中拿到所有的permissionCodeList,里面放的就是每个权限代表的code,
这时我们可以写一个自定义指令。
例如:菜单里只有用户管理,这个页面对应的code为2,他的children有4个按钮,分别为创建用户,删除用户,修改用户,查询用户,每个按钮对应的code为5,6,7,8,见图1-3
那么上面我们路由处理完之后,我们的总路由表应该是只有登录页和用户管理菜单能看到,所有的权限code数组里应该是[2,5,6,7,8],那么如果我想给这四个按钮增加按钮权限控制,应该判断他们对应的权限代码值在不在上面定义的数组里,如果不在就让他的display为none就隐藏掉了,自定义指令实现如下:
<el-button v-auth='5' v-xxx='xxx'> 创建用户 </el-button>
app.directive('auth', {
mounted(dom, binding) {
// 如果从所有的权限数组里没找到当前绑定的权限值,那么说明你没有这个按钮权限
if (!store.state.permissionCodeList.includes(binding.value)) {
//这里的dom就是上面的el-button,binding.value就是指令绑定的值,自己可以打印一下
// 这个值不能乱绑,要跟后端返回的对应起来,见图1-3
dom.style.display = 'none'
}
},
})
// 那既然上面存在vuex里,那么就意味着刷新页面后就没有权限了,那么如何处理?
//处理思路:在路由导航守卫处理,因为前面存储了token,所以可以在导航守卫重新根据token获取菜单权限
// 这里有个问题,为什么不把权限数组存到本地,每次从本地拿,因为如果你改了这个角色的菜单,存本地下次来拿还是之前存的,相当于没改,所以不能存本地,只能每次刷新后从新请求新的菜单
// 白名单页面:不需要token的页面
const whiltList = ['/login']
router.beforeEach(async (to,from,next)=>{
const token = sessionStorage.getItem('token')
if(token){
// vuex里面的permissionCodeList数组为空了重置了,说明页面刷新了,这个时候回到第二部即可,重新根据token发起权限菜单请求
if(!store.state.permissionCodeList.length){
// 这种方法一般都放在vuex的action里,直接调用就好了
await store.dispatch('getUserInfo')
// 拿到新权限后继续跳转到对应的页面
if (to.path === '/') {
next({ path: '/home', replace: true })
} else {
next({ path: to.path, query: to.query, params: to.params, replace: true })
}
}
}else{
// 如果去白名单页面,就直接放行。否则跳转到登录
if(whiltList.includes(to.path)){
next()
}else{
next('/login')
}
}
})
四、vuex代码示例
import router from '@/router'
import { constantRouter } from '@/router/config'
const test = {
state: {
token: '',
menuList: [],
permissionCodeList: [],
},
mutations: {
/**
* @method 设置token
*/
SET_TOKEN(state, token) {
state.token = token
},
/**
* @method 设置理由菜单
*/
SET_MENULIST(state, menuList) {
state.menuList = menuList
},
/**
* @method 设置所有权限数组
*/
SET_PERMISSION_LIST(state, permissionCodeList) {
state.permissionCodeList = permissionCodeList
},
},
actions: {
/**
* @method 登录
*/
async login({ commit, dispatch }, params) {
try {
const { Success, Tag } = await userLogin(params)
if (Success) {
commit('SET_TOKEN', Tag.token)
sessionStorage.setItem('token', Tag.token)
await dispatch('getUserInfo')
return Promise.resolve()
}
} catch (error) {
console.log('error-登录', error)
return Promise.reject(error)
}
},
/**
* @method 获取用户对应的菜单
*/
async getUserInfo({ commit }) {
try {
const { Success, Tag } = await getUserMenu()
if (Success) {
const asyncRouter = handleMenuList(Tag.menuList)
const totalRoute = [...constantRouter, ...asyncRouter.totalMenuList]
totalRoute.forEach((item) => {
router.addRouter(item)
})
commit('SET_MENULIST', totalRoute)
commit('SET_PERMISSION_LIST', asyncRouter.permissionCodeList)
return Promise.resolve()
}
} catch (error) {
console.log('error-获取用户菜单', error)
return Promise.reject(error)
}
},
/**
* @method 退出登录,清空本地存储
*/
logout({commit}){
commit('SET_TOKEN','')
commit('SET_MENULIST', [])
commit('SET_PERMISSION_LIST', [])
}
},
}
export default test