布局做好以后就可以开始构建整个系统路由及其对应的路由组件,但是有时候我们的需求是要求路由的页面在Tabs标签页中的,然后点击各个标签页可以进行切换,接下来就来探讨一下这种布局的实现。
添加Tabs组件,在src/components/layout中添加Tabs.js组件,首先在官网https://ant.design/components/tabs-cn/复制一个Tabs组件如下:
import React from 'react'
import { Tabs, Button } from 'antd';
const TabPane = Tabs.TabPane;
class Mytab extends React.Component {
constructor(props) {
super(props);
this.newTabIndex = 0;
const panes = [
{ title: 'Tab 1', content: 'Content of Tab Pane 1', key: '1' },
{ title: 'Tab 2', content: 'Content of Tab Pane 2', key: '2' },
];
this.state = {
activeKey: panes[0].key,
panes,
};
}
onChange = (activeKey) => {
this.setState({ activeKey });
}
onEdit = (targetKey, action) => {
this[action](targetKey);
}
add = () => {
const panes = this.state.panes;
const activeKey = `newTab${this.newTabIndex++}`;
panes.push({ title: 'New Tab', content: 'New Tab Pane', key: activeKey });
this.setState({ panes, activeKey });
}
remove = (targetKey) => {
let activeKey = this.state.activeKey;
let lastIndex;
this.state.panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const panes = this.state.panes.filter(pane => pane.key !== targetKey);
if (panes.length && activeKey === targetKey) {
if (lastIndex >= 0) {
activeKey = panes[lastIndex].key;
} else {
activeKey = panes[0].key;
}
}
this.setState({ panes, activeKey });
}
render() {
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button onClick={this.add}>ADD</Button>
</div>
<Tabs
hideAdd
onChange={this.onChange}
activeKey={this.state.activeKey}
type="editable-card"
onEdit={this.onEdit}
>
{this.state.panes.map(pane => <TabPane tab={pane.title} key={pane.key}>{pane.content}</TabPane>)}
</Tabs>
</div>
);
}
}
export default Mytab
然后将其替换src/layouts/index.js中的Content
rander函数如下:
return (
<Layout>
<MySider collapsed={collapsed}>
<MyMenu {...menuProps}/>
MySider>
<Layout>
<Header collapsed={collapsed} onCollapseChange={onCollapseChange}/>
<MyTab>
{this.props.children}
MyTab>
Layout>
Layout>
);
此时可以看到页面有了tab组件,并可以正常添加删除
状态提升
接下来需要将tab组件状态提升,由dva管理,那就先改造tab组件
import React from 'react'
import {Tabs,Button} from 'antd';
import PropTypes from "prop-types";
const TabPane = Tabs.TabPane;
class MyTab extends React.Component {
onEdit = (targetKey, action) => {
this[action](targetKey);
}
remove = (targetKey) => {
const {onPaneRemove} = this.props
onPaneRemove(targetKey)
}
render() {
const {
panes,
activeKey,
onPaneChange,
onPaneAdd,
} = this.props
return (
<div>
<div style={{ marginBottom: 16 }}>
<Button onClick={onPaneAdd}>ADD</Button>
</div>
<Tabs
hideAdd
onChange={onPaneChange.bind(this)}
activeKey={activeKey}
type="editable-card"
onEdit={this.onEdit}
>
{panes.map(pane => <TabPane tab={pane.title} key={pane.key} closable={pane.closable}>
{pane.content}
</TabPane>)}
</Tabs>
</div>
);
}
}
MyTab.propTypes = {
panes: PropTypes.array.isRequired,
activeKey: PropTypes.string.isRequired,
onPaneChange: PropTypes.func.isRequired,
}
export default MyTab
然后修改src/layouts/index.js
import React from 'react'
import {Layout} from 'antd';
import Header from '../components/layout/Header'
import MySider from '../components/layout/Sider'
import MyMenu from '../components/layout/Menu'
import MyTab from '../components/layout/Tabs'
import {connect} from 'dva';
class BasicLayout extends React.Component {
onCollapseChange = collapsed => {
this.props.dispatch({
type: 'app/handleCollapseChange',
payload: {collapsed},
})
}
handelMenuClick = (element) => {
//路由到相关页面
this.props.history.push(element.item.props.url)
}
onPaneAdd = () => {
//todo something
}
onPaneChange = () => {
//todo something
}
onPaneRemove = () => {
//todo something
}
render() {
const {app, children} = this.props
const {menus, theme, collapsed,panes,activeKey,} = app
const {handelMenuClick, onCollapseChange,onPaneAdd,onPaneChange,onPaneRemove} = this
const menuProps = {
theme,
menus,
children,
handelMenuClick,
onCollapseChange,
}
const tapProps = {
panes,
children,
activeKey,
onPaneAdd,
onPaneChange,
onPaneRemove,
}
return (
<Layout>
<MySider collapsed={collapsed}>
<MyMenu {...menuProps}/>
</MySider>
<Layout>
<Header collapsed={collapsed} onCollapseChange={onCollapseChange}/>
<MyTab {...tapProps}>
{this.props.children}
</MyTab>
</Layout>
</Layout>
);
}
}
export default connect((({app}) => ({app})))(BasicLayout)
然后修改src/models/app.js
export default {
namespace: 'app',
state: {
collapsed: false,
activeKey: '1',
panes: [
{title: `dashboard`, content: '', key: '1', closable: false, url: '/dashboard'},
],
theme: 'dark',
menus: [{
id: '1',
name: 'dashboard',
icon: 'dashboard',
url: '/dashboard',
}, {
id: '2',
name: '用户管理',
icon: 'user',
url: '1',
children: [{
id: '3',
name: '用户管理',
icon: 'user',
url: '/user',
},]
}, {
id: '3',
name: '用户管理',
icon: 'user',
url: '1',
children: [{
id: '4',
name: '用户管理',
icon: 'user',
url: '/user',
},]
}],
},
subscriptions: {},
effects: {},
reducers: {
updateState(state, {payload}) {
return {
...state,
...payload,
}
},
handleCollapseChange(state, {payload}) {
return {
...state,
...payload,
}
},
},
}
最后完成src/layouts/index.js中几个控制状态的函数
onPaneAdd = () => {
const {app, dispatch} = this.props
const {panes,} = app
let newPane = {title: 'new Tab', content: '', key: panes.length + '1',}
let flag = false
let newPanes = panes.map(item => {
if (newPane.key === item.key) {
flag = true
return {...item, ...newPane}
}
return item
})
dispatch({
type: 'app/updateState',
payload: {
panes: flag ? newPanes : [...newPanes, newPane],
activeKey: newPane.key,
},
})
}
onPaneChange = targetKey => {
const {dispatch,} = this.props
dispatch({
type: 'app/updateState',
payload: {
activeKey: targetKey
},
})
}
onPaneRemove = targetKey => {
const {dispatch,} = this.props
const payload = this._removeTap(targetKey)
dispatch({
type: 'app/updateState',
payload: payload,
})
}
_removeTap = (targetKey) => {
const {panes, activeKey} = this.props.app
let newActiveKey = activeKey
let lastIndex;
panes.forEach((pane, i) => {
if (pane.key === targetKey) {
lastIndex = i - 1;
}
});
const newPanes = panes.filter(pane => pane.key !== targetKey);
if (newPanes.length && activeKey === targetKey) {
if (lastIndex >= 0) {
newActiveKey = newPanes[lastIndex].key;
} else {
newActiveKey = newPanes[0].key;
}
}
return {
panes: newPanes,
activeKey: newActiveKey
}
}
这样就可以添加与删除了,但是我们需要的是点击菜单添加,并且显示该页面,所以再稍微改造一下src/layouts/index.js中的函数,并且把Tab组件中的添加按钮删掉
index.js
//菜单点击事件
handelMenuClick = element => {
let newPane = {title: element.item.props.name, content: '', key: element.key, url: element.item.props.url}
this.onPaneAdd(newPane)
}
//打开新tab
onPaneAdd = (newPane) => {
const {app, dispatch, history,} = this.props
const {panes,} = app
let flag = false
//路由到相关页面
history.push(newPane.url)
setTimeout(()=>{
newPane.content = this.props.children
let newPanes = panes.map(item => {
if (newPane.key === item.key) {
flag = true
return {...item, ...newPane}
}
return item
})
dispatch({
type: 'app/updateState',
payload: {
panes: flag ? newPanes : [...newPanes, newPane],
activeKey: newPane.key,
},
})
},100)
}
Tabs.js
//初始化加载第一个tab
componentWillMount(){
const {panes, onPaneAdd,} = this.props
if (panes.length === 1) {
const pane = panes[0]
onPaneAdd(pane)
}
}
这样就可以实现tab的添加与删除了:
参考代码:https://github.com/enusune/myapp