揭秘如何使用react 18 + antd (v5.3.0) 动态生成左侧菜单栏?

antd专门为react定制的中后台组件库,提供了大量的组件供开发者使用,

官网地址 点击跳转

在中后台中,菜单项是必不可少的,今天就使用react结合antd V5.0 配置一个菜单栏目.

由于antdV5.0的做了升级,Menu导航菜单组件使用有了比较大的变化。

下面就开始使用antd v5构建菜单栏目。

第一步:获取路由数据

从后台得到的数据结构(根据不同角色返回的不同菜单数据),比如:

const menusListData = [
    {
        "id": 1,
        "title": "首页",
        "key": "/home",
        "pagepermisson": 1,
        "grade": 1,
        "children": []
    },
    {
        "id": 2,
        "title": "用户管理",
        "key": "/user-manage",
        "pagepermisson": 1,
        "grade": 1,
        "children": [
            {
                "id": 3,
                "title": "添加用户",
                "rightId": 2,
                "key": "/user-manage/add",
                "grade": 2
            }
        ]
    },
    {
        "id": 7,
        "title": "权限管理",
        "key": "/right-manage",
        "pagepermisson": 1,
        "grade": 1,
        "children": [
            {
                "id": 8,
                "title": "角色列表",
                "rightId": 7,
                "key": "/right-manage/role/list",
                "pagepermisson": 1,
                "grade": 2
            }
        ]
    },
    {
        "id": 14,
        "title": "新闻管理",
        "key": "/news-manage",
        "pagepermisson": 1,
        "grade": 1,
        "children": [
            {
                "id": 15,
                "title": "新闻列表",
                "rightId": 14,
                "key": "/news-manage/list",
                "grade": 2
            }
        ]
    },
    {
        "id": 21,
        "title": "审核管理",
        "key": "/audit-manage",
        "pagepermisson": 1,
        "grade": 1,
        "children": [
            {
                "id": 22,
                "title": "审核新闻",
                "rightId": 21,
                "key": "/audit-manage/audit",
                "pagepermisson": 1,
                "grade": 2
            }
        ]
    },
    {
        "id": 24,
        "title": "发布管理",
        "key": "/publish-manage",
        "pagepermisson": 1,
        "grade": 1,
        "children": [
            {
                "id": 25,
                "title": "待发布",
                "rightId": 24,
                "key": "/publish-manage/unpublished",
                "pagepermisson": 1,
                "grade": 2
            }
        ]
    }
]

第二步:处理获取到的路由数据

特别注意:useEffect中直接使用async/await会报错

解释

  • useEffect中的第一个回调参数返回的是一个clean-up函数,所以不能返回promise对象,更不能直接使用async/await,否则会报错;
  • 可以在回调参数中使用async/await:
useEffect(()=>{
	const fn=async ()=>{
		// do something
		await otherFn()
	}
	fn()
},[])

处理获取到的路由数据处理函数

useEffect(() => {
    async function fetchData() {
      const menusListData = await fetchGetMenus(); 
      let tempItems = [],
      rootSubmenuKeys  = [];  // submenu keys of first level

      // 注意此处 
      menusListData.forEach((item) => { 
        item.key !== "/login" && rootSubmenuKeys.push(item.key)
        item.key !== "/login" &&
          item.pagepermisson === 1 &&
          tempItems.push({
            label: item.title,
            key: item.key,
            icon: iconList[item.key],
            children:
              item.children &&
              item.children.length > 0 &&
              item.children.map((child) => {
                if (child.pagepermisson === 1) {
                  return {
                    label: child.title,
                    key: child.key,
                    icon: iconList[item.key],
                    children:
                      child.children &&
                      child.children.length > 0 &&
                      child.children.map((sun) => {
                        if (child.pagepermisson === 1) {
                          return {
                            label: sun.title,
                            key: sun.key,
                            icon: iconList[item.key],
                          };
                        }
                      }),
                  };
                }
              }),
          });
      });

      setitems(tempItems);  
    }

    fetchData();
  }, []);

第三步:利用antd提供的菜单menu渲染

<Sider trigger={null} collapsible collapsed={collapsed}>
  <div className={styles.logo}> {siteBaseConfig.siteName} </div> 
  <Menu
    theme="dark"
    mode="inline"
    defaultSelectedKeys={defaultSelectedKeys} //  初始选中的菜单项 key 数组  string[]
    defaultOpenKeys={defaultOpenKeys} // 初始展开的 SubMenu 菜单项 key 数组
    openKeys={openKeys} // 当前展开的 SubMenu 菜单项 key 数组
    onOpenChange={onOpenChange} // SubMenu 展开/关闭的回调
    onClick={onClick}
    style={{
      height: "100%",
      borderRight: 0,
    }}
    items={items}
  ></Menu>
</Sider>

小细节

使用icon – iconList

这里,定义了一组导航icon数据,根据路径,使用不同的icon

“./iconList”

// "./iconList"
import {
    UserOutlined,
    LaptopOutlined,
    NotificationOutlined,
  } from "@ant-design/icons";
  
export const iconList = {
  "/home": <UserOutlined/>,
  "/user-manage": <UserOutlined/>, 
  // 权限管理
  "/right-manage": <UserOutlined/>, 
  // 新闻管理
  "/news-manage": <UserOutli 
  ... 
}

SideMenu.js

// SideMenu.js
import { iconList } from "./iconList";  

// 使用
icon: iconList[item.key],

初始选中的菜单项

defaultSelectedKeys --初始选中的菜单项 key 数组 string[]

defaultOpenKeys={defaultOpenKeys} // 初始展开的 SubMenu 菜单项 key 数组

// defaultSelectedKeys 初始选中的菜单项 key 数组
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState([]); 

// defaultSelectedKeys 初始选中的菜单项 key 数组
const [defaultSelectedKeys, setDefaultSelectedKeys] = useState([]); 

useEffect(() => {
  const routes = matchRoutes(routers, location.pathname); // 返回匹配到的路由数组对象,每一个对象都是一个路由对象
  const pathArr = [];
  if (routes !== null) {
    routes.forEach((item) => {
      const path = item.pathname;
      if (path) {
        pathArr.push(path);
      }
    });
  }
  setDefaultSelectedKeys(pathArr);
  setDefaultOpenKeys(pathArr); 
}, [location.pathname]);

当前展开的 SubMenu 菜单项

openKeys 当前展开的 SubMenu 菜单项 key 数组

onOpenChange SubMenu 展开/关闭的回调

// submenu keys of first level
const [rootSubmenuKeys , setrootSubmenuKeys ] = useState([]) 
// openKeys 当前展开的 SubMenu 菜单项 key 数组
const [openKeys, setOpenKeys] = useState([]);

const onOpenChange = (keys) => { 
  const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1); 
  
  if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
    setOpenKeys(keys);
  } else {
    setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
  }
};

全部代码

import { useEffect, useState } from "react";
import { Layout, Menu } from "antd"; 
import { matchRoutes,   useLocation,  useNavigate } from "react-router-dom";

import { routers } from "../../router"; 
import styles from "./SideMenu.module.css"; 
import siteBaseConfig from "../../config"; 

// 接口请求
import { fetchGetMenus } from "../../utils/api"; 
import { iconList } from "./iconList";   

const { Sider } = Layout;

export default function AppLayout() {
  const location = useLocation();
  const navigate = useNavigate(); 

  const [isInit, setIsInit] = useState(false);
  const [collapsed, setCollapsed] = useState(false); 

  // items 菜单内容	ItemType[]
  const [items, setitems] = useState([]); 
  // defaultSelectedKeys 初始选中的菜单项 key 数组
  const [defaultSelectedKeys, setDefaultSelectedKeys] = useState([]); 
  // defaultOpenKeys 初始展开的 SubMenu 菜单项 key 数组
  const [defaultOpenKeys, setDefaultOpenKeys] = useState([]);
  // submenu keys of first level
  const [rootSubmenuKeys , setrootSubmenuKeys ] = useState([]) 
  // openKeys 当前展开的 SubMenu 菜单项 key 数组
  const [openKeys, setOpenKeys] = useState([]);
  
  useEffect(() => {
    async function fetchData() {
      const menusListData = await fetchGetMenus(); 
      let tempItems = [],
      rootSubmenuKeys  = [];  // submenu keys of first level
      menusListData.forEach((item) => { 
        item.key !== "/login" && rootSubmenuKeys.push(item.key)
        item.key !== "/login" &&
          item.pagepermisson === 1 &&
          tempItems.push({
            label: item.title,
            key: item.key,
            icon: iconList[item.key],
            children:
              item.children &&
              item.children.length > 0 &&
              item.children.map((child) => {
                if (child.pagepermisson === 1) {
                  return {
                    label: child.title,
                    key: child.key,
                    icon: iconList[item.key],
                    children:
                      child.children &&
                      child.children.length > 0 &&
                      child.children.map((sun) => {
                        if (child.pagepermisson === 1) {
                          return {
                            label: sun.title,
                            key: sun.key,
                            icon: iconList[item.key],
                          };
                        }
                      }),
                  };
                }
              }),
          });
      });

      setitems(tempItems); 
      setrootSubmenuKeys(rootSubmenuKeys) 
    }

    fetchData();
  }, []);

  useEffect(() => {
    const routes = matchRoutes(routers, location.pathname); // 返回匹配到的路由数组对象,每一个对象都是一个路由对象
    const pathArr = [];
    if (routes !== null) {
      routes.forEach((item) => {
        const path = item.pathname;
        if (path) {
          pathArr.push(path);
        }
      });
    }
    setDefaultSelectedKeys(pathArr);
    setDefaultOpenKeys(pathArr);
    setIsInit(true);
  }, [location.pathname]);

  if (!isInit) {
    return null;
  }

  const onClick = (e) => { 
    navigate(e.key);
  };
  
  const onOpenChange = (keys) => { 
    const latestOpenKey = keys.find((key) => openKeys.indexOf(key) === -1); 
    
    if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  };

  return (
    <>   
      <Sider trigger={null} collapsible collapsed={collapsed}>
        <div className={styles.logo}> {siteBaseConfig.siteName} </div> 
        <Menu
          theme="dark"
          mode="inline"
          defaultSelectedKeys={defaultSelectedKeys} //  初始选中的菜单项 key 数组  string[]
          defaultOpenKeys={defaultOpenKeys} // 初始展开的 SubMenu 菜单项 key 数组
          openKeys={openKeys} // openKeys  当前展开的 SubMenu 菜单项 key 数组
          onOpenChange={onOpenChange} //onOpenChange SubMenu 展开/关闭的回调
          onClick={onClick}
          style={{
            height: "100%",
            borderRight: 0,
          }}
          items={items}
        ></Menu>
      </Sider>
    </>
  );
}

展示结果

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端布道人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值