Vue 动态路由的实现(后台传递路由表)

项目需求:为提高动态路由灵活性,减少手工维护路由的繁琐。采用后端返回路由列表,前端再渲染。不用在前端配置。 比如:根据用户的角色,登录进来显示不同的菜单。

文章目录

  • 思路
  • 代码实现
  • 总结


思路

  1. 后端返回一个json格式的路由表;
  2. 前端处理后端返回的数据格式,将格式转化成符合路由要求的组件对象;
  3. 利用vue-router的beforeEach、addRoutes、vuex来配合上边两步实现效果;
  4. 左侧菜单拦截根据拿到转换好的路由列表进行展示;

代码实现

  1. 新建一个router.js
import Layout from '@/components/Layout';

export default [{
        path: '/',
        name: 'Home',
        redirect: '/home',
        component: () =>  import ('@/components/main'),
        meta: {
            hideInMenu: true,
            notCache: true
        },
    },
    {
        path: '/login',
        name: 'Login',
        meta: {
            title: '登录',
            hideInMenu: true,
        },
        component: () =>
            import ('@/view/login/Login.vue'),
    },
    {
        path: '/home',
        name: 'Home',
        meta: {
            title: '首页',
            hideInMenu: true,
            icon: 'md-home',
        },
        component: () =>
            import ('@/view/home/Home.vue'),
    },
    {
        path: '/404',
        name: 'error_404',
        meta: {
            hideInMenu: true,
        },
        component: () =>
            import ('@/view/error-page/404.vue'),
    }
];
  1. 处理后台返回的路由表并添加到路由
    利用全局前置守卫router.beforeEach,在跳转路由前先判断是否已经添加过动态路由了,如果没有,则先获取数据进行添加路 由。(router.beforeEach也会做登录等拦截,这里省略)
const whiteList = ['/login']
router.beforeEach(async (to, from, next) => {
  iView.LoadingBar.start();
  const token = getToken()
  if (!token) {
    if (to.path === '/login') {
      next({ path: '/' })
      iView.LoadingBar.finish();
    } else {
      const hasPermList = store.state.isAddRoutes
      if (hasPermList) { // 已经添加动态路由
        next()
      } else {
        try {
          const data = await store.dispatch('get_permission');//获取用户菜单和功能权限
          const routerList = setComponent(JSON.parse(JSON.stringify(data[0])))
          if (routerList) {
            var accessRoutes = []
            routerList.forEach(item => {
              if (item.code === 'zfzt:sjzt') {
                getAsyncRoutes(item.children).forEach(item => {
                  accessRoutes.push(item)
                })
              }
            })
            if (accessRoutes.length > 0) {
              accessRoutes.push({ path: '*', redirect: '/404' })
              router.addRoutes(accessRoutes)
              store.state.isAddRoutes = true
              next({ ...to, replace: true })
            } else {
              next()
            }
          }
        } catch (error) {
          // 清除用户登录信息后,回跳到登录页去
          next(`/login?redirect=${to.path}`)
          iView.LoadingBar.finish();
        }
      }
    }
  } else {
    // 用户未登录
    if (whiteList.indexOf(to.path) !== -1) {
      // 需要跳转的路由是否是whiteList中的路由,若是,则直接条状
      next()
    } else {
      // 需要跳转的路由不是whiteList中的路由,直接跳转到登录页
      next(`/login?redirect=${to.path}`)
      // 结束精度条
      iView.LoadingBar.finish();
    }
  }
})

setComponent方法,这里对后台配置的remark备注进行格式处理,正常不需要处理
Vue 动态路由的实现(后台传递路由表)_第1张图片
Vue 动态路由的实现(后台传递路由表)_第2张图片

export const setComponent = (data) => {
  if (!data || !data.length) return
  data.forEach(item => {
    if (item.remark) {//remark 是路由配置时候的手动填写的备注内容
      let remark=JSON.parse(item.remark)
      item.component=remark.component
      item.path=item.url
      item.meta={title:item.name}
      if(remark.redirect){
        item.redirect=remark.redirect
      }
    }
    if (item.children && item.children.length) {
      setComponent(item.children)
    }
  })
  return data

}

getAsyncRoutes方法,遍历后台传来的路由字符串,转换为组件对象。Layout 这个组件是整体的页面布局:左侧菜单列,右侧页面

import Layout from '@/components/main';
export function getAsyncRoutes(routes) {
  const res = []
  const keys = ['path', 'name', 'children', 'redirect', 'meta', 'hidden']
  // 遍历路由数组去重组可用的路由
  routes.forEach(item => {
    const newItem = {};
    if (item.component) {
      // 判断 item.component 是否等于 'Layout',若是则直接替换成引入的 Layout 组件
      if (item.component === 'Layout') {
        newItem.component = Layout
      } else {
        //  item.component 不等于 'Layout',则说明它是组件路径地址,因此直接替换成路由引入的方法
        newItem.component = resolve => require([`@/view/${item.component}`], resolve)
      }
    }
    for (const key in item) {
      if (keys.includes(key)) {
        newItem[key] = item[key]
      }
    }
    // 若遍历的当前路由存在子路由,需要对子路由进行递归遍历
    if (newItem.children && newItem.children.length) {
      newItem.children = getAsyncRoutes(item.children)
    }
    res.push(newItem)
  })
  // 返回处理好且可用的路由数组
  return res
}
  1. 左侧菜单渲染
    很多UI组件库都可以实现这个功能,重点就是组件递归
    使用v-for循环菜单数据数组,渲染组件库 iview的菜单组件,这时分两种情况:
  • 如果有children,那么渲染side-menu-item(父级菜单),并包裹自身组件,把children数据传递给调用的自身组件,也就是递归调用组件自身,那么调用的自身组件就会重复上面逻辑的判断,直到没有children,也就是遇到了第二种情况,结束递归调用。

  • 如果没有children,那么直接显示 menu-item (子菜单)
    此处代码省略。。。

总结

你可能感兴趣的:(工作积累,vue.js,前端,javascript)