如何使用 react + antd 实现后台管理系统的二级菜单

1,869 阅读3分钟

一、效果介绍

话不多说,咱们先来上一张图看看效果

img

具体效果展示

二、准备工作

1、创建项目

npx create-react-app my-react-admin

2、安装需要的依赖

npm i antd -S

3、创建项目基本结构

my-react-admin
	public
	src
		api
			banner.js
			nav.js
			pro.js
			user.js
		components // 公共组件
		layout // 布局结构
			main // 主界面结构
				Breadcrumb.jsx
				Index.jsx
				MainHeader.jsx
				SideMenu.jsx
		router // 路由相关
			menus.js
			RedirectRouterView.jsx
			RouterView.jsx
		store // 状态管理器
			actionCreators
			modules
				common.js
			actionTypes.js
			index.js
		utils // 工具
			request.js
		views // 页面
		App.jsx // 主界面
		index.css // 样式
		index.js // 入口文件
	config-overrides.js // 配置装饰器语法
	package.json // 描述文件

4.设计主界面

import React from 'react'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Main from './layout/main/Index'
const App = () => {
  return (
    <Router >
      <Switch>
        <Route path="/" component = { Main } />
      </Switch>
    </Router>
  )
}

export default App

5.设计主布局页面

结合UI库的Layout组件 layout/main/Index.jsx

import React from 'react'
import { Layout } from 'antd';
import { connect } from 'react-redux'
import logo from './../../logo.svg'
import SideMenu from './SideMenu'
import RouterView from './../../router/RouterView'
import MainHeader from './MainHeader';
const { Sider, Content } = Layout;

@connect(state => {
  return {
    collapsed: state.getIn(['common', 'collapsed'])
  }
})
class Index extends React.Component {
  
  render() {
    const { collapsed } = this.props
    console.log('11', collapsed)
    return (
      <Layout>
        <Sider trigger={null} collapsible collapsed={collapsed}>
          <div className="logo">
            <img src={ logo } style={{width: '32px', height: '32px', margin: '0 10px 0 0'}} alt=""/>
            { collapsed ? null : <span>JD_ADMIN_PRO</span>  }
          </div>
          <SideMenu/>
        </Sider>
        <Layout className="site-layout">
          {/* <Header className="site-layout-background" style={{ padding: 0 }}>
            {React.createElement(this.state.collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
              className: 'trigger',
              onClick: this.toggle,
            })}
          </Header> */}
          <MainHeader />
          <Content
            className="site-layout-background"
            style={{
              margin: '24px 16px',
              padding: 24,
              minHeight: 280,
              position: 'relative'
            }}
          >
            <RouterView />
          </Content>
        </Layout>
      </Layout>
    );
  }
}

export default Index

6.设计路由

router/menus.js

import React, { lazy } from 'react'
import {
  HomeOutlined,
  PictureOutlined,
  MenuOutlined,
  PicLeftOutlined,
  SwapLeftOutlined,
  BorderTopOutlined,
  ClockCircleOutlined,
  UserOutlined,
  AppstoreOutlined
} from '@ant-design/icons'
const menus = [
  { 
    path: '/',
    redirect: '/home',
    meta: { // 该路由不出现在左侧菜单栏
      hidden: true
    }
  },
  {
    path: '/home',
    title: '系统首页',
    icon: <HomeOutlined />,
    component: lazy(() => import('./../views/home/Index'))
  },
  {
    path: '/bannermanager',
    title: '轮播图管理',
    icon: <PictureOutlined />,
    redirect: '/bannermanager/list',
    children: [
      {
        path: '/bannermanager/list',
        title: '轮播图列表',
        icon: <MenuOutlined />,
        component: lazy(() => import('./../views/banner/Index'))
      },
      {
        path: '/bannermanager/add',
        title: '添加轮播图',
        icon: <MenuOutlined />,
        component: lazy(() => import('./../views/banner/Add')),
        meta: {
          hidden: true
        }
      }
    ]
  },
  {
    path: '/navigatormanager',
    title: '快捷导航管理',
    icon: <PicLeftOutlined />,
    redirect: '/navigatormanager/list',
    children: [
      {
        path: '/navigatormanager/list',
        title: '导航列表',
        icon: <MenuOutlined />,
        component: lazy(() => import('./../views/navigator/List'))
      },
      {
        path: '/navigatormanager/category',
        title: '导航分类',
        icon: <SwapLeftOutlined />,
        component: lazy(() => import('./../views/navigator/Category'))
      },
      {
        path: '/navigatormanager/hlist',
        title: '首页导航',
        icon: <BorderTopOutlined />,
        component: lazy(() => import('./../views/navigator/HomeList'))
      }
    ]
  },
  {
    path: '/hmanager',
    title: '首页数据管理',
    icon: <ClockCircleOutlined />,
    redirect: '/hmanager/seckilllist',
    children: [
      {
        path: '/hmanager/seckilllist',
        title: '首页秒杀列表',
        icon: <MenuOutlined />,
        component: lazy(() => import('../views/homedata/SeckillList'))
      },
      {
        path: '/hmanager/recommentlist',
        title: '首页推荐列表',
        icon: <MenuOutlined />,
        component: lazy(() => import('../views/homedata/RecommentList'))
      }
    ]
  },
  {
    path: '/usermanager',
    title: '用户管理',
    icon: <UserOutlined />,
    redirect: '/usermanager/list',
    children: [
      {
        path: '/usermanager/list',
        title: '用户列表',
        icon: <MenuOutlined />,
        component: lazy(() => import('./../views/user/List'))
      },
      {
        path: '/usermanager/register',
        title: '注册用户',
        icon: <MenuOutlined />,
        component: lazy(() => import('./../views/user/RegisterUser'))
      },
    ]
  },
  {
    path: '/productmanager',
    title: '商品管理',
    icon: <AppstoreOutlined />,
    redirect: '/productmanager/list',
    children: [
      {
        path: '/productmanager/list',
        title: '商品列表',
        icon: <MenuOutlined />,
        component: lazy(() => import('./../views/product/List'))
      },
      {
        path: '/productmanager/sortlist',
        title: '筛选商品',
        icon: <MenuOutlined />,
        component: lazy(() => import('../views/product/SortList'))
      },
    ]
  },
  {
    path: '/setting',
    title: '设置',
    icon: <MenuOutlined />,
    component: lazy(() => import('../views/setting/Index')),
    meta: { // 该路由不出现在左侧菜单栏
      hidden: true
    }
  }
]

export default  menus

7.设计路由渲染组件

router/RouterView.jsx

import React, { Suspense } from 'react'
import { Spin } from 'antd'
import { Switch, Route } from 'react-router-dom'
import RedirectRouterView from './RedirectRouterView'
import menus from './menus'
function RouterView() {
  const renderRoute = (menus) => {
    return menus.map(item => {
      if (item.children) {
        return renderRoute(item.children)
      } else {
        return item.path === '/' ? null : <Route
          path={ item.path }
          key={item.path}
          exact
          component = { item.component } />
        }
    })
  }
  return (
    <Suspense fallback={<div className="loading"><Spin size="large" /></div>} >
      <Switch>
        {/* <Redirect path="/" exact to="/home" /> */}
        {
          renderRoute(menus)
        }
        {/* <Route path="/" exact component = { lazy(() => import('../../views/home/Index'))} /> */}
        <RedirectRouterView />
        {/* <Redirect path="/bannermanager" to="/bannermanager/list" />
        <Redirect path="/navigatormanager" to="/navigatormanager/list" />
        <Redirect path="/seckillmanager" to="/seckillmanager/list" />
        <Redirect path="/usermanager" to="/usermanager/list" /> */}
      </Switch>
    </Suspense>
)
}

export default RouterView

8.设计左侧菜单渲染组件

Layout/main/SideMenu.jsx

import React, { useState, useEffect } from 'react'
import { Menu } from 'antd';
import { withRouter, useHistory, useLocation } from 'react-router-dom'
import menus from './../../router/menus'
const { SubMenu } = Menu
const rootSubmenuKeys = []
menus.forEach(item => {
  item.children && rootSubmenuKeys.push(item.path)
})
const SideMenu = withRouter((props) => {
  const [openKeys, setOpenKeys ] = useState([])
  const [selectedKeys, setSelectedKeys ] = useState([])
  const history = useHistory()
  const renderMenu = (menus) => {
    return (
      <>
        {
          menus.map( item => {
            if (item.children) { // 有多级菜单
              return (
                <SubMenu key={item.path} icon={item.icon} title={item.title}>
                  {
                    renderMenu(item.children)
                  }
                </SubMenu>
              )
            } else { // 只有一级菜单
              // 此处判断要不要在侧边栏出现该路由
              return  item.meta && item.meta.hidden ? null : <Menu.Item key={item.path} icon={ item.icon }>
              { item.title }
            </Menu.Item>
            }
          })
        }
      </>
    )
  }
  const goPage = ({ key }) => {
    history.push(key)
  }

  const onOpenChange = keys => { // keys [] ,包含上一个和点击之后的那一个
    console.log(keys) //  ["/bannermanager", "/navigatormanager"]
    const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
    console.log(latestOpenKey) // 当前的这一个 /navigatormanager
    if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  }
  // 为了 显示当前左侧菜单选中的 状态  ----  string[ key ] key 就是path
  // defaultSelectedKeys
  // defaultOpenKeys
  const { pathname } = useLocation() // '/usermanager/list'
  const type = '/' + pathname.split('/')[1]

  useEffect(() => {
    setOpenKeys([type])
    setSelectedKeys([pathname])
  }, [pathname])
  return (
    <Menu 
      theme="dark"
      mode="inline"
      defaultSelectedKeys={[pathname]} // {['/usermanager/list']} 数组
      defaultOpenKeys={[type]}  // {['/usermanager‘]} 数组
      openKeys = { openKeys }
      selectedKeys = { selectedKeys }
      onClick={ goPage } 
      onOpenChange = { onOpenChange }
    >
      {
        // 方便做多级菜单 --- 递归的设计思想
        renderMenu(menus)
      }
    </Menu>
  )
})

export default SideMenu

9.创建对应的各个页面

views/home/Index.jsx views/banner/Index.jsx views/banner/Add.jsx views/homedata/SeckillList.jsx views/homedata/RecommendList.jsx views/navigator/Category.jsx views/navigator/HomeList.jsx views/navigator/List.jsx views/product/List.jsx views/product/SortList.jsx views/user/List.jsx views/user/RegisterUser.jsx views/setting/Index.jsx

三、完结

如果还想要查看后续的代码,可以参考百度网盘地址:

链接: pan.baidu.com/s/1d0f3Z_y2… 密码: 8loq