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 react-router@7.0.0 -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 />;
}
相关推荐
hackchen8 分钟前
从0到1解锁Element-Plus组件二次封装El-Dialog动态调用
前端·vue.js·elementui
君子宜耘心8 分钟前
el-table虚拟列表封装
前端
黄瓜沾糖吃11 分钟前
大佬们指点一下倒计时有什么问题吗?
前端·javascript
温轻舟12 分钟前
3D词云图
前端·javascript·3d·交互·词云图·温轻舟
buibui12 分钟前
打包一个工具类
前端
巴别塔的饿灵14 分钟前
Webpack[TBC]
前端
LinHan15 分钟前
我的浏览器插件 Horizon-Hop 又又又更新啦!
前端
一个小潘桃鸭15 分钟前
需求:el-upload实现上传/粘贴图片功能
前端
胡清波16 分钟前
# vue 的 Diff 算法
前端·面试
浩龙不eMo17 分钟前
✅ Lodash 常用函数精选(按用途分类)
前端·javascript