vue-element-admin实现动态权限管理的思路总结

·  阅读 176

总体思路

- 使用vue的状态管理模式(vuex)结合路由导航守卫来实现
复制代码

具体实现代码分析

  • 在登录页面(src/login/index.vue)点击登录,触发handleLogin方法
  handleLogin() {
  		//表单验证后触发里面的回调
        this.$refs.loginForm.validate(valid => {
        	//表单验证成功
          if (valid) {
            this.loading = true
            //将表单提交到vuex中的user模块中的action中的login方法 src/store/module/user.js
            //实际这里并没有向服务器发送登录请求、登录请求是在src/store/modules/user.js中的action里面发送的
            //等待vuex中的登录操作完成后返回的Promise状态改变后 如果登录成功执行then中的代码
            this.$store.dispatch('user/login', this.loginForm).then(() => {
            //登录成功触发路由跳转 由此转入到@/premission.js中的router.beforeEach()路由守卫中执行里面的代码
              this.$router.push({ path: this.redirect || '/' })
              this.loading = false
            }).catch(() => {
              this.loading = false
            })
          } else {
          	//表单验证失败
            console.log('error submit!!')
            return false
          }
        })
      }
复制代码
  • store中的modules里的user.js代码如下

    import { login, logout, getInfo } from '@/api/user'
    import { getToken, setToken, removeToken } from '@/utils/auth'
    import router, { resetRouter } from '@/router'
    
    const state = {
      token: getToken(),
      name: '',
      avatar: '',
      introduction: '',
      roles: []
    }
    
    const mutations = {
      SET_TOKEN: (state, token) => {
        state.token = token
      },
      SET_INTRODUCTION: (state, introduction) => {
        state.introduction = introduction
      },
      SET_NAME: (state, name) => {
        state.name = name
      },
      SET_AVATAR: (state, avatar) => {
        state.avatar = avatar
      },
      SET_ROLES: (state, roles) => {
        state.roles = roles
      }
    }
    
    const actions = {
      //处理用户登录
      login({ commit }, userInfo) {
        const { username, password } = userInfo
        
        return new Promise((resolve, reject) => {
        //向后台发送登录请求
          login({ username: username.trim(), password: password }).then(response => {
            const { data } = response
            
            //拿到token后将token保存在vuex中
            commit('SET_TOKEN', data.token)
            //将token保存在cookie中
            setToken(data.token)
            //执行src/login/index.vue中提交状态改变代码的地方的then中的代码
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // get user info
      getInfo({ commit, state }) {
        return new Promise((resolve, reject) => {
          getInfo(state.token).then(response => {
            const { data } = response
    
            if (!data) {
              reject('Verification failed, please Login again.')
            }
    
            const { roles, name, avatar, introduction } = data
    
            // roles must be a non-empty array
            if (!roles || roles.length <= 0) {
              reject('getInfo: roles must be a non-null array!')
            }
    
            commit('SET_ROLES', roles)
            commit('SET_NAME', name)
            commit('SET_AVATAR', avatar)
            commit('SET_INTRODUCTION', introduction)
    
            resolve(data)
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // user logout
      logout({ commit, state, dispatch }) {
        return new Promise((resolve, reject) => {
          logout(state.token).then(() => {
            commit('SET_TOKEN', '')
            commit('SET_ROLES', [])
            removeToken()
            resetRouter()
    
            // reset visited views and cached views
            // to fixed https://github.com/PanJiaChen/vue-element-admin/issues/2485
            dispatch('tagsView/delAllViews', null, { root: true })
    
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      },
    
      // remove token
      resetToken({ commit }) {
        return new Promise(resolve => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          removeToken()
          resolve()
        })
      },
    
      // dynamically modify permissions
      async changeRoles({ commit, dispatch }, role) {
        const token = role + '-token'
    
        commit('SET_TOKEN', token)
        setToken(token)
    
        const { roles } = await dispatch('getInfo')
    
        resetRouter()
    
        // generate accessible routes map based on roles
        const accessRoutes = await dispatch('permission/generateRoutes', roles, { root: true })
        // dynamically add accessible routes
        router.addRoutes(accessRoutes)
    
        // reset visited views and cached views
        dispatch('tagsView/delAllViews', null, { root: true })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    
    复制代码
    • 3.main.js中的引入的permission.js这个文件是关键。里面代码使用路由守卫结合vuex实现了根据不同的角色生成不同的可访问路由的功能,也就是实现了权限管理。就好像你放在每个页面的一条看门狗

  import router from './router'
  import store from './store'
  import { Message } from 'element-ui'
  import NProgress from 'nprogress' // progress bar
  import 'nprogress/nprogress.css' // progress bar style
  import { getToken } from '@/utils/auth' // get token from cookie
  import getPageTitle from '@/utils/get-page-title'

  NProgress.configure({ showSpinner: false }) // NProgress Configuration

  const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
  //每次路由跳转时都要走这里
  router.beforeEach(async(to, from, next) => {
    // start progress bar

    NProgress.start()

    // 设置页面title
    document.title = getPageTitle(to.meta.title)

    // determine whether the user has logged in
    const hasToken = getToken()

    if (hasToken) {
      if (to.path === '/login') {
        // if is logged in, redirect to the home page
        next({ path: '/' })
        NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
      } else {
        // determine whether the user has obtained his permission roles through getInfo
        
        const hasRoles = store.getters.roles && store.getters.roles.length > 0
        //在vuex中获取roles 如果是第一次登录  这个时候hasRoles为false
        if (hasRoles) {
          next()
        } else {
          try {
            // get user info
            // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
            // 在vuex中向后端发送获取角色信息的请求 并返回角色
            const { roles } = await store.dispatch('user/getInfo')

            //** 生成该角色能访问的路由 **
            //生成路由表的工作由src/store/modules/permission.js完成
            const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
            console.log(accessRoutes)
            // dynamically add accessible routes

            // hack method to ensure that addRoutes is complete
            // set the replace: true, so the navigation will not leave a history record
            next({ ...to, replace: true })
          } catch (error) {
            // remove token and go to login page to re-login
            await store.dispatch('user/resetToken')
            Message.error(error || 'Has Error')
            next(`/login?redirect=${to.path}`)
            NProgress.done()
          }
        }
      }
    } else {
      /* has no token*/

      if (whiteList.indexOf(to.path) !== -1) {
        // in the free login whitelist, go directly
        next()
      } else {
        // other pages that do not have permission to access are redirected to the login page.

        next(`/login?redirect=${to.path}`)
        NProgress.done()
      }
    }
  })

  router.afterEach(() => {
    // finish progress bar
    NProgress.done()
  })
复制代码
  • 动态生成路由的具体代码 src/store/modules/permission.js
     import { asyncRoutes, constantRoutes } from '@/router'

    /**
     * Use meta.role to determine if the current user has permission
     * @param roles
     * @param route
     */
    function hasPermission(roles, route) {
      if (route.meta && route.meta.roles) {
        return roles.some(role => route.meta.roles.includes(role))
      } else {
        return true
      }
    }

    /**
     * Filter asynchronous routing tables by recursion
     * 使用递归过滤异步路由(实现不同的角色不同的路由)
     * @param routes asyncRoutes
     * @param roles
     */
    export function filterAsyncRoutes(routes, roles) {
      const res = []

      routes.forEach(route => {
        const tmp = { ...route }
        if (hasPermission(roles, tmp)) {
          if (tmp.children) {
            tmp.children = filterAsyncRoutes(tmp.children, roles)
          }
          res.push(tmp)
        }
      })

      return res
    }

    const state = {
      routes: [],
      addRoutes: []
    }

    const mutations = {
      SET_ROUTES: (state, routes) => {
        state.addRoutes = routes
        state.routes = constantRoutes.concat(routes)

      }
    }

    const actions = {
      generateRoutes({ commit }, roles) {
        return new Promise(resolve => {
          let accessedRoutes
          if (roles.includes('admin')) {
          //admin拥有所有路由权限
            accessedRoutes = asyncRoutes || []
          } else {
          //从异步路由中根据不同的角色递归的过滤出该角色能访问的路由
            accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
          }
          commit('SET_ROUTES', accessedRoutes)
          //将生成的有权限的路由返回给调用该方法处的then 流程走完
          resolve(accessedRoutes)
        })
      }
    }
复制代码
分类:
前端
标签: