Vue 动态路由的实现

5,318 阅读3分钟

我们在日常开发中,经常会遇到项目需要权限管理的,当项目越来越大的时候,我们还是非常需要做动态路由这一块的,现在我们一起来了解一下如果制作vue的动态路由;

1.基础路由的配置

// router.config.js
import { BasicLayout } from '@/layouts'
import { bxAnaalyse } from '@/core/icons'

/**
 * 基础路由
 * @type { *[] }
 */
export const constantRouterMap = [
  {
    path: '/user',
    component: BlankLayout,
    redirect: '/user/login',
    hidden: true,
    children: [
      {
        path: 'login',
        name: 'login',
        component: () => import(/* webpackChunkName: "user" */ '@/views/user/Login')
      }
    ]
  },

  {
    path: '/404',
    component: () => import(/* webpackChunkName: "fail" */ '@/views/exception/404')
  }
]

2.实现路由的主体,动态路由做配置

// router index.js
import Vue from 'vue'
import Router from 'vue-router'
import { constantRouterMap } from '@/config/router.config'

// hack router push callback
// 重写vue-router的push方法 避免相同路由报错
const originalPush = Router.prototype.push
Router.prototype.push = function push (location, onResolve, onReject) {
  if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
  return originalPush.call(this, location).catch(err => err)
}

Vue.use(Router)

/**
 * 防止 指向路由重复 报错问题
 * reset-router 文件中 门户切换 重定向到首页(如果当前是在首页中切换就会出现router报错,指向路由重复错误, 使用下面解决函数解决)
 */
const RouterReplace = Router.prototype.replace
Router.prototype.replace = function replace (location) {
  return RouterReplace.call(this, location).catch(err => err)
}

/*
* 创建路由实例子函数 用于动态路由重置
*/
const createRouter = () => {
  return new Router({
    mode: 'history',
    base: process.env.BASE_URL,
    scrollBehavior: () => ({ y: 0 }),
    routes: constantRouterMap // 基础的路由 比如 登录页面 404页面等
  })
}

/**
 * 初始化路由
 */
const router = createRouter()

/**
 * 重置路由matcher 函数, 避免相同路由重复警告
 * 切换不同角色时 重置对应路由时使用
 */
const resetRouter = () => {
  const newRouter = createRouter()
  router && (router.matcher = newRouter.matcher)
}

export { resetRouter }
export default router

3.路由请求与存储

/**
 * 向后端请求用户路由,动态生成路由
 */
import { constantRouterMap } from '@/config/router.config'
import { generatorDynamicRouter } from '@/router/generator-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 => {
        const { portalId } = data
        generatorDynamicRouter(portalId).then(routers => {
          console.log(routers)
          commit('SET_ROUTERS', routers)
          resolve()
        })
      })
    }
  }
}

export default permission

4.创建路由守卫

// permission.js
import Vue from 'vue'
import router from './router'
import store from './store'
import i18n from './common/index'
import Cookies from 'js-cookie'

import NProgress from 'nprogress' // progress bar
import '@/components/NProgress/nprogress.less' // progress bar custom style
import notification from 'ant-design-vue/es/notification'
import { setDocumentTitle, domTitle } from '@/utils/domUtil'
import { ACCESS_TOKEN, TOKEN, PORTAL_ID, TO_DO, SIAMJWT, USER_INFO } from '@/store/mutation-types'
import { idmLogin } from '@/api/login'

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

const whiteList = ['login', 'register', 'registerResult'] // no redirect whitelist
const defaultRoutePath = '/'
const isProd = process.env.NODE_ENV === 'production' && process.env.VUE_APP_PREVIEW === 'false'

router.beforeEach(async (to, from, next) => {
  NProgress.start() // start progress bar
  to.meta && (typeof to.meta.title !== 'undefined' && setDocumentTitle(`${to.meta.isI18n ? i18n.t(to.meta.title) : to.meta.title} - ${i18n.t(domTitle)}`)) // 设置页面title
  if (Vue.ls.get(ACCESS_TOKEN)) { // 判断是否登录
    /* has token */
    if (to.path === '/user/login') {
      next({ path: defaultRoutePath }) // 跳转到首页
      NProgress.done()
    } else {
    // 是否有权限
      if (store.getters.roles.length === 0) {
        store
          .dispatch('GetInfo')
          .then(res => {
            const roles = res && res.role
            const portalId = Vue.ls.get(PORTAL_ID)
            store.dispatch('GenerateRoutes', { roles, portalId }).then(() => {
              // 根据roles权限生成可访问的路由表
              // 动态添加可访问路由表
              router.addRoutes(store.getters.addRouters)
              // 请求带有 redirect 重定向时,登录自动重定向到该地址
              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({
              key: 'loginPageError',
              message: i18n.t('basicLayout.error'),
              description: i18n.t('basicLayout.requestUserInfoError')
            })
          })
      } else {
        if (to.matched.length === 0) {
          next({ path: '/404' })
        }
        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
    }
  }
})
router.afterEach(() => {
  NProgress.done() // finish progress bar
})

5.在mian.js中引入permission.js路由守卫文件

⚠ vue是单页面应用程序,所以页面一刷新数据部分数据也会跟着丢失,所以我们需要将store中的数据存储到本地,我们项目中是vue-ls 来做存储,这里可以去找一些符合自己项目的插件或者自己编写存储库