React+Umi实现根据菜单动态生成路由

1,098 阅读7分钟

一般页面定义好路径之后需要路由来转发到对应的组件上面,路径通常是固定的,比如user.login页面就是指向登录页组件,但如果菜单栏是可以增加和修改的,那么就会出现user/123,user/456,这样的路径,我们不可能在路由表中提前设置,那么这个时候就需要动态的把路由信息写到路由表中

问题

先看下问题,我需要设置一个路由,这个路由是用来通过iframe展示嵌入页的,嵌入的链接都可以正常到iframe页面进行展示,但是会出现下面的问题。

image.png

由于配置的都是不同的url,就会出现一个问题,不同的url都走了这个相同的iframe页面,虽然功能上没问题,但是在tabs标签中会被识别为同一个标签页,即使我嵌入两个不同的页面,那么tabs就只会出现一个同样的tab标签

32323.gif

所以这种路由的方式是不可取的,因为每一个页面都会进入相同的路由,并且title是相同的,无法区分嵌入链接都页面,应该按照当前返回的菜单来动态生成我们的路由信息

动态更新路由PatchRoutes

image.png

根据官方文档可以看到能够修改路由表的信息的api

export async function patchRoutes({ routes }) {

  console.log('routes123', routes);
}

在这里可以看到已经定义好的静态路由 image.png

image.png

由于是动态添加路由,所以我们需要整理出菜单中的ifrmae页面,因为这些ifram页面是不确定的,所以只能通过动态的方式加入到路由中

重新梳理下路由信息的定义(可跳过)

后面这一串是要嵌入的地址,但是当前情况下页面会不正常,因为放入的是一个链接,需要进行一下url转码 image.png

转码后就能正常现实

image.png

但是由于是动态路由,每个的地址都需要不一样,才能在路由表中配置各自的名称,所以上面的办法取的是嵌入地址的路径来作为前置地址,比如嵌入的页面是 .../content/special/static/index.do,那么我生成的路由path就是

{
        path: '/iframe/content/special/static/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '嵌入页面1',
},

根据我的嵌入的地址去截取,去生成这个path,

如果我有多个的话,就应该是下面这样的路径,其中aaa bbb ccc就是截取的地址,这样才能把各个页面区分开

{
        path: '/iframe/content/special/static/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '嵌入页面1',
},{
        path: '/iframe/ccc/aaa/bbb/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '嵌入页面2',
},{
        path: '/iframe/ccc111/aaa111/bbb111/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '嵌入页面3',
},

但是还要截取,第一个是麻烦,第二个是可能会出现相同路径也不一定,所以直接换成简单点,保证唯一性就直接用时间戳来做地址的前缀

{
        path: '/iframe/1728540816447/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '嵌入页面1',
},{
        path: '/iframe/1728540816427/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '嵌入页面2',
},{
        path: '/iframe/1728540816437/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '嵌入页面3',
},

个人感觉这样会简洁很多

现在静态路由中是这样的

image.png

两个固定的路由,并且指向同一个组件,但是可以生成两个不同名称的标签页,同时根据url来嵌入到页面的iframe中

接下来要做的事情就是将这两路由在页面加载的时候通过umi的patchRoutes,去动态的生成并且插入到路由表中,因为菜单是用户自己加的,并不知道用户要插入什么链接,所以无法提前预设静态路由

PatchRoutes功能实现

如果要动态的生成路由表,Umi提供了一个api在app.tsx的时候供我们插入路由信息,参数routes打印的就路由表,可以直接通过unshift插入路由信息去改变

export async function patchRoutes({ routes }) {
  console.log('routes123', routes);
}

在PatchRoutes这个函数中打印一下routes,也就是路由表

image.png

可以看到静态路由中的两个路由是出现在这里的

现在我们把路由表的这两个静态路由注释掉

image.png

在patchRoutes中加入这两个试试

export async function patchRoutes({ routes }) {
  console.log('routes123', routes);
  // const menus = await getRoutersInfo();
  const menuRoutes = routes[1]?.routes; // 确认routes[0]是否有嵌套的routes
  // if (Array.isArray(menuRoutes)) {
    let id = menuRoutes.findIndex((item)=>{
       return item.path == "/iframe"
    })
    // console.log('iframeRouter', iframeRouter);
    // iframeRouter[0].children
    menuRoutes[id].routes.unshift({
        path: '/iframe/1728542330024/:url',
        icon: 'PartitionOutlined',
        component: 'bi/analysis/iframe',
        wrappers: ['@/components/KeepAlive'],
        keppAlive: true,
        title: '进阶分析2',
      }
      // {
      //   path: '/iframe/1728542591255/:url',
      //   icon: 'PartitionOutlined',
      //   component: 'bi/analysis/iframe',
      //   wrappers: ['@/components/KeepAlive'],
      //   keppAlive: true,
      //   title: '进阶分析1',
      // }
      );
  // }
  console.log('menuRoutes', menuRoutes);
}

image.png

可以插入,但是出现问题

image.png

可能是写法的问题,在patchroute中不能那么写,先看看放开一个静态和加入一个动态的后,生成的路由信息有什么区别

image.png

可以看到上面这个是动态插入的,component这块和下面的静态路由信息有区别

image.png

顺便看了一眼原来人家官方是这样写的,那我们也改一下

用require的方法直接启动不了项目,不知道是什么情况

component: () => import('./pages/bi/analysis/iframe')

改用成这个貌似也不行

image.png

直接来换个思路,定义一个静态路由例子,直接拿静态路由里面的数据的component

image.png

export async function patchRoutes({ routes }) {
  console.log('routes123', routes);
  const menuRoutes = routes[1]?.routes; // 确认routes[0]是否有嵌套的routes
  // if (Array.isArray(menuRoutes)) {
    let id = menuRoutes.findIndex((item)=>{
       return item.path == "/iframe"
    })
    // console.log('iframeRouter', iframeRouter);
    menuRoutes[id].routes.unshift({
        path: '/iframe/1728542330024/:url',
        icon: 'PartitionOutlined',
        component: menuRoutes[id].routes[0].component,
        wrappers: menuRoutes[id].routes[0].wrappers,
        keppAlive: true,
        title: '进阶分析2',
      }
      );
  // }
  console.log('menuRoutes', menuRoutes);
}

menuRoutes[id].routes[0].component 直接拿到静态路由的component来生成动态路由,因为指向的component都是相同的,先用这个笨办法先

处理完成,直接修改了路由表中的内容

image.png

下面这份是菜单数据

image.png

可以看到我们新建的菜单,链接都做了转码的处理,并且有时间戳去规定唯一标识,我新建多少个菜单,都可以对应生成多少个路由

这样的好处是即使每个带有链接都菜单都是用的iframe这个组件页面,但是我在tabs中可以区分开他们,不然会被识别成同一个路由,切换不了tab,而且应用成功后tab的名称就是菜单的名称

最后结果

最后路由也是根据菜单中带有iframe标识的路径生成了两个

image.png

分别访问

/bi/analysis/iframe/http%3A%2F%2Flocalhost%3A18080%2Fbi_portal7_3_16_war_exploded%2Fcontent%2Forder%2Fdue%2Findex.do

/bi/analysis/iframe/http%3A%2F%2Flocalhost%3A18080%2Fbi_portal7_3_16_war_exploded%2Fcontent%2Fspecial%2Fstatic%2Findex.do

能够出现自己的title,并且tab页也能正确的区分开是两个路由

image.png

image.png

如果觉得有趣或有收获,请关注我的更新,给个喜欢和分享。您的支持是我写作的最大动力!

往期好文推荐