1. 需求描述

最近开发的一个项目,涉及到这样一个需求:随着项目的不断推进,后台管理功能逐渐增多,与此同时,静态路由表也逐渐扩大,需要把静态路由方式转换为动态路由方式。要完成这样一个转换,有几个技术要点需要解决,其中一个就是前端的实现方式。那么,前端如何实现呢?

2. 实现

Vue动态路由的前端实现,网上有不少参考资料。但大多存在代码冗余,思路模糊不清的情况。现在整理一下思路。当前端发送登录请求login向后台请求数据后,后台会返回一个user对象至前端,前端把该对象保存至vuex中,同时,保存user中的token对象至vuex中。在vuex模块对应的store文件家中,Vue2对应两个文件:getters和index。
getters 文件源码

let getters = {
  user: state => state.user,
  token: state => state.token,
  routes: state => state.routes
}
export default getters

index文件源码

import Vue from 'vue'
import Vuex from 'vuex'
import getters from './getters'
import persistedState from 'vuex-persistedstate'

Vue.use(Vuex)

const state = {
  token: '',
  user: '',
  routes: []
}
const mutations = {
  setToken (state, token) {
    state.token = token
  },
  setUser (state, user) {
    state.user = user
  },
  setRouter (state, routes) {
    state.routes = routes
  },
  clear (state) {
    state.token = ''
    state.user = ''
    state.routes = []
  }
}

const store = new Vuex.Store({
  state,
  getters,
  mutations,
  plugins: [persistedState({ storage: window.sessionStorage })]
})

export default store

注意: 其中persistedState 为持久化插件,使得刷新的时候,保存vuex中的数据不丢失,从而回到当前页面,而不至于刷新后丢失数据后回到登录页。

回到刚才的话题,在前端登录请求中,登录认证成功后,在回调函数中,会获取到user对象。该对象中保存有token信息,同时,也保存有treeMenu信息。

如何在静态路由和动态路由之间比较自由的切换,显然,修改的地方越少越好。
由于router文件夹中的静态路由涉及到权限敏感的路由数据会被注释掉,并放入后台数据库,此为需要修改的第一个地方。即 注释和反注释。
需要特别说明的是,在登录方法中,并不需要修改代码。
需要修改代码的第二个地方,就是路由守卫的文件里面。
源码如下所示

import router from './router'
import store from './store'
import {deepClone} from './utils'
// 获取组件的方法
let _import = file => require('@/views' + file + '.vue').default
// 页面全局路由列表
let getRouter = []
// 默认静态路由的长度
let staticRouterLength = 3
let staticRouter = []

router.beforeEach((to, from, next) => {
  // 判断该路由是否需要登录权限
  if (to.path === '/' || to.path === '/app2' || to.path === '/login') {
    next()
  } else {
    if (!store.state.token) { // 未登录
      next({
        path: '/login'
        // 将跳转的路由path作为参数,登录成功后跳转到该路由。
        // query: {redirect: to.fullPath}
      })
    } else {
      // 已登录
      // next()
      // ------------切换为动态路由----------------
      // 已登录的情况下,从vuex中获取用户菜单列表,进行组件的初始化。
      // 静态路由为登录等基本的路由。
      if (store.state.routes.length === 0 || router.options.routes.length === staticRouterLength) {
        if (router.options.routes.length === staticRouterLength) {
          // 静态路由的情况下,把静态路由信息存入静态变量
          staticRouter = deepClone(router.options.routes)
        }
        // 从vuex的user对象中获取菜单树后台数据
        let treeMenu = deepClone(store.state.user['treeMenu'])
        // 通过页面全局变量,进一步加载getRouter
        getRouter = filterAsyncRouter(treeMenu)
        // 把该加载的全局变量存入vuex中
        store.commit('setRouter', getRouter)
        // 跳转到异步加载路由
        nextAsyncRouter(to, next)
      } else {
        next()
      }
    }
  }
})

function nextAsyncRouter (to, next) {
  // 更新路由数据 : 静态路由数据 + 动态路由数据
  router.options.routes = deepClone(staticRouter).concat(getRouter)
  router.addRoutes(getRouter)
  // 跳转到目标位置,循环加载,直到组件初始化好。
  next(to, {replace: true})
}

/**
 * 递归加载路由组件
 * @param asyncRouterMap 可获取的路由列表
 * @returns {*}
 */
function filterAsyncRouter (asyncRouterMap) {
  const accessedRouters = asyncRouterMap.filter(route => {
    if (route.component) {
      route.component = _import(route.component)
      if (route.children && route.children.length) {
        // 递归调用
        route.children = filterAsyncRouter(route.children)
      }
    }
    return true
  })
  return accessedRouters
}

注意:上述代码中,静态路由和动态路由的切换很简单,只需要else中,已登录的情况下,静态路由变为next(),把其他代码注释即可。
上述代码,实现了静态路由表和动态路由表的拼接,即基本的一些功能可以放在前端静态路由中,涉及到用户角色权限的路由部分,放在数据库中,根据用户的权限,向数据库请求对应的动态菜单返回加载即可。
干货收集分享:
下面,附上之前收集整理并若干修改之后的工具代码的两个方法。

/**
 * 深度拷贝
 */
export function deepClone (obj) {
  /**
   * 加入空值判断
   */
  if (obj === null) {
    return null
  }
  let newObj = obj instanceof Array ? [] : {}
  for (let k in obj) {
    newObj[k] = typeof obj[k] === 'object' ? deepClone(obj[k]) : obj[k]
  }
  return newObj
}

/**
 * 判断对象是否是数组
 */
export function isArrayFn (value) {
  // 首先判断浏览器是否支持Array.isArray这个方法
  if (typeof Array.isArray === 'function') {
    return Array.isArray(value)
  } else {
    return Object.prototype.toString.call(value) === '[object Array]'
    // return obj.__proto__ === Array.prototype;
  }
}

3. 结语

Vue动态路由涉及到的知识从动态组件的加载,到后台,再到前端,每一个地方都有若干需要注意的细节要点,可以说,实现这个功能,参考了不少网络资料,从中获益匪浅。在Vue动态路由实现的参考文章中,差不多都会在路由守卫中重新发送请求,获取treeMenu数据,其实,这个地方是没有必要再次发送请求的,因为在login方法中,已经把user对象存入vuex中,该user对象中,已经包含了完整的treeMenu信息。 只是在动态加载组件的时候,因为是按需异步加载,如果加载没有完成,需要从新跳转到目标位置,循环加载,直到组件初始化好(参考上面源码注释即可理解)。

4. 参考资源

(1)vue 实现动态路由
(2)关于vue-router动态添加路由$router.options不更新的解决办法
(3)js如何判断一个对象是数组(函数)

Logo

前往低代码交流专区

更多推荐