Commit e221476b authored by jiang's avatar jiang Committed by ddcat1115

Mobile menu (#463)

* Increase the sliding menu

* Add a simple animation

* update mobile menu

* update

* update

* update

* rebase master

* recovery import/first
parent ce84e3f7
import React, { PureComponent } from 'react';
import { Layout, Menu, Icon, Spin, Tag, Dropdown, Avatar, message } from 'antd';
import { Layout, Menu, Icon, Spin, Tag, Dropdown, Avatar, message, Divider } from 'antd';
import moment from 'moment';
import groupBy from 'lodash/groupBy';
import Debounce from 'lodash-decorators/debounce';
import { Link } from 'dva/router';
import NoticeIcon from '../../components/NoticeIcon';
import HeaderSearch from '../../components/HeaderSearch';
import logo from '../../assets/logo.svg';
import styles from './index.less';
const { Header } = Layout;
......@@ -82,7 +84,7 @@ export default class GlobalHeader extends PureComponent {
}
render() {
const {
currentUser, collapsed, fetchingNotices,
currentUser, collapsed, fetchingNotices, isMobile,
} = this.props;
const menu = (
<Menu className={styles.menu} selectedKeys={[]} onClick={this.handleMenuClick}>
......@@ -95,6 +97,14 @@ export default class GlobalHeader extends PureComponent {
const noticeData = this.getNoticeData();
return (
<Header className={styles.header}>
{isMobile && (
[(
<Link to="/" className={styles.logo} key="logo">
<img src={logo} alt="logo" width="32" />
</Link>),
<Divider type="vertical" key="line" />,
]
)}
<Icon
className={styles.trigger}
type={collapsed ? 'menu-unfold' : 'menu-fold'}
......@@ -146,7 +156,7 @@ export default class GlobalHeader extends PureComponent {
<Dropdown overlay={menu}>
<span className={`${styles.action} ${styles.account}`}>
<Avatar size="small" className={styles.avatar} src={currentUser.avatar} />
{currentUser.name}
<span className={styles.name}>{currentUser.name}</span>
</span>
</Dropdown>
) : <Spin size="small" style={{ marginLeft: 8 }} />}
......
......@@ -13,6 +13,20 @@
}
}
.logo {
height: 64px;
line-height: 58px;
vertical-align: top;
display: inline-block;
padding: 0 0 0 24px;
cursor: pointer;
font-size: 20px;
img {
display: inline-block;
vertical-align: middle;
}
}
.menu {
:global(.anticon) {
margin-right: 8px;
......@@ -26,19 +40,13 @@ i.trigger {
font-size: 20px;
line-height: 64px;
cursor: pointer;
transition: all .3s;
transition: all .3s, padding 0s;
padding: 0 24px;
&:hover {
background: @primary-1;
}
}
@media screen and (max-width: @screen-xs) {
.trigger {
display: none;
}
}
.right {
float: right;
height: 100%;
......@@ -73,3 +81,32 @@ i.trigger {
}
}
}
@media only screen and (max-width: @screen-md) {
.header {
:global(.ant-divider-vertical) {
vertical-align: unset;
}
.name {
display: none;
}
i.trigger {
padding: 0 12px;
}
.logo {
padding-right: 12px;
position: relative;
}
.right {
position: absolute;
right: 12px;
top: 0;
background: #fff;
.account {
.avatar {
margin-right: 0;
}
}
}
}
}
import React, { PureComponent } from 'react';
import { Layout, Menu, Icon } from 'antd';
import { Link } from 'dva/router';
import logo from '../../assets/logo.svg';
import styles from './index.less';
import { getMenuData } from '../../common/menu';
const { Sider } = Layout;
const { SubMenu } = Menu;
export default class SiderMenu extends PureComponent {
constructor(props) {
super(props);
this.menus = getMenuData();
this.state = {
openKeys: this.getDefaultCollapsedSubMenus(props),
};
}
getDefaultCollapsedSubMenus(props) {
const { location: { pathname } } = props || this.props;
const snippets = pathname.split('/').slice(1, -1);
const currentPathSnippets = snippets.map((item, index) => {
const arr = snippets.filter((_, i) => i <= index);
return arr.join('/');
});
let currentMenuSelectedKeys = [];
currentPathSnippets.forEach((item) => {
currentMenuSelectedKeys = currentMenuSelectedKeys.concat(this.getSelectedMenuKeys(item));
});
if (currentMenuSelectedKeys.length === 0) {
return ['dashboard'];
}
return currentMenuSelectedKeys;
}
getFlatMenuKeys(menus) {
let keys = [];
menus.forEach((item) => {
if (item.children) {
keys.push(item.path);
keys = keys.concat(this.getFlatMenuKeys(item.children));
} else {
keys.push(item.path);
}
});
return keys;
}
getSelectedMenuKeys = (path) => {
const flatMenuKeys = this.getFlatMenuKeys(this.menus);
if (flatMenuKeys.indexOf(path.replace(/^\//, '')) > -1) {
return [path.replace(/^\//, '')];
}
if (flatMenuKeys.indexOf(path.replace(/^\//, '').replace(/\/$/, '')) > -1) {
return [path.replace(/^\//, '').replace(/\/$/, '')];
}
return flatMenuKeys.filter((item) => {
const itemRegExpStr = `^${item.replace(/:[\w-]+/g, '[\\w-]+')}$`;
const itemRegExp = new RegExp(itemRegExpStr);
return itemRegExp.test(path.replace(/^\//, ''));
});
}
getNavMenuItems(menusData) {
if (!menusData) {
return [];
}
return menusData.map((item) => {
if (!item.name) {
return null;
}
let itemPath;
if (item.path && item.path.indexOf('http') === 0) {
itemPath = item.path;
} else {
itemPath = `/${item.path || ''}`.replace(/\/+/g, '/');
}
if (item.children && item.children.some(child => child.name)) {
return item.hideInMenu ? null :
(
<SubMenu
title={
item.icon ? (
<span>
<Icon type={item.icon} />
<span>{item.name}</span>
</span>
) : item.name
}
key={item.key || item.path}
>
{this.getNavMenuItems(item.children)}
</SubMenu>
);
}
const icon = item.icon && <Icon type={item.icon} />;
return item.hideInMenu ? null :
(
<Menu.Item key={item.key || item.path}>
{
/^https?:\/\//.test(itemPath) ? (
<a href={itemPath} target={item.target}>
{icon}<span>{item.name}</span>
</a>
) : (
<Link
to={itemPath}
target={item.target}
replace={itemPath === this.props.location.pathname}
onClick={this.props.isMobile && (() => { this.props.onCollapse(true); })}
>
{icon}<span>{item.name}</span>
</Link>
)
}
</Menu.Item>
);
});
}
handleOpenChange = (openKeys) => {
const lastOpenKey = openKeys[openKeys.length - 1];
const isMainMenu = this.menus.some(
item => lastOpenKey && (item.key === lastOpenKey || item.path === lastOpenKey)
);
this.setState({
openKeys: isMainMenu ? [lastOpenKey] : [...openKeys],
});
}
render() {
const { collapsed, location: { pathname }, onCollapse } = this.props;
// Don't show popup menu when it is been collapsed
const menuProps = collapsed ? {} : {
openKeys: this.state.openKeys,
};
return (
<Sider
trigger={null}
collapsible
collapsed={collapsed}
breakpoint="md"
onCollapse={onCollapse}
width={256}
className={styles.sider}
>
<div className={styles.logo}>
<Link to="/">
<img src={logo} alt="logo" />
<h1>Ant Design Pro</h1>
</Link>
</div>
<Menu
theme="dark"
mode="inline"
{...menuProps}
onOpenChange={this.handleOpenChange}
selectedKeys={this.getSelectedMenuKeys(pathname)}
style={{ padding: '16px 0', width: '100%' }}
>
{this.getNavMenuItems(this.menus)}
</Menu>
</Sider>
);
}
}
import 'rc-drawer-menu/assets/index.css';
import React, { PureComponent } from 'react';
import { Layout, Menu, Icon } from 'antd';
import { Link } from 'dva/router';
import logo from '../../assets/logo.svg';
import styles from './index.less';
import { getMenuData } from '../../common/menu';
import DrawerMenu from 'rc-drawer-menu';
import SiderMenu from './SiderMenu';
const { Sider } = Layout;
const { SubMenu } = Menu;
export default class SiderMenu extends PureComponent {
constructor(props) {
super(props);
this.menus = getMenuData();
this.state = {
openKeys: this.getDefaultCollapsedSubMenus(props),
};
}
export default class Index extends PureComponent {
onCollapse = (collapsed) => {
this.props.dispatch({
type: 'global/changeLayoutCollapsed',
payload: collapsed,
});
}
getDefaultCollapsedSubMenus(props) {
const { location: { pathname } } = props || this.props;
const snippets = pathname.split('/').slice(1, -1);
const currentPathSnippets = snippets.map((item, index) => {
const arr = snippets.filter((_, i) => i <= index);
return arr.join('/');
});
let currentMenuSelectedKeys = [];
currentPathSnippets.forEach((item) => {
currentMenuSelectedKeys = currentMenuSelectedKeys.concat(this.getSelectedMenuKeys(item));
});
if (currentMenuSelectedKeys.length === 0) {
return ['dashboard'];
}
return currentMenuSelectedKeys;
}
getFlatMenuKeys(menus) {
let keys = [];
menus.forEach((item) => {
if (item.children) {
keys.push(item.path);
keys = keys.concat(this.getFlatMenuKeys(item.children));
} else {
keys.push(item.path);
}
});
return keys;
}
getSelectedMenuKeys = (path) => {
const flatMenuKeys = this.getFlatMenuKeys(this.menus);
if (flatMenuKeys.indexOf(path.replace(/^\//, '')) > -1) {
return [path.replace(/^\//, '')];
}
if (flatMenuKeys.indexOf(path.replace(/^\//, '').replace(/\/$/, '')) > -1) {
return [path.replace(/^\//, '').replace(/\/$/, '')];
}
return flatMenuKeys.filter((item) => {
const itemRegExpStr = `^${item.replace(/:[\w-]+/g, '[\\w-]+')}$`;
const itemRegExp = new RegExp(itemRegExpStr);
return itemRegExp.test(path.replace(/^\//, ''));
});
}
getNavMenuItems(menusData) {
if (!menusData) {
return [];
}
return menusData.map((item) => {
if (!item.name) {
return null;
}
let itemPath;
if (item.path && item.path.indexOf('http') === 0) {
itemPath = item.path;
} else {
itemPath = `/${item.path || ''}`.replace(/\/+/g, '/');
}
if (item.children && item.children.some(child => child.name)) {
return item.hideInMenu ? null :
(
<SubMenu
title={
item.icon ? (
<span>
<Icon type={item.icon} />
<span>{item.name}</span>
</span>
) : item.name
}
key={item.key || item.path}
>
{this.getNavMenuItems(item.children)}
</SubMenu>
);
}
const icon = item.icon && <Icon type={item.icon} />;
return item.hideInMenu ? null :
(
<Menu.Item key={item.key || item.path}>
{
/^https?:\/\//.test(itemPath) ? (
<a href={itemPath} target={item.target}>
{icon}<span>{item.name}</span>
</a>
) : (
<Link
to={itemPath}
target={item.target}
replace={itemPath === this.props.location.pathname}
>
{icon}<span>{item.name}</span>
</Link>
)
}
</Menu.Item>
);
});
}
handleOpenChange = (openKeys) => {
const lastOpenKey = openKeys[openKeys.length - 1];
const isMainMenu = this.menus.some(
item => lastOpenKey && (item.key === lastOpenKey || item.path === lastOpenKey)
);
this.setState({
openKeys: isMainMenu ? [lastOpenKey] : [...openKeys],
});
}
render() {
const { collapsed, location: { pathname } } = this.props;
// Don't show popup menu when it is been collapsed
const menuProps = collapsed ? {} : {
openKeys: this.state.openKeys,
};
return (
<Sider
trigger={null}
collapsible
collapsed={collapsed}
breakpoint="md"
onCollapse={this.onCollapse}
width={256}
className={styles.sider}
const { collapsed, isMobile } = this.props;
return isMobile ? (
<DrawerMenu
parent={null}
level={null}
iconChild={null}
open={!collapsed}
onMaskClick={() => { this.onCollapse(true); }}
width="256px"
>
<div className={styles.logo}>
<Link to="/">
<img src={logo} alt="logo" />
<h1>Ant Design Pro</h1>
</Link>
</div>
<Menu
theme="dark"
mode="inline"
{...menuProps}
onOpenChange={this.handleOpenChange}
selectedKeys={this.getSelectedMenuKeys(pathname)}
style={{ padding: '16px 0', width: '100%' }}
>
{this.getNavMenuItems(this.menus)}
</Menu>
</Sider>
<SiderMenu
{...this.props}
isMobile={isMobile}
onCollapse={this.onCollapse}
collapsed={isMobile ? false : collapsed}
/>
</DrawerMenu>
) : (
<SiderMenu
{...this.props}
isMobile={isMobile}
onCollapse={this.onCollapse}
/>
);
}
}
@import "~antd/lib/style/themes/default.less";
@ease-in-out-circ: cubic-bezier(.78, .14, .15, .86);
.logo {
height: 64px;
position: relative;
......@@ -23,10 +23,14 @@
font-weight: 600;
}
}
.sider {
min-height: 100vh;
box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
position: relative;
z-index: 10;
}
:global {
.drawer .drawer-content {
background: #001529;
}
}
......@@ -6,6 +6,7 @@ import { connect } from 'dva';
import { Route, Redirect, Switch } from 'dva/router';
import { ContainerQuery } from 'react-container-query';
import classNames from 'classnames';
import { enquireScreen } from 'enquire-js';
import GlobalHeader from '../components/GlobalHeader';
import GlobalFooter from '../components/GlobalFooter';
import SiderMenu from '../components/SiderMenu';
......@@ -55,11 +56,20 @@ const query = {
},
};
let isMobile;
enquireScreen((b) => {
isMobile = b;
});
class BasicLayout extends React.PureComponent {
static childContextTypes = {
location: PropTypes.object,
breadcrumbNameMap: PropTypes.object,
}
state = {
isMobile,
};
getChildContext() {
const { location, routerData } = this.props;
return {
......@@ -67,6 +77,13 @@ class BasicLayout extends React.PureComponent {
breadcrumbNameMap: routerData,
};
}
componentDidMount() {
enquireScreen((b) => {
this.setState({
isMobile: !!b,
});
});
}
getPageTitle() {
const { routerData, location } = this.props;
const { pathname } = location;
......@@ -86,6 +103,7 @@ class BasicLayout extends React.PureComponent {
collapsed={collapsed}
location={location}
dispatch={dispatch}
isMobile={this.state.isMobile}
/>
<Layout>
<GlobalHeader
......@@ -94,6 +112,7 @@ class BasicLayout extends React.PureComponent {
notices={notices}
collapsed={collapsed}
dispatch={dispatch}
isMobile={this.state.isMobile}
/>
<Content style={{ margin: '24px 24px 0', height: '100%' }}>
<div style={{ minHeight: 'calc(100vh - 260px)' }}>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment