关于布局,我们以偏向管理系统的风格为例,采用上左右布局,即:上放置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.youkuaiyun.com/download/xw505501936/10477511