从0开始的中后台管理系统-7(司机列表展示以及面包屑和页签分页实现还有懒加载)

先看效果图。

​编辑

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)

相关推荐
Awbeci6 分钟前
微前端-解决MicroApp微前端内存泄露问题
前端
前端领航者8 分钟前
重学Vue3《Vue Watch 监听器深度指南:场景、技巧与底层优化原理剖析》
前端·vue.js
布列瑟农的星空11 分钟前
34岁老前端的一周学习总结(2025/8/15)
前端·后端
豆苗学前端25 分钟前
vue3+TypeScript 实现一个图片占位符生成器
前端·面试·github
neon120426 分钟前
Vue 3 父子组件通信核心机制详解:defineProps、defineEmits 与 defineExpose 完全指南
前端·javascript·vue.js·前端框架
Juchecar43 分钟前
Vue3 开发环境搭建及循序渐进学习指南
前端·javascript
Data_Adventure1 小时前
@scqilin/phone-ui手机外观组件库
前端
一点一木1 小时前
Vue Vapor 事件机制深潜:从设计动机到源码解析
前端·vue.js·vapor
FSHOW1 小时前
记一次开源_大量SVG的高性能渲染
前端·react.js
小牛.7932 小时前
Web第二次作业
前端·javascript·css