当前位置: 首页 > news >正文

寻找做网站/网络推广哪个平台最好

寻找做网站,网络推广哪个平台最好,网站设置手机版,做网站的公司 经营范围前言 本文将引导开发者从零基础开始,运用vite、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈,构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导,文章旨在为开发者揭示如何利用这些技术…

 前言


本文将引导开发者从零基础开始,运用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: stringpath: stringtitle: 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<typeof store.getState>;
// 推断类型:{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<RootState> = 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();//修改菜单展开的keyconst 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: <HomeOutlined />, 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<AppSiderProps> = ({ 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 (<Header style={{ padding: 0, background: colorBgContainer }}><Flex gap="middle" justify="space-between" align="center"><Flex justify="space-between" align="center"><Buttontype="text"icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}style={{fontSize: "16px",width: 64,height: 64,}}onClick={stateHandleCollapsed}/><Breadcrumbitems={menuList}itemRender={(item) => <span onClick={handleLink(item)}>{item.title}</span>}/></Flex><Buttontype="primary"className="mr-4"icon={<SettingOutlined />}onClick={() => {stateHandleShowPopup(true);}}/></Flex><Setting showPoup={showPoup} /></Header>);
};
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<AppMainProps> = ({ 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 (<><Divider style={{ margin: 0 }} /><Flexgap="0"justify="flex-start"className="bg-white  pl-5 pr-5"align="center"><><Tagbordered={false}icon={<HomeOutlined />}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"><span className="mr-1">{homeTag.title}</span></Tag></>{tabList.map<React.ReactNode>((item) => {return (<Tagbordered={false}onClick={() => {handleTabClick(item);}}key={item.key}closablestyle={{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"><span className="mr-1">{item.title}</span></Tag>);})}</Flex><Contentstyle={{margin: "24px 16px",padding: 24,minHeight: 280,background: colorBgContainer,borderRadius: borderRadiusLG,}}><Outlet /></Content></>);
};
export default AppMain;

效果如下

优化

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

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<AppSiderProps> = ({ 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 (<divclassName="w-6 h-6 flex justify-center items-center  rounded cursor-pointer items"style={{ background: color }}><CheckOutlined style={{ color: "#fff" }} /></div>);} else {return (<divclassName="w-6 h-6 flex justify-center items-center  rounded cursor-pointer items"style={{ background: color }}onClick={() => stateHandleColorPrimary(color)}></div>);}};return (<Drawertitle="设置"width={300}closeIcon={false}open={showPoup}extra={<Space><Buttontype="text"onClick={() => {stateHandleShowPopup(false);}}icon={<CloseOutlined />}></Button></Space>}><div className="mb-3 font-bold">主题颜色</div><Flex gap="middle" justify="space-between" align="center">{colors.map((item) => (<ColorItemkey={item.value}color={item.value}isSelectd={curColor == item.value}/>))}</Flex><div className="mb-3 mt-3 font-bold">主题模式</div><div className="flex justify-between mb-3"><div className="flex gap-2"><span>开启暗黑模式</span></div><div className="flex gap-2"><SwitchdefaultCheckedchecked={isSelectdDark}onChange={stateHandleIsDark}/></div></div><div className="flex justify-between"><div className="flex gap-2"><span>开启圆角主题</span></div><div className="flex gap-2"><SwitchdefaultCheckedchecked={isSelectdRadius}onChange={stateHandleIsRadius}/></div></div></Drawer>);
};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 = (<Layout className="app-layout"><AppSider menu={menu} pathname={pathname} navigate={navigate}  collapsed={collapsed} /><Layout><AppHeader menu={menu} collapsed={collapsed} navigate={navigate} /><AppMain pathname={pathname} navigate={navigate} /></Layout></Layout>);return (<ConfigProvidertheme={{token: {colorPrimary: themeColor,borderRadius: isRadius ? 6 : 0,motion: true,},algorithm: isDark ? theme.darkAlgorithm : theme.defaultAlgorithm,}}>{appLayout}</ConfigProvider>);
};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<AppMainProps> = ({ 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 (<><Divider style={{ margin: 0 }} /><Flexgap="0"justify="flex-start"className="bg-white  pl-5 pr-5"align="center"><><Tagbordered={false}icon={<HomeOutlined />}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"><span className="mr-1">{homeTag.title}</span></Tag></>{tabList.map<React.ReactNode>((item) => {return (<Tagbordered={false}onClick={() => {handleTabClick(item);}}key={item.key}closablestyle={{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"><span className="mr-1">{item.title}</span></Tag>);})}</Flex><Contentstyle={{margin: "24px 16px",padding: 24,minHeight: 280,background: colorBgContainer,borderRadius: borderRadiusLG,}}><Outlet /></Content></>);
};
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 ? <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<AppMenuProps> = ({ 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 ? <IconByName iconName={item.icon} /> : undefined,children: item.children?.map((child) => ({key: child.key,label: child.label,})),};});return (<MenuonClick={handleMenu}theme="dark"selectedKeys={currentMenu}onOpenChange={onOpenChange}openKeys={openKeys}mode="inline"items={menuData}/>);
};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<AppSiderProps> = ({menu,collapsed,navigate,pathname,
}) => {// 返回侧边栏return (<Sider trigger={null} collapsible collapsed={collapsed}><div className="demo-logo-vertical" /><AppMenu menu={menu} pathname={pathname} navigate={navigate} /></Sider>);
};export default AppSider;

相关文章:

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

前言 本文将引导开发者从零基础开始&#xff0c;运用vite、react、react-router、react-redux、Ant Design、less、tailwindcss、axios等前沿技术栈&#xff0c;构建一个高效、响应式的后台管理系统。通过详细的步骤和实践指导&#xff0c;文章旨在为开发者揭示如何利用这些技术…...

Spring Boot实现多数据源连接和切换

文章目录 前言一、多数据源配置与切换方案二、实现步骤1. 创建多个 DataSource 配置类2. 创建 DataSource 配置类3. 创建动态数据源路由类4. 实现 DynamicDataSource 类5. 创建 DataSourceContextHolder 来存储当前的数据源标识6. AOP 方式切换数据源7. 自定义注解来指定数据源…...

发布 VectorTraits v3.0(支持 X86架构的Avx512系列指令集,支持 Wasm架构及PackedSimd指令集等)

文章目录 支持 X86架构的Avx512系列指令集支持Avx512时的输出信息 支持 Wasm架构及PackedSimd指令集支持PackedSimd时的输出信息VectorTraits.Benchmarks.Wasm 使用说明 新增了向量方法支持 .NET 8.0 新增的向量方法提供交织与解交织的向量方法YGroup3Unzip的范例代码 提供重新…...

详解如何创建SpringBoot项目

目录 点击New Project 选择依赖 简单使用SpringBoot 前面已经讲解了如何获取IDEA专业版&#xff0c;下面将以此为基础来讲解如何创建SpringBoot项目。 点击New Project 选择依赖 注意&#xff0c;在选择SpringBoot版本时&#xff0c;不要选择带SNAPSHOT的版本。 这样&#…...

IT架构管理

目录 总则 IT架构管理目的 明确组织与职责 IT架构管理旨在桥接技术实施与业务需求之间的鸿沟&#xff0c;通过深入理解业务战略和技术能力&#xff0c;推动技术创新以支持业务增长&#xff0c;实现技术投资的最大价值。 设定目标与范围 IT架构管理的首要目的是确立清晰的组织…...

Feign入门实践

引言 随着微服务架构的兴起&#xff0c;服务间的通信变得越来越频繁和复杂。为了简化服务之间的调用过程&#xff0c;提高开发效率和系统的可维护性&#xff0c;Spring Cloud 生态系统提供了多种解决方案&#xff0c;其中 OpenFeign 是一种声明式的 HTTP 客户端&#xff0c;它使…...

Leetcode 买卖股票的最佳时机 Ⅱ

使用贪心算法来解决此问题&#xff0c;通过在价格上涨的每一天买入并在第二天卖出的方式&#xff0c;累计所有上涨的利润&#xff0c;以实现最大收益。关键点是从第二天开始遍历&#xff0c;并且只要当前比前一天价格高&#xff0c;我们就在前一天买入然后第二天卖出去。下面是…...

书生大模型实战营-玩转HF/魔搭社区闯关任务

通过Github Codespace下载InternLM模型并运行 本篇博客是记录《书生大模型实战营第四期-玩转HF/魔搭/魔乐》章节的闯关任务从HF上下载模型文件&#xff0c;对实战营感兴趣的小伙伴也可以扫码报名哦。 一、通过模版创建Codespace环境 访问codespace 点击Jupyter Notebook 模版…...

混响(Reverb):原理、应用与发展趋势的深度解析

目录 引言1. 混响的基本原理2. 混响的应用3. 混响的技术实现4. 混响的未来发展趋势5. 总结 引言 混响&#xff08;Reverb&#xff09;是音频信号处理中的重要概念之一&#xff0c;在自然界和音频工程中都扮演着关键角色。从音乐制作到语音识别&#xff0c;从电影音效到虚拟现实…...

Java学习教程,从入门到精通,Java修饰符语法知识点及案例代码(23)

1.Java修饰符语法知识点及案例代码 Java修饰符用于改变类、方法、变量、接口等元素的行为和可见性。主要分为两大类&#xff1a;访问修饰符和非访问修饰符。 访问修饰符&#xff08;Access Modifiers&#xff09; public 提供最大的访问权限&#xff0c;任何类都可以访问。使…...

钉钉小程序使用getApp实现类型provide inject的功能 应用场景:解决页面同步子组件弹窗的滚动问题

前言:在开发钉钉小程序的时候 组件内部的弹窗滚动会带着视图同步滚动 所以需要在组件内部弹窗显示的时候禁用视图的scroll滚动 由于我组件封装的比较深 不可能逐级传递 dd也么有provide的语法 所以我使用的getApp 完成控制的效果 最终完美运行 觉得有帮助相互关注一下 后续会持…...

标准化 Git 提交信息的约定

在使用 Git 进行版本控制时&#xff0c;良好的提交信息可以帮助团队成员更好地理解每次提交的目的和影响。为了规范化提交信息&#xff0c;一些团队采用了特定的格式或约定&#xff0c;比如 Angular 团队提出的 Commit Message Conventions。这种规范有助于自动化工具的使用&am…...

React教程(详细版)

React教程&#xff08;详细版&#xff09; 1&#xff0c;简介 1.1 概念 react是一个渲染html界面的一个js库&#xff0c;类似于vue&#xff0c;但是更加灵活&#xff0c;写法也比较像原生js&#xff0c;之前我们写出一个完成的是分为html&#xff0c;js&#xff0c;css&…...

Perfect Forwarding(完美转发)

文章目录 1. 引用折叠2. 万能引用3. 完美转发3.1对比&#xff1a;std::move and std::forward比较 3.2使用时机3.3 返回值优化&#xff08;RVO)两个前提条件注意事项 4. 完美转发失败情况完美转发失败五种情况 完美转发的实现要依赖于模版类型推导和引用折叠和万能引用。 1. 引…...

PHP露营地管理平台小程序系统源码

⛺️【露营新风尚】露营地管理平台系统全攻略⛺️ &#x1f3d5;️一、露营热潮下的管理难题&#xff1a;如何高效运营露营地&#xff1f;&#x1f914; 随着露营文化的兴起&#xff0c;越来越多的人选择在大自然中享受宁静与自由。然而&#xff0c;露营地的管理却面临着诸多…...

速盾:vue的cdn是干嘛的?

CDN&#xff0c;即内容分发网络&#xff08;Content Delivery Network&#xff09;&#xff0c;是一种将网站的静态资源分发到全球各个节点并缓存起来的技术。它可以帮助网站提供更快的加载速度&#xff0c;更好的用户体验&#xff0c;并且可以减轻源服务器的负载压力。 Vue.j…...

线性代数:Matrix2x2和Matrix3x3

今天整理自己的框架代码&#xff0c;将Matrix2x2和Matrix3x3给扩展了一下&#xff0c;发现网上unity数学计算相关挺少的&#xff0c;所以记录一下。 首先扩展Matrix2x2&#xff1a; using System.Collections; using System.Collections.Generic; using Unity.Mathemati…...

Windows 中 Electron 项目实现运行时权限提升以杀掉特定进程

#Windows 中 Electron 项目实现运行时权限提升以杀掉特定进程 一、引言 在 Windows 操作系统中&#xff0c;有时我们需要以管理员权限来执行某些操作&#xff0c;特别是当需要杀掉由管理员启动的进程时。Electron 是一个开源的框架&#xff0c;用于使用 JavaScript、HTML 和 C…...

赠你一只金色的眼 - 富集分析和表达数据可视化

GOplot包介绍 GOplot包用于生物数据的可视化。更确切地说&#xff0c;该包将表达数据与功能分析的结果整合并进行可视化。但是要注意该包不能用于执行这些分析&#xff0c;只能把分析结果进行可视化。在所有科学领域&#xff0c;由于空间限制和结果所需的简洁性&#xff0c;切…...

鸿蒙的进击之路

1. 题记&#xff1a; 为什么要写鸿蒙&#xff0c;因为她是华为的&#xff0c;为什么是华为就要写&#xff0c;因为华为背负了国人太多太多的包袱&#xff0c;或点赞或抨击。 我是强烈支持华为的&#xff0c;但我会客观公正地去评价华为的产品&#xff0c;就比如这篇博文&#…...

c语言中的线程管理pthread详解

在C语言中,多线程编程常用的POSIX线程(POSIX Threads, pthreads)库主要由pthread.h头文件提供。pthread.h定义了许多用于线程创建、管理、同步的函数和数据结构。下面是pthread.h中的核心概念和主要函数的详细介绍。 1. 基本概念 线程:线程是一个轻量级的进程,可以并发执…...

关于qiskit版本>1.0.0,execute函数被替换

关于下列代码&#xff0c;当qiskit版本大于1时&#xff0c;无法使用execute函数&#xff0c;定义transpiled_circuit和run函数来替换。 import numpy as np from qiskit import QuantumCircuit, Aer, executedef calculate_hydrogen_energy():# Definir el circuito cunticoci…...

给 Docker 配置网络代理

参考 https://www.cnblogs.com/Chary/p/18096678如何优雅的给 Docker 配置网络代理 有时因为网络原因,比如公司 NAT,或其它啥的,需要使用代理。Docker 的代理配置,略显复杂,因为有三种场景。但基本原理都是一致的,都是利用 Linux 的 http_proxy 等环境变量。 Dockerd 代…...

软件测试基础十七(python Unittest)

Unittest 一、Unittest 简介 unittest是 Python 内置的标准测试框架&#xff0c;用于编写和运行单元测试。它提供了一组工具和类&#xff0c;帮助开发者组织、编写和执行测试用例&#xff0c;以验证代码的正确性。 二、Unittest 核心要素 1. TestCase&#xff08;测试用例类…...

技术领导者的道与术:从领导者到领导力

目录标题 领导者现实看起来是这样技术领导者不应该和个人坐在一起技术领导力仍然是必须的从技术领导到技术领导力小结领导者 你可能想成为或者已经是一位技术领导者,估计你现在心里想成为超级英雄的想法正在爆棚。 你是Java、JavaScript、Angular等技术的专家,公司的项目代…...

Starrocks Compaction的分析

背景 本文基于 Starrocks 3.1.7 结论 Starrocks 会启动一个线程周期性的去进行Compaction&#xff0c;该周期间隔为 200 MS, 该Compaction以table的partition为切入点&#xff0c;tablet(也就是bucket)为粒度进行task的创建。 分析 CompactionMgr start 方法会启动一个Com…...

淘淘商城实战高并发分布式项目(有源码)

通过百度网盘分享的文件&#xff1a;淘淘商城实战高并发分布式项目(有源码) 链接&#xff1a;https://pan.baidu.com/s/1V94gRALxHgMVwpcXoE-miA?pwdglu7 提取码&#xff1a;glu7 在互联网技术飞速发展的当下&#xff0c;高并发分布式项目成为了众多电商平台等大型应用的核心…...

内网部署web项目,外网访问不了?只有局域网能访问!怎样解决?

相关技术 要实现“内网部署&#xff0c;外网访问”&#xff0c;可以使用内网穿透、VPN技术、DMZ主机、端口映射等方法。以下是对这些方法的详细解释&#xff1a; 一、内网穿透 内网穿透是一种技术&#xff0c;它通过将内网设备映射到公网上的方式&#xff0c;实现外网访问内…...

Jenkins系列

jenkins 1、搭建Jenkins 搭建Jenkins 2、这是什么 3、这是什么 4、 这是什么 5、这是什么 文章目录 jenkins1、搭建Jenkins2、这是什么3、这是什么4、 这是什么5、这是什么 前言 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 例如&#xff1a;随…...

技术总结(二十四)

一、Redis 分布式锁的常见使用场景有哪些&#xff1f; 资源竞争控制 数据库事务控制&#xff1a;在分布式系统中&#xff0c;多个服务可能会同时对数据库中的同一行数据进行操作。例如&#xff0c;在一个电商系统里&#xff0c;多个订单处理服务可能会同时尝试更新同一个订单的…...