动态路由实现:在导航守卫中判断用户是否有用户信息, 通过调用接口,拿到后台根据用户角色生成的菜单树, 格式化菜单树结构信息并递归生成层级路由表并 使用Vuex保存,通过 router.addRoutes 动态挂载到 router 上,按钮级别的权限控制,则需使用自定义指令去实现。
实现:
导航守卫代码:
router.beforeEach((to, from, next) => { NProgress.start() // start progress bar to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.title} - ${domTitle}`)) if (getStore('ACCESS_TOKEN')) { /* has token */ if (to.path === '/user/login') { next({ path: '/other/list/user-list' }) NProgress.done() } else { if (store.getters.roles.length === 0) { store .dispatch('GetInfo') .then(res => { const username = res.principal.username store.dispatch('GenerateRoutes', { username }).then(() => { // 根据roles生成可访问的路由表 // 动态添加可访问路由表 router.addRoutes(store.getters.addRouters) const redirect = decodeURIComponent(from.query.redirect || to.path) if (to.path === redirect) { // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record next({ ...to, replace: true }) } else { // 跳转到目的路由 next({ path: redirect }) } }) }) .catch(() => { notification.error({ message: '错误', description: '请求用户信息失败,请重试' }) store.dispatch('Logout').then(() => { next({ path: '/user/login', query: { redirect: to.fullPath } }) }) }) } else { next() } } } else { if (whiteList.includes(to.name)) { // 在免登录白名单,直接进入 next() } else { next({ path: '/user/login', query: { redirect: to.fullPath } }) NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it } } })
Vuex保存routers
const permission = { state: { routers: constantRouterMap, addRouters: [] }, mutations: { SET_ROUTERS: (state, routers) => { state.addRouters = routers state.routers = constantRouterMap.concat(routers) } }, actions: { GenerateRoutes ({ commit }, data) { return new Promise(resolve => { generatorDynamicRouter(data).then(routers => { commit('SET_ROUTERS', routers) resolve() }) }) } } }
路由工具,访问后端接口获得菜单树,然后对菜单树进行处理,把菜单树的组件字符串进行转换为前端的组件如:
userlist: () => import('@/views/other/UserList'),这样生成的路由就是我们所要的了。
import { axios } from '@/utils/request' import { UserLayout, BasicLayout, RouteView, BlankLayout, PageView } from '@/layouts' // 前端路由表 const constantRouterComponents = { // 基础页面 layout 必须引入 BasicLayout: BasicLayout, BlankLayout: BlankLayout, RouteView: RouteView, PageView: PageView, // 需要动态引入的页面组件 analysis: () => import('@/views/dashboard/Analysis'), workplace: () => import('@/views/dashboard/Workplace'), monitor: () => import('@/views/dashboard/Monitor'), userlist: () => import('@/views/other/UserList') // ...more } // 前端未找到页面路由(固定不用改) const notFoundRouter = { path: '*', redirect: '/404', hidden: true } /** * 获取后端路由信息的 axios API * @returns {Promise} */ export const getRouterByUser = (parameter) => { return axios({ url: '/menu/' + parameter.username, method: 'get' }) } /** * 获取路由菜单信息 * * 1. 调用 getRouterByUser() 访问后端接口获得路由结构数组 * 2. 调用 * @returns {Promise<any>} */ export const generatorDynamicRouter = (data) => { return new Promise((resolve, reject) => { // ajax getRouterByUser(data).then(res => { // const result = res.result const routers = generator(res) routers.push(notFoundRouter) resolve(routers) }).catch(err => { reject(err) }) }) } /** * 格式化 后端 结构信息并递归生成层级路由表 * * @param routerMap * @param parent * @returns {*} */ export const generator = (routerMap, parent) => { return routerMap.map(item => { const currentRouter = { // 路由地址 动态拼接生成如 /dashboard/workplace path: `${item && item.path || ''}`, // 路由名称,建议唯一 name: item.name || item.key || '', // 该路由对应页面的 组件 component: constantRouterComponents[item.component || item.key], // meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉) meta: { title: item.name, icon: item.icon || undefined, permission: item.key && [ item.key ] || null } } // 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠 currentRouter.path = currentRouter.path.replace('//', 'https://www.jb51.net/') // 重定向 item.redirect && (currentRouter.redirect = item.redirect) // 是否有子菜单,并递归处理 if (item.children && item.children.length > 0) { // Recursion currentRouter.children = generator(item.children, currentRouter) } return currentRouter }) }
后端菜单树生成工具类