diff --git a/src/components/GlobalHeader/index.js b/src/components/GlobalHeader/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fa7aac798af33437358893cb285d011b1e54b15e
--- /dev/null
+++ b/src/components/GlobalHeader/index.js
@@ -0,0 +1,157 @@
+import React, { PureComponent } from 'react';
+import { Layout, Menu, Icon, Spin, Tag, Dropdown, Avatar, message } from 'antd';
+import moment from 'moment';
+import groupBy from 'lodash/groupBy';
+import Debounce from 'lodash-decorators/debounce';
+import NoticeIcon from '../../components/NoticeIcon';
+import HeaderSearch from '../../components/HeaderSearch';
+import styles from './index.less';
+
+const { Header } = Layout;
+
+export default class GlobalHeader extends PureComponent {
+ componentDidMount() {
+ this.props.dispatch({
+ type: 'user/fetchCurrent',
+ });
+ }
+ componentWillUnmount() {
+ this.triggerResizeEvent.cancel();
+ }
+ getNoticeData() {
+ const { notices = [] } = this.props;
+ if (notices.length === 0) {
+ return {};
+ }
+ const newNotices = notices.map((notice) => {
+ const newNotice = { ...notice };
+ if (newNotice.datetime) {
+ newNotice.datetime = moment(notice.datetime).fromNow();
+ }
+ // transform id to item key
+ if (newNotice.id) {
+ newNotice.key = newNotice.id;
+ }
+ if (newNotice.extra && newNotice.status) {
+ const color = ({
+ todo: '',
+ processing: 'blue',
+ urgent: 'red',
+ doing: 'gold',
+ })[newNotice.status];
+ newNotice.extra = {newNotice.extra};
+ }
+ return newNotice;
+ });
+ return groupBy(newNotices, 'type');
+ }
+ handleNoticeClear = (type) => {
+ message.success(`清空了${type}`);
+ this.props.dispatch({
+ type: 'global/clearNotices',
+ payload: type,
+ });
+ }
+ handleNoticeVisibleChange = (visible) => {
+ if (visible) {
+ this.props.dispatch({
+ type: 'global/fetchNotices',
+ });
+ }
+ }
+ handleMenuClick = ({ key }) => {
+ if (key === 'logout') {
+ this.props.dispatch({
+ type: 'login/logout',
+ });
+ }
+ }
+ toggle = () => {
+ const { collapsed } = this.props;
+ this.props.dispatch({
+ type: 'global/changeLayoutCollapsed',
+ payload: !collapsed,
+ });
+ this.triggerResizeEvent();
+ }
+ @Debounce(600)
+ triggerResizeEvent() { // eslint-disable-line
+ const event = document.createEvent('HTMLEvents');
+ event.initEvent('resize', true, false);
+ window.dispatchEvent(event);
+ }
+ render() {
+ const {
+ currentUser, collapsed, fetchingNotices,
+ } = this.props;
+ const menu = (
+
+ );
+ const noticeData = this.getNoticeData();
+ return (
+
+
+
+
{
+ console.log('input', value); // eslint-disable-line
+ }}
+ onPressEnter={(value) => {
+ console.log('enter', value); // eslint-disable-line
+ }}
+ />
+ {
+ console.log(item, tabProps); // eslint-disable-line
+ }}
+ onClear={this.handleNoticeClear}
+ onPopupVisibleChange={this.handleNoticeVisibleChange}
+ loading={fetchingNotices}
+ popupAlign={{ offset: [20, -16] }}
+ >
+
+
+
+
+ {currentUser.name ? (
+
+
+
+ {currentUser.name}
+
+
+ ) : }
+
+
+ );
+ }
+}
diff --git a/src/layouts/BasicLayout.less b/src/components/GlobalHeader/index.less
similarity index 65%
rename from src/layouts/BasicLayout.less
rename to src/components/GlobalHeader/index.less
index dc40a1e56b0039212736afecdea7e496c9b4b6c7..dc9ac299e2f25763194434f4399c451039500ce4 100644
--- a/src/layouts/BasicLayout.less
+++ b/src/components/GlobalHeader/index.less
@@ -7,27 +7,18 @@
position: relative;
}
-.logo {
- height: 64px;
- position: relative;
- line-height: 64px;
- padding-left: (@menu-collapsed-width - 32px) / 2;
- transition: all .3s;
- background: #002140;
- overflow: hidden;
- img {
- display: inline-block;
- vertical-align: middle;
- height: 32px;
+:global {
+ .ant-layout {
+ overflow-x: hidden;
}
- h1 {
- color: #fff;
- display: inline-block;
- vertical-align: middle;
- font-size: 20px;
- margin: 0 0 0 12px;
- font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
- font-weight: 600;
+}
+
+.menu {
+ :global(.anticon) {
+ margin-right: 8px;
+ }
+ :global(.ant-dropdown-menu-item) {
+ width: 160px;
}
}
@@ -82,25 +73,3 @@ i.trigger {
}
}
}
-
-.menu {
- :global(.anticon) {
- margin-right: 8px;
- }
- :global(.ant-dropdown-menu-item) {
- width: 160px;
- }
-}
-
-:global {
- .ant-layout {
- overflow-x: hidden;
- }
-}
-
-.sider {
- min-height: 100vh;
- box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
- position: relative;
- z-index: 10;
-}
diff --git a/src/components/SiderMenu/index.js b/src/components/SiderMenu/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a6d58f47755f8140335decfe160633096e7dfe3
--- /dev/null
+++ b/src/components/SiderMenu/index.js
@@ -0,0 +1,139 @@
+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';
+
+const { Sider } = Layout;
+const { SubMenu } = Menu;
+
+export default class SiderMenu extends PureComponent {
+ constructor(props) {
+ super(props);
+ // 把一级 Layout 的 children 作为菜单项
+ this.menus = props.navData.reduce((arr, current) => arr.concat(current.children), []);
+ this.state = {
+ openKeys: this.getDefaultCollapsedSubMenus(props),
+ };
+ }
+ onCollapse = (collapsed) => {
+ this.props.dispatch({
+ type: 'global/changeLayoutCollapsed',
+ payload: collapsed,
+ });
+ }
+ getDefaultCollapsedSubMenus(props) {
+ const currentMenuSelectedKeys = [...this.getCurrentMenuSelectedKeys(props)];
+ currentMenuSelectedKeys.splice(-1, 1);
+ if (currentMenuSelectedKeys.length === 0) {
+ return ['dashboard'];
+ }
+ return currentMenuSelectedKeys;
+ }
+ getCurrentMenuSelectedKeys(props) {
+ const { location: { pathname } } = props || this.props;
+ const keys = pathname.split('/').slice(1);
+ if (keys.length === 1 && keys[0] === '') {
+ return [this.menus[0].key];
+ }
+ return keys;
+ }
+ getNavMenuItems(menusData, parentPath = '') {
+ if (!menusData) {
+ return [];
+ }
+ return menusData.map((item) => {
+ if (!item.name) {
+ return null;
+ }
+ let itemPath;
+ if (item.path.indexOf('http') === 0) {
+ itemPath = item.path;
+ } else {
+ itemPath = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/');
+ }
+ if (item.children && item.children.some(child => child.name)) {
+ return (
+
+
+ {item.name}
+
+ ) : item.name
+ }
+ key={item.key || item.path}
+ >
+ {this.getNavMenuItems(item.children, itemPath)}
+
+ );
+ }
+ const icon = item.icon && ;
+ return (
+
+ {
+ /^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 } = this.props;
+
+ // Don't show popup menu when it is been collapsed
+ const menuProps = collapsed ? {} : {
+ openKeys: this.state.openKeys,
+ };
+ return (
+
+
+
+
+
Ant Design Pro
+
+
+
+
+ );
+ }
+}
diff --git a/src/components/SiderMenu/index.less b/src/components/SiderMenu/index.less
new file mode 100644
index 0000000000000000000000000000000000000000..15d256f31ef174d08378e9c4dec88725c06013bc
--- /dev/null
+++ b/src/components/SiderMenu/index.less
@@ -0,0 +1,32 @@
+@import "~antd/lib/style/themes/default.less";
+
+.logo {
+ height: 64px;
+ position: relative;
+ line-height: 64px;
+ padding-left: (@menu-collapsed-width - 32px) / 2;
+ transition: all .3s;
+ background: #002140;
+ overflow: hidden;
+ img {
+ display: inline-block;
+ vertical-align: middle;
+ height: 32px;
+ }
+ h1 {
+ color: #fff;
+ display: inline-block;
+ vertical-align: middle;
+ font-size: 20px;
+ margin: 0 0 0 12px;
+ font-family: 'Myriad Pro', 'Helvetica Neue', Arial, Helvetica, sans-serif;
+ font-weight: 600;
+ }
+}
+
+.sider {
+ min-height: 100vh;
+ box-shadow: 2px 0 6px rgba(0, 21, 41, .35);
+ position: relative;
+ z-index: 10;
+}
diff --git a/src/e2e/home.e2e.js b/src/e2e/home.e2e.js
index 6de577b969659d8e5727133fe5d33d06d6e295cf..61b0cd83a89568f5a88364c15196f2ff5b1064e4 100644
--- a/src/e2e/home.e2e.js
+++ b/src/e2e/home.e2e.js
@@ -3,7 +3,7 @@ import Nightmare from 'nightmare';
describe('Homepage', () => {
it('it should have logo text', async () => {
const page = Nightmare().goto('http://localhost:8000');
- const text = await page.evaluate(() => document.body.innerHTML).end();
+ const text = await page.wait('h1').evaluate(() => document.body.innerHTML).end();
expect(text).toContain('Ant Design Pro
');
});
});
diff --git a/src/layouts/BasicLayout.js b/src/layouts/BasicLayout.js
index a46cb5728c6a4cbca0550609defed4daf4c4f753..cf7aeb6634eeaadc88650c8525e1cb9b1577c1f0 100644
--- a/src/layouts/BasicLayout.js
+++ b/src/layouts/BasicLayout.js
@@ -1,23 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Layout, Menu, Icon, Avatar, Dropdown, Tag, message, Spin } from 'antd';
+import { Layout, Icon } from 'antd';
import DocumentTitle from 'react-document-title';
import { connect } from 'dva';
-import { Link, Route, Redirect, Switch } from 'dva/router';
-import moment from 'moment';
-import groupBy from 'lodash/groupBy';
+import { Route, Redirect, Switch } from 'dva/router';
import { ContainerQuery } from 'react-container-query';
import classNames from 'classnames';
-import Debounce from 'lodash-decorators/debounce';
-import HeaderSearch from '../components/HeaderSearch';
-import NoticeIcon from '../components/NoticeIcon';
+import GlobalHeader from '../components/GlobalHeader';
import GlobalFooter from '../components/GlobalFooter';
+import SiderMenu from '../components/SiderMenu';
import NotFound from '../routes/Exception/404';
-import styles from './BasicLayout.less';
-import logo from '../assets/logo.svg';
-const { Header, Sider, Content } = Layout;
-const { SubMenu } = Menu;
+const { Content } = Layout;
const query = {
'screen-xs': {
@@ -45,14 +39,6 @@ class BasicLayout extends React.PureComponent {
location: PropTypes.object,
breadcrumbNameMap: PropTypes.object,
}
- constructor(props) {
- super(props);
- // 把一级 Layout 的 children 作为菜单项
- this.menus = props.navData.reduce((arr, current) => arr.concat(current.children), []);
- this.state = {
- openKeys: this.getDefaultCollapsedSubMenus(props),
- };
- }
getChildContext() {
const { location, navData, getRouteData } = this.props;
const routeData = getRouteData('BasicLayout');
@@ -68,106 +54,6 @@ class BasicLayout extends React.PureComponent {
});
return { location, breadcrumbNameMap };
}
- componentDidMount() {
- this.props.dispatch({
- type: 'user/fetchCurrent',
- });
- }
- componentWillUnmount() {
- this.triggerResizeEvent.cancel();
- }
- onCollapse = (collapsed) => {
- this.props.dispatch({
- type: 'global/changeLayoutCollapsed',
- payload: collapsed,
- });
- }
- onMenuClick = ({ key }) => {
- if (key === 'logout') {
- this.props.dispatch({
- type: 'login/logout',
- });
- }
- }
- getMenuData = (data, parentPath) => {
- let arr = [];
- data.forEach((item) => {
- if (item.children) {
- arr.push({ path: `${parentPath}/${item.path}`, name: item.name });
- arr = arr.concat(this.getMenuData(item.children, `${parentPath}/${item.path}`));
- }
- });
- return arr;
- }
- getDefaultCollapsedSubMenus(props) {
- const currentMenuSelectedKeys = [...this.getCurrentMenuSelectedKeys(props)];
- currentMenuSelectedKeys.splice(-1, 1);
- if (currentMenuSelectedKeys.length === 0) {
- return ['dashboard'];
- }
- return currentMenuSelectedKeys;
- }
- getCurrentMenuSelectedKeys(props) {
- const { location: { pathname } } = props || this.props;
- const keys = pathname.split('/').slice(1);
- if (keys.length === 1 && keys[0] === '') {
- return [this.menus[0].key];
- }
- return keys;
- }
- getNavMenuItems(menusData, parentPath = '') {
- if (!menusData) {
- return [];
- }
- return menusData.map((item) => {
- if (!item.name) {
- return null;
- }
- let itemPath;
- if (item.path.indexOf('http') === 0) {
- itemPath = item.path;
- } else {
- itemPath = `${parentPath}/${item.path || ''}`.replace(/\/+/g, '/');
- }
- if (item.children && item.children.some(child => child.name)) {
- return (
-
-
- {item.name}
-
- ) : item.name
- }
- key={item.key || item.path}
- >
- {this.getNavMenuItems(item.children, itemPath)}
-
- );
- }
- const icon = item.icon && ;
- return (
-
- {
- /^https?:\/\//.test(itemPath) ? (
-
- {icon}{item.name}
-
- ) : (
-
- {icon}{item.name}
-
- )
- }
-
- );
- });
- }
getPageTitle() {
const { location, getRouteData } = this.props;
const { pathname } = location;
@@ -179,175 +65,37 @@ class BasicLayout extends React.PureComponent {
});
return title;
}
- getNoticeData() {
- const { notices = [] } = this.props;
- if (notices.length === 0) {
- return {};
- }
- const newNotices = notices.map((notice) => {
- const newNotice = { ...notice };
- if (newNotice.datetime) {
- newNotice.datetime = moment(notice.datetime).fromNow();
- }
- // transform id to item key
- if (newNotice.id) {
- newNotice.key = newNotice.id;
- }
- if (newNotice.extra && newNotice.status) {
- const color = ({
- todo: '',
- processing: 'blue',
- urgent: 'red',
- doing: 'gold',
- })[newNotice.status];
- newNotice.extra = {newNotice.extra};
+ getMenuData = (data, parentPath) => {
+ let arr = [];
+ data.forEach((item) => {
+ if (item.children) {
+ arr.push({ path: `${parentPath}/${item.path}`, name: item.name });
+ arr = arr.concat(this.getMenuData(item.children, `${parentPath}/${item.path}`));
}
- return newNotice;
- });
- return groupBy(newNotices, 'type');
- }
- 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],
});
- }
- toggle = () => {
- const { collapsed } = this.props;
- this.props.dispatch({
- type: 'global/changeLayoutCollapsed',
- payload: !collapsed,
- });
- this.triggerResizeEvent();
- }
- @Debounce(600)
- triggerResizeEvent() { // eslint-disable-line
- const event = document.createEvent('HTMLEvents');
- event.initEvent('resize', true, false);
- window.dispatchEvent(event);
- }
- handleNoticeClear = (type) => {
- message.success(`清空了${type}`);
- this.props.dispatch({
- type: 'global/clearNotices',
- payload: type,
- });
- }
- handleNoticeVisibleChange = (visible) => {
- if (visible) {
- this.props.dispatch({
- type: 'global/fetchNotices',
- });
- }
+ return arr;
}
render() {
- const { currentUser, collapsed, fetchingNotices, getRouteData } = this.props;
-
- const menu = (
-
- );
- const noticeData = this.getNoticeData();
-
- // Don't show popup menu when it is been collapsed
- const menuProps = collapsed ? {} : {
- openKeys: this.state.openKeys,
- };
+ const {
+ currentUser, collapsed, fetchingNotices, notices, getRouteData, navData, location, dispatch,
+ } = this.props;
const layout = (
-
-
-
-
-
Ant Design Pro
-
-
-
-
+ navData={navData}
+ location={location}
+ dispatch={dispatch}
+ />
-
-
-
-
{
- console.log('input', value); // eslint-disable-line
- }}
- onPressEnter={(value) => {
- console.log('enter', value); // eslint-disable-line
- }}
- />
- {
- console.log(item, tabProps); // eslint-disable-line
- }}
- onClear={this.handleNoticeClear}
- onPopupVisibleChange={this.handleNoticeVisibleChange}
- loading={fetchingNotices}
- popupAlign={{ offset: [20, -16] }}
- >
-
-
-
-
- {currentUser.name ? (
-
-
-
- {currentUser.name}
-
-
- ) : }
-
-
+