react后台管理系统(一)_react.js

🌈个人主页:前端青山 🔥系列专栏:React篇
🔖人终将被年少不可得之物困其一生

依旧青山,本期给大家带来React篇专栏内容:react后台管理系统(一)

前言

本文档详细介绍了如何从零开始搭建一个基于 React 和 Ant Design 的前端项目。通过逐步引导,读者可以了解从项目初始化到配置装饰器、集成 UI 库、设计布局结构、配置路由以及实现左侧菜单栏的完整流程。本文档适合初学者和有一定经验的开发者,帮助他们在实际项目中快速上手。

目录

前言

1.创建项目

2.配置装饰器(但不是必须)

3.配置UI库

4.修改目录结构

5.搭建项目的主结构

6.设计左侧菜单栏

7.左侧菜单

总结

1.创建项目

代码解读
npx create-react-app react-pro
  • 1.

2.配置装饰器(但不是必须)

安装模块

代码解读
cnpm i @babel/plugin-proposal-decorators customize-cra react-app-rewired -D
yarn add @babel/plugin-proposal-decorators customize-cra react-app-rewired -D
  • 1.
  • 2.

根目录下创建config-overrides.js

代码解读
// 参考配置连接:https://www.npmjs.com/package/customize-cra
const {override,addDecoratorsLegacy}=require("customize-cra");

module.exports=override(
    addDecoratorsLegacy(),//配置装饰器模式
);
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

修改package.json运行命令

代码解读
...,
"scripts": {
    "start": "react-app-rewired start", // update
    "build": "react-app-rewired build", // update
    "test": "react-app-rewired test",  // update
    "eject": "react-scripts eject"
},
...
import { connect } from 'react-redux'
class App extends Component {
  render () {
    return ()
  }
}
export default connect(mapStateToProps, mapDispatchToProps)(App)

==装饰器(只能用在类组件中,可以装饰类以及类的方法)==>
import { connect } from 'react-redux'

@connect(mapStateToProps, mapDispatchToProps)
class App extends Component {
  render () {
    return ()
  }
}

export default App
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

3.配置UI库

 Ant Design - 一套企业级 UI 设计语言和 React 组件库 查看文档

 在 create-react-app 中使用 - Ant Design 查看具体的使用文档

代码解读
cnpm i antd -S 
yarn add antd -S
  • 1.
  • 2.

删除src文件内容,添加index.css 以及 index.js

index.css

代码解读
@import '~antd/dist/antd.css';
  • 1.

index.js

代码解读
import React from 'react'
import ReactDOM from 'react-dom'

import App from './App'
import './index.css'
ReactDOM.render(
  <App />,
  document.querySelector('#root')
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

测试UI库 App.jsx

代码解读
import React from 'react'
import { Button } from 'antd'
const App = () => {
  return (
    <>
      <Button  type="primary">hello</Button>
    </>
  )
}

export default App
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

4.修改目录结构

代码解读
src
    api
    components
    layout
    router
    store
    utils
    views
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

5.搭建项目的主结构

查看Layout 组件,修改App.jsx

代码解读
import React from 'react'
import { Layout, Menu } from 'antd';
import {
  MenuUnfoldOutlined,
  MenuFoldOutlined,
  UserOutlined,
  VideoCameraOutlined,
  UploadOutlined,
} from '@ant-design/icons';

const { Header, Sider, Content } = Layout;

class App extends React.Component {
  state = {
    collapsed: false,
  };

  toggle = () => {
    this.setState({
      collapsed: !this.state.collapsed,
    });
  };

  render() {
    return (
      <Layout>
        <Sider className="mySider" trigger={null} collapsible collapsed={this.state.collapsed}>
          <div className="logo" />
          <Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
            <Menu.Item key="1" icon={<UserOutlined />}>
              nav 1
            </Menu.Item>
            <Menu.Item key="2" icon={<VideoCameraOutlined />}>
              nav 2
            </Menu.Item>
            <Menu.Item key="3" icon={<UploadOutlined />}>
              nav 3
            </Menu.Item>
          </Menu>
        </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>
          <Content
            className="site-layout-background"
            style={{
              margin: '24px 16px',
              padding: 24,
              minHeight: 280,
            }}
          >
            Content
          </Content>
        </Layout>
      </Layout>
    );
  }
}

export default App
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.

添加logo,

代码解读
import logo from './logo.svg'


<Sider trigger={null} collapsible collapsed={this.state.collapsed}>
  // ++++++++
  <div className="logo">
    <img src={ logo } style={{width: '32px', height: '32px', margin: '0 10px 0 0'}} alt=""/>
      { this.state.collapsed ? null : '嗨购管理系统' }
  </div>
  // ++++++++
  <Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
    <Menu.Item key="1" icon={<UserOutlined />}>
       nav 1
        </Menu.Item>
    <Menu.Item key="2" icon={<VideoCameraOutlined />}>
      nav 2
    </Menu.Item>
    <Menu.Item key="3" icon={<UploadOutlined />}>
      nav 3
    </Menu.Item>
    </Menu>
</Sider>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

相关样式 index.css

代码解读
@import '~antd/dist/antd.css';
#root { height: 100%;}
.ant-layout { height: 100%;}
.mySider .trigger {
  padding: 0 24px;
  font-size: 18px;
  line-height: 64px;
  cursor: pointer;
  transition: color 0.3s;
}

.mySider .trigger:hover {
  color: #1890ff;
}

.mySider .logo {
  height: 32px;
  margin: 16px;
  /* background: rgba(255, 255, 255, 0.3); */
  color: #fff;
  font-size: 18px;
  line-height: 32px;
}

.site-layout .site-layout-background {
  background: #fff;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

移植App.jsx代码至 layout/main/index.jsx,注意logo的路径

Logo.svg图片在src目录下

代码解读
import logo from '../../logo.svg'
  • 1.

安装路由模块

代码解读
yarn add react-router-dom -S
  • 1.

修改App.jsx

代码解读
import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import Main from './layout/main/index.jsx'
function App () {
  return (
    <Router >
      <Switch>
        <Route path="/" component = {Main}/>
      </Switch>
    </Router>
  )
}

export default App
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

6.设计左侧菜单栏

 https://gp.adouzaizai.top/haigou/#/login

7.左侧菜单

按照UI库 的 Menu组件以及 Tree树形控件。设计字段

生成左侧菜单的基本配置信息 router/menus.js

代码解读
import { 
  HomeOutlined,
  PictureOutlined,
  NotificationOutlined,
  UsergroupDeleteOutlined,
  SwitcherOutlined
} from '@ant-design/icons'
const menus = [
  {
    title: '系统首页',
    path: '/home',
    key: '0-0',
    icon: <HomeOutlined />
  },
  {
    title: '轮播图管理',
    path: '/banner',
    key: '0-1',
    icon: <PictureOutlined />,
    children: [
      {
        title: '轮播图列表',
        path: '/banner/list',
        key: '0-1-0'
      },
      {
        title: '添加轮播图',
        path: '/banner/add',
        key: '0-1-1'
      },
    ]
  },
  {
    title: '首页数据管理',
    path: '/homeData',
    key: '0-2',
    icon: <NotificationOutlined />,
    children: [
      {
        title: '秒杀列表',
        path: '/homeData/skill',
        key: '0-2-0',
      },
      {
        title: '推荐列表',
        path: '/homeData/recommend',
        key: '0-2-1',
      }
    ]
  },
  {
    title: '用户管理',
    path: '/users',
    key: '0-3',
    icon: <UsergroupDeleteOutlined />,
    children: [
      {
        title: '用户列表',
        path: '/users/list',
        key: '0-3-0',
      },
      {
        title: '管理员列表',
        path: '/users/adminList',
        key: '0-3-1',
      }
    ]
  },
  {
    title: '商品管理',
    path: '/pro',
    key: '0-4',
    icon: <SwitcherOutlined />,
    children: [
      {
        title: '商品列表',
        path: '/pro/list',
        key: '0-4-0',
      },
      {
        title: '筛选商品',
        path: '/pro/search',
        key: '0-4-1',
      }
    ]
  }
]
export default menus
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.

配置左侧菜单栏(抽离左侧菜单栏组件/layout/main/SideMenu.jsx)

挑选UI库中 Menus 菜单,选择 只展开当前父级菜单

代码解读
import React from 'react'
import { Menu } from 'antd';
import { AppstoreOutlined, MailOutlined, SettingOutlined } from '@ant-design/icons';

const { SubMenu } = Menu;

// submenu keys of first level
const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'];

const SideMenu = () => {
  const [openKeys, setOpenKeys] = React.useState(['sub1']);

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

  return (
    <Menu mode="inline" openKeys={openKeys} onOpenChange={onOpenChange} style={{ width: 256 }}>
      <SubMenu key="sub1" icon={<MailOutlined />} title="Navigation One">
        <Menu.Item key="1">Option 1</Menu.Item>
        <Menu.Item key="2">Option 2</Menu.Item>
        <Menu.Item key="3">Option 3</Menu.Item>
        <Menu.Item key="4">Option 4</Menu.Item>
      </SubMenu>
      <SubMenu key="sub2" icon={<AppstoreOutlined />} title="Navigation Two">
        <Menu.Item key="5">Option 5</Menu.Item>
        <Menu.Item key="6">Option 6</Menu.Item>
        <SubMenu key="sub3" title="Submenu">
          <Menu.Item key="7">Option 7</Menu.Item>
          <Menu.Item key="8">Option 8</Menu.Item>
        </SubMenu>
      </SubMenu>
      <SubMenu key="sub4" icon={<SettingOutlined />} title="Navigation Three">
        <Menu.Item key="9">Option 9</Menu.Item>
        <Menu.Item key="10">Option 10</Menu.Item>
        <Menu.Item key="11">Option 11</Menu.Item>
        <Menu.Item key="12">Option 12</Menu.Item>
      </SubMenu>
    </Menu>
  );
};

export default SideMenu
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.

主界面 调用该组件,

react后台管理系统(一)_前端框架_02

修改菜单栏组件以及渲染正确的菜单栏数据

代码解读
import React from 'react'
import { Menu } from 'antd';
import menus from '../../router/menus'
const { SubMenu } = Menu;

// submenu keys of first level
const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'];

const SideMenu = () => {
  const [openKeys, setOpenKeys] = React.useState(['sub1']);

  const onOpenChange = keys => {
    const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
    if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  };
  const renderMenus = (menus) => {
    return menus.map(item => {
      // 判断当前的选项有没有子菜单
      if (item.children) { // 有子级菜单
        return (
          <SubMenu key={ item.path } icon={ item.icon } title={item.title}>
            {/* 递归调用自身,渲染多级菜单 */}
            { renderMenus(item.children) }
          </SubMenu>
        )
      } else {
        // path具有唯一性,key也具有唯一性
        // 为了后续操作方便,此时建议将path作为遍历的key值
        return <Menu.Item key={ item.path } icon = { item.icon }>
          { item.title }
        </Menu.Item>
      }
    })
  }
  return (
    <Menu theme="dark" mode="inline" openKeys={openKeys} onOpenChange={onOpenChange} >
      {
        renderMenus(menus)
      }
      {/* <Menu.Item key="1">Option 1</Menu.Item>
      <SubMenu key="sub1" icon={<MailOutlined />} title="Navigation One">
        <Menu.Item key="1">Option 1</Menu.Item>
        <Menu.Item key="2">Option 2</Menu.Item>
        <Menu.Item key="3">Option 3</Menu.Item>
        <Menu.Item key="4">Option 4</Menu.Item>
      </SubMenu> */}
    </Menu>
  );
};

export default SideMenu
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.

浏览器测试发现并么有只展开一项,点击之后全部可以展开

代码解读
import React from 'react'
import { Menu } from 'antd';
import menus from '../../router/menus'
const { SubMenu } = Menu;

// submenu keys of first level
const keyArr = []
menus.forEach(item => { // 只需要一级菜单项即可
  keyArr.push(item.path)
})
// const rootSubmenuKeys = ['sub1', 'sub2', 'sub4'];
const rootSubmenuKeys = keyArr

const SideMenu = () => {
  const [openKeys, setOpenKeys] = React.useState([]);

  const onOpenChange = keys => {
    const latestOpenKey = keys.find(key => openKeys.indexOf(key) === -1);
    if (rootSubmenuKeys.indexOf(latestOpenKey) === -1) {
      setOpenKeys(keys);
    } else {
      setOpenKeys(latestOpenKey ? [latestOpenKey] : []);
    }
  };
  const renderMenus = (menus) => {
    return menus.map(item => {
      // 判断当前的选项有没有子菜单
      if (item.children) { // 有子级菜单
        return (
          <SubMenu key={ item.path } icon={ item.icon } title={item.title}>
            {/* 递归调用自身,渲染多级菜单 */}
            { renderMenus(item.children) }
          </SubMenu>
        )
      } else {
        // path具有唯一性,key也具有唯一性
        // 为了后续操作方便,此时建议将path作为遍历的key值
        return <Menu.Item key={ item.path } icon = { item.icon }>
          { item.title }
        </Menu.Item>
      }
    })
  }
  return (
    <Menu theme="dark" mode="inline" openKeys={openKeys} onOpenChange={onOpenChange} >
      {
        renderMenus(menus)
      }
      {/* <Menu.Item key="1">Option 1</Menu.Item>
      <SubMenu key="sub1" icon={<MailOutlined />} title="Navigation One">
        <Menu.Item key="1">Option 1</Menu.Item>
        <Menu.Item key="2">Option 2</Menu.Item>
        <Menu.Item key="3">Option 3</Menu.Item>
        <Menu.Item key="4">Option 4</Menu.Item>
      </SubMenu> */}
    </Menu>
  );
};

export default SideMenu
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.

react后台管理系统(一)_前端_03

总结

通过本文档,我们完成了以下主要步骤:

  1. 创建项目:使用 create-react-app 快速初始化项目。
  2. 配置装饰器:安装必要的依赖并配置 Babel 插件,支持类组件的装饰器语法。
  3. 配置 UI 库:引入 Ant Design 并进行基本配置,确保项目中可以正常使用 Ant Design 的组件。
  4. 修改目录结构:整理项目目录,使其更加符合实际开发需求。
  5. 搭建主结构:设计并实现项目的主布局,包括顶部导航和侧边栏。
  6. 配置路由:安装并配置 react-router-dom,实现页面之间的导航。
  7. 设计左侧菜单栏:使用 Ant Design 的 Menu 组件,实现动态生成的多级菜单,并确保只展开当前选中的父级菜单。

通过以上步骤,我们成功搭建了一个功能完善、结构清晰的 React 项目。希望本文档能为读者在实际开发中提供有价值的参考。