先看效果图。
编辑
1.面包屑和分页
本质上面包屑和分页都是用antd组件,BreadCrumb和Tabs都是需要一个数组去展示,我们在layout路由的loader中以及获取到了所有菜单的列表,那么面包屑展示的就是去遍历我们的菜单列表,找到于当前地址栏pathname一样的路径的菜单名称以及递归找到的上级路径的菜单名称。
这是面包屑的代码实现,首先是函数递归查找菜单列表中pathname对应的路径。
scss
/*
递归查找树的路径
*/
export const findTreeNode=(tree:Menu.MenuItem[],pathName:string,path:string[]):string[]=>{
if(!tree) return []
for(const data of tree){
path.push(data.menuName)
if(data.path===pathName) return path
if(data.children?.length){
const list = findTreeNode(data.children,pathName,path)
if(list?.length) return list
}
path.pop()
}
return []
}
然后组件传入useLocation钩子获取的pathname以及useRouteLoaderData获取到的菜单列表传递给函数获取新数组展示就可以了。
typescript
//面包屑实现
//获取当前页面pathname ,然后去当前的菜单列表查找,而且查找的时候保留父元素的菜单名称 最后生成一个数组
import type { IAuthLoader } from '@/router/AuthLoader'
import { findTreeNode } from '@/utils'
import { Breadcrumb } from 'antd'
import React, { useEffect, useState, type ReactNode } from 'react'
import { useLocation, useRouteLoaderData } from 'react-router-dom'
import TabsFC from './Tabs'
export default function BreadCrumb() {
const { pathname } = useLocation()
const [breadList, setBreadList] = useState<(string | ReactNode)[]>([])
const data = useRouteLoaderData('layout') as IAuthLoader
const list = findTreeNode(data.menuList, pathname, [])
useEffect(() => {
setBreadList([<a href='/welcome'>首页</a>, ...list])
}, [pathname])
return (
<>
<Breadcrumb
items={breadList.map(item => {
return { title: item }
})}
style={{ marginLeft: '10px' }}
/>
</>
)
}
分页的实现大差不差,也是通过当前地址栏pathname以及菜单列表进行筛选,只不过是点击之后也就是pathname和菜单列表然后递归获取路由对象,然后把路由对象中的菜单名称和路径都做为一个对象推入一个数组,然后展示即可。
typescript
// 递归获取路由对象
export const searchRoute: any = (path: string, routes: any = []) => {
for (const item of routes) {
if (item.path === path) return item
if (item.children) {
const result = searchRoute(path, item.children)
if (result) return result
}
}
return ''
}
获取到路由对象之后进行简单判断,数组里面有没有当前的path,也就是不可以重复。key不能等于路由的path。然后推入数组展示。
typescript
import React, { useEffect, useState } from 'react'
import { Tabs } from 'antd'
import {
Navigate,
useLocation,
useNavigate,
useRouteLoaderData
} from 'react-router-dom'
import type { IAuthLoader } from '@/router/AuthLoader'
import { searchRoute } from '@/utils'
interface TabsItem {
key: string
label: string
closable: boolean
}
export default function TabsFC() {
const { pathname } = useLocation()
const [tabsList, setTabsList] = useState<TabsItem[]>([
{ key: '/welcome', label: '首页', closable: false }
])
const [activeKey, setActiveKey] = useState('')
const data = useRouteLoaderData('layout') as IAuthLoader
const navigate = useNavigate()
useEffect(() => {
addTabs()
}, [pathname])
const addTabs = () => {
const route = searchRoute(pathname, data.menuList)
console.log('route', route)
if (!tabsList.find(item => item.key === route.path)) {
tabsList.push({
key: route.path,
label: route.menuName,
closable: pathname !== 'welcome'
})
}
setTabsList([...tabsList])
setActiveKey(pathname)
}
const handleChange = (path: string) => {
navigate(path)
}
const handleDel = (path: string) => {
if (pathname === path) {
tabsList.forEach((item, index: number) => {
if (item.key !== pathname) return
const nextTab = tabsList[index + 1] || tabsList[index - 1]
if (!nextTab) return
navigate(nextTab.key)
})
}
setTabsList(tabsList.filter(item => item.key !== path))
}
return (
<Tabs
items={tabsList}
activeKey={activeKey}
tabBarStyle={{ height: 40, marginBottom: 0, background: '#fff' }}
type='editable-card'
hidden
onChange={handleChange}
onEdit={path => {
handleDel(path as string)
}}
/>
)
}
2.懒加载
懒加载就是子组件在没有渲染的时候,对应的js代码不会被浏览器下载解析和执行,只有当这个组件需要显示的时候,才会发起请求渲染。具体实现看官方react文档。
编辑
需要一个suspense容器组件包裹懒加载的子组件。子组件通过React.lazy回调函数的方式引入渲染。
typescript
import { Suspense, type JSX } from 'react'
import { Spin } from 'antd'
/**
- 组件懒加载,结合Suspense实现
- @param Component 组件对象
- @returns 返回新组件
*/
export const lazyLoad = (
Component: React.LazyExoticComponent<() => JSX.Element>
): React.ReactNode => {
return (
<Suspense
fallback={
<Spin
size='large'
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%'
}}
/>
}
>
<Component />
</Suspense>
)
}
封装一个容器接收懒加载的子组件然后路由组件在element中懒加载。
javascript
import React from 'react'
import { createBrowserRouter, Navigate } from 'react-router-dom'
import Login from '@/views/login/Login'
import Welcome from '@/views/Welcome'
import Error403 from '@/views/Error403'
import Error404 from '@/views/Error404'
import Layout from '@/layout'
import AuthLoader from './AuthLoader'
import { lazyLoad } from './LazyLoad'
export const router = [
{
path: '/',
element: <Navigate to='/welcome' />
},
{
path: '/login',
element: <Login />
},
{
id: 'layout',
element: <Layout />,
loader: AuthLoader,
children: [
{
path: '/welcome',
element: <Welcome />
},
{
path: '/dashboard',
element: lazyLoad(React.lazy(() => import('@/views/dashboard')))
},
{
path: '/userList',
element: lazyLoad(React.lazy(() => import('@/views/system/user')))
},
{
path: '/deptList',
element: lazyLoad(React.lazy(() => import('@/views/system/dept')))
},
{
path: '/menuList',
element: lazyLoad(React.lazy(() => import('@/views/system/menu')))
},
{
path: '/roleList',
element: lazyLoad(React.lazy(() => import('@/views/system/role')))
},
{
path: '/orderList',
element: lazyLoad(
React.lazy(() => import('@/views/system/order/OrderList'))
)
},
{
path: '/cluster',
element: lazyLoad(
React.lazy(
() =>
import(
'@/views/system/order/OrderList/components/OrderCluster/index'
)
)
)
},
{
path: '/driverList',
element: lazyLoad(
React.lazy(() => import('@/views/system/order/DriverList/index'))
)
}
]
},
{
path: '*',
element: <Navigate to='/404' />
},
{
path: '/404',
element: <Error404 />
},
{
path: '/403',
element: <Error403 />
}
]
export default createBrowserRouter(router)