React menu 菜单栏

react-menu:React menu 菜单栏,基于 antd 的 menu 组件和 react-router

1. 代码仓库

github.com/chuxin-cs/r...

2. 主线任务

1、目录结构

2、下载项目依赖

tsx 复制代码
pnpm add antd @ant-design/icons [email protected] -S

2.1. V1版本

先实现基本功能,点击菜单能跳转即可

1、layouts/index.tsx

tsx 复制代码
import { Outlet } from 'react-router';
import MenuLayout from './v1';
import { Layout, Card } from 'antd';
const { Sider, Content } = Layout;

export default function Layouts() {
  return (
    <Layout style={{ minHeight: '100vh' }}>
      <Sider width={256} theme='dark'>
        <MenuLayout />
      </Sider>
      <Content>
        <Card>
          <Outlet />
        </Card>
      </Content>
    </Layout>
  );
}

2、layouts/v1.tsx

tsx 复制代码
import React from 'react';
import {useNavigate} from "react-router"
import { Menu, type MenuProps } from 'antd';
import { AppstoreOutlined } from '@ant-design/icons';
type MenuItem = Required<MenuProps>['items'][number];
const items: MenuItem[] = [
  {
    key: '1',
    label: 'V1版本',
    icon: <AppstoreOutlined />,
    children: [
      { key: 'home', label: 'home' },
      { key: 'about', label: 'about' },
    ],
  },
  {
    key: '2',
    label: '公开路由',
    icon: <AppstoreOutlined />,
    children: [
      { key: 'login', label: 'login' },
    ],
  },
];
export const MenuLayout: React.RC = () => {
  const navigate = useNavigate()
  const onClick: MenuProps['onClick'] = (e) => {
    console.log(e);
    navigate(e.key)
  };
  return (
    <Menu
      onClick={onClick}
      style={{ width: 256 }}
      defaultSelectedKeys={['1']}
      defaultOpenKeys={['1']}
      mode='inline'
      items={items}
    />
  );
};
export default MenuLayout;

2.2. V2版本

完善 routes 中的 mate 属性,menu 数据走动态读取

1、layouts/v2.tsx

tsx 复制代码
import { useNavigate } from 'react-router';
import { Menu, type MenuProps } from 'antd';
import React, { useMemo } from 'react';
import { AppstoreOutlined } from '@ant-design/icons';
import {
  getRoutesFromModules,
  menuFilter,
  useRouteToMenuFn,
} from '@/router/utils';
export const MenuLayout: React.RC = () => {
  const routes = getRoutesFromModules();
  const routeToMenuFn = useRouteToMenuFn();
  const menuList = useMemo(() => {
    const menuRoutes = menuFilter(routes);
    return routeToMenuFn(menuRoutes);
  }, [routeToMenuFn]);
  const navigate = useNavigate();
  const onClick: MenuProps['onClick'] = (e) => {
    console.log(e);
    navigate(e.key);
  };
  return (
    <Menu
      onClick={onClick}
      style={{ width: 256 }}
      defaultSelectedKeys={['1']}
      defaultOpenKeys={['1']}
      mode='inline'
      items={menuList}
    />
  );
};
export default MenuLayout;

2、router/utils.tsx

tsx 复制代码
import { useCallback } from 'react';
export const menuFilter = (list) => {
  return list.filter((item) => {
    const key = item.meta?.key;
    if ((key && item.children) || item.children?.length) {
      item.children = menuFilter(item.children);
    }
    return key;
  });
};

// 基于 src/router/routes.tsx 文件结构动态生成路由
export function getRoutesFromModules() {
  const menuModules = [];
  const modules = import.meta.glob('./routes/**/*.tsx', { eager: true });
  for (const key in modules) {
    const mod = (modules as any)[key].default || {};
    const modList = Array.isArray(mod) ? [...mod] : [mod];
    menuModules.push(...modList);
  }
  return menuModules;
}
import * as Icons from '@ant-design/icons';
const getIcon = (iconName) => {
  const Icon = Icons[iconName];
  return <Icon />;
};
export function useRouteToMenuFn() {
  const routeToMenuFn = useCallback((items) => {
    return items.map((item) => {
      const { meta, children } = item;
      // 如果没有meta,直接返回空对象
      if (!meta) return {};
      const menuItem = {
        key: meta.key, // antd menu 需要的 key
        disabled: meta.disabled,
        // 这里可以写成函数 然后国际化在这个函数中处理
        label: meta.label, // antd menu 需要的 label
        icon: getIcon(meta.icon),
        ...(children && { children: routeToMenuFn(children) }),
      };
      return menuItem;
    });
  }, []);
  return routeToMenuFn;
}

3. 支线任务

3.1. router

1、router/index.tsx:

tsx 复制代码
import { RouterProvider } from 'react-router/dom';
import { createBrowserRouter, Navigate } from 'react-router';
import Layouts from '@/layouts';
import { publicConfig } from './routes/public';
import { dashboardConfig } from './routes/dashboard';
const Router = () => {
  const routes = [
    {
      path: '/',
      element: <Layouts></Layouts>,
      children: [
        { index: true, element: <Navigate to='dashboard/home' replace /> },
        ...dashboardConfig,
      ],
    },
  ];
  const router = createBrowserRouter([
    ...routes,
    ...publicConfig,
    { path: '*', element: <Navigate to='/login' replace /> },
  ]);
  return <RouterProvider router={router} />;
};
export default Router;

2、router/routes/dashboard.tsx:

tsx 复制代码
import Home from '@/pages/home';
import About from '@/pages/about';
export const dashboardConfig = [
  {
    order: 1,
    path: 'dashboard',
    meta: {
      label: 'V2 版本',
      key: '/dashboard',
      icon: 'AppstoreOutlined',
    },
    children: [
      {
        path: 'home',
        element: <Home />,
        meta: {
          label: '首页',
          key: '/dashboard/home',
          icon: 'AppstoreOutlined',
        },
      },
      {
        path: 'about',
        element: <About />,
        meta: {
          label: '关于',
          key: '/dashboard/about',
          icon: 'AppstoreOutlined',
        },
      },
    ],
  },
];
export default dashboardConfig;

3、router/routes/public.tsx:

tsx 复制代码
import Login from '@/pages/login';
export const publicConfig = [
  {
    order: 2,
    path: 'public',
    meta: {
      label: '公开路由',
      key: '/public',
      icon: 'AppstoreOutlined',
    },
    children: [
      {
        path: 'login',
        element: <Login />,
        meta: {
          label: '首页',
          key: '/public/login',
          icon: 'AppstoreOutlined',
        },
      }
    ],
  },
];
export default publicConfig;

4、pages目录下页面:

tsx 复制代码
// home/index.tsx
import React from 'react';
export const Home: React.RC = () => {
  return <h1>home</h1>;
};
export default Home;

// about/index.tsx
import React from 'react';
export const About: React.RC = () => {
  return <h1>about</h1>;
};
export default About;

// login/index.tsx
import React from 'react';
export const Login: React.RC = () => {
  return <h1>登录页面</h1>
};
export default Login;

5、App.tsx

tsx 复制代码
import Router from './router';

export default function App() {
  return <Router />;
}
相关推荐
美团技术团队3 分钟前
无需代码!美团 NoCode 像聊天一样轻松搭建你的专属网站
前端
一只会跑会跳会发疯的猴子19 分钟前
ajax访问阿里云天气接口,获取7天天气
前端·ajax·okhttp
利刃大大21 分钟前
【在线五子棋对战】五、前端扫盲:html && css && javascript && ajax && jquery && websocket
前端·javascript·html
石小石Orz28 分钟前
被谷歌插件劝退,我半小时学会了油猴脚本开发
前端
安心不心安42 分钟前
React状态管理——zustand
javascript·react.js·ecmascript
阁下何不同风起?1 小时前
前端导出PDF(适配ios Safari浏览器)
前端·pdf
zhangxingchao1 小时前
Flutter网络编程与数据存储技术
前端
啪叽1 小时前
探索鲜为人知的浏览器API:document.currentScript的实用案例
前端·javascript·dom