使用vite+react+ts+Ant Design开发后台管理项目(五)

 前言


本文将引导开发者从零基础开始,运用vite、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈,构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导,文章旨在为开发者揭示如何利用这些技术工具,从项目构思到最终实现的全过程,提供清晰的开发思路和实用的技术应用技巧。

 项目gitee地址:lbking666666/enqi-admin

 本系列文章:

  • 使用vite+react+ts+Ant Design开发后台管理项目(一)
  • 使用vite+react+ts+Ant Design开发后台管理项目(二)
  • 使用vite+react+ts+Ant Design开发后台管理项目(三)
  • 使用vite+react+ts+Ant Design开发后台管理项目(四)
  • 使用vite+react+ts+Ant Design开发后台管理项目(五)

最近比较忙更新比较慢本章节添加面包屑和tab标签页及一些优化,下一章节系统管理下的用户管理和角色管理

状态管理

在store文件夹下的reducers下新增menu.ts文件,记录左侧菜单展开和点击及所有已经打开的状态

代码如下:

import { createSlice } from "@reduxjs/toolkit";
import type { RootState } from "@/store/index.ts";
import type { MenuState, OpenedMenu } from "@/types/menu.ts";

const initialState: MenuState = {
  openMenuKey: [], // 展开的菜单栏的key  用于侧边栏
  selectMenuKey: [], // 选中菜单栏的key  用户侧边栏
  openedMenu: [], // 保存已经打开的菜单栏 用于顶部导航
  currentPath: "", // 页面当前路径
};
export const menuSlice = createSlice({
  name: "menu",
  initialState,
  reducers: {
    setOpenKey(state, action) {
      const oldKeys = state.openMenuKey;
      const keys = action.payload;
      const isSame = keys.every(
        (item: string, index: number) => item === oldKeys[index]
      );
      const flag = keys.length === oldKeys.length && isSame;
      if (flag) {
        return state;
      }
      return { ...state, openMenuKey: keys };
    },
    setCurrent(state, action) {
      const keys = action.payload;
      if (state.selectMenuKey[0] === keys[0]) {
        return state;
      }
      const openedMenu = [...state.openedMenu];
      const useCurrentPath = openedMenu.find(
        (item: OpenedMenu) => item.key === keys[0]
      );
      return {
        ...state,
        selectMenuKey: keys,
        currentPath: useCurrentPath?.path || "/",
      };
    },

    addMenu(state, action) {
      const menuItem = action.payload;
      if (state.openedMenu.find((item) => item.path === menuItem.path)) {
        return state;
      } else {
        const openedMenu = [...state.openedMenu];
        const currentPath = menuItem.path;
        openedMenu.push(menuItem);
        return { ...state, openedMenu, currentPath };
      }
    },
    removeMenu(state, action) {
      const keys = action.payload;
      const openedMenu = state.openedMenu.filter((i) => !keys.includes(i.key));
      const currentPath =
        openedMenu.length > 0 ? openedMenu[openedMenu.length - 1].path : "/";
      if (state.openedMenu.length === openedMenu.length) {
        return state;
      }
      return { ...state, openedMenu, currentPath };
    },
    clearMenu(state) {
      const currentPath = "";
      const openedMenu: OpenedMenu[] = [];
      return { ...state, openedMenu, currentPath };
    },
  },
});

export const { setCurrent, setOpenKey, addMenu, removeMenu, clearMenu } =
  menuSlice.actions;
export const selectOpenKey = (state: RootState) => state.menu.openMenuKey;
export const selectMenu = (state: RootState) => state.menu.selectMenuKey;
export const selectOpenedMenu = (state: RootState) => state.menu.openedMenu;
export const selectCurrentPath = (state: RootState) => state.menu.currentPath;
export default menuSlice.reducer;

types文件夹下新增menu.d.ts类型定义代码如下:

// 菜单项属性
export interface MenuItemProps {
  id?: string;
  key: string;
  icon?: string;
  label: string;
  children?: MenuItemProps[];
}
export interface OpenedMenu {
  key: string
  path: string
  title: string
}
// 菜单状态属性
export interface MenuState {
  openedMenu: OpenedMenu[]
  openMenuKey: string[]
  selectMenuKey: string[]
  currentPath: string
}
export interface MenuItem {
  [MENU_ICON]: string | null
  [MENU_KEEPALIVE]: string
  [MENU_KEY]: string | number
  [MENU_ORDER]?: number
  [MENU_PARENTKEY]: number | null
  [MENU_PATH]: string
  [MENU_TITLE]: string
  [MENU_CHILDREN]?: MenuList
  [MENU_PARENTPATH]?: string
  [MENU_SHOW]?: boolean | string
  [key: string]: any
}

 在store文件夹中的index.ts中引入menu

import { configureStore } from "@reduxjs/toolkit";
import globalReducer from "./reducers/global";
import menuReducer from "./reducers/menu";
//处理eslint报错
/* eslint-disable @typescript-eslint/no-unused-vars */
const store = configureStore({
  reducer: {
    global: globalReducer,
    menu: menuReducer,
  },
});

// 从 store 本身推断 `RootState` 和 `AppDispatch` 类型
export type RootState = ReturnType;
// 推断类型:{posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;

export default store;

hook管理

因为之前的使用的文件名称是UseGlobal.hooks.ts这个并不是设置全局配置的有点歧义,把原来UseGlobal.hooks.ts的内容复制一份放入到新建一个UseStore.hooks.ts文件

把之前组件中使用到UseGlobal.hooks.ts的地方抽离到hook中

UseGlobal.hooks.ts代码如下:

import { useAppSelector, useAppDispatch } from "@/hooks/UseStore.hooks";
import { useCallback } from "react";
import {
  setCollapsed,
  selectCollapsed,
  selectShowSetting,
  setShowSetting,
  selectColorPrimary,
  selectIsDark,
  selectIsRadius,
  setIsDark,
  setColorPrimary,
  setIsRadius,
} from "@/store/reducers/global";

//获取当前菜单栏是否折叠状态
export const useIsCollapsed = () => useAppSelector(selectCollapsed);
//获取当前弹窗是否显示状态
export const useShowPoup = () => useAppSelector(selectShowSetting);
//获取当前主题颜色的值
export const useCurColor = () => useAppSelector(selectColorPrimary);
//获取当前主题是否是暗黑模式
export const useIsSelectdDark = () => useAppSelector(selectIsDark);
//获取当前主题是否是圆角
export const useIsSelectdRadius = () => useAppSelector(selectIsRadius);

export const useDispatchGlobal = () => {
  const dispatch = useAppDispatch();

  // 更改菜单栏的折叠状态
  const stateHandleCollapsed = useCallback(() => {
    dispatch(setCollapsed());
  }, [dispatch]);

  // 更新主题颜色
  const stateHandleColorPrimary = useCallback(
    (color: string) => {
      dispatch(setColorPrimary(color));
    },
    [dispatch]
  );

  // 切换主题是否是暗黑模式
  const stateHandleIsDark = useCallback(
    () => {
      dispatch(setIsDark());
    },
    [dispatch]
  );

  // 切换主题是否是圆角
  const stateHandleIsRadius = useCallback(
    () => {
      dispatch(setIsRadius());
    },
    [dispatch]
  );

  // 更新是否显示设置弹窗
  const stateHandleShowPopup = useCallback(
    (isShow: boolean) => {
      dispatch(setShowSetting(isShow));
    },
    [dispatch]
  );

  return {
    stateHandleCollapsed,
    stateHandleColorPrimary,
    stateHandleIsDark,
    stateHandleIsRadius,
    stateHandleShowPopup,
  };
};

UseStore.hooks.ts代码如下:

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '@/store/index';

// 在整个应用程序中使用,而不是简单的 `useDispatch` 和 `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook = useSelector;

面包屑和顶部tab的hook

hooks文件夹下新增UseMenu.hooks.ts文件把状态及hook的方法封装到这里代码如下:

import { useAppSelector, useAppDispatch } from "@/hooks/UseStore.hooks";
import { useCallback } from "react";
import type { OpenedMenu } from "@/types/menu.ts";
import {
  selectOpenKey,
  selectMenu,
  selectOpenedMenu,
  selectCurrentPath,
  setOpenKey,
  setCurrent,
  addMenu,
  removeMenu,
  clearMenu,
} from "@/store/reducers/menu";

//获取当前菜单展开的key
export const useOpenKey = () => useAppSelector(selectOpenKey);

//获取当前菜单
export const useMenu = () => useAppSelector(selectMenu);

//获取当前菜单列表
export const useOpenedMenu = () => useAppSelector(selectOpenedMenu);

//获取当前路径
export const useCurrentPath = () => useAppSelector(selectCurrentPath);

export const useDispatchMenu = () => {
  const dispatch = useAppDispatch();
  //修改菜单展开的key
  const stateChangeOpenKey = useCallback(
    (menu: string[]) => {
      dispatch(setOpenKey(menu));
    },
    [dispatch]
  );
  //修改当前菜单
  const stateChangeCurrent = useCallback(
    (menu: string[]) => {
      dispatch(setCurrent(menu));
    },
    [dispatch]
  );
  //添加菜单
  const stateAddMenu = useCallback(
    (menu: OpenedMenu) => {
      dispatch(addMenu(menu));
    },
    [dispatch]
  );
  //删除菜单
  const stateRemoveMenu = useCallback(
    (menu: string) => {
      dispatch(removeMenu(menu));
    },
    [dispatch]
  );
  //清空菜单
  const stateClearMenu = useCallback(() => {
    dispatch(clearMenu());
  }, [dispatch]);
  return {
    stateChangeOpenKey,
    stateChangeCurrent,
    stateAddMenu,
    stateRemoveMenu,
    stateClearMenu,
  };
};

面包屑和顶部tab标签

面包屑

修改header.tsx文件使用antd的组件Breadcurmb,根据当前useCurrentPath获取到当前url的路由地址进行分隔和组装Breadcurmb所需数据格式的组装

import React from "react";
import { Button, Layout, theme, Flex, Breadcrumb } from "antd";
import {
  HomeOutlined,
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  SettingOutlined,
} from "@ant-design/icons";
import { useShowPoup, useDispatchGlobal } from "@/hooks/UseGlobal.hooks";
import { useCurrentPath } from "@/hooks/UseMenu.hooks";
import Setting from "./setting";
import { MenuItemProps } from "@/types/menu";
import { NavigateFunction } from "react-router-dom";
const { Header } = Layout;
interface AppSiderProps {
  menu: MenuItemProps[];
  collapsed: boolean;
  navigate:NavigateFunction;
}
const setMenuData = (arr: MenuItemProps[], keys: string[]) => {
  const menuList:Array<{title:React.ReactNode,href?:string}> = [{ title: , href: "/" }];
  const subKey = keys[0];
  const key = keys[1];
  arr.forEach((item) => {
    if (item.key === subKey) {
      menuList.push({ title: <> {item.label}  });
      if (item.children) {
        item.children.forEach((child) => {
          if (child.key === key) {
            menuList.push({ title: <> {child.label}  });
          }
        });
      }
    }
  });
  return menuList;
};
const AppHeader: React.FC = ({ menu, collapsed,navigate }) => {
  const {
    token: { colorBgContainer },
  } = theme.useToken();

  const showPoup: boolean = useShowPoup();
  const { stateHandleShowPopup, stateHandleCollapsed } = useDispatchGlobal();
  const currentMenu = useCurrentPath();
  const menuList = setMenuData(
    JSON.parse(JSON.stringify(menu)),
    currentMenu.split("/")
  );
  const handleLink = (item: { href?: string }) => () => {
    if (item.href) {
      navigate(item.href)
    }
  };
  console.log(menu, currentMenu, menuList, "menu");
  //设置按钮点击事件
  return (
    
); }; export default AppHeader;

tab标签

修改main.tsx文件,使用antd的Tag组件根据状态中存储的所有打开的页面显示和添加关闭操作

import { Layout, theme, Flex, Divider, Tag } from "antd";
import { useCallback, useEffect } from "react";
import {
  useOpenedMenu,
  useCurrentPath,
  useDispatchMenu,
} from "@/hooks/UseMenu.hooks";
import { useIsCollapsed } from "@/hooks/UseGlobal.hooks";
import { NavigateFunction, Outlet } from "react-router-dom";
import type { OpenedMenu } from "@/types/menu.ts";
import { HomeOutlined } from "@ant-design/icons";
const { Content } = Layout;
interface AppMainProps {
  pathname: string;
  navigate: NavigateFunction;
}
const homeTag: OpenedMenu = {
  key: "home",
  path: "/",
  title: "首页",
};
//获取路径的层级
const getPathParts = (path: string): string[] =>
  path.replace("/", "").split("/");
const AppMain: React.FC = ({ pathname, navigate }) => {
  const {
    token: { colorBgContainer, borderRadiusLG,colorPrimaryBg, colorPrimary },
  } = theme.useToken();

  const tabList = useOpenedMenu();
  const currentMenu = useCurrentPath();
  const isCIsCollapsed = useIsCollapsed();
  const { stateChangeOpenKey, stateChangeCurrent, stateRemoveMenu } =
    useDispatchMenu();

  // 点击tab时,更新路径状态

  const handleTabClick = (item: OpenedMenu) => {
    navigate(item.path || "/");
    stateChangeCurrent([item.key]);
  };
  const handleTabClose = (key: string) => {
    stateRemoveMenu(key);
    // 关闭当前tab,并打开上一个
    const tabMenu = tabList.filter((i) => !key.includes(i.key));

    if (tabMenu.length === 0) {
      navigate("/");
      stateChangeCurrent(["home"]);
      return;
    }
    const item = tabMenu[tabMenu.length - 1];
    navigate(item.path || "/");
    stateChangeCurrent([item.key]);
  };

  // 路径变化时,更新菜单状态
  const onPathChange = useCallback(() => {
    const parts = getPathParts(pathname);
    stateChangeOpenKey([parts[0]]);
    stateChangeCurrent([parts[1] || "home"]);
  }, [pathname, stateChangeOpenKey, stateChangeCurrent]);

  // 菜单展开/收起时,更新路径状态
  useEffect(() => {
    onPathChange();
  }, [pathname, isCIsCollapsed, onPathChange]);
  return (
    <>
      
      
        <>
          }
            onClick={() => handleTabClick(homeTag)}
            style={{
              background: "/" == currentMenu ? colorPrimaryBg : "transparent",
              color: "/" == currentMenu ? colorPrimary : "rgba(0, 0, 0, 0.88)",
            }}
            className="cursor-pointer flex items-center pt-2  pb-2 pl-4 pr-4 text-base rounded-b-none"
          >
            {homeTag.title}
          
        

        {tabList.map((item) => {
          return (
             {
                handleTabClick(item);
              }}
              key={item.key}
              closable
              style={{
                background: item.path == currentMenu ? colorPrimaryBg : "transparent",
                color:
                  item.path == currentMenu ? colorPrimary : "rgba(0, 0, 0, 0.88)",
              }}
              onClose={() => handleTabClose(item.key)}
              className="cursor-pointer flex items-center pt-2 pb-2 pl-4 text-base rounded-b-none"
            >
              {item.title}
            
          );
        })}
      
      
        
      
    
  );
};
export default AppMain;

效果如下

使用vite+react+ts+Ant Design开发后台管理项目(五)_第1张图片

优化

对已经完成的部分做一些代码的抽离和封装

1.主题颜色抽离及设置hook方法

types文件夹下新增color.d.ts文件


export interface color {
    name:string;
    value:string;
}

在src文件夹下新增utils文件夹,创建文件color.ts


export const colors = [
  {
    name: "拂晓蓝",
    value: "#1677ff",
  },
  {
    name: "薄暮",
    value: "#5f80c7",
  },
  {
    name: "日暮",
    value: "#faad14",
  },
  {
    name: "火山",
    value: "#f5686f",
  },
  {
    name: "酱紫",
    value: "#9266f9",
  },
  {
    name: "极光绿",
    value: "#3c9",
  },
  {
    name: "极客蓝",
    value: "#32a2d4",
  },
];

修改setting.tsx文件

import React from "react";
import { Button, Flex, Drawer, Space, Switch } from "antd";
import { CloseOutlined, CheckOutlined } from "@ant-design/icons";
import {
  useCurColor,
  useIsSelectdDark,
  useIsSelectdRadius,
  useDispatchGlobal,
} from "@/hooks/UseGlobal.hooks";
import { colors } from "@/utils/color";
type AppSiderProps = {
  showPoup: boolean;
};
const Setting: React.FC = ({ showPoup }) => {
  //主题颜色
  const curColor: string = useCurColor();
  //暗黑模式
  const isSelectdDark: boolean = useIsSelectdDark();
  //圆角模式
  const isSelectdRadius: boolean = useIsSelectdRadius();
  const {
    stateHandleColorPrimary,
    stateHandleIsDark,
    stateHandleIsRadius,
    stateHandleShowPopup,
  } = useDispatchGlobal();
  const ColorItem: React.FC<{ color: string; isSelectd: boolean }> = ({
    color,
    isSelectd,
  }) => {
    if (isSelectd) {
      return (
        
); } else { return (
stateHandleColorPrimary(color)} >
); } }; return ( } >
主题颜色
{colors.map((item) => ( ))}
主题模式
开启暗黑模式
开启圆角主题
); }; export default Setting;

2.布局组件hooks抽离和封装

  • layout文件夹下的index.tsx文件修改
import React, { useEffect, useState } from "react";
import { Layout, ConfigProvider, theme } from "antd";
import { useNavigate, useLocation } from "react-router-dom";
import {
  useIsCollapsed,
  useCurColor,
  useIsSelectdDark,
  useIsSelectdRadius,
} from "@/hooks/UseGlobal.hooks";
import AppHeader from "./header";
import AppSider from "./sider";
import AppMain from "./main";
import { MenuItemProps } from "@/types/menu";
import { getMenu } from "@/api/menu";
const App: React.FC = () => {
  const collapsed: boolean = useIsCollapsed();
  const isDark: boolean = useIsSelectdDark();
  const isRadius: boolean = useIsSelectdRadius();
  const themeColor: string = useCurColor();
  // 菜单数据
  const [menu, setMenu] = useState([] as MenuItemProps[]);
  const { pathname } = useLocation();
  const navigate = useNavigate();

  // 获取菜单数据
  useEffect(() => {
    // 获取菜单数据
    const getData = async () => {
      const res = await getMenu();
      const menuData = res?.data as MenuItemProps[];
      // 设置菜单数据
      setMenu([...menuData]);
    };
    getData();
  }, []);
  // 简化返回内容的嵌套
  const appLayout = (
    
      
      
        
        
      
    
  );

  return (
    
      {appLayout}
    
  );
};

export default App;
  • layout文件夹下的main.tsx组件文件修改
import { Layout, theme, Flex, Divider, Tag } from "antd";
import { useCallback, useEffect } from "react";
import {
  useOpenedMenu,
  useCurrentPath,
  useDispatchMenu,
} from "@/hooks/UseMenu.hooks";
import { useIsCollapsed } from "@/hooks/UseGlobal.hooks";
import { NavigateFunction, Outlet } from "react-router-dom";
import type { OpenedMenu } from "@/types/menu.ts";
import { HomeOutlined } from "@ant-design/icons";
const { Content } = Layout;
interface AppMainProps {
  pathname: string;
  navigate: NavigateFunction;
}
const homeTag: OpenedMenu = {
  key: "home",
  path: "/",
  title: "首页",
};
//获取路径的层级
const getPathParts = (path: string): string[] =>
  path.replace("/", "").split("/");
const AppMain: React.FC = ({ pathname, navigate }) => {
  const {
    token: { colorBgContainer, borderRadiusLG,colorPrimaryBg, colorPrimary },
  } = theme.useToken();

  const tabList = useOpenedMenu();
  const currentMenu = useCurrentPath();
  const isCIsCollapsed = useIsCollapsed();
  const { stateChangeOpenKey, stateChangeCurrent, stateRemoveMenu } =
    useDispatchMenu();

  // 点击tab时,更新路径状态

  const handleTabClick = (item: OpenedMenu) => {
    navigate(item.path || "/");
    stateChangeCurrent([item.key]);
  };
  const handleTabClose = (key: string) => {
    stateRemoveMenu(key);
    // 关闭当前tab,并打开上一个
    const tabMenu = tabList.filter((i) => !key.includes(i.key));

    if (tabMenu.length === 0) {
      navigate("/");
      stateChangeCurrent(["home"]);
      return;
    }
    const item = tabMenu[tabMenu.length - 1];
    navigate(item.path || "/");
    stateChangeCurrent([item.key]);
  };

  // 路径变化时,更新菜单状态
  const onPathChange = useCallback(() => {
    const parts = getPathParts(pathname);
    stateChangeOpenKey([parts[0]]);
    stateChangeCurrent([parts[1] || "home"]);
  }, [pathname, stateChangeOpenKey, stateChangeCurrent]);

  // 菜单展开/收起时,更新路径状态
  useEffect(() => {
    onPathChange();
  }, [pathname, isCIsCollapsed, onPathChange]);
  return (
    <>
      
      
        <>
          }
            onClick={() => handleTabClick(homeTag)}
            style={{
              background: "/" == currentMenu ? colorPrimaryBg : "transparent",
              color: "/" == currentMenu ? colorPrimary : "rgba(0, 0, 0, 0.88)",
            }}
            className="cursor-pointer flex items-center pt-2  pb-2 pl-4 pr-4 text-base rounded-b-none"
          >
            {homeTag.title}
          
        

        {tabList.map((item) => {
          return (
             {
                handleTabClick(item);
              }}
              key={item.key}
              closable
              style={{
                background: item.path == currentMenu ? colorPrimaryBg : "transparent",
                color:
                  item.path == currentMenu ? colorPrimary : "rgba(0, 0, 0, 0.88)",
              }}
              onClose={() => handleTabClose(item.key)}
              className="cursor-pointer flex items-center pt-2 pb-2 pl-4 text-base rounded-b-none"
            >
              {item.title}
            
          );
        })}
      
      
        
      
    
  );
};
export default AppMain;
  • layout文件夹下的menu.tsx组件文件修改
import React, { useCallback, useEffect } from "react";
import { HomeOutlined, SettingOutlined, ShopOutlined } from "@ant-design/icons";
import { Menu } from "antd";
import { MenuItemProps } from "@/types/menu";
import { NavigateFunction } from "react-router-dom";
import { useOpenKey, useMenu, useDispatchMenu } from "@/hooks/UseMenu.hooks";
// 图标映射
const Icons = {
  home: HomeOutlined,
  setting: SettingOutlined,
  shop: ShopOutlined,
};

interface AppMenuProps {
  pathname:string;
  menu:MenuItemProps[];
  navigate:NavigateFunction;
}
// 获取图标组件
const IconByName: React.FC<{ iconName: string }> = ({ iconName }) => {
  // 获取图标组件
  const IconComponent = Icons[iconName as keyof typeof Icons];
  // 返回图标组件
  return IconComponent ?  : null;
};
// 查找菜单项
const findMenuByKey = (
  arr: MenuItemProps[],
  key: string
): MenuItemProps | undefined => {
  for (const item of arr) {
    if (item.key === key) {
      return item;
    }
    if (item.children) {
      const found = findMenuByKey(item.children, key);
      if (found) {
        return found;
      }
    }
  }
  return undefined;
};
// 获取路径
const getPathParts = (path: string): string[] =>
  path.replace("/", "").split("/");
// 侧边栏
const AppMenu: React.FC = ({ menu,pathname,navigate }) => {
  const openKeys = useOpenKey();
  const currentMenu = useMenu();
  const { stateChangeOpenKey: onOpenChange, stateAddMenu } = useDispatchMenu();
  // 设置当前菜单
  const setTabMenu = useCallback(
    (keyPath: string[]) => {
      const itemMenu: MenuItemProps | undefined = findMenuByKey(
        menu,
        keyPath[1] as string
      );
      if (itemMenu) {
        stateAddMenu({
          key: itemMenu?.key,
          path: keyPath.join("/"),
          title: itemMenu?.label,
        });
      }
    },
    [menu, stateAddMenu]
  );
  // 路由地址变化后设置当前菜单
  useEffect(() => {
    const keyPath = getPathParts(pathname);
    setTabMenu(keyPath);
  }, [pathname, setTabMenu]);

  // 点击菜单项
  const handleMenu = ({ keyPath }: { keyPath: string[] }) => {
    const routerPath: string = keyPath.reverse().join("/");
    setTabMenu(keyPath);
    navigate(routerPath);
  };
  // 使用递归查找匹配的菜单项
  const menuData = menu.map((item: MenuItemProps) => {
    return {
      key: item.key,
      label: item.label,
      icon: item.icon ?  : undefined,
      children: item.children?.map((child) => ({
        key: child.key,
        label: child.label,
      })),
    };
  });
  return (
    
  );
};

export default AppMenu;
  • layout文件夹下的sider.tsx文件修改
import React from "react";
import { Layout } from "antd";
import AppMenu from "./menu";
import { MenuItemProps } from "@/types/menu";
import { NavigateFunction } from "react-router-dom";
const { Sider } = Layout;

interface AppSiderProps {
  pathname: string;
  menu: MenuItemProps[];
  navigate: NavigateFunction;
  collapsed: boolean;
}
// 侧边栏
const AppSider: React.FC = ({
  menu,
  collapsed,
  navigate,
  pathname,
}) => {
  // 返回侧边栏
  return (
    
      
); }; export default AppSider;

你可能感兴趣的:(react.js,javascript,前端)