react项目开发-布局和导航菜单(前三篇续)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xw505501936/article/details/80661379

关于布局,我们以偏向管理系统的风格为例,采用上左右布局,即:上放置logo,账户信息等公共数据,左放置菜单分类,多级导航等,右放置主体业务内容等。

1 先来改造layout/layout.js,增加主体布局,并且判断是否是登录页面,布局不同,代码如下:

import {connect} from 'dva';
import React from 'react';
import pathToRegexp from 'path-to-regexp'
import Helmet from 'react-helmet';
import classnames from 'classnames';
import styles from './index.less';

const Layout=({ children,dispatch,menu,locationPathname })=>{

  const menuList=menu.getIn(['byId']).toList();
  let menuName='';
  menuList.map(item=>{
    if(pathToRegexp(item.get('path')).exec(locationPathname)){
      menuName = item.get('name');
    }
  });

  //判断是否是登录页,登录页面和内页是不同的布局
  const loginUrl=menu.getIn(['byId','login','path']);
  const isLoginPage=pathToRegexp(loginUrl).exec(locationPathname)?true:false;

  return (
    <React.Fragment>
      <Helmet>
        <title>
          {menuName}
        </title>
      </Helmet>
      {isLoginPage?
        children
      :
        <div className={classnames(styles.LBodyOuter)}>
          <div className={classnames(styles.LHeader)}>
            logo
          </div>
          <div className={classnames(styles.LBody)}>
            <div className={classnames(styles.LTree)}>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
              <p>哈哈哈哈</p>
            </div>
            <div className={classnames(styles.LContent)}>
              {children}
            </div>
          </div>
        </div>
      }
      
    </React.Fragment>
  );
}

export default connect(({
  app
})=>({
  menu:app.get('menu'),
  locationPathname:app.get('locationPathname'),
}))(Layout)

2 layout目录下增加index.less样式文件,控制整体布局

目录结构如下:

代码如下:

@import '../index.less';

.LBodyOuter{
    height:100vh;
    display:flex;
    flex-direction:column;
    .LHeader{
        height:50px;
        width:100vw;
        background:@bg-color01;
        flex-shrink:0;
    }
    .LBody{
        flex-grow: 1;
        width:100vw;
        display:flex;
        flex-direction:row;
        .LTree{
            width:200px;
            background:@bg-color02;
            flex-shrink:0;
            overflow:auto;
        }
        .LContent{
            flex-grow:1;
            overflow:auto;
        }
    }
}

修改src/index.less全局样式,定义全局样式配置和滚动条样式设置,代码如下:

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

//定义全局样式配置
@bg-color01:#eee;
@bg-color02:#ddd;

body {
  margin: 0;
  padding: 0;
  font-family: sans-serif;
  //滚动条样式设置
  ::-webkit-scrollbar-thumb {
    background-color: rgba(128,128,128,.5);
    border-radius: 5px;
  }
  
  ::-webkit-scrollbar {
    width: 8px;
    height: 8px;
  }
}

3 修改routes/home/index.js,增加内容撑开页面,如图:

到这里,我们展示下效果:

 

接下来,我们要对菜单稍微进行一下重新规划,不再以aaa啊bbb啊什么的,这样看着让人没有头绪。

Demo菜单设计,共包含三级菜单,如下,

1 书籍
 (1)历史古典
   (1)古诗文集
   (2)古词
   (3)歌赋浏览
 (2)现代文学
 (3)儿童读物
 (4)小说娱乐
2 电影
 (1)欧美大片
 (2)大陆影视
 (3)港台电影

3 音乐

大概步骤如下:

(1)修改config中menuGlobal配置

(2)models目录下,创建对应的model文件

(3)routes目录下,创建对应模板组件

(4)layout目录下,创建对应布局组件

(5)样式调整

调整后的目录结构如下:

效果演示如下:

 

此次改动处比较多,之后每次的修改,都提供下源码,会放在csdn资源下载处,可免费下载查看,博客文章中,只记录当前版本的主要更改点,过程中所遇到的一些坑,便于大家直观的发现问题,快速理解。

此次改动(20180613)版本,所涉及重要点:

1 对src/index.js入口文件,做了修改,主要是处理dva和intl国际化的对接逻辑,国际化组件放在最外层,代码:

import dva from 'dva';
import './index.less';
import React from 'react';
import ReactDOM from 'react-dom';
import Locale from './layout/locale';
import createHistory from 'history/createBrowserHistory'

// 1. Initialize
const app = dva({
    history:createHistory()
});

// 2. Plugins
// app.use({});

// 3. Model
app.model(require('./models/app').default);

// 4. Router
app.router(require('./router').default);

// 5. Start
const App = app.start();

//此处,是dva配合国际化使用方式
ReactDOM.render(<Locale store={app._store}><App /></Locale>, document.getElementById('root'));

2 src/router.js路由文件修改,去除locale国际化组件,修改auth权限组件位置,代码:

import React from 'react';
import { Router, Route, Switch } from 'dva/router';
import dynamic from 'dva/dynamic'
import Auth from './layout/auth'
import {config} from './utils'
const { menuGlobal } = config

function RouterConfig({ history, app }) {
  return (
    <Router history={history}>
      <Auth>
        <Switch>
          {
            menuGlobal.map(({path,...dynamics},index)=>(
              <Route
                key={index} 
                path={path} 
                exact 
                component={dynamic({
                  app,
                  ...dynamics
                })} 
              />
            ))
          }
        </Switch>
      </Auth>
    </Router>
  );
}

export default RouterConfig;

3 layout/tree.js文件修改,使用Link做路由跳转,代码:

import {connect} from 'dva';
import {Link} from 'dva/router';
import React from 'react';
import pathToRegexp from 'path-to-regexp'
import classnames from 'classnames';
import { Menu, Icon } from 'antd';
import styles from './index.less';

const SubMenu = Menu.SubMenu;

const Tree=({ children,dispatch,menu,locationPathname })=>{

  const menuById=menu.get('byId');
  const menuByPid=menu.get('byPid');
  const currentById=menuById.filter(item=>pathToRegexp(`${item.get('path')}`).exec(locationPathname)).toList().get(0);
  const currentMenu = menuById.filter(item=>pathToRegexp(`${item.get('path')}/:path*`).exec(locationPathname)).map(item=>item.get('id')).toArray();
  
  //计算菜单方法支持无限级
  function CalChildMenu(item){
    if(menuById.getIn([item,'display']) && menuById.getIn([item,'display'])=='block'){
        if(menuByPid.get(item)){
        return <SubMenu key={item} title={<span><Icon type={menuById.getIn([item,'icon'])} /><span>{menuById.getIn([item,'name'])}</span></span>}>
            {menuByPid.get(item).map(item2=>{
            return CalChildMenu(item2)
            })}
        </SubMenu>
        }else{
        return <Menu.Item key={item} to={menuById.getIn([item,'path'])}>
            <Link to={menuById.getIn([item,'path'])}><span><Icon type={menuById.getIn([item,'icon'])} /><span>{menuById.getIn([item,'name'])}</span></span></Link>
        </Menu.Item>
        }
    }
  }

  return (
    <div className={classnames(styles.CTree)}>
      <Menu
        defaultSelectedKeys={[currentById.get('id')]}
        defaultOpenKeys={currentMenu}
        mode="inline"
      > 
        {menuByPid.get('0').map(item=>{
          return CalChildMenu(item)          
        })}
      </Menu>
    </div>
  );
}

export default connect(({
  app
})=>({
  menu:app.get('menu'),
  locationPathname:app.get('locationPathname'),
}))(Tree)

4 layout下增加header.js,content.js 做页面布局使用,代码:

header.js

import {connect} from 'dva';
import React from 'react';
import {injectIntl} from 'react-intl'
import classnames from 'classnames';
import { Menu, Dropdown, Button } from 'antd';
import styles from './index.less';
import logoPng from '../assets/logo.png';

const Header=({ children,dispatch,i18n,intl:{formatMessage} })=>{

  function changeLang(e){
    dispatch({
      type:'app/changeLang',
      payload:{
        value:e.key
      }
    })
  }

  function logout(){
    dispatch({
      type:'app/logout'
    })
  }

  const menuLang = (
    <Menu selectedKeys={[i18n]} onClick={changeLang}>
      <Menu.Item key="zh_CN">中文</Menu.Item>
      <Menu.Item key="en_US">英文</Menu.Item>
      <Menu.Item key="zh_HK">繁体</Menu.Item>
    </Menu>
  );

  return (
    <div className={classnames(styles.CHeader)}>
      <div className={classnames(styles.CLogo)}>
        <img src={logoPng} width={70} />
      </div>
      <div className={classnames(styles.CNavBar)}>
        <Dropdown trigger={['click']} overlay={menuLang}>
          <Button size={'small'} style={{marginRight:'10px'}} icon={'global'}>{formatMessage({id: 'App.lang'})}</Button>
        </Dropdown>
        <Button size={'small'} icon={'logout'} onClick={logout}>退出</Button>
      </div>
    </div>
  );
}

export default connect(({
  app
})=>({
  i18n:app.get('i18n'),
}))(injectIntl(Header))

content.js

import {connect} from 'dva';
import React from 'react';
import pathToRegexp from 'path-to-regexp'
import classnames from 'classnames';
import { Menu, Icon } from 'antd';
import styles from './index.less';

const Content=({ children,dispatch,menu,locationPathname })=>{

  const menuById=menu.get('byId');
  const currentMenu = menuById.filter(item=>pathToRegexp(`${item.get('path')}/:path*`).exec(locationPathname)).map(item=>item.get('id')).toArray();

  return (
    <div className={classnames(styles.CContent)}>
      <div className={classnames(styles.CToolBar)}>
        <Icon type={'flag'} /> 当前位置:
        {currentMenu.map((item,index)=>{
          if(index==0){
            return <span key={index}>{menuById.getIn([item,'name'])}</span>
          }else{
            return <span key={index}> / {menuById.getIn([item,'name'])}</span>
          }
        })}
      </div>
      <div className={classnames(styles.CMain)}>
        {children}
      </div>
    </div>
  );
}

export default connect(({
  app
})=>({
  menu:app.get('menu'),
  locationPathname:app.get('locationPathname'),
}))(Content)

5 utils/config.js文件修改,重构菜单,构造Map数据时改为使用有序Map对象OrderedMap,代码:

import {OrderedMap,OrderedSet,Map,fromJS} from 'immutable'

const menuGlobal=[
    {
        id:'login',
        pid:'0',
        name:'登录',
        icon:'user',
        path: '/login',
        models: () => [import('../models/login')], //models可多个
        component: () => import('../routes/login'),
    }, 
    {
        id:'books',
        pid:'0',
        name:'书籍',
        icon:'book',
        display:'block',
        path: '/books',
        models: '', //models可多个
        component: '',
    }, 
    {
        id:'books-classicalHistory',
        pid:'books',
        name:'历史古典',
        icon:'file',
        display:'block',
        path: '/books/classicalHistory',
        models: '', //models可多个
        component: '',
    }, 
    {
        id:'books-classicalHistory-AncientPoetry',
        pid:'books-classicalHistory',
        name:'古诗文集',
        icon:'file-text',
        display:'block',
        path: '/books/classicalHistory/AncientPoetry',
        models: () => [import('../models/books')], //models可多个
        component: () => import('../routes/books/classicalHistory/AncientPoetry'),
    }, 
    {
        id:'books-classicalHistory-AncientWords',
        pid:'books-classicalHistory',
        name:'古词',
        icon:'file-word',
        display:'block',
        path: '/books/classicalHistory/AncientWords',
        models: () => [import('../models/books')], //models可多个
        component: () => import('../routes/books/classicalHistory/AncientWords'),
    }, 
    {
        id:'books-classicalHistory-SongBrowsing',
        pid:'books-classicalHistory',
        name:'歌赋浏览',
        icon:'file-excel',
        display:'block',
        path: '/books/classicalHistory/SongBrowsing',
        models: () => [import('../models/books')], //models可多个
        component: () => import('../routes/books/classicalHistory/SongBrowsing'),
    }, 
    {
        id:'books-modernLiterature',
        pid:'books',
        name:'现代文学',
        icon:'file-markdown',
        display:'block',
        path: '/books/modernLiterature',
        models: () => [import('../models/books')], //models可多个
        component: () => import('../routes/books/modernLiterature'),
    }, 
    {
        id:'books-childrenBooks',
        pid:'books',
        name:'儿童读物',
        icon:'file-pdf',
        display:'block',
        path: '/books/childrenBooks',
        models: () => [import('../models/books')], //models可多个
        component: () => import('../routes/books/childrenBooks'),
    }, 
    {
        id:'books-fiction',
        pid:'books',
        name:'小说娱乐',
        icon:'file-jpg',
        display:'block',
        path: '/books/fiction',
        models: () => [import('../models/books')], //models可多个
        component: () => import('../routes/books/fiction'),
    }, 
    {
        id:'film',
        pid:'0',
        name:'电影',
        icon:'play-circle',
        display:'block',
        path: '/film',
        models: '', //models可多个
        component: '',
    }, 
    {
        id:'film-europeAmerica',
        pid:'film',
        name:'欧美大片',
        icon:'dribbble',
        display:'block',
        path: '/film/europeAmerica',
        models: () => [import('../models/film')], //models可多个
        component: () => import('../routes/film/europeAmerica'),
    }, 
    {
        id:'film-mainland',
        pid:'film',
        name:'大陆影视',
        icon:'api',
        display:'block',
        path: '/film/mainland',
        models: () => [import('../models/film')], //models可多个
        component: () => import('../routes/film/mainland'),
    }, 
    {
        id:'film-hongKong',
        pid:'film',
        name:'港台电影',
        icon:'trophy',
        display:'block',
        path: '/film/hongKong',
        models: () => [import('../models/film')], //models可多个
        component: () => import('../routes/film/hongKong'),
    }, 
    {
        id:'music',
        pid:'0',
        name:'音乐',
        icon:'sound',
        display:'block',
        path: '/music',
        models: () => [import('../models/music')], //models可多个
        component: () => import('../routes/music'),
    }
];

/**
 * 封装路由数据,利用id和pid的关联性处理
 */
const menuMap = (() => {
    let byId = OrderedMap();
    let byPid = OrderedMap();
    menuGlobal.map(item => {
      byId = byId.set(item.id, fromJS(item));
      byPid = byPid.update(item.pid, obj => obj ? obj.add(item.id) : OrderedSet([item.id]))
      
    });
    return OrderedMap({
        byId,
        byPid
    });
})();
  
export default {
    menuGlobal,
    menuMap
}

6 layout/index.less文件修改,增加布局样式,代码:

@import '../index.less';

.LBodyOuter{
    height:100vh;
    display:flex;
    flex-direction:column;
    background:@bg-color01;
    .LHeader{
        height:48px;
        width:100vw;
        background:@wihte-color;
        flex-shrink:0;
        border-bottom:2px solid @line-color01
    }
    .LBody{
        flex-grow: 1;
        width:100vw;
        display:flex;
        flex-direction:row;
        .LTree{
            width:200px;
            background:@wihte-color;
            flex-shrink:0;
        }
        .LContent{
            flex-grow:1;
            overflow:auto;
            padding:10px;
        }
    }
}

//顶部
.CHeader{
    display:flex;
    flex-direction:row;
    height:100%;
    padding:0 10px;
    .CLogo{
        width:100px;
        height:100%;
        padding:5px 0 0 0;
    }
    .CNavBar{
        flex-grow:1;
        height:100%;
        display:flex;
        align-items: center;
        justify-content:flex-end;
    }
}

//菜单
.CTree{
    height:100%;
    overflow-y:auto;
    overflow-x:hidden;
    :global{
        .ant-menu{
            background:@wihte-color;
            height:100%;
            border-right:0;
        }
    }
}

//主体内容
.CContent{
    display:flex;
    height:100%;
    background:@wihte-color;
    flex-direction:column;
    .CToolBar{
        height: 40px;
        flex-shrink:0;
        line-height:40px;
        padding:0 20px;
        border-bottom:1px solid @line-color01;
    }
    .CMain{
        flex-grow: 1;
        overflow:auto;
    }
}

 

代码资源,上传必须选择1个资源,没办法将就下:

https://download.csdn.net/download/xw505501936/10477511

展开阅读全文

没有更多推荐了,返回首页