【React】Antd的Menu菜单详细使用方法

【React】React代码中Antd的Menu菜单使用方法

这里需要注意的是:antd的Menu菜单尽可能使用items的格式来渲染,最好不要用自己的SubMenu以及Menu.Item来做自定义渲染,否则控制台会报红色警告的 :
[antd: Menu] children is deprecated. Please use items instead.

Menu的数据结构List

const MenuItems = [
  {
    key: 'manage',
    label: '管理',
    type: 'group',
  },
  {
    key: 'management-1',
    path: '/management/page', // 页面路由
    label: '管理1',
    icon: '' , // 设置你的icon
  },
  {
    key: 'project',
    label: '项目管理',
    icon: '' , // 设置你的icon
    children: [
      { key: 'project-management', path: '/project/page', label: '项目管理' },
      { key: 'create', path: '/create/page', label: '创建' },
    ],
  },
 
]

Menu菜单的TS类型定义:

interface MenuItem {
  key: string
  label: React.ReactNode
  path?: string
  icon?: React.ReactNode
  children?: MenuItem[]
  type?: string
}

下面是完整代码

export default function Sidebar(props: { menuItems: MenuItem[] }) {
  const { menuItems } = props
  const location = useLocation()
  const [isCollapsed, setIsCollapsed] = useState(window.innerWidth < 1024 ? true : false)
  const [openKeys, setOpenKeys] = useState<string[]>([])
  const [selectedKeys, setSelectedKeys] = useState<string[]>([])
  const navigate = useNavigate()

  const handleCollapse = useCallback(() => {
    setIsCollapsed(!isCollapsed)
  }, [isCollapsed])

  // 监听外部窗口宽度的变化,如果小于 1024px,自动收起侧边栏
  useEffect(() => {
    const handleResize = () => {
      if (window.innerWidth < 1024) {
        setIsCollapsed(true)
      } else {
        setIsCollapsed(false)
      }
    }

    window.addEventListener('resize', handleResize)

    return () => {
      window.removeEventListener('resize', handleResize)
    }
  }, [])

  useEffect(() => {
    const currentKey = getKeyFromPath(location.pathname, menuItems)
    const parentKey = findParentKey(location.pathname, menuItems)
    // 如果找到了对应的 key,则将其设置为选中项和展开项
    setSelectedKeys(currentKey ? [currentKey] : [])
    setOpenKeys(parentKey ? [parentKey] : [])
  }, [location.pathname, menuItems])

  // 菜单的点击事件
  const onMenuClick: MenuProps['onClick'] = (e) => {
    const menuItem = menuItems.find((item) => item.key === e.key)
    if (menuItem && menuItem.path) {
      navigate(menuItem.path)
    }
  }

  return (
    <div
      className={clsx(
        'sidebar-menu duration-400 group relative hidden flex-col gap-1 bg-[#F8F8FC] p-2 transition-all dark:border-slate-700 dark:bg-slate-800 md:flex',
        {
          'w-[208px]': !isCollapsed,
          'w-[70px]': isCollapsed,
        },
      )}
    >
      <Menu
        theme={'light'}
        onClick={onMenuClick}
        defaultSelectedKeys={selectedKeys}
        mode="inline"
        selectedKeys={selectedKeys}
        inlineCollapsed={isCollapsed}
        items={menuItems.filter((item) => !isCollapsed || (isCollapsed && item.type !== 'group'))} // 过滤掉 'group' 类型的菜单项
        openKeys={openKeys}
        onOpenChange={(keys) => setOpenKeys(keys)}
        className="mb-4 dark:bg-slate-800"
      />

      <div
        className={clsx('absolute bottom-0 flex h-14 cursor-pointer select-none items-center gap-2 dark:bg-gray-700 ', {
          'w-[190px] px-4': !isCollapsed,
          'w-[54px] justify-center  px-2': isCollapsed,
        })}
        onClick={handleCollapse}
      >
        <LeftIcon
          className={clsx('h-5 w-5 text-slate-400 transition-all', {
            'rotate-180 transform': isCollapsed,
          })}
        />
        {!isCollapsed && <div className="whitespace-nowrap">收起侧边栏</div>}
      </div>
    </div>
  )
}

其中有调用两个方法获取当前的key和上一级的key,用于处理selectedKeys和openKeys

/**
 * menu菜单 - 根据当前页面路径找到对应的菜单项的 key
 * @param path
 * @param items
 * @returns
 */
export const getKeyFromPath = (path: string, menuItems: MenuItem[]): string | null => {
  const menuItem = menuItems.find((item) => item.path === path)
  if (menuItem) {
    return menuItem.key
  }
  for (const item of menuItems) {
    if (item.children) {
      const foundKey = getKeyFromPath(path, item.children)
      if (foundKey) {
        return foundKey
      }
    }
  }
  return null
}

/**
 * menu菜单 - 根据path寻找上一级的key
 * @param path
 * @param menuItems
 * @returns
 */
export const findParentKey = (path: string, menuItems: MenuItem[]): string | null => {
  for (const item of menuItems) {
    if ((item.children || []).some((childItem) => childItem.path === path)) {
      return item.key // 找到了匹配的路径,返回父级的 key
    }
  }
  return null // 如果未找到匹配的菜单项,返回 null
}

最后就搞定啦 ~~~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是基于Ant Design Pro的动态菜单生成的完整方法附代码,可以实现左侧多级菜单: ```javascript import React from 'react';import { Menu, Icon } from 'antd'; import { Link } from 'dva/router'; import { getMenuData } from '../common/menu'; const { SubMenu } = Menu; export default class SiderMenu extends React.Component { constructor(props) { super(props); this.menus = getMenuData(); this.state = { openKeys: this.getDefaultCollapsedSubMenus(props), }; } componentWillReceiveProps(nextProps) { const { pathname } = nextProps.location; if (this.props.location.pathname !== pathname) { this.setState({ openKeys: this.getDefaultCollapsedSubMenus(nextProps), }); } } /** * Convert pathname to openKeys * /list/search/articles = > ['list','/list/search'] * @param props */ getDefaultCollapsedSubMenus(props) { const { location: { pathname } } = props || this.props; // eg. /list/search/articles = > ['','list','search','articles'] const snippets = pathname.split('/').slice(1, -1); const currentPathSnippets = snippets.map((item, index) => { const arr = snippets.slice(0, index + 1); return `/${arr.join('/')}`; }); let currentMenuSelectedKeys = []; currentPathSnippets.forEach((item) => { currentMenuSelectedKeys = currentMenuSelectedKeys.concat(this.getSelectedMenuKeys(item)); }); if (currentMenuSelectedKeys.length === 0) { return ['dashboard']; } return currentMenuSelectedKeys; } getFlatMenuKeys(menus) { let keys = []; menus.forEach((item) => { if (item.children) { keys = keys.concat(this.getFlatMenuKeys(item.children)); } keys.push(item.path); }); return keys; } getSelectedMenuKeys = (path) => { const flatMenuKeys = this.getFlatMenuKeys(this.menus); const key = flatMenuKeys.filter(item => (item && path.indexOf(item) === 0)); return key; } isMainMenu = (key) => { const { menus } = this; return menus.some(item => key && (item.key === key || item.path === key)); } handleOpenChange = (openKeys) => { const lastOpenKey = openKeys[openKeys.length - 1]; const moreThanOne = openKeys.filter(openKey => this.isMainMenu(openKey)).length > 1; this.setState({ openKeys: moreThanOne ? [lastOpenKey] : [...openKeys], }); } render() { const { logo, collapsed, onCollapse } = this.props; const { openKeys } = this.state; // Don't show popup menu when it is been collapsed const menuProps = collapsed ? {} : { openKeys, }; // if pathname can't match, use the nearest parent's key let selectedKeys = this.getSelectedMenuKeys(this.props.location.pathname); if (!selectedKeys.length) { selectedKeys = [openKeys[openKeys.length - 1]]; } return ( <div> <div className="logo" id="logo"> <Link to="/"> <img src={logo} alt="logo" /> <h1>Ant Design Pro</h1> </Link> </div> <Menu key="Menu" theme="dark" mode="inline" {...menuProps} onOpenChange={this.handleOpenChange} selectedKeys={selectedKeys} style={{ padding: '16px 0', width: '100%' }} > {this.menus .filter(item => !item.hideInMenu) .map((item) => { if (item.children && item.children.some(child => !child.hideInMenu)) { return ( <SubMenu key={item.key} title={<span><Icon type={item.icon} /><span>{item.name}</span></span>} > {item.children.map(child => child.hideInMenu ? null : ( <Menu.Item key={child.key}> <Link to={child.path}> <span>{child.name}</span> </Link> </Menu.Item> ))} </SubMenu> ); } return ( <Menu.Item key={item.key}> <Link to={item.path}> <Icon type={item.icon} /> <span>{item.name}</span> </Link> </Menu.Item> ); })} </Menu> </div> ); } } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

郭_昊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值