添加动态路由

  • router/index.js

在router.js里定义asyncRoutes作为动态路由数组,

import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/layout'

Vue.use(Router)

export const constantRoutes = [
  {
    path: "",
    redirect: '/portal',
  },
  {
    path: '/login',
    component: () => import('@/views/login/index'),
  },
  {
    path: '/404',
    component: () => import('@/views/404'),
  },
  {
    path: '/portal',
    component: () => import('@/views/portal/index'),
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () =>
                  import ('@/views/dashboard/index'),
      meta: { title: '资源认证平台', affix: true, icon: 'dashboard' }
    }]
  },

  {
    path: '/user',
    component: Layout,
    redirect: '/user',
    children: [{
      path: 'user',
      name: 'User',
      component: () =>
                  import ('@/views/user/index'),
      meta: { title: '用户管理', affix: true, icon: 'user', }
    }]
  }
  // 404 page must be placed at the end !!!
]

export const asyncRoutes = [
  {
    path: '/application',
    component: Layout,
    redirect: '/application',
    children: [{
      path: 'application',
      name: 'Application',
      component: () =>
                  import ('@/views/application/index'),
      meta: { title: '应用管理', affix: true, icon: 'table', roles: ['system'] }
    }]
  },
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

  • store/modules/permission.js

在store/modules下添加permission.js

// 首先,从index.js中引入已经定义过的2个router数组
import { asyncRoutes, constantRoutes } from '@/router'
 
// 全局变量state,routes和addRoutes数组
const state = {
  routes: [],
  addRoutes: []
}
 
// mutations 是唯一可以更改state的函数,使用SET_ROUTES定义更改方法,SET_ROUTES(state, routes)的入参routes赋值给addRoutes数组,将constantRoutes静态路由数组增加routes;
 
const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}
 
// vue中store状态管理,通过actions调用 mutations 中封装的方法来实现对于state更改,
// 这里是vue-element-admin中动态路由的主要判断逻辑发生地方,首先判定用户角色是否包含admin(可能多角色),是则将所有asyncRoutes 加入到constantRoutes,若用户角色没有包含admin,则调用filterAsyncRoutes函数,递归地判定asyncRoutes.roles属性是否有该角色,即是否有权限,将有权限的router赋值accessedRoutes 后加入到constantRoutes;
 
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('system')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}
 
//-----------------
// 两个上面使用的方法
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}
 
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
}
 
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
  • src/permission.js

修改根目录src下的permission.js

if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
       try {
         const roles = await store.dispatch('user/getInfo')
         await store.dispatch('permission/generateRoutes', roles).then(res => {
           router.addRoutes(res)
           next({ ...to, replace: true })
         })
       } catch (error) {
         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) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }

问题一:路由守卫死循环

在这里插入图片描述

如图,按上面配置动态路由后,在进入路由守卫时,不断循环,无法正常转发

其实在路由守卫中,只有next()是放行,其他的诸如:next('/logon') 、 next(to) 或者 next({ ...to, replace: true })都不是放行,而是:中断当前导航,执行新的导航

例如现在我有一个守卫,在守卫中我使用next('/logon'),肯定有同学认为是会直接跳转到/logon路由:

beforeEach((to, from, next) => {
  next('/logon')
}

其实他是这么执行的:

beforeEach((to, from, next) => {
  beforeEach(('/logon', from, next) => {
  	 beforeEach(('/logon', from, next) => {
  	 	 beforeEach(('/logon', from, next) => {
  	 	 	beforeEac...  // 一直循环下去...... , 因为我们没有使用 next() 放行
 		}
 	 }
  }
}

next('/logon')不是说直接去/logon路由,而是中断这一次路由守卫的操作,又进入一次路由守卫,就像嵌套一样,一层路由守卫,然后又是一层路由守卫,此时路由守卫进入到第二层时,to.path已经不是/home了,这个时候才执行next()放行操作。
如果守卫中没有正确的放行出口的话,会一直next({ ...to})进入死循环 !!!

因此你还需要确保在当addRoutes()已经完成时,所执行到的这一次beforeEach((to, from, next)中有一个正确的next()方向出口。
因此想实现动态添加路由的操作的话,代码应该是这样的:

const hasToken = getToken()['Authorization']
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if(hasRoles){
        next()
      }else{
        try {
          const roles = await store.dispatch('user/getInfo')
          await store.dispatch('permission/generateRoutes', roles).then(res => {
            router.addRoutes(res)
            next({ ...to, replace: true })
          })
        } catch (error) {
          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) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})
getInfo({ commit, state }){
    return new Promise((resolve, reject) => {
      const { roles } = JSON.parse(getUser())
      commit("SET_ROLES", roles)
      resolve(roles)
    })
  },

问题二:addRouters()不生效

执行完addRouters()后,我查看了store里的routes,已经有将动态路由添加进去,但是菜单中还是没有显示

查看了/layout/components/Sidebar/index.vue,发现使用的是this. r o u t e r . o p t i o n s . r o u t e s 而 不 是 s t o r e 中 的 , 所 以 我 们 需 要 在 a d d R o u t e r s ( ) 后 也 修 改 t h i s . router.options.routes而不是store中的,所以我们需要在addRouters()后也修改this. router.options.routesstoreaddRouters()this.router.options.routes的值,或者直接修改下面routes()方法,直接使用store中的routes

routes() {
  return this.$router.options.routes
}

修改后的permission.js

const roles = await store.dispatch('user/getInfo')
await store.dispatch('permission/generateRoutes', roles).then(res => {
  router.addRoutes(res)
  router.options.routes = store.getters.routes
  next({ ...to, replace: true })
})

最终版,所有相关文件

  • router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import Layout from '@/layout'

Vue.use(Router)

export const constantRoutes = [
  {
    path: "",
    redirect: '/portal',
  },
  
  {
    path: '/login',
    component: () => import('@/views/login/index'),
  },

  {
    path: '/404',
    component: () => import('@/views/404'),
  },

  {
    path: '/portal',
    component: () => import('@/views/portal/index'),
  },

  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [{
      path: 'dashboard',
      name: 'Dashboard',
      component: () =>
                  import ('@/views/dashboard/index'),
      meta: { title: '资源认证平台', affix: true, icon: 'dashboard' }
    }]
  },

  {
    path: '/user',
    component: Layout,
    redirect: '/user',
    children: [{
      path: 'user',
      name: 'User',
      component: () =>
                  import ('@/views/user/index'),
      meta: { title: '用户管理', affix: true, icon: 'user', }
    }]
  }
  // 404 page must be placed at the end !!!
]

export const asyncRoutes = [
  {
    path: '/application',
    component: Layout,
    redirect: '/application',
    children: [{
      path: 'application',
      name: 'Application',
      component: () =>
                  import ('@/views/application/index'),
      meta: { title: '应用管理', affix: true, icon: 'table', roles: ['system'] }
    }]
  },
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new Router({
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router
  • store/modules/permission.js
// 首先,从index.js中引入已经定义过的2个router数组
import { asyncRoutes, constantRoutes } from '@/router'
 
// 全局变量state,routes和addRoutes数组
const state = {
  routes: [],
  addRoutes: []
}
 
// mutations 是唯一可以更改state的函数,使用SET_ROUTES定义更改方法,SET_ROUTES(state, routes)的入参routes赋值给addRoutes数组,将constantRoutes静态路由数组增加routes;
 
const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}
 
// vue中store状态管理,通过actions调用 mutations 中封装的方法来实现对于state更改,
// 这里是vue-element-admin中动态路由的主要判断逻辑发生地方,首先判定用户角色是否包含admin(可能多角色),是则将所有asyncRoutes 加入到constantRoutes,若用户角色没有包含admin,则调用filterAsyncRoutes函数,递归地判定asyncRoutes.roles属性是否有该角色,即是否有权限,将有权限的router赋值accessedRoutes 后加入到constantRoutes;
 
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('system')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}
 
//-----------------
// 两个上面使用的方法
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}
 
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
}
 
export default {
  namespaced: true,
  state,
  mutations,
  actions
}
  • src/permission.js
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'] // no redirect whitelist

router.beforeEach(async(to, from, next) => {
  NProgress.start()
  document.title = getPageTitle(to.meta.title)

  const hasToken = getToken()['Authorization']
  if (hasToken) {
    if (to.path === '/login') {
      next({ path: '/' })
      NProgress.done()
    } else {
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if(hasRoles){
        next()
      }else{
        try {
          const roles = await store.dispatch('user/getInfo')
          await store.dispatch('permission/generateRoutes', roles).then(res => {
            router.addRoutes(res)
            router.options.routes = store.getters.routes
            next({ ...to, replace: true })
          })
        } catch (error) {
          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) {
      next()
    } else {
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})

GitHub 加速计划 / vu / vue
82
16
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:4 个月前 )
9e887079 [skip ci] 2 个月前
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 6 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐