【复用侧边栏框架】React中后台管理系统侧边栏设计-redux-组件中函数的递归-immutable数据持久化-实现

本篇文章主要讲解,利用主流插件 Ant Design 实现多场景、可复用的后台管理系统侧边栏 -----【注】整体代码不建议直接拖拽运行、可参考本文利用递归增加复用性的思想

实现效果

导航效果.png

起步

//创建一个名为pxx-admin的项目
$ npx create-react-app pxx-admin
//下载插件库,可去官网查看具体使用方法
cnpm i antd -S /yarn add antd -S

在src文件夹下创建index.js、 App.jsx、index.css等文件夹,如下图


基础配置.png

其中index.css内引入Ant插件的样式

@import '~antd/dist/antd.css';

App.js内使用组件

import React from 'react'
//这里使用路由插件配置 需要下载插件cnpm i react-router-dom -S
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

index.js下需要对整体进行引入

//需要下载react 插件 cnpm i react  react-dom react-redux -S
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './index.css'
// 引入状态管理器redux
import {Provider} from 'react-redux'
import store from './store'

    ReactDOM.render(
//利用Provider 包裹 组件 可以在组件中实现传值
        <Provider store={store}>
            <App />
        </Provider>,
    document.getElementById('root')
)

然后我们需要引入插件

如图这个插件代码
layout.png

在src文件夹下面创建一个laout文件夹,在layout文件夹下创建main文件夹、main下创建一个index.js文件


layoutmain.png
// 路由的懒加载需要引入 Suspense包裹路由
import React from 'react'
import { Layout } from 'antd';
// 引入侧边栏组件
import SideMenu from './sideMenu'

// logo图片
import logo from '../../logo.svg'
// 加载动画
import RouterView from '../../router/RouterView'
import MainHeader from './MainHeader'

// 用装饰器来传递参数
import {connect} from 'react-redux'

const {  Sider, Content } = Layout;
@connect(state=>{
  return{
    collapsed:state.getIn(['common','collapsed'])
  }
})
class Index extends React.Component {
  // 路由的渲染
  render() {
    // provider传来一个store 可以用this.props访问
    const {collapsed}=this.props
   console.log('11',collapsed)
    return (
      <Layout>
        <Sider trigger={null} collapsible collapsed={collapsed}>
          <div className="logo" >
            <img src={logo} style={{ height: '32px', width: '32px' }} alt="" />
            {collapsed ? null : <span>PXX-ADMIN</span>}
          </div>
          //抽离出来的菜单栏
          <SideMenu />

        </Sider>
        <Layout className="site-layout">
          <MainHeader/>
          
          {/* 内容块 */}
          <Content
            className="site-layout-background"
            style={{
              margin: '24px 16px',
              padding: 24,
              minHeight: 280,
            }}
          >
          //路由跳转配置
           <RouterView/>
          </Content>

        </Layout>
      </Layout>
    );
  }
}
export default Index

:用到递归

设置侧边栏内容 SideMenu.js

// 抽离侧边栏的配置

import React from 'react'
//antd插件中抽离
import { Menu } from 'antd';
// 获取不到url的数据
import {withRouter,useHistory,useLocation} from 'react-router-dom'
import menus from '../../router/menus'
const { SubMenu } = Menu;
//使用hooks实现路由跳转
const SideMenu =withRouter((props)=>{
// 调用以下hooks,实现点击跳转
const history = useHistory()
//定义一个函数(递归实现左侧菜单栏)
//里面的item都为路由配置文件里的数据利用map遍历
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(
                        <Menu.Item key={item.path} icon={item.icon}>
                            {item.title}
                        </Menu.Item>
                        )   
                    }          
               })
            }
           </>
        )
    }
//点击跳转事件,参数为解构的地址 key为props里结构出来的相对地址
const goPage = ({key})=>{
history.push(key)
}
// 解构一个地址
const {pathname} = useLocation()
// 切割一个初始展开的地址
const type = '/' + pathname.split('/')[1]
//defaultSelectedKeys、defaultOpenKeys为antd属性 可以参考Ant插件文档
    return(
    <>
            <Menu 
            theme="dark" 
            mode="inline" 
            defaultSelectedKeys={[pathname]}
            defaultOpenKeys={[type]}
            onClick={goPage} >
            {
                // 设计一个函数的递归调用实现左侧菜单栏的调用
                renderMenu(menus)
            }
          </Menu>
    </> 
    
    )   
})
export default SideMenu

router文件夹下 创建menus.js路由配置文件

import React,{lazy} from 'react'
//icon图标
import {
    BankOutlined,
    CodepenOutlined,
    SplitCellsOutlined,
    DatabaseOutlined,
    HomeOutlined,
    ClockCircleOutlined,
    BarsOutlined
} from '@ant-design/icons'
//路由
const menus=[
    {
        path:'/',
        title:'系统首页',
        icon:<BankOutlined/>,
//懒加载
        component:lazy(()=>import('../views/home/Index'))
    },
    {
        path:'/bannermanager',
        title:'轮播首页',
        icon:<CodepenOutlined/>,
        children:[
            {
                path:'/bannermanager/list',
                title:'轮播列表',
                icon:<BarsOutlined />,
                component:lazy(()=>import('../views/banner/Index'))
            }
        ]
    },
    {
        path:'/navigatormanager',
        title:'导航管理',
        icon:<SplitCellsOutlined/>,
        children:[
            {
                path:'/navigatormanager/list',
                title:'导航列表',
                icon:<BarsOutlined />,
                component:lazy(()=>import('../views/navigator/List'))

            },
            {
                path:'/navigatormanager/category',
                title:'导航分类',
                icon:<DatabaseOutlined />,
                component:lazy(()=>import('../views/navigator/Category'))

            },
            {
                path:'/navigatormanager/homelist',
                title:'首页导航',
                icon:<HomeOutlined />,
                component:lazy(()=>import('../views/navigator/HomeList'))

            }
        ]
    },
    {
        path:'/seckillmanager',
        title:'秒杀管理',
        icon:<ClockCircleOutlined/>,
        children:[
            {
                path:'/seckillmanager/list',
                title:'秒杀列表',
                icon:<BarsOutlined />,
                component:lazy(()=>import('../views/seckill/List'))

            }
        ]
    }, 

//下面可随便添加需要的功能
 {
        path:'',
        title:'',
        icon:,
        children:[
            {
                path:'',
                title:'',
                icon:,
                component:lazy(()=>import('../'))

            }
        ]
]
export default menus

:用到递归

routerView.js路由跳转文件

// 引入menus里面的路由
import menus from './menus'
import React,{Suspense} from 'react'
import { Switch,Route } from 'react-router-dom';
import { Spin } from 'antd';

function RouterView() {
    const  renderRouter=(menus)=>{
        return menus.map(item=>{
          if(item.children){
            return renderRouter(item.children)
          }else{
            return <Route path={item.path} key={item.path} exact component={item.component} />
    
          }
        })
      }
    return (
        <div>

            {/* Suspense用于懒加载的数据获取 用盒子包裹加载动画<Spin>*/}
            <Suspense fallback={<div className="loading"><Spin size='large' /></div>}>
            <Switch>
            {
                renderRouter(menus)
            }
            </Switch>
            </Suspense>
        </div>
        )
    }

export default RouterView

- 存在一个问题 - 就是将左侧菜单栏单独分离成一个组件时候 点击展开 收回按钮失效则需要引入状态管理器,控制展开 收回变量collapsed的状态改变

MainHeader.js


// 想要将这里面的collapsed值传递出去给首页 控制左侧的菜单栏的显示与收起
// 所以引入状态管理器
import  {connect} from 'react-redux'
import * as types from '../../store/actionTypes'

import React from 'react'
// antd库的icon图标
import { Layout } from 'antd';

import {
    MenuUnfoldOutlined,
    MenuFoldOutlined,
  } from '@ant-design/icons';
// 引入面包屑组件
import Breadcrumb from './Breadcrumb'
const { Header} = Layout;
// 组件需要使用到的参数
function MainHeader({collapsed,changeCollapse}) {
    // const [collapsed,setCollapsed]=useState(false)
    // 小图标的箭头的点击事件
    const toggle=()=>{
        changeCollapse()   
        // changeCollapsed() 
    }
   
        return (
            <div>
                <Header className="site-layout-background" style={{ padding: "0 16px" }}>
                {/* {React.createElement(collapsed ? MenuUnfoldOutlined : MenuFoldOutlined, {
                className: 'trigger',
                onClick:toggle,
                })} */}
                {/* 等价写法 */}
                <Breadcrumb />
                {
                    collapsed?
                    <MenuUnfoldOutlined style={{fontSize:'24px'}} className="trigger" onClick={toggle}/>:
                    <MenuFoldOutlined style={{fontSize:'24px'}} className="trigger" onClick={toggle}/>

                }
                
                </Header>
            </div>
        )
    }
    
// 需要用connect对组件进行包装
export default connect(state=>({
    collapsed:state.getIn(['common','collapsed'])
}),dispatch=>({
    changeCollapse(){
        // 修改状态
        dispatch({
            type:types.CHANGE_COLLAPSED
        })
    }
}))(MainHeader)

配置完上面的基础设置之后 我们需要 去配置store状态管理器
store

//下载redux
cnpm i react-redux

在src文件夹下创建一个store文件同上面的配置图所示


store.png

首先配置actionTypes文件 里面存放单纯的reducer函数名

// 控制左侧菜单栏收缩,展开的变量
const  CHANGE_COLLAPSED = 'CHANGE_COLLAPSED'

export{
    CHANGE_COLLAPSED
}

然后配置recuer函数文件夹modules 下的common.js

//引入数据持久化 需要下载 cnpm i immutable -D
import {Map} from 'immutable'
//引入函数名文件
import * as types from '../actionTypes'
// 数据持久化
const reducer = (state=Map({
//Ant插件控制左侧菜单栏是否展开的变量
    collapsed:false
}),action)=>{
//抛发函数
    switch(action.type){
        case types.CHANGE_COLLAPSED:
            return state.set('collapsed',!state.get('collapsed'))
        default:
            return state;
    }
}
export default reducer

配置一个store入口文件index.js

// 引入状态管理器
import {createStore,applyMiddleware} from 'redux'
// 引入合并reducer的方法。
import {combineReducers} from 'redux-immutable'
//引入thunk中间件
import thunk from 'redux-thunk'
//引入reducer函数
import commonReducer from './modules/common'

const reducer = combineReducers({
    common:commonReducer
})
//创建store状态管理器
const store = createStore(reducer,applyMiddleware(thunk))

export default store

--- 如有疑问可留言,看到即回。欢迎一起探讨问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容