From e221476b348e7c243730dc7b1b22d0e1dbf9b9bd Mon Sep 17 00:00:00 2001 From: jiang <155259966@qq.com> Date: Wed, 27 Dec 2017 14:16:44 +0800 Subject: [PATCH] Mobile menu (#463) * Increase the sliding menu * Add a simple animation * update mobile menu * update * update * update * rebase master * recovery import/first --- package.json | 2 + src/components/GlobalHeader/index.js | 16 ++- src/components/GlobalHeader/index.less | 51 ++++++- src/components/SiderMenu/SiderMenu.js | 162 ++++++++++++++++++++++ src/components/SiderMenu/index.js | 179 ++++--------------------- src/components/SiderMenu/index.less | 8 +- src/layouts/BasicLayout.js | 19 +++ 7 files changed, 272 insertions(+), 165 deletions(-) create mode 100644 src/components/SiderMenu/SiderMenu.js diff --git a/package.json b/package.json index 163c65ca..1c072e77 100755 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "classnames": "^2.2.5", "core-js": "^2.5.1", "dva": "^2.1.0", + "enquire-js": "^0.1.1", "g-cloud": "^1.0.2-beta", "g2": "^2.3.13", "g2-plugin-slider": "^1.2.1", @@ -35,6 +36,7 @@ "numeral": "^2.0.6", "prop-types": "^15.5.10", "qs": "^6.5.0", + "rc-drawer-menu": "^0.5.0", "react": "^16.0.0", "react-container-query": "^0.9.1", "react-document-title": "^2.0.3", diff --git a/src/components/GlobalHeader/index.js b/src/components/GlobalHeader/index.js index fa7aac79..6e39d2f8 100644 --- a/src/components/GlobalHeader/index.js +++ b/src/components/GlobalHeader/index.js @@ -1,10 +1,12 @@ 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 = ( @@ -95,6 +97,14 @@ export default class GlobalHeader extends PureComponent { const noticeData = this.getNoticeData(); return (
+ {isMobile && ( + [( + + logo + ), + , + ] + )} - {currentUser.name} + {currentUser.name} ) : } diff --git a/src/components/GlobalHeader/index.less b/src/components/GlobalHeader/index.less index dc9ac299..057612b4 100644 --- a/src/components/GlobalHeader/index.less +++ b/src/components/GlobalHeader/index.less @@ -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; + } + } + } + } +} diff --git a/src/components/SiderMenu/SiderMenu.js b/src/components/SiderMenu/SiderMenu.js new file mode 100644 index 00000000..e4ea989c --- /dev/null +++ b/src/components/SiderMenu/SiderMenu.js @@ -0,0 +1,162 @@ +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 : + ( + + + {item.name} + + ) : item.name + } + key={item.key || item.path} + > + {this.getNavMenuItems(item.children)} + + ); + } + const icon = item.icon && ; + return item.hideInMenu ? null : + ( + + { + /^https?:\/\//.test(itemPath) ? ( + + {icon}{item.name} + + ) : ( + { this.props.onCollapse(true); })} + > + {icon}{item.name} + + ) + } + + ); + }); + } + 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 ( + +
+ + logo +

Ant Design Pro

+ +
+ + {this.getNavMenuItems(this.menus)} + +
+ ); + } +} diff --git a/src/components/SiderMenu/index.js b/src/components/SiderMenu/index.js index fd015f5b..f8278fc1 100644 --- a/src/components/SiderMenu/index.js +++ b/src/components/SiderMenu/index.js @@ -1,167 +1,40 @@ +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 : - ( - - - {item.name} - - ) : item.name - } - key={item.key || item.path} - > - {this.getNavMenuItems(item.children)} - - ); - } - const icon = item.icon && ; - return item.hideInMenu ? null : - ( - - { - /^https?:\/\//.test(itemPath) ? ( - - {icon}{item.name} - - ) : ( - - {icon}{item.name} - - ) - } - - ); - }); - } - 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 ( - { this.onCollapse(true); }} + width="256px" > -
- - logo -

Ant Design Pro

- -
- - {this.getNavMenuItems(this.menus)} - -
+ + + ) : ( + ); } } diff --git a/src/components/SiderMenu/index.less b/src/components/SiderMenu/index.less index 15d256f3..37c59cb5 100644 --- a/src/components/SiderMenu/index.less +++ b/src/components/SiderMenu/index.less @@ -1,5 +1,5 @@ @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; + } +} diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js index 3593b4d5..fb0a83f4 100644 --- a/src/layouts/BasicLayout.js +++ b/src/layouts/BasicLayout.js @@ -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} />
-- GitLab