基于vue3的后台管理权限划分

153 阅读4分钟

   一、获取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