Vue动态匹配路由

这个研究了大半天了,由于对vue不是很熟悉,所以走了很多弯路,这里记录一下开发步骤,参考链接
https://juejin.im/post/591aa14f570c35006961acac

思路
  1. 用户登录,获取token,把token存入cookie
  2. 使用router.beforeEach
  3. 判断用户是否已经拉取用户信息,如果没有,则拉取用户信息
  4. 根据token获取用户可访问的路由表
  5. 动态添加可访问路由表
具体步骤和代码展示

user.js

import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import qs from 'qs'

const user = {
  state: {
    token: getToken(),
    name: '',
    roles: [],
    asyncRouterMap: []
  },

  mutations: {
    SET_TOKEN: (state, token) => {
      state.token = token
    },
    SET_NAME: (state, name) => {
      state.name = name
    },
    SET_ROLES: (state, roles) => {
      state.roles = roles
    },
    SET_ROUTER: (state, asyncRouterMap) => {
      state.asyncRouterMap = asyncRouterMap
    }
  },

  actions: {
    // 登录
    Login({ commit }, userInfo) {
      const username = userInfo.username.trim()
      return new Promise((resolve, reject) => {
        const params = qs.stringify({ username: username, password: userInfo.password })
        login(params).then(response => {
          const data = response.data.data
          setToken(data.token)
          commit('SET_TOKEN', data.token)
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 获取用户信息
    GetInfo({ commit, state }) {
      return new Promise((resolve, reject) => {
        getInfo(state.token).then(response => {
          const data = response.data.data
           // 验证返回的roles是否是一个非空数组
          if (data.roles && data.roles.length > 0) {
            commit('SET_ROLES', data.roles)
          }
          commit('SET_NAME', data.name)
          resolve(response)
        }).catch(error => {
          reject(error)
        })
      })
    },

    // 登出
    LogOut({ commit, state }) {
      return new Promise((resolve, reject) => {
        // console.log(state.token)
        logout().then(() => {
          commit('SET_TOKEN', '')
          commit('SET_ROLES', [])
          localStorage.setItem('User-Roles', '')
          removeToken()
          resolve()
        }).catch(error => {
          reject(error)
        })
      })
    },
  }
}

export default user

permission.js

import router from './router'
import store from './store'
import NProgress from 'nprogress' // Progress 进度条
import 'nprogress/nprogress.css'// Progress 进度条样式
import { Message } from 'element-ui'
import { getToken } from '@/utils/auth' // 验权

NProgress.configure({ showSpinner: false })

// 权限判断方法
function hasPermission(roles, permissionRoles) {
  if (roles.indexOf('admin') >= 0) return true
  if (!permissionRoles) return true
  return roles.some(role => permissionRoles.indexOf(role) > -1)
}
// 不重定向白名单
const whiteList = ['/login']
router.beforeEach((to, from, next) => {
  NProgress.start()
  const token = getToken()
  if (token) {
    if (to.path === '/login') {
      next({ path: '/' })
      // if current page is dashboard will not trigger	afterEach hook, so manually handle it
      NProgress.done()
    } else {
      // 判断当前用户是否已拉取完user_info信息
      if (store.getters.roles.length === 0) {
        // 拉取用户信息
        store.dispatch('GetInfo').then(res => {
          // 根据用户权限生成可访问路由表
          store.dispatch('getAsynvRoutes', token).then(res => {
            // 动态添加可访问路由表
            router.addRoutes(store.getters.addRouters)
            // 防止导航留下历史记录
            next({ ...to, replace: true })
          })
        }).catch((err) => {
          store.dispatch('FedLogOut').then(() => {
            Message.error(err || 'Verification failed, please login again')
            next({ path: '/' })
          })
        })
      } else {
        // 动态改变权限判断路由是否可跳转
        if (hasPermission(store.getters.roles, to.meta.permission)) {
          next()
        } else {
          next({ path: '/404', replace: true })
        }
      }
    }
  } else {
    // 未存在登录态(token) 判断是否在免登录白名单中
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      // 否则全部重定向到登录页
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // 结束Progress
  NProgress.done()
})

routerPermission.js

import { constantRouterMap } from '@/router'
import sysPermissionApi from '@/api/merchant/sysPermission.api'
import Layout from '../../views/layout/Layout'
import Vue from 'vue'

function filterRoutes(routers) {
  // 转component字段的字符串为import
  const routerTree = []
  for (let i = 0; i < routers.length; i++) {
    const obj = routers[i]
    const newObj = {
      path: obj.path,
      redirect: obj.redirect,
      name: obj.name,
      meta: { title: obj.title, btnPermission: obj.btns }
    }
    if (obj.component !== '') {
      Vue.set(newObj, 'component', () => import(`@/views/${obj.component}`))
    } else {
      Vue.set(newObj, 'component', Layout)
    }
    // 需要递归
    if (obj.children.length > 0) {
      Vue.set(newObj, 'children', filterRoutes(obj.children))
    }
    routerTree.push(newObj)
  }
  return routerTree
}

const permission = {
  state: {
    routers: constantRouterMap,
    addRouters: [],
    newAddrouters: []
  },
  mutations: {
    SET_ROUTERS: (state, routers) => {
      // 本地
      // state.routers = constantRouterMap
      // 动态
      // state.routers = routers
      // 动态合并本地
      state.routers = constantRouterMap.concat(routers)
    },
    SET_ADDROUTES: (state, routers) => {
      state.addRouters = routers
    }
  },
  actions: {
    getAsynvRoutes({ commit }, token) {
      return new Promise(resolve => {
        sysPermissionApi.getRolePermissionTree(token).then(res => {
          const accessedRouters = filterRoutes(res.data.data)
          commit('SET_ADDROUTES', accessedRouters)
          commit('SET_ROUTERS', accessedRouters)
          resolve(accessedRouters)
        })
      })
    }
  }
}

export default permission

btnPermission.js

export default (Vue) => {
  Vue.directive('btnPermission', {
    bind: function(el, binding, vnode) {
      const value = binding.value
      if (!vnode.context.$route.meta || !vnode.context.$route.meta.btnPermission) return false
      const permissionList = vnode.context.$route.meta.btnPermission
      if (value && typeof value === 'string') {
        const hasPermission = value in permissionList
        // 还需要做角色判断
        if (!hasPermission) {
          el.parentNode && el.parentNode.removeChild(el)
        }
      } else {
        throw new Error('binding value must be a string!')
      }
    }
  })
}

后端返回的数据格式

{
	"code": 200,
	"data": [{
		"children": [{
			"children": [],
			"component": "merchant/sysRole/index",
			"hidden": 1,
			"icon": "",
			"meta": "",
			"name": "SysRole",
			"path": "sysRole",
			"redirect": "",
			"title": "角色"
		}],
		"component": "",
		"hidden": 1,
		"icon": "",
		"meta": "",
		"name": "Merchant",
		"path": "/merchant",
		"redirect": "/merchant",
		"title": "基础设置"
	}],
	"message": "SUCCESS"
}

页面展示
权限管理页面
在这里插入图片描述
用户角色页面
在这里插入图片描述
角色分配页面
在这里插入图片描述

Logo

前往低代码交流专区

更多推荐