美文网首页
2024.3 vue2实现动态生成路由

2024.3 vue2实现动态生成路由

作者: wo不是黄蓉 | 来源:发表于2024-04-09 21:50 被阅读0次

    要求:

    • 后端返回菜单结构,根据菜单结构动态生成除了详情页之外的菜单

    实现思路:

    • 在store存储菜单的地方添加动态路由

    菜单结构

    {
        "code": 200,
        "msg": "成功!",
        "data": [
            {
                "id": 12,
                "menuName": "分销管理",
                "path": "views/Distribution/index",
                "sort": 0,
                "label": "分销管理",
                "hidden": 0,
                "menuKey": "distribution",
                "parentIds": "11,12",
                "icon": null,
                "children": null,
                "pid": 11
            },
            {
                "id": 23,
                "menuName": "积分记录",
                "path": "views/SystemSetting/Integral/index",
                "sort": 0,
                "label": "积分记录",
                "hidden": 0,
                "menuKey": "integral",
                "parentIds": "15,23",
                "icon": null,
                "children": null,
                "pid": 15
            },
            {
                "id": 22,
                "menuName": "充值查询",
                "path": "views/SystemSetting/RechargeQuery/index",
                "sort": 0,
                "label": "充值查询",
                "hidden": 0,
                "menuKey": "rechargeQuery",
                "parentIds": "15,22",
                "icon": null,
                "children": null,
                "pid": 15
            },
            {
                "id": 21,
                "menuName": "充值审核",
                "path": "views/SystemSetting/RechargeCheck/index",
                "sort": 0,
                "label": "充值审核",
                "hidden": 0,
                "menuKey": "rechargeCheck",
                "parentIds": "15,21",
                "icon": null,
                "children": null,
                "pid": 15
            },
            {
                "id": 20,
                "menuName": "充值服务",
                "path": "views/SystemSetting/Recharge/index",
                "sort": 0,
                "label": "充值服务",
                "hidden": 0,
                "menuKey": "recharge",
                "parentIds": "15,20",
                "icon": null,
                "children": null,
                "pid": 15
            },
            {
                "id": 19,
                "menuName": "小程序&系统设置",
                "path": "views/SystemSetting/SystemSet/index",
                "sort": 0,
                "label": "小程序&系统设置",
                "hidden": 0,
                "menuKey": "systemSet",
                "parentIds": "15,19",
                "icon": null,
                "children": null,
                "pid": 15
            },
            {
                "id": 11,
                "menuName": "财务管理",
                "path": "views/Finance/index",
                "sort": 0,
                "label": "财务管理",
                "hidden": 0,
                "menuKey": "finance",
                "parentIds": "11",
                "icon": "el-icon-s-finance",
                "children": null,
                "pid": 0
            },
            {
                "id": 5,
                "menuName": "商品采购单",
                "path": "views/SystemSetting/ProductSalesSummary/index",
                "sort": 1,
                "label": "商品采购单",
                "hidden": 0,
                "menuKey": "productSalesSummary",
                "parentIds": "2,5",
                "icon": "",
                "children": null,
                "pid": 2
            },
            {
                "id": 6,
                "menuName": "订单管理",
                "path": "views/Order/index",
                "sort": 2,
                "label": "订单管理",
                "hidden": 0,
                "menuKey": "order",
                "parentIds": "6",
                "icon": "el-icon-s-order",
                "children": null,
                "pid": 0
            },
            {
                "id": 7,
                "menuName": "订单列表",
                "path": "views/Order/index",
                "sort": 3,
                "label": "订单列表",
                "hidden": 0,
                "menuKey": "order",
                "parentIds": "6,7",
                "icon": "el-icon-s-order",
                "children": null,
                "pid": 6
            },
            {
                "id": 1,
                "menuName": "门店管理",
                "path": "views/Store/index",
                "sort": 4,
                "label": "门店管理",
                "hidden": 0,
                "menuKey": "store",
                "parentIds": "1",
                "icon": "el-icon-s-shop",
                "children": null,
                "pid": 0
            },
            {
                "id": 2,
                "menuName": "商品管理",
                "path": null,
                "sort": 5,
                "label": "商品管理",
                "hidden": 0,
                "menuKey": "productSet",
                "parentIds": "2",
                "icon": "el-icon-sell",
                "children": null,
                "pid": 0
            },
            {
                "id": 3,
                "menuName": "商品管理",
                "path": "views/Product/index",
                "sort": 6,
                "label": "商品管理",
                "hidden": 0,
                "menuKey": "product",
                "parentIds": "2,3",
                "icon": null,
                "children": null,
                "pid": 2
            },
            {
                "id": 4,
                "menuName": "商品分类",
                "path": "views/SystemSetting/Category/index",
                "sort": 7,
                "label": "商品分类",
                "hidden": 0,
                "menuKey": "category",
                "parentIds": "2,4",
                "icon": null,
                "children": null,
                "pid": 2
            },
            {
                "id": 8,
                "menuName": "售后列表",
                "path": "views/AfterSales/index",
                "sort": 8,
                "label": "售后列表",
                "hidden": 0,
                "menuKey": "afterSales",
                "parentIds": "6,8",
                "icon": "el-icon-service",
                "children": null,
                "pid": 6
            },
            {
                "id": 9,
                "menuName": "活动管理",
                "path": "views/Coupon/index",
                "sort": 9,
                "label": "活动管理",
                "hidden": 0,
                "menuKey": "coupon",
                "parentIds": "9",
                "icon": "el-icon-s-ticket",
                "children": null,
                "pid": 0
            },
            {
                "id": 10,
                "menuName": "公益服务",
                "path": "views/activity/index",
                "sort": 10,
                "label": "公益服务",
                "hidden": 0,
                "menuKey": "activity",
                "parentIds": "10",
                "icon": "el-icon-s-ticket",
                "children": null,
                "pid": 0
            },
            {
                "id": 13,
                "menuName": "资金记录",
                "path": "views/Finance/FinancialRecords/index",
                "sort": 20,
                "label": "资金记录",
                "hidden": 0,
                "menuKey": "financialRecords",
                "parentIds": "11,13",
                "icon": null,
                "children": null,
                "pid": 11
            },
            {
                "id": 14,
                "menuName": "员工工资",
                "path": "views/Finance/EmplyeeIncome/index",
                "sort": 21,
                "label": "员工工资",
                "hidden": 0,
                "menuKey": "emplyeeIncome",
                "parentIds": "11,14",
                "icon": "",
                "children": null,
                "pid": 11
            },
            {
                "id": 15,
                "menuName": "系统管理",
                "path": "",
                "sort": 22,
                "label": "系统管理",
                "hidden": 0,
                "menuKey": "systemSetting",
                "parentIds": "15",
                "icon": "el-icon-setting",
                "children": null,
                "pid": 0
            },
            {
                "id": 16,
                "menuName": "角色管理",
                "path": "views/SystemSetting/Role/index",
                "sort": 23,
                "label": "角色管理",
                "hidden": 0,
                "menuKey": "role",
                "parentIds": "15,16",
                "icon": null,
                "children": null,
                "pid": 15
            },
            {
                "id": 17,
                "menuName": "用户管理",
                "path": "views/SystemSetting/User/index",
                "sort": 24,
                "label": "用户管理",
                "hidden": 0,
                "menuKey": "user",
                "parentIds": "15,17",
                "icon": null,
                "children": null,
                "pid": 15
            },
            {
                "id": 18,
                "menuName": "用户审核",
                "path": "views/SystemSetting/CheckUser/index",
                "sort": 25,
                "label": "用户审核",
                "hidden": 0,
                "menuKey": "checkUser",
                "parentIds": "15,18",
                "icon": null,
                "children": null,
                "pid": 15
            }
        ]
    }
    

    store代码

    import { getLoginResources } from '@/api/menu';
    import { menus } from '@/api/menu';
    import utils from '@/utils/utils';
    import Layout from '@/layout'
    import Nest2 from '@/layout/Nest2.vue'
    import router from '@/router'
    
    const Types = {
      SET_MENU: 'SET_MENU',
    };
    // const rm = new RouteMenu()
    
    const menu = {
      state: {
        activeMenuInfo: { activeMenuObj: {}, menuList: [] },
        menus: [],
      },
      actions: {
        getMenu({ commit }, menus) {
          return new Promise(async (resolve, rejct) => {
            const res = await getLoginResources();
            if (res.status) {
              //将数组变成层级树
              res.data =
                res.data &&
                res.data.map((item) => ({
                  ...item,
                  menuName: item.label,
                  label: item.label,
                  // menuKey: item.path,
                  // hidden: item.hidden === 0,
                }));
              let menus = res.data;
              //菜单不是树形的,先将菜单变成树形结构
              menus = rebuildTree(res.data);
            
                //遍历菜单添加路由
              menus.forEach(menu => {
                //获取路由深度
                let deep = getRouteDeep(menu, 1, true)
                //路由对象
                let obj = {}
                generateRoute(menu, obj, 1, deep, '')
                console.log(obj);
                router.addRoute(obj)
              })
    
              console.log(router.getRoutes());
              commit('SET_MENU', menus);
              resolve(menus);
            }
            // commit('SET_MENU', menus);
            // resolve(menus);
          });
        },
      },
      mutations: {
        //设置菜单
        [Types.SET_MENU]: (state, data) => {
          state.menus = data;
        },
      },
      namespaced: true,
    };
    export default menu;
    
    

    最终生成的路由结构,以systemSetting下的checkUser和user为例

    image.png

    注释掉本地router中注册的路由,点击菜单可以正常访问就是动态路由没问题

    但是会遇到另外一个问题,刷新页面后会跳转到404问题

    需要在路由导航守卫中进行处理beforeEach,判断store中menus的长度为空的话重新获取菜单结构,重新生成就可以了,然后将路由重定向到to的路由

    用到的工具类

    function getRouteDeep(menu, deep, addRoute) {
      //判断obj.children的长度是否大于0,大于0则deep+1,否则返回0
      if (menu.children.length) {
        deep++
        menu.children.forEach(item => {
          getRouteDeep(item, deep)
        })
      }
      //包含layout和Nest2的层级
      return deep + 2
    }
    //生成路由
    /**
     *
     * @param {*} obj 路由对象
     * @param {*} index 当前路由深度
     * @param {*} deep 总路由深度
     */
    function generateRoute(menu, obj, index, deep, path) {
      obj.path = index === 1 ? '/' : ''
      // // / 重定向到home
      // if (menu.menuKey === 'home') {
      //   obj.redirect = '/home'
      // }
    
      if (index === 1) {
        obj.component = Layout
        // obj.path = '/'
        if (menu.children.length) {
          obj.path += menu.menuKey
        }
      }
      if (index === 2) {
        obj.component = Nest2
        // obj.path = ''
      }
      // //其他层级
      if (index === 3) {
        obj.component = () => import(`@/${menu.path}.vue`)
        obj.path += menu.menuKey
        obj.name = menu.menuKey
      }
      //路由小于deep时,要生成children
      if (index < deep) {
        obj.children = []
        //children多个
        if (menu.children.length > 0) {
          for (let v of menu.children) {
            const objSub = {}
            obj.children.push(generateRoute(v, objSub, index + 1, deep, obj.path))
          }
        } else {
          const objCur = {}
          obj.children.push(generateRoute(menu, objCur, index + 1, deep, obj.path))
        }
      }
      return obj
    }
    

    router代码

    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Layout from '@/layout'
    import Nest2 from '@/layout/Nest2.vue'
    import NProgress from 'nprogress'
    import Utils from '@/utils/utils'
    import { constantRouterMap } from './constant'
    import { LoaclRouterMap } from './local'
    import store from '@/store'
    // import {rebuildTree,getRouteDeep} from "@/store/modules/menu"
    Vue.use(VueRouter)
    //固定路由
    const routes = [
      {
        path: '/',
        name: 'Layout',
        redirect: '/home',
        component: Layout,
        children: [
          {
            path: 'home',
            name: 'home',
            component: () => import('@/views/HomeView.vue'),
            meta: {
              label: '首页',
              cache: true,
              closable: false
            }
          },
        //本地一些404和详情页路由
          ...constantRouterMap,
          ...LoaclRouterMap
        ]
      },
      {
        path: '/login',
        name: 'login',
        component: () => import('@/views/login/index.vue')
      }
    ]
    
    //处理路由导航守卫
    let router = new VueRouter({
      routes
    })
    
    //重写push方法,跳转相同路由报错问题
    const originalPush = VueRouter.prototype.push
    VueRouter.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)
    }
    
    
    router.beforeEach(async (to, from, next) => {
      console.log(store.state.menu.menus)
      NProgress.start()
      if (store.state.menu.menus.length) {
        //未匹配到的路由
        if (!to.matched || !to.matched.length) {
          next({ name: 'Error404' })
          return
        }
        // console.log(Utils.getStorage('userInfo'))
        let userInfo = Utils.getStorage('userInfo')
        if (to.path === '/login') {
          next()
          return
        }
        //用户信息存在
        if (!userInfo || Object.keys(userInfo).length === 0) {
          next({ name: 'login' })
          return
        } else {
          next()
          NProgress.done()
          return
        }
      } else {
        router = new VueRouter({
          routes
        })
        store
          .dispatch('menu/getMenu')
          .then(res => {
            res.forEach(menu => {
              //获取路由深度
              let deep = getRouteDeep(menu, 1, true)
              //路由对象
              let obj = {}
              generateRoute(menu, obj, 1, deep, '')
    
              router.addRoute(obj)
            })
    
            console.log(router);
            //重新添加路由后重定向
            next({ ...to, replace: true })
          })
          .catch(err => {
            next({ name: 'home' })
          })
      }
    })
    
    export default router
    
    

    验证跳转到一个不是本地注册的路由里面,刷新路由可以正常访问,即达到目的。

    相关文章

      网友评论

          本文标题:2024.3 vue2实现动态生成路由

          本文链接:https://www.haomeiwen.com/subject/yfzfxjtx.html