react项目中使用后端返回的JSON数据生成路由

问题背景

​​在这里插入图片描述

近期一个项目,一个官网管理系统,左侧有这些菜单。菜单通常是根据router路由来生成的。在以往的一些管理系统方案中,菜单的权限 通常是 给每个路由约定一个权限key,在用户登录后,调用后端的权限数据接口,比如 数据面板的key是1,全局配置的key是2。获取到的数据权限数组= [ 1 ],则表示 这个用户有数据面板的权限,没有2,则这个用户没有全局配置的权限。前端在是否渲染这个菜单和是否跳转到404(您无权限查看)就是判断要跳转的页面的key是否在后端返回的权限列表中。

let routes = [

    {
        path: "/",
        element: <MainFrame />,
        // element: <Navigate to="/home" />,

        id: "300001",
        tabs: false,
        menu: true,
        meta: {
            title: "根路径",
        },
        // children: [
        //     {
        //         key: 'home',
        //         path: 'home',
        //         menu: false,
        //         element: <MainFrame />
        //     }
        // ]
        // children: [
        //     {
        //         key: "dathboard",
        //         path: "dathboard",
        //         menu: true,
        //         id: "0000001",
        //         icon: <PieChartOutlined />,
        //         element: withLoadingComponent(<WorkBench />)
        //         meta: {
        //             title: '数据面板',
        //             accessId: '',
        //             hideMenu: false,
        //             noAuth: true,
        //         },
        //     },
        //     {
        //         path: "globalConfig",
        //         key: 'globalConfig',
        //         id: '300000',
        //         icon: <SettingOutlined />,
        //         menu: true,
        //         meta: {
        //             title: "全局配置"
        //         },
        //         children: [
        //             {
        //                 path: 'module',
        //                 key: 'module',
        //                 id: '300010',
        //                 menu: true,
        //                 icon: <UngroupOutlined />,
        //                 meta: {
        //                     title: "模型管理"
        //                 }
        //             },
        //             {
        //                 path: 'moduleFiled',
        //                 key: 'moduleFiled',
        //                 id: '300020',
        //                 menu: true,
        //                 icon: <DeploymentUnitOutlined />,
        //                 meta: {
        //                     title: "模型字段"
        //                 }
        //             }
        //         ]
        //     },
        //     {
        //         path: "article",
        //         key: "article",
        //         id: "400000",
        //         icon: <FolderOpenOutlined />,
        //         menu: true,
        //         meta: {
        //             title: "内容管理"
        //         },
        //         children: [
        //             {
        //                 path: "news",
        //                 key: "news",
        //                 id: "400010",
        //                 menu: true,
        //                 icon: <ReadOutlined />,
        //                 element: withLoadingComponent(<NewsView />),
        //                 meta: {
        //                     title: "新闻中心"
        //                 },
        //                 // children: [
        //                 //     {
        //                 //         path: "new-add",
        //                 //         key: "new-add",
        //                 //         id: "400011",
        //                 //         element: withLoadingComponent(<AddNews />),
        //                 //         menu: true,
        //                 //         meta: {
        //                 //             title: "新闻发布",
        //                 //             accessId: '',
        //                 //             hideMenu: false,
        //                 //             noAuth: true
        //                 //         }
        //                 //     },
        //                 //     {
        //                 //         path: 'news-list',
        //                 //         key: "news-list",
        //                 //         id: "400012",
        //                 //         menu: true,
        //                 //         element: withLoadingComponent(<NewsList />),
        //                 //         meta: {
        //                 //             title: "新闻列表",
        //                 //             accessId: "",
        //                 //             hideMenu: false,
        //                 //             noAuth: true
        //                 //         }
        //                 //     }
        //                 // ]
        //             },
        //             {
        //                 path: "product",
        //                 key: "product",
        //                 id: "400020",
        //                 menu: true,
        //                 icon: <ReadOutlined />,
        //                 element: withLoadingComponent(<ProductView />),
        //                 meta: {
        //                     title: "产品中心"
        //                 },
        //                 // children: [
        //                 //     {
        //                 //         path: "add",
        //                 //         key: "add",
        //                 //         id: "400021",
        //                 //         element: withLoadingComponent(<AddProduct />),
        //                 //         menu: true,
        //                 //         meta: {
        //                 //             title: "产品发布",
        //                 //             accessId: '',
        //                 //             hideMenu: false,
        //                 //             noAuth: true
        //                 //         }
        //                 //     },
        //                 //     {
        //                 //         path: 'list',
        //                 //         key: "list",
        //                 //         id: "400022",
        //                 //         menu: true,
        //                 //         element: withLoadingComponent(<ProductList />),
        //                 //         meta: {
        //                 //             title: "产品列表",
        //                 //             accessId: "",
        //                 //             hideMenu: false,
        //                 //             noAuth: true
        //                 //         }
        //                 //     }
        //                 // ]
        //             },
        //             {
        //                 path: "about",
        //                 key: "about",
        //                 id: "400030",
        //                 icon: <ReadOutlined />,
        //                 menu: true,
        //                 element: withLoadingComponent(<AboutUs />),
        //                 meta: {
        //                     title: "关于我们"
        //                 },
        //             },
        //             {
        //                 path: "brand",
        //                 key: "brand",
        //                 id: "400040",
        //                 menu: true,
        //                 icon: <ReadOutlined />,
        //                 element: withLoadingComponent(<BrandView />),
        //                 meta: {
        //                     title: "品牌文化"
        //                 },

        //             },
        //             {
        //                 path: "server",
        //                 key: "server",
        //                 id: "400050",
        //                 menu: true,
        //                 icon: <ReadOutlined />,
        //                 element: withLoadingComponent(<ServiceView />),
        //                 meta: {
        //                     title: "客户服务"
        //                 },

        //             },
        //             {
        //                 path: 'pubinfo',
        //                 key: 'pubinfo',
        //                 id: "400060",
        //                 menu: true,
        //                 icon: <ReadOutlined />,
        //                 element: withLoadingComponent(<PublicView />),
        //                 meta: {
        //                     title: "公开信息"
        //                 },
        //             },

        //         ],

        //     },
        //     {
        //         path: "set",
        //         key: "set",
        //         id: "500000",
        //         menu: true,
        //         icon: <GlobalOutlined />,
        //         meta: {
        //             title: "网站管理"
        //         },
        //         children: [
        //             {
        //                 path: "base-set",
        //                 key: "base-set",
        //                 id: "5000001",
        //                 menu: true,
        //                 icon: <HddOutlined />,
        //                 element: withLoadingComponent(<BaseSetView />),
        //                 meta: {
        //                     title: "网站配置"
        //                 },

        //             },
        //             {
        //                 path: "setBanner",
        //                 key: "setBanner",
        //                 id: "500002",
        //                 icon: <PictureOutlined />,
        //                 element: withLoadingComponent(<BannerSetView />),
        //                 menu: true,
        //                 meta: {
        //                     title: "轮播图片"
        //                 },

        //             },
        //             {
        //                 path: "cate-set",
        //                 key: "cate-set",
        //                 id: "500004",
        //                 icon: <ProfileOutlined />,
        //                 element: withLoadingComponent(<CateView />),
        //                 menu: true,
        //                 meta: {
        //                     title: "栏目设置"
        //                 },

        //             },
        //             {
        //                 path: "float-advert",
        //                 key: "float-advert",
        //                 icon: <OneToOneOutlined />,
        //                 element: withLoadingComponent(<FloatAdvertView />),
        //                 id: "500005",
        //                 menu: true,
        //                 meta: {
        //                     title: "侧边浮窗"
        //                 },

        //             },
        //             {
        //                 path: "advert",
        //                 key: "advert",
        //                 icon: <StarOutlined />,
        //                 element: withLoadingComponent(<FloatAdvertView />),
        //                 id: "500006",
        //                 menu: true,
        //                 meta: {
        //                     title: "广告管理"
        //                 },

        //             },
        //             {
        //                 path: "security",
        //                 key: "security",
        //                 icon: <UnlockOutlined />,
        //                 element: withLoadingComponent(<SafeConfig />),
        //                 id: "500007",
        //                 menu: true,
        //                 meta: {
        //                     title: "安全配置"
        //                 },

        //             },
        //             {
        //                 path: "access",
        //                 key: "access",
        //                 icon: <HistoryOutlined />,
        //                 element: withLoadingComponent(<VisitorLogList />),
        //                 id: "500008",
        //                 menu: true,
        //                 meta: {
        //                     title: "访问统计"
        //                 },

        //             },
        //         ],

        //     },
        //     {
        //         path: 'member',
        //         key: 'member',
        //         id: '600000',
        //         menu: false,
        //         icon: <WindowsOutlined />,
        //         meta: {
        //             title: '会员管理'
        //         }
        //     },
        //     {
        //         path: 'system',
        //         key: 'system',
        //         id: '700000',
        //         menu: true,
        //         icon: <SettingOutlined />,
        //         meta: {
        //             title: '系统管理'
        //         },
        //         children: [
        //             {
        //                 path: 'permission',
        //                 key: 'permission',
        //                 id: '700010',
        //                 menu: true,
        //                 icon: <SecurityScanOutlined />,
        //                 element: withLoadingComponent(<PerMissionView />),
        //                 meta: {
        //                     title: '角色权限'
        //                 }
        //             },
        //             {
        //                 path: 'operationLog',
        //                 key: 'operationLog',
        //                 id: '700020',
        //                 menu: true,
        //                 icon: <ScheduleOutlined />,
        //                 element: withLoadingComponent(<SystemLogList />),
        //                 meta: {
        //                     title: '系统日志'
        //                 }
        //             },
        //             {
        //                 path: 'account',
        //                 key: 'account',
        //                 id: '700030',
        //                 menu: true,
        //                 icon: <UserOutlined />,
        //                 element: withLoadingComponent(<SystemUserList />),
        //                 meta: {
        //                     title: '系统用户'
        //                 }
        //             },
        //         ]
        //     }
        // ]
    },
    {
        path: "login",
        menu: false,
        element: withLoadingComponent(<Login />)
    },
    {
        path: "*",
        menu: false,
        element: withLoadingComponent(<Page404 />)
    }
]

但是涉及到内容管理系统,则存在一个需求可能 性,即 内容管理 下面这6个项,现在是只有6个项,而后期可能会有7个项,比如,通过如下 的栏目配置 新建一一个顶级栏目,
在这里插入图片描述

那么这个栏目和 公开信息那一栏平级,在内容管理那里 公开信息 下面就应该会有出现 刚刚添加的一级栏目。
在这里插入图片描述

所以,就决定把整路由部分由后端来进行返回,即前面示例代码中的那些。

后端返回的路由数据

决定把整个路由后端返回,所以,后端nodeJs返回JSON数据如下:

  ctx.body = {
    status: "0000",
    message: "菜单权限获取成功",
    data: {
      list: [
        {
          key: "dathboard",
          path: "dathboard",
          menu: true,
          id: "0000001",
          icon: `PieChartOutlined`,
          element: `WorkBench`,
          meta: {
            title: '数据面板',
            // accessId: '',
            // hideMenu: false,
            // noAuth: true,
          },
        },
        {
          path: "globalConfig",
          key: 'globalConfig',
          id: '300000',
          icon: `SettingOutlined`,
          menu: true,
          meta: {
            title: "全局配置"
          },
          children: [
            {
              path: 'module',
              key: 'module',
              id: '300010',
              menu: true,
              icon: `UngroupOutlined`,
              meta: {
                title: "模型管理"
              }
            },
            {
              path: 'moduleFiled',
              key: 'moduleFiled',
              id: '300020',
              menu: true,
              icon: `DeploymentUnitOutlined`,
              meta: {
                title: "模型字段"
              }
            }
          ]
        },
        {
          path: "article",
          key: "article",
          id: "400000",
          icon: `FolderOpenOutlined`,
          menu: true,
          meta: {
            title: "内容管理"
          },
          children: [
            {
              path: "news",
              key: "news",
              id: "400010",
              menu: true,
              icon: `ReadOutlined`,
              element: 'NewsView',
              meta: {
                title: "新闻中心"
              },

            },
            {
              path: "product",
              key: "product",
              id: "400020",
              menu: true,
              icon: `ReadOutlined`,
              element: `ProductView`,
              meta: {
                title: "产品中心"
              },
            },
            {
              path: "about",
              key: "about",
              id: "400030",
              icon: `ReadOutlined`,
              menu: true,
              element: `AboutUs`,
              meta: {
                title: "关于我们"
              },
            },
            {
              path: "brand",
              key: "brand",
              id: "400040",
              menu: true,
              icon: `ReadOutlined`,
              element: `BrandView`,
              meta: {
                title: "品牌文化"
              },

            },
            {
              path: "server",
              key: "server",
              id: "400050",
              menu: true,
              icon: `ReadOutlined`,
              element: `ServiceView`,
              meta: {
                title: "客户服务"
              },

            },
            {
              path: 'pubinfo',
              key: 'pubinfo',
              id: "400060",
              menu: true,
              icon: `ReadOutlined`,
              element: `ServiceView`,
              meta: {
                title: "公开信息"
              },
            },

          ],

        },
        {
          path: "set",
          key: "set",
          id: "500000",
          menu: true,
          icon: `GlobalOutlined`,
          meta: {
            title: "网站管理"
          },
          children: [
            {
              path: "base-set",
              key: "base-set",
              id: "5000001",
              menu: true,
              icon: `HddOutlined`,
              element: `BaseSetView`,
              meta: {
                title: "网站配置"
              },

            },
            {
              path: "setBanner",
              key: "setBanner",
              id: "500002",
              icon: `PictureOutlined`,
              element: `BannerSetView`,
              menu: true,
              meta: {
                title: "轮播图片"
              },

            },
            {
              path: "cate-set",
              key: "cate-set",
              id: "500004",
              icon: `ProfileOutlined`,
              element: `CateView`,
              menu: true,
              meta: {
                title: "栏目设置"
              },

            },
            {
              path: "float-advert",
              key: "float-advert",
              icon: `OneToOneOutlined`,
              element: `FloatAdvertView`,
              id: "500005",
              menu: true,
              meta: {
                title: "侧边浮窗"
              },

            },
            {
              path: "advert",
              key: "advert",
              icon: `StarOutlined`,
              element: `FloatAdvertView`,
              id: "500006",
              menu: true,
              meta: {
                title: "广告管理"
              },

            },
            {
              path: "security",
              key: "security",
              icon: `UnlockOutlined`,
              element: `SafeConfig`,
              id: "500007",
              menu: true,
              meta: {
                title: "安全配置"
              },

            },
            {
              path: "access",
              key: "access",
              icon: `HistoryOutlined`,
              element: `VisitorLogList`,
              id: "500008",
              menu: true,
              meta: {
                title: "访问统计"
              },

            },
          ],

        },
        {
          path: 'member',
          key: 'member',
          id: '600000',
          menu: false,
          icon: `WindowsOutlined`,
          meta: {
            title: '会员管理'
          }
        },
        {
          path: 'system',
          key: 'system',
          id: '700000',
          menu: true,
          icon: `SettingOutlined`,
          meta: {
            title: '系统管理'
          },
          children: [
            {
              path: 'permission',
              key: 'permission',
              id: '700010',
              menu: true,
              icon: `SecurityScanOutlined`,
              element: `PerMissionView`,
              meta: {
                title: '角色权限'
              }
            },
            {
              path: 'operationLog',
              key: 'operationLog',
              id: '700020',
              menu: true,
              icon: `ScheduleOutlined`,
              element: `SystemLogList`,
              meta: {
                title: '系统日志'
              }
            },
            {
              path: 'account',
              key: 'account',
              id: '700030',
              menu: true,
              icon: `UserOutlined`,
              element: `SystemUserList`,
              meta: {
                title: '系统用户'
              }
            },
          ]
        }
      ]
    }
  }

​后端返回权限路由,那么,就开发本地只保留*路由和login路由,因为这部分路由本地必须 要有。所以routes的如下:

let routes = [

    {
        path: "/",
        element: <MainFrame />,
        // element: <Navigate to="/home" />,

        id: "300001",
        tabs: false,
        menu: true,
        meta: {
            title: "根路径",
        },
		children:[],   //从后端请求返回来的数据就要放进这里面。
    {
        path: "login",
        menu: false,
        element: withLoadingComponent(<Login />)
    },
    {
        path: "*",
        menu: false,
        element: withLoadingComponent(<Page404 />)
    }
]

const setRoutes = (list) => {
    routes = list
}

export {
    setRoutes
}

export default routes;

即 从后端返回来的路由数据就放进routes[0].chilren 里面。当然,这个文件中默认导出routes,同时也导出setRoutes方法来改变这个routes数据。所以routes没有按平时的习惯写成const常量,得写成变量。

路由鉴权

路由鉴权这部分好说,主要是登录,判断用户是否登录,没有登录就给它跳转到登录页面,否则允许 它跳到需要的页面。在下面的示例中,如果跳转的是登录页面并且已经有token即已经登录,则返回到主页。如果不是跳到登录页面又没有token即没有登录,则跳到登录,重点是 return <Routers /> 这个组件

路由鉴权组件:

const AuthRouter = () => {
    const location = useLocation();
    let token = getToken();
    if (location.pathname === "/login" && token) {
        return <ToHome />
    }
    if (location.pathname !== "/login" && !token) {
        return <ToLogin />
    }
    return <Routers />
}
export default AuthRouter;

Routers组件:



const Route = ({ routes }) => {
    let outer = useRoutes(routes)
    return outer
}


const Routers = () => {
    const [loading, setLoading] = useState(false)
    const [curRoutes, setCurRoutes] = useState([...routes]);

    // 动态获取路由
    const getRoutes = () => {
        http.post({
            url: "/admin_users-api/getMenu",
            before: () => setLoading(true),
            params: {},
            success: (res) => {
                if (res.status === "0000") {
                    let list = routes;
                    list[0].children = getRouteList(res?.data?.list)
                    setCurRoutes(list)
                    setRoutes(list)
                }
            },
            failure: () => { },
            complete: () => setLoading(false)
        })
    }
    useEffect(() => {
        getRoutes()
    }, [])

    return loading ? <>
        <LayoutLoad />
    </> : <Route routes={curRoutes} />
}
export default Routers;

Routers组件中useEffect生命周期调用获取路由数据的函数。在拿到路由后,通过调用导入的setRoutes函数来改变routes路由数据数据,并且赋值给到组件本身的curRoutes状态。而curRoutes状态更新了,就会触发重新渲染 <Route routes={curRoutes} /> ,从而实现动态路由。

方案研究中遇到的坑点

  1. 起初的时候return 那里的写法是下面这样的
     return <Route routes={curRoutes} />

即直接返回Route,然后我就遇到404的问题,即 比如我跳转到http://localhost:3000/website-admin/article/about 路径的时候,如果按了浏览器的刷新按钮。会在有一瞬间浏览器会闪过显示404页面。分析了一下,原因是 直接刷新路由的时候,其实是因为还没有获得 /article/about 这个路由,即路由中还没有这个数据。因为还在请求数据当中。所以解决方案是 给一个loading状态,在loading的时候,让它显示加载中,于是就是使用了下面的代码。

    return loading ? <>
        <LayoutLoad />
    </> : <Route routes={curRoutes} />

2.遇到的第二个坑是是在哪里获取权限的问题,在之前的项目中,我们获取用户权限是在MainFrame这个基础组件中获取权限,这两天发现,在上面的情况中,即直接在浏览器地址栏输入http://localhost:3000/website-admin/article/about 的时候,这个组件没有加载到,基础框架组件只有在访问 http://localhost:3000/ 这个根路由的时候才加载。所以昨天调试了好多次,发现获取路由权限就不能像之前那样写在MainFrame组件中。要写在路由鉴权组件这边。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值