0%

[仅供参考]基于 ant design pro 2.1.1 页面标签化展示的研究与实现

升级

最新方案可参考基于 ant design pro 2.3.1 页面标签化展示的研究与实现

已知该方案缺陷:不可在页面组件中正常使用路由切换子组件。

前言

官方仓库下的 issues 中有看到很多关于页面标签化展示的讨论,也有很多实现方式,比如在能否提供tab切换模式这个 issue 下就展开了一系列讨论,官方也给了很多例子甚至换脚手架……

研究了几个例子后发现都没有在现有基础上的升级指南,都是一整个仓库……这就有些难受了。没办法了,自己动手吧。看了几个实现效果后选定了 kuhami/react-ant 这个仓库。

刚开始根据该仓库提供的文档做了些改动,未果。由于该仓库没有 live demo ,使用 Gitpod 看了看实际效果发现还不错,所以对 layouts/BasicLayout.js 展开了深入研究。

研究与实现

通过学习,了解了他的实现方式,并在此基础上进行了部分优化,得到以下的方案。

工具方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export function transferMenuTreeToList(treeData, childrenNodeName = 'children') {
const treeList = [];
function getTreeList(item) {
item.forEach(node => {
treeList.push({
tab: node.name,
key: node.path,
locale: node.locale,
closable: true,
content: node.component,
});
if (node[childrenNodeName] && node[childrenNodeName].length > 0) {
getTreeList(node[childrenNodeName]);
}
});
}
getTreeList(treeData);
return treeList;
}

BasicLayout.js

导入模块

1
2
3
4
5
import { Layout, Tabs, Dropdown, Menu, Icon, Spin } from 'antd';
import router from 'umi/router';
import _find from 'lodash/find';
import _findIndex from 'lodash/findIndex';
import { transferTreeToList } from '@/utils/utils'; // 引入工具函数

声明常量

1
2
3
4
5
6
7
8
const { TabPane } = Tabs;

// tabs 菜单选项 key 值
const closeCurrentTabMenuKey = 'closeCurrent';
const closeOthersTabMenuKey = 'closeOthers';
const closeAllTabMenuKey = 'closeAll';

const rootTabKey = '/device/manage';

添加 BasicLayout 初始化方法

1
2
3
4
5
6
7
8
constructor(props) {
super(props);
this.state = {
useableTabs: [],
activedTabs: [],
activeKey: null,
};
}

添加 componentWillReceiveProps 生命周期方法

用于在更新了 menuData 之后初始化 tabs 相关数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
componentWillReceiveProps(nextProps) {
const { menuData } = this.props;
if (nextProps.menuData !== menuData) {
const { menuData: nextMenuData } = nextProps;
const useableTabs = transferMenuTreeToList(nextMenuData);
if (showPageTabs) router.push(rootTabKey);
this.setState({
activedTabs: useableTabs.filter(item => item.key === rootTabKey),
activeKey: rootTabKey,
useableTabs,
});
}
}

添加点击设置 tabs 状态的方法

1
2
3
4
5
6
7
8
9
10
11
12
handleActiveTabClick = (event) => {
const { key } = event;
const { useableTabs, activedTabs } = this.state;

router.push(key);
if (!_find(activedTabs, { key })) {
this.setState({
activedTabs: [...activedTabs, _find(useableTabs, { key })],
});
}
this.setState({ activeKey: key });
};

添加 tabs 操作处理方法

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
handleTabChange = (key) => {
this.setState({ activeKey: key });
router.push(key);
};

onEdit = (targetKey, action) => {
this[action](targetKey);
};

remove = (targetKey) => {
const { activedTabs } = this.state;
if (targetKey === rootTabKey) return;
this.setState({
activedTabs: activedTabs.filter(item => item.key !== targetKey),
activeKey: activedTabs[_findIndex(activedTabs, { key: targetKey }) - 1].key,
});
};

handleTabsMenuClick = (event) => {
const { key } = event;
const { activeKey, activedTabs } = this.state;

if (key === closeCurrentTabMenuKey) {
this.remove(activeKey);
} else if (key === closeOthersTabMenuKey) {
this.setState({
activedTabs: activedTabs.filter(item => item.key === activeKey || item.key === rootTabKey),
activeKey,
});
} else if (key === closeAllTabMenuKey) {
this.setState({
activedTabs: activedTabs.filter(item => item.key === rootTabKey),
activeKey: rootTabKey,
});
}
};

更新 render() 方法

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
const {
navTheme,
menuData,
breadcrumbNameMap,
fixedHeader,

// 添加以下两个变量
showPageTabs,
loading,
} = this.props;

const { activedTabs, activeKey } = this.state;

const menu = (
<Menu onClick={this.handleTabsMenuClick}>
<Menu.Item key={closeCurrentTabMenuKey}>关闭当前标签页</Menu.Item>
<Menu.Item key={closeOthersTabMenuKey}>关闭其他标签页</Menu.Item>
<Menu.Item key={closeAllTabMenuKey}>关闭全部标签页</Menu.Item>
</Menu>
);
const operations = (
<Dropdown overlay={menu}>
<a className={styles.antDropdownLink} href="#">
标签操作&nbsp;
<Icon type="down" />
</a>
</Dropdown>
);
const showTabsOrNot =
showPageTabs && activedTabs && activedTabs.length
? (
<Tabs
// className={styles.tabs}
activeKey={activeKey}
// animated
onChange={this.handleTabChange}
tabBarExtraContent={operations}
tabBarStyle={{ margin: 0 }}
tabPosition="top"
tabBarGutter={-1}
hideAdd
type="editable-card"
onEdit={this.onEdit}
>
{activedTabs.map(item => {
return (
<TabPane tab={item.tab} key={item.key} closable={item.key !== rootTabKey}>
{/* <Route key={item.key} path={item.path} component={item.content} exact={item.exact} /> */}
<item.content />
{/* {children} */}
</TabPane>
);
})}
</Tabs>
)
: children;

<SiderMenu
logo={logo}
theme={navTheme}
onCollapse={this.handleMenuCollapse}
menuData={menuData}
isMobile={isMobile}
{...this.props}

// 添加侧边菜单点击处理事件
handleActiveTabClick={this.handleActiveTabClick}
/>

<Header
menuData={menuData}
handleMenuCollapse={this.handleMenuCollapse}
logo={logo}
isMobile={isMobile}
{...this.props}

// 添加 Header 点击处理事件
handleActiveTabClick={this.handleActiveTabClick}
/>

<Content className={styles.content} style={contentStyle}>
{showTabsOrNot}
</Content>

// Spin 组件包裹 ContainerQuery
<Spin spinning={loading}>
<ContainerQuery query={query}>
// ...
</ContainerQuery>
</Spin>

// 添加 loading
export default connect(({ global, setting, menu: menuModel, loading }) => ({
collapsed: global.collapsed,
layout: setting.layout,
menuData: menuModel.menuData,
breadcrumbNameMap: menuModel.breadcrumbNameMap,
loading: loading.effects['menu/getMenuData'],
...setting,
}))(props => (
<Media query="(max-width: 599px)">
{isMobile => <BasicLayout {...props} isMobile={isMobile} />}
</Media>
));

以上,完成了对 BasicLayout.js 改造。其中分别为 SiderMenuHeader 组件注入了点击处理方法。

components/SiderMenu/BaseMenu.js 中添加 onClick 事件接收并绑定该事件方法。在 layouts/Header.jshandleMenuClick 方法中为除了 logout 的路由 router.push('path') 其后添加 handleActiveTabClick({ key: 'path'}) 即可。

补充

BasicLayout.less

1
2
3
.antDropdownLink {
margin-right: 8px;
}

showPageTabs

  • showPageTabs 在 defaultSetting.js 中配置即可。