Browse Source

add top nav layout

jim 7 năm trước cách đây
mục cha
commit
b021ef4de0

+ 141 - 0
src/components/GlobalHeader/RightContent.js

@@ -0,0 +1,141 @@
+import React, { PureComponent } from 'react';
+import { Spin, Tag, Menu, Icon, Dropdown, Avatar, Tooltip } from 'antd';
+import moment from 'moment';
+import groupBy from 'lodash/groupBy';
+import NoticeIcon from '../NoticeIcon';
+import HeaderSearch from '../HeaderSearch';
+import styles from './index.less';
+
+export default class GlobalHeaderRight extends PureComponent {
+  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 = (
+          <Tag color={color} style={{ marginRight: 0 }}>
+            {newNotice.extra}
+          </Tag>
+        );
+      }
+      return newNotice;
+    });
+    return groupBy(newNotices, 'type');
+  }
+  render() {
+    const {
+      currentUser,
+      fetchingNotices,
+      onNoticeVisibleChange,
+      onMenuClick,
+      onNoticeClear,
+    } = this.props;
+    const menu = (
+      <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
+        <Menu.Item key="userCenter">
+          <Icon type="user" />个人中心
+        </Menu.Item>
+        <Menu.Item key="userinfo">
+          <Icon type="setting" />设置
+        </Menu.Item>
+        <Menu.Item key="triggerError">
+          <Icon type="close-circle" />触发报错
+        </Menu.Item>
+        <Menu.Divider />
+        <Menu.Item key="logout">
+          <Icon type="logout" />退出登录
+        </Menu.Item>
+      </Menu>
+    );
+    const noticeData = this.getNoticeData();
+    let className = styles.right;
+    if (this.props.theme === 'white') {
+      className = `${styles.right}  ${styles.white}`;
+    }
+    return (
+      <div className={className} >
+        <HeaderSearch
+          className={`${styles.action} ${styles.search}`}
+          placeholder="站内搜索"
+          dataSource={['搜索提示一', '搜索提示二', '搜索提示三']}
+          onSearch={(value) => {
+            console.log('input', value); // eslint-disable-line
+          }}
+          onPressEnter={(value) => {
+            console.log('enter', value); // eslint-disable-line
+          }}
+        />
+        <Tooltip title="使用文档">
+          <a
+            target="_blank"
+            href="http://pro.ant.design/docs/getting-started"
+            rel="noopener noreferrer"
+            className={styles.action}
+          >
+            <Icon type="question-circle-o" />
+          </a>
+        </Tooltip>
+        <NoticeIcon
+          className={styles.action}
+          count={currentUser.notifyCount}
+          onItemClick={(item, tabProps) => {
+            console.log(item, tabProps); // eslint-disable-line
+          }}
+          onClear={onNoticeClear}
+          onPopupVisibleChange={onNoticeVisibleChange}
+          loading={fetchingNotices}
+          popupAlign={{ offset: [20, -16] }}
+        >
+          <NoticeIcon.Tab
+            list={noticeData['通知']}
+            title="通知"
+            emptyText="你已查看所有通知"
+            emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
+          />
+          <NoticeIcon.Tab
+            list={noticeData['消息']}
+            title="消息"
+            emptyText="您已读完所有消息"
+            emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
+          />
+          <NoticeIcon.Tab
+            list={noticeData['待办']}
+            title="待办"
+            emptyText="你已完成所有待办"
+            emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
+          />
+        </NoticeIcon>
+        {currentUser.name ? (
+          <Dropdown overlay={menu}>
+            <span className={`${styles.action} ${styles.account}`}>
+              <Avatar
+                size="small"
+                className={styles.avatar}
+                src={currentUser.avatar}
+              />
+              <span className={styles.name}>{currentUser.name}</span>
+            </span>
+          </Dropdown>
+        ) : (
+          <Spin size="small" style={{ marginLeft: 8 }} />
+        )}
+      </div>
+    );
+  }
+}

+ 17 - 123
src/components/GlobalHeader/index.js

@@ -1,148 +1,42 @@
 import React, { PureComponent } from 'react';
-import { Menu, Icon, Spin, Tag, Dropdown, Avatar, Divider, Tooltip } from 'antd';
-import moment from 'moment';
-import groupBy from 'lodash/groupBy';
 import Debounce from 'lodash-decorators/debounce';
+import { Icon, Divider } from 'antd';
 import { Link } from 'dva/router';
-import NoticeIcon from '../NoticeIcon';
-import HeaderSearch from '../HeaderSearch';
 import styles from './index.less';
+import RightContent from './RightContent';
 
 export default class GlobalHeader extends PureComponent {
   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 = <Tag color={color} style={{ marginRight: 0 }}>{newNotice.extra}</Tag>;
-      }
-      return newNotice;
-    });
-    return groupBy(newNotices, 'type');
-  }
-  toggle = () => {
-    const { collapsed, onCollapse } = this.props;
-    onCollapse(!collapsed);
-    this.triggerResizeEvent();
-  }
   @Debounce(600)
-  triggerResizeEvent() { // eslint-disable-line
+  triggerResizeEvent() {  // eslint-disable-line
     const event = document.createEvent('HTMLEvents');
     event.initEvent('resize', true, false);
     window.dispatchEvent(event);
   }
+  toggle = () => {
+    const { collapsed, onCollapse } = this.props;
+    onCollapse(!collapsed);
+    this.triggerResizeEvent();
+  };
   render() {
-    const {
-      currentUser, collapsed, fetchingNotices, isMobile, logo,
-      onNoticeVisibleChange, onMenuClick, onNoticeClear,
-    } = this.props;
-    const menu = (
-      <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
-        <Menu.Item key="userCenter"><Icon type="user" />个人中心</Menu.Item>
-        <Menu.Item key="userinfo"><Icon type="setting" />设置</Menu.Item>
-        <Menu.Item key="triggerError"><Icon type="close-circle" />触发报错</Menu.Item>
-        <Menu.Divider />
-        <Menu.Item key="logout"><Icon type="logout" />退出登录</Menu.Item>
-      </Menu>
-    );
-    const noticeData = this.getNoticeData();
+    const { collapsed, isMobile, logo } = this.props;
     return (
       <div className={styles.header}>
-        {isMobile && (
-          [
-            (
-              <Link to="/" className={styles.logo} key="logo">
-                <img src={logo} alt="logo" width="32" />
-              </Link>
-            ),
-            <Divider type="vertical" key="line" />,
-          ]
-        )}
+        {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'}
           onClick={this.toggle}
         />
-        <div className={styles.right}>
-          <HeaderSearch
-            className={`${styles.action} ${styles.search}`}
-            placeholder="站内搜索"
-            dataSource={['搜索提示一', '搜索提示二', '搜索提示三']}
-            onSearch={(value) => {
-              console.log('input', value); // eslint-disable-line
-            }}
-            onPressEnter={(value) => {
-              console.log('enter', value); // eslint-disable-line
-            }}
-          />
-          <Tooltip title="使用文档">
-            <a
-              target="_blank"
-              href="http://pro.ant.design/docs/getting-started"
-              rel="noopener noreferrer"
-              className={styles.action}
-            >
-              <Icon type="question-circle-o" />
-            </a >
-          </Tooltip>
-          <NoticeIcon
-            className={styles.action}
-            count={currentUser.notifyCount}
-            onItemClick={(item, tabProps) => {
-              console.log(item, tabProps); // eslint-disable-line
-            }}
-            onClear={onNoticeClear}
-            onPopupVisibleChange={onNoticeVisibleChange}
-            loading={fetchingNotices}
-            popupAlign={{ offset: [20, -16] }}
-          >
-            <NoticeIcon.Tab
-              list={noticeData['通知']}
-              title="通知"
-              emptyText="你已查看所有通知"
-              emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
-            />
-            <NoticeIcon.Tab
-              list={noticeData['消息']}
-              title="消息"
-              emptyText="您已读完所有消息"
-              emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
-            />
-            <NoticeIcon.Tab
-              list={noticeData['待办']}
-              title="待办"
-              emptyText="你已完成所有待办"
-              emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
-            />
-          </NoticeIcon>
-          {currentUser.name ? (
-            <Dropdown overlay={menu}>
-              <span className={`${styles.action} ${styles.account}`}>
-                <Avatar size="small" className={styles.avatar} src={currentUser.avatar} />
-                <span className={styles.name}>{currentUser.name}</span>
-              </span>
-            </Dropdown>
-          ) : <Spin size="small" style={{ marginLeft: 8 }} />}
-        </div>
+
+        <RightContent {...this.props} />
       </div>
     );
   }

+ 23 - 7
src/components/GlobalHeader/index.less

@@ -1,10 +1,10 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
 
 .header {
   height: 64px;
   padding: 0 12px 0 0;
   background: #fff;
-  box-shadow: 0 1px 4px rgba(0, 21, 41, .08);
+  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
   position: relative;
 }
 
@@ -42,7 +42,7 @@ i.trigger {
   font-size: 20px;
   line-height: 64px;
   cursor: pointer;
-  transition: all .3s, padding 0s;
+  transition: all 0.3s, padding 0s;
   padding: 0 24px;
   &:hover {
     background: @primary-1;
@@ -56,7 +56,7 @@ i.trigger {
     cursor: pointer;
     padding: 0 12px;
     display: inline-block;
-    transition: all .3s;
+    transition: all 0.3s;
     height: 100%;
     > i {
       font-size: 16px;
@@ -69,8 +69,7 @@ i.trigger {
     }
   }
   .search {
-    padding: 0;
-    margin: 0 12px;
+    padding: 0 12px;
     &:hover {
       background: transparent;
     }
@@ -79,12 +78,29 @@ i.trigger {
     .avatar {
       margin: 20px 8px 20px 0;
       color: @primary-color;
-      background: rgba(255, 255, 255, .85);
+      background: rgba(255, 255, 255, 0.85);
       vertical-align: middle;
     }
   }
 }
 
+.white {
+  height: 64px;
+  .action {
+    color: rgba(255, 255, 255, 0.85);
+    > i {
+      color: rgba(255, 255, 255, 0.85);
+    }
+    &:hover,
+    &:global(.ant-popover-open) {
+      background: @primary-color;
+    }
+    :global(.ant-badge) {
+      color: rgba(255, 255, 255, 0.85);
+    }
+  }
+}
+
 @media only screen and (max-width: @screen-md) {
   .header {
     :global(.ant-divider-vertical) {

+ 175 - 0
src/components/SiderMenu/BaseMeun.js

@@ -0,0 +1,175 @@
+import React, { PureComponent } from 'react';
+import { Menu, Icon } from 'antd';
+import { Link } from 'dva/router';
+import pathToRegexp from 'path-to-regexp';
+import { urlToList } from '../utils/pathTools';
+import styles from './index.less';
+
+const { SubMenu } = Menu;
+
+// Allow menu.js config icon as string or ReactNode
+//   icon: 'setting',
+//   icon: 'http://demo.com/icon.png',
+//   icon: <Icon type="setting" />,
+const getIcon = (icon) => {
+  if (typeof icon === 'string' && icon.indexOf('http') === 0) {
+    return <img src={icon} alt="icon" className={styles.icon} />;
+  }
+  if (typeof icon === 'string') {
+    return <Icon type={icon} />;
+  }
+  return icon;
+};
+
+export const getMeunMatcheys = (flatMenuKeys, path) => {
+  return flatMenuKeys.filter((item) => {
+    return pathToRegexp(item).test(path);
+  });
+};
+
+export default class BaseMeun extends PureComponent {
+  constructor(props) {
+    super(props);
+    this.menus = props.menuData;
+    this.flatMenuKeys = this.getFlatMenuKeys(props.menuData);
+  }
+  /**
+   * Recursively flatten the data
+   * [{path:string},{path:string}] => {path,path2}
+   * @param  menus
+   */
+  getFlatMenuKeys(menus) {
+    let keys = [];
+    menus.forEach((item) => {
+      if (item.children) {
+        keys = keys.concat(this.getFlatMenuKeys(item.children));
+      }
+      keys.push(item.path);
+    });
+    return keys;
+  }
+  /**
+   * 获得菜单子节点
+   * @memberof SiderMenu
+   */
+  getNavMenuItems = (menusData) => {
+    if (!menusData) {
+      return [];
+    }
+    return menusData
+      .filter(item => item.name && !item.hideInMenu)
+      .map((item) => {
+        // make dom
+        const ItemDom = this.getSubMenuOrItem(item);
+        return this.checkPermissionItem(item.authority, ItemDom);
+      })
+      .filter(item => item);
+  };
+  // Get the currently selected menu
+  getSelectedMenuKeys = () => {
+    const { location: { pathname } } = this.props;
+    return urlToList(pathname).map(itemPath =>
+      getMeunMatcheys(this.flatMenuKeys, itemPath).pop(),
+    );
+  };
+  /**
+   * get SubMenu or Item
+   */
+  getSubMenuOrItem = (item) => {
+    if (item.children && item.children.some(child => child.name)) {
+      return (
+        <SubMenu
+          title={
+            item.icon ? (
+              <span>
+                {getIcon(item.icon)}
+                <span>{item.name}</span>
+              </span>
+            ) : (
+              item.name
+            )
+          }
+          key={item.path}
+        >
+          {this.getNavMenuItems(item.children)}
+        </SubMenu>
+      );
+    } else {
+      return (
+        <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>
+      );
+    }
+  };
+  /**
+   * 判断是否是http链接.返回 Link 或 a
+   * Judge whether it is http link.return a or Link
+   * @memberof SiderMenu
+   */
+  getMenuItemPath = (item) => {
+    const itemPath = this.conversionPath(item.path);
+    const icon = getIcon(item.icon);
+    const { target, name } = item;
+    // Is it a http link
+    if (/^https?:\/\//.test(itemPath)) {
+      return (
+        <a href={itemPath} target={target}>
+          {icon}
+          <span>{name}</span>
+        </a>
+      );
+    }
+    return (
+      <Link
+        to={itemPath}
+        target={target}
+        replace={itemPath === this.props.location.pathname}
+        onClick={
+          this.props.isMobile
+            ? () => {
+                this.props.onCollapse(true);
+              }
+            : undefined
+        }
+      >
+        {icon}
+        <span>{name}</span>
+      </Link>
+    );
+  };
+  // permission to check
+  checkPermissionItem = (authority, ItemDom) => {
+    if (this.props.Authorized && this.props.Authorized.check) {
+      const { check } = this.props.Authorized;
+      return check(authority, ItemDom);
+    }
+    return ItemDom;
+  };
+  conversionPath = (path) => {
+    if (path && path.indexOf('http') === 0) {
+      return path;
+    } else {
+      return `/${path || ''}`.replace(/\/+/g, '/');
+    }
+  };
+  render() {
+    const { openKeys } = this.props;
+    // if pathname can't match, use the nearest parent's key
+    let selectedKeys = this.getSelectedMenuKeys();
+    if (!selectedKeys.length && openKeys) {
+      selectedKeys = [openKeys[openKeys.length - 1]];
+    }
+    return (
+      <Menu
+        key="Menu"
+        theme="dark"
+        mode="inline"
+        onOpenChange={this.props.handleOpenChange}
+        selectedKeys={selectedKeys}
+        style={this.props.style}
+        {...this.props}
+      >
+        {this.getNavMenuItems(this.menus)}
+      </Menu>
+    );
+  }
+}

+ 15 - 158
src/components/SiderMenu/SiderMenu.js

@@ -1,32 +1,12 @@
 import React, { PureComponent } from 'react';
-import { Layout, Menu, Icon } from 'antd';
-import pathToRegexp from 'path-to-regexp';
+import { Layout } from 'antd';
 import { Link } from 'dva/router';
 import styles from './index.less';
+import BaseMeun, { getMeunMatcheys } from './BaseMeun';
 import { urlToList } from '../utils/pathTools';
 
-const { Sider } = Layout;
-const { SubMenu } = Menu;
-
-// Allow menu.js config icon as string or ReactNode
-//   icon: 'setting',
-//   icon: 'http://demo.com/icon.png',
-//   icon: <Icon type="setting" />,
-const getIcon = (icon) => {
-  if (typeof icon === 'string' && icon.indexOf('http') === 0) {
-    return <img src={icon} alt="icon" className={styles.icon} />;
-  }
-  if (typeof icon === 'string') {
-    return <Icon type={icon} />;
-  }
-  return icon;
-};
 
-export const getMeunMatcheys = (flatMenuKeys, path) => {
-  return flatMenuKeys.filter((item) => {
-    return pathToRegexp(item).test(path);
-  });
-};
+const { Sider } = Layout;
 
 export default class SiderMenu extends PureComponent {
   constructor(props) {
@@ -45,19 +25,6 @@ export default class SiderMenu extends PureComponent {
     }
   }
   /**
-   * Convert pathname to openKeys
-   * /list/search/articles = > ['list','/list/search']
-   * @param  props
-   */
-  getDefaultCollapsedSubMenus(props) {
-    const { location: { pathname } } = props || this.props;
-    return urlToList(pathname)
-      .map((item) => {
-        return getMeunMatcheys(this.flatMenuKeys, item)[0];
-      })
-      .filter(item => item);
-  }
-  /**
    * Recursively flatten the data
    * [{path:string},{path:string}] => {path,path2}
    * @param  menus
@@ -73,115 +40,17 @@ export default class SiderMenu extends PureComponent {
     return keys;
   }
   /**
-   * 判断是否是http链接.返回 Link 或 a
-   * Judge whether it is http link.return a or Link
-   * @memberof SiderMenu
-   */
-  getMenuItemPath = (item) => {
-    const itemPath = this.conversionPath(item.path);
-    const icon = getIcon(item.icon);
-    const { target, name } = item;
-    // Is it a http link
-    if (/^https?:\/\//.test(itemPath)) {
-      return (
-        <a href={itemPath} target={target}>
-          {icon}
-          <span>{name}</span>
-        </a>
-      );
-    }
-    return (
-      <Link
-        to={itemPath}
-        target={target}
-        replace={itemPath === this.props.location.pathname}
-        onClick={
-          this.props.isMobile
-            ? () => {
-                this.props.onCollapse(true);
-              }
-            : undefined
-        }
-      >
-        {icon}
-        <span>{name}</span>
-      </Link>
-    );
-  };
-  /**
-   * get SubMenu or Item
-   */
-  getSubMenuOrItem = (item) => {
-    if (item.children && item.children.some(child => child.name)) {
-      return (
-        <SubMenu
-          title={
-            item.icon ? (
-              <span>
-                {getIcon(item.icon)}
-                <span>{item.name}</span>
-              </span>
-            ) : (
-              item.name
-            )
-          }
-          key={item.path}
-        >
-          {this.getNavMenuItems(item.children)}
-        </SubMenu>
-      );
-    } else {
-      return (
-        <Menu.Item key={item.path}>{this.getMenuItemPath(item)}</Menu.Item>
-      );
-    }
-  };
-  /**
-   * 获得菜单子节点
-   * @memberof SiderMenu
+   * Convert pathname to openKeys
+   * /list/search/articles = > ['list','/list/search']
+   * @param  props
    */
-  getNavMenuItems = (menusData) => {
-    if (!menusData) {
-      return [];
-    }
-    return menusData
-      .filter(item => item.name && !item.hideInMenu)
+  getDefaultCollapsedSubMenus(props) {
+    const { location: { pathname } } = props || this.props;
+    return urlToList(pathname)
       .map((item) => {
-        // make dom
-        const ItemDom = this.getSubMenuOrItem(item);
-        return this.checkPermissionItem(item.authority, ItemDom);
+        return getMeunMatcheys(this.flatMenuKeys, item)[0];
       })
       .filter(item => item);
-  };
-  // Get the currently selected menu
-  getSelectedMenuKeys = () => {
-    const { location: { pathname } } = this.props;
-    return urlToList(pathname).map(itemPath =>
-      getMeunMatcheys(this.flatMenuKeys, itemPath).pop(),
-    );
-  };
-  // conversion Path
-  // 转化路径
-  conversionPath = (path) => {
-    if (path && path.indexOf('http') === 0) {
-      return path;
-    } else {
-      return `/${path || ''}`.replace(/\/+/g, '/');
-    }
-  };
-  // permission to check
-  checkPermissionItem = (authority, ItemDom) => {
-    if (this.props.Authorized && this.props.Authorized.check) {
-      const { check } = this.props.Authorized;
-      return check(authority, ItemDom);
-    }
-    return ItemDom;
-  };
-  isMainMenu = (key) => {
-    return this.menus.some(
-      item =>
-        key && (item.key === key || item.path === key),
-    );
   }
   handleOpenChange = (openKeys) => {
     const lastOpenKey = openKeys[openKeys.length - 1];
@@ -193,17 +62,6 @@ export default class SiderMenu extends PureComponent {
   render() {
     const { logo, collapsed, onCollapse } = this.props;
     const { openKeys } = this.state;
-    // Don't show popup menu when it is been collapsed
-    const menuProps = collapsed
-      ? {}
-      : {
-        openKeys,
-      };
-    // if pathname can't match, use the nearest parent's key
-    let selectedKeys = this.getSelectedMenuKeys();
-    if (!selectedKeys.length) {
-      selectedKeys = [openKeys[openKeys.length - 1]];
-    }
     return (
       <Sider
         trigger={null}
@@ -220,17 +78,16 @@ export default class SiderMenu extends PureComponent {
             <h1>Ant Design Pro</h1>
           </Link>
         </div>
-        <Menu
+        <BaseMeun
+          {...this.props}
           key="Menu"
           theme="dark"
           mode="inline"
-          {...menuProps}
+          handleOpenChange={this.handleOpenChange}
+          openKeys={collapsed ? [] : openKeys}
           onOpenChange={this.handleOpenChange}
-          selectedKeys={selectedKeys}
           style={{ padding: '16px 0', width: '100%' }}
-        >
-          {this.getNavMenuItems(this.menus)}
-        </Menu>
+        />
       </Sider>
     );
   }

+ 1 - 1
src/components/SiderMenu/SilderMenu.test.js

@@ -1,4 +1,4 @@
-import { getMeunMatcheys } from './SiderMenu';
+import { getMeunMatcheys } from './BaseMeun';
 
 const meun = [
   '/dashboard',

+ 26 - 0
src/components/TopNavHeader/index.js

@@ -0,0 +1,26 @@
+import React, { PureComponent } from 'react';
+import { Link } from 'dva/router';
+import RightContent from '../GlobalHeader/RightContent';
+import BaseMeun from '../SiderMenu/BaseMeun';
+import styles from './index.less';
+
+export default class TopNavHeader extends PureComponent {
+  render() {
+    return (
+      <div className={styles.main}>
+        <div className={styles.left}>
+          <div className={styles.logo} key="logo">
+            <Link to="/">
+              <img src={this.props.logo} alt="logo" />
+              <h1>Ant Design Pro</h1>
+            </Link>
+          </div>
+          <BaseMeun {...this.props} style={{ padding: '9px 0' }} />
+        </div>
+        <div className={styles.right}>
+          <RightContent theme="white" {...this.props} />
+        </div>
+      </div>
+    );
+  }
+}

+ 31 - 0
src/components/TopNavHeader/index.less

@@ -0,0 +1,31 @@
+.main {
+  display: flex;
+  height: 64px;
+  margin: auto;
+  max-width: 1200px;
+  .left {
+    flex: 1;
+    display: flex;
+    .logo {
+      width: 160px;
+      height: 64px;
+      position: relative;
+      line-height: 64px;
+      transition: all 0.3s;
+      overflow: hidden;
+      img {
+        display: inline-block;
+        vertical-align: middle;
+        height: 32px;
+      }
+      h1 {
+        color: #fff;
+        display: inline-block;
+        vertical-align: middle;
+        font-size: 16px;
+        margin: 0 0 0 12px;
+        font-weight: 400;
+      }
+    }
+  }
+}

+ 31 - 9
src/layouts/BasicLayout.js

@@ -9,6 +9,7 @@ import classNames from 'classnames';
 import { enquireScreen } from 'enquire-js';
 import GlobalHeader from '../components/GlobalHeader';
 import GlobalFooter from '../components/GlobalFooter';
+import TopNavHeader from '../components/TopNavHeader';
 import SiderMenu from '../components/SiderMenu';
 import NotFound from '../routes/Exception/404';
 import { getRoutes } from '../utils/utils';
@@ -153,26 +154,45 @@ class BasicLayout extends React.PureComponent {
     }
   }
   render() {
+    const isFluid = this.props.layout === 'fluid';
     const {
       currentUser, collapsed, fetchingNotices, notices, routerData, match, location,
     } = this.props;
     const bashRedirect = this.getBashRedirect();
     const layout = (
       <Layout>
-        <SiderMenu
-          logo={logo}
+        {isFluid && !isMobile ? null : (
+          <SiderMenu
+            logo={logo}
           // 不带Authorized参数的情况下如果没有权限,会强制跳到403界面
           // If you do not have the Authorized parameter
           // you will be forced to jump to the 403 interface without permission
-          Authorized={Authorized}
-          menuData={getMenuData()}
-          collapsed={collapsed}
-          location={location}
-          isMobile={this.state.isMobile}
-          onCollapse={this.handleMenuCollapse}
-        />
+            Authorized={Authorized}
+            menuData={getMenuData()}
+            collapsed={collapsed}
+            location={location}
+            isMobile={this.state.isMobile}
+            onCollapse={this.handleMenuCollapse}
+          />
+        )}
         <Layout>
           <Header style={{ padding: 0 }}>
+            {isFluid && !isMobile ? (
+              <TopNavHeader
+                logo={logo}
+                mode="horizontal"
+                location={location}
+                menuData={getMenuData()}
+                isMobile={this.state.isMobile}
+                onNoticeClear={this.handleNoticeClear}
+                onCollapse={this.handleMenuCollapse}
+                onMenuClick={this.handleMenuClick}
+                onNoticeVisibleChange={this.handleNoticeVisibleChange}
+                notices={notices}
+                currentUser={currentUser}
+                fetchingNotices={fetchingNotices}
+              />
+          ) : (
             <GlobalHeader
               logo={logo}
               currentUser={currentUser}
@@ -185,6 +205,7 @@ class BasicLayout extends React.PureComponent {
               onMenuClick={this.handleMenuClick}
               onNoticeVisibleChange={this.handleNoticeVisibleChange}
             />
+                    )}
           </Header>
           <Content style={{ margin: '24px 24px 0', height: '100%' }}>
             <Switch>
@@ -253,6 +274,7 @@ class BasicLayout extends React.PureComponent {
 export default connect(({ user, global, loading }) => ({
   currentUser: user.currentUser,
   collapsed: global.collapsed,
+  layout: global.layout,
   fetchingNotices: loading.effects['global/fetchNotices'],
   notices: global.notices,
 }))(BasicLayout);

+ 17 - 0
src/layouts/GridContent.js

@@ -0,0 +1,17 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import styles from './GridContent.less';
+
+class GridContent extends PureComponent {
+  render() {
+    let className = `${styles.main}`;
+    if (this.props.layout === 'fluid') {
+      className = `${styles.main} ${styles.fluid}`;
+    }
+    return <div className={className}>{this.props.children}</div>;
+  }
+}
+
+export default connect(({ global }) => ({
+  layout: global.layout,
+}))(GridContent);

+ 9 - 0
src/layouts/GridContent.less

@@ -0,0 +1,9 @@
+.main {
+  width: 100%;
+  height: 100%;
+  min-height: 100%;
+  &.fluid {
+    max-width: 1200px;
+    margin: 0 auto;
+  }
+}

+ 6 - 1
src/layouts/PageHeaderLayout.js

@@ -1,12 +1,17 @@
 import React from 'react';
 import { Link } from 'dva/router';
 import PageHeader from '../components/PageHeader';
+import GridContent from './GridContent';
 import styles from './PageHeaderLayout.less';
 
 export default ({ children, wrapperClassName, top, ...restProps }) => (
   <div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}>
     {top}
     <PageHeader key="pageheader" {...restProps} linkElement={Link} />
-    {children ? <div className={styles.content}>{children}</div> : null}
+    {children ? (
+      <div className={styles.content}>
+        <GridContent>{children}</GridContent>
+      </div>
+    ) : null}
   </div>
 );

+ 1 - 1
src/layouts/PageHeaderLayout.less

@@ -1,4 +1,4 @@
-@import "~antd/lib/style/themes/default.less";
+@import '~antd/lib/style/themes/default.less';
 
 .content {
   margin: 24px 24px 0;

+ 7 - 0
src/models/global.js

@@ -5,6 +5,7 @@ export default {
 
   state: {
     collapsed: false,
+    layout: 'fluid',
     notices: [],
   },
 
@@ -40,6 +41,12 @@ export default {
         collapsed: payload,
       };
     },
+    changeLayout(state, { payload }) {
+      return {
+        ...state,
+        layout: payload,
+      };
+    },
     saveNotices(state, { payload }) {
       return {
         ...state,

+ 4 - 3
src/routes/Dashboard/Analysis.js

@@ -1,4 +1,4 @@
-import React, { Component, Fragment } from 'react';
+import React, { Component } from 'react';
 import { connect } from 'dva';
 import {
   Row,
@@ -14,6 +14,7 @@ import {
   Dropdown,
 } from 'antd';
 import numeral from 'numeral';
+import GridContent from '../../layouts/GridContent';
 import {
   ChartCard,
   yuan,
@@ -241,7 +242,7 @@ export default class Analysis extends Component {
     };
 
     return (
-      <Fragment>
+      <GridContent>
         <Row gutter={24}>
           <Col {...topColResponsiveProps}>
             <ChartCard
@@ -482,7 +483,7 @@ export default class Analysis extends Component {
             ))}
           </Tabs>
         </Card>
-      </Fragment>
+      </GridContent>
     );
   }
 }

+ 4 - 3
src/routes/Dashboard/Monitor.js

@@ -1,7 +1,8 @@
-import React, { PureComponent, Fragment } from 'react';
+import React, { PureComponent } from 'react';
 import { connect } from 'dva';
 import { Row, Col, Card, Tooltip } from 'antd';
 import numeral from 'numeral';
+import GridContent from '../../layouts/GridContent';
 import Authorized from '../../utils/Authorized';
 import { Pie, WaterWave, Gauge, TagCloud } from '../../components/Charts';
 import NumberInfo from '../../components/NumberInfo';
@@ -35,7 +36,7 @@ export default class Monitor extends PureComponent {
     const { tags } = monitor;
 
     return (
-      <Fragment>
+      <GridContent>
         <Row gutter={24}>
           <Col xl={18} lg={24} md={24} sm={24} xs={24} style={{ marginBottom: 24 }}>
             <Card title="活动实时交易情况" bordered={false}>
@@ -164,7 +165,7 @@ export default class Monitor extends PureComponent {
             </Card>
           </Col>
         </Row>
-      </Fragment>
+      </GridContent>
     );
   }
 }

+ 243 - 137
src/routes/UserProfile/UserCenter.js

@@ -3,14 +3,28 @@ import { connect } from 'dva';
 import { Link } from 'dva/router';
 import moment from 'moment';
 import numeral from 'numeral';
-import { List, Card, Row, Col, Icon, Dropdown,
-  Menu, Avatar, Tag, Divider, Tooltip, Spin, Input } from 'antd';
+import {
+  List,
+  Card,
+  Row,
+  Col,
+  Icon,
+  Dropdown,
+  Menu,
+  Avatar,
+  Tag,
+  Divider,
+  Tooltip,
+  Spin,
+  Input,
+} from 'antd';
 import AvatarList from '../../components/AvatarList';
 import { formatWan } from '../../utils/utils';
 import styles from './UserCenter.less';
-import stylesArticles from '../List/Articles.less';
-import stylesApplications from '../List/Applications.less';
 import stylesProjects from '../List/Projects.less';
+import stylesArticles from './List/Articles.less';
+import stylesApplications from './List/Applications.less';
+import GridContent from '../layouts/GridContent';
 
 @connect(({ list, loading, user, project }) => ({
   list,
@@ -26,7 +40,7 @@ export default class UserCenter extends PureComponent {
     newTags: [],
     inputVisible: false,
     inputValue: '',
-  }
+  };
 
   componentDidMount() {
     const { dispatch } = this.props;
@@ -46,33 +60,39 @@ export default class UserCenter extends PureComponent {
 
   onTabChange = (key) => {
     this.setState({ key });
-  }
+  };
 
   showInput = () => {
     this.setState({ inputVisible: true }, () => this.input.focus());
-  }
+  };
 
   saveInputRef = (input) => {
     this.input = input;
-  }
+  };
 
   handleInputChange = (e) => {
     this.setState({ inputValue: e.target.value });
-  }
+  };
 
   handleInputConfirm = () => {
     const { state } = this;
     const { inputValue } = state;
     let { newTags } = state;
-    if (inputValue && newTags.filter(tag => tag.label === inputValue).length === 0) {
-      newTags = [...newTags, { key: `new-${newTags.length}`, label: inputValue }];
+    if (
+      inputValue &&
+      newTags.filter(tag => tag.label === inputValue).length === 0
+    ) {
+      newTags = [
+        ...newTags,
+        { key: `new-${newTags.length}`, label: inputValue },
+      ];
     }
     this.setState({
       newTags,
       inputVisible: false,
       inputValue: '',
     });
-  }
+  };
 
   renderArticles = (list, loading) => {
     const IconText = ({ type, text }) => (
@@ -81,11 +101,14 @@ export default class UserCenter extends PureComponent {
         {text}
       </span>
     );
-    const ListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => (
+    const ListContent = ({
+      data: { content, updatedAt, avatar, owner, href },
+    }) => (
       <div className={stylesArticles.listContent}>
         <div className={stylesArticles.description}>{content}</div>
         <div className={stylesArticles.extra}>
-          <Avatar src={avatar} size="small" /><a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a>
+          <Avatar src={avatar} size="small" />
+          <a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a>
           <em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em>
         </div>
       </div>
@@ -108,9 +131,14 @@ export default class UserCenter extends PureComponent {
             ]}
           >
             <List.Item.Meta
-              title={(
-                <a className={stylesArticles.listItemMetaTitle} href={item.href}>{item.title}</a>
-              )}
+              title={
+                <a
+                  className={stylesArticles.listItemMetaTitle}
+                  href={item.href}
+                >
+                  {item.title}
+                </a>
+              }
               description={
                 <span>
                   <Tag>Ant Design</Tag>
@@ -124,19 +152,37 @@ export default class UserCenter extends PureComponent {
         )}
       />
     );
-  }
+  };
 
   renderApplications = (list, loading) => {
     const itemMenu = (
       <Menu>
         <Menu.Item>
-          <a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a>
+          <a
+            target="_blank"
+            rel="noopener noreferrer"
+            href="http://www.alipay.com/"
+          >
+            1st menu item
+          </a>
         </Menu.Item>
         <Menu.Item>
-          <a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a>
+          <a
+            target="_blank"
+            rel="noopener noreferrer"
+            href="http://www.taobao.com/"
+          >
+            2nd menu item
+          </a>
         </Menu.Item>
         <Menu.Item>
-          <a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">3d menu item</a>
+          <a
+            target="_blank"
+            rel="noopener noreferrer"
+            href="http://www.tmall.com/"
+          >
+            3d menu item
+          </a>
         </Menu.Item>
       </Menu>
     );
@@ -165,10 +211,18 @@ export default class UserCenter extends PureComponent {
               hoverable
               bodyStyle={{ paddingBottom: 20 }}
               actions={[
-                <Tooltip title="下载"><Icon type="download" /></Tooltip>,
-                <Tooltip title="编辑"><Icon type="edit" /></Tooltip>,
-                <Tooltip title="分享"><Icon type="share-alt" /></Tooltip>,
-                <Dropdown overlay={itemMenu}><Icon type="ellipsis" /></Dropdown>,
+                <Tooltip title="下载">
+                  <Icon type="download" />
+                </Tooltip>,
+                <Tooltip title="编辑">
+                  <Icon type="edit" />
+                </Tooltip>,
+                <Tooltip title="分享">
+                  <Icon type="share-alt" />
+                </Tooltip>,
+                <Dropdown overlay={itemMenu}>
+                  <Icon type="ellipsis" />
+                </Dropdown>,
               ]}
             >
               <Card.Meta
@@ -186,7 +240,7 @@ export default class UserCenter extends PureComponent {
         )}
       />
     );
-  }
+  };
 
   renderProjects = (list, loading) => {
     return (
@@ -211,15 +265,13 @@ export default class UserCenter extends PureComponent {
                 <span>{moment(item.updatedAt).fromNow()}</span>
                 <div className={stylesProjects.avatarList}>
                   <AvatarList size="mini">
-                    {
-                      item.members.map(member => (
-                        <AvatarList.Item
-                          key={`${item.id}-avatar-${member.id}`}
-                          src={member.avatar}
-                          tips={member.name}
-                        />
-                      ))
-                    }
+                    {item.members.map(member => (
+                      <AvatarList.Item
+                        key={`${item.id}-avatar-${member.id}`}
+                        src={member.avatar}
+                        tips={member.name}
+                      />
+                    ))}
                   </AvatarList>
                 </div>
               </div>
@@ -228,22 +280,137 @@ export default class UserCenter extends PureComponent {
         )}
       />
     );
+  };
+  renderContent() {
+    const { newTags, inputVisible, inputValue } = this.state;
+    const {
+      currentUser,
+      project: { notice },
+      projectLoading,
+    } = this.props;
+    return (
+      <div>
+        <div className={styles.avatarHolder}>
+          <img alt="" src={currentUser.avatar} />
+          <div className={styles.name}>{currentUser.name}</div>
+          <div>{currentUser.signature}</div>
+        </div>
+        <div className={styles.detail}>
+          <p>
+            <i className={styles.title} />
+            {currentUser.title}
+          </p>
+          <p>
+            <i className={styles.group} />
+            {currentUser.group}
+          </p>
+          <p>
+            <i className={styles.address} />
+            {currentUser.geographic.province.label}
+            {currentUser.geographic.city.label}
+          </p>
+        </div>
+        <Divider dashed />
+        <div className={styles.tags}>
+          <div className={styles.tagsTitle}>标签</div>
+          {currentUser.tags.map(item => <Tag key={item.key}>{item.label}</Tag>)}
+          <Tag style={{ background: '#fff', borderStyle: 'dashed' }}>
+            <Icon type="plus" />
+          </Tag>
+        </div>
+        <Divider dashed />
+        <div className={styles.team}>
+          <div className={styles.teamTitle}>团队</div>
+          <Spin spinning={projectLoading}>
+            <Row gutter={36}>
+              {notice.map(item => (
+                <Col key={item.id} className={styles.item} lg={24} xl={12}>
+                  <Avatar size="small" src={item.logo} />
+                  {item.member}
+                </Col>
+              ))}
+            </Row>
+          </Spin>
+        </div>
+        <Divider dashed />
+        <div className={styles.tags}>
+          <div className={styles.tagsTitle}>标签</div>
+          {currentUser.tags
+            .concat(newTags)
+            .map(item => <Tag key={item.key}>{item.label}</Tag>)}
+          {inputVisible && (
+            <Input
+              ref={this.saveInputRef}
+              type="text"
+              size="small"
+              style={{ width: 78 }}
+              value={inputValue}
+              onChange={this.handleInputChange}
+              onBlur={this.handleInputConfirm}
+              onPressEnter={this.handleInputConfirm}
+            />
+          )}
+          {!inputVisible && (
+            <Tag
+              onClick={this.showInput}
+              style={{ background: '#fff', borderStyle: 'dashed' }}
+            >
+              <Icon type="plus" />
+            </Tag>
+          )}
+        </div>
+        <Divider style={{ marginTop: 16 }} dashed />
+        <div className={styles.team}>
+          <div className={styles.teamTitle}>团队</div>
+          <Spin spinning={projectLoading}>
+            <Row gutter={36}>
+              {notice.map(item => (
+                <Col key={item.id} lg={24} xl={12}>
+                  <Link to={item.href}>
+                    <Avatar size="small" src={item.logo} />
+                    {item.member}
+                  </Link>
+                </Col>
+              ))}
+            </Row>
+          </Spin>
+        </div>
+      </div>
+    );
   }
-
   render() {
-    const { key, newTags, inputVisible, inputValue } = this.state;
-    const { list: { list }, listLoading, currentUser, currentUserLoading,
-      project: { notice }, projectLoading } = this.props;
-    const operationTabList = [{
-      key: 'article',
-      tab: <span>文章 <span style={{ fontSize: 14 }}>(8)</span></span>,
-    }, {
-      key: 'application',
-      tab: <span>应用 <span style={{ fontSize: 14 }}>(8)</span></span>,
-    }, {
-      key: 'project',
-      tab: <span>项目 <span style={{ fontSize: 14 }}>(8)</span></span>,
-    }];
+    const {
+      list: { list },
+      listLoading,
+      currentUser,
+      currentUserLoading,
+    } = this.props;
+    const operationTabList = [
+      {
+        key: 'article',
+        tab: (
+          <span>
+            文章 <span style={{ fontSize: 14 }}>(8)</span>
+          </span>
+        ),
+      },
+      {
+        key: 'application',
+        tab: (
+          <span>
+            应用 <span style={{ fontSize: 14 }}>(8)</span>
+          </span>
+        ),
+      },
+      {
+        key: 'project',
+        tab: (
+          <span>
+            项目 <span style={{ fontSize: 14 }}>(8)</span>
+          </span>
+        ),
+      },
+    ];
     const contentMap = {
       article: this.renderArticles(list, listLoading),
       application: this.renderApplications(list, listLoading),
@@ -251,94 +418,33 @@ export default class UserCenter extends PureComponent {
     };
 
     return (
-      <div className={styles.userCenter}>
-        <Row gutter={24}>
-          <Col lg={7} md={24}>
-            <Card
-              bordered={false}
-              style={{ marginBottom: 24 }}
-              loading={currentUserLoading}
-            >
-              {
-                currentUser && Object.keys(currentUser).length ?
-                  (
-                    <div>
-                      <div className={styles.avatarHolder}>
-                        <img alt="" src={currentUser.avatar} />
-                        <div className={styles.name}>{currentUser.name}</div>
-                        <div>{currentUser.signature}</div>
-                      </div>
-                      <div className={styles.detail}>
-                        <p><i className={styles.title} />{currentUser.title}</p>
-                        <p><i className={styles.group} />{currentUser.group}</p>
-                        <p><i className={styles.address} />
-                          {currentUser.geographic.province.label}
-                          {currentUser.geographic.city.label}
-                        </p>
-                      </div>
-                      <Divider dashed />
-                      <div className={styles.tags}>
-                        <div className={styles.tagsTitle}>标签</div>
-                        {
-                          currentUser.tags.concat(newTags).map(item =>
-                            <Tag key={item.key}>{item.label}</Tag>)
-                        }
-                        {inputVisible && (
-                          <Input
-                            ref={this.saveInputRef}
-                            type="text"
-                            size="small"
-                            style={{ width: 78 }}
-                            value={inputValue}
-                            onChange={this.handleInputChange}
-                            onBlur={this.handleInputConfirm}
-                            onPressEnter={this.handleInputConfirm}
-                          />
-                        )}
-                        {!inputVisible && (
-                          <Tag
-                            onClick={this.showInput}
-                            style={{ background: '#fff', borderStyle: 'dashed' }}
-                          >
-                            <Icon type="plus" />
-                          </Tag>
-                        )}
-                      </div>
-                      <Divider style={{ marginTop: 16 }} dashed />
-                      <div className={styles.team}>
-                        <div className={styles.teamTitle}>团队</div>
-                        <Spin spinning={projectLoading}>
-                          <Row gutter={36}>
-                            {
-                              notice.map(item => (
-                                <Col key={item.id} lg={24} xl={12}>
-                                  <Link to={item.href}>
-                                    <Avatar size="small" src={item.logo} />
-                                    {item.member}
-                                  </Link>
-                                </Col>
-                              ))
-                            }
-                          </Row>
-                        </Spin>
-                      </div>
-                    </div>
-                  ) : 'loading...'
-              }
-            </Card>
-          </Col>
-          <Col lg={17} md={24}>
-            <Card
-              className={styles.tabsCard}
-              bordered={false}
-              tabList={operationTabList}
-              onTabChange={this.onTabChange}
-            >
-              {contentMap[key]}
-            </Card>
-          </Col>
-        </Row>
-      </div>
+      <GridContent>
+        <div className={styles.userCenter}>
+          <Row gutter={24}>
+            <Col lg={7} md={24}>
+              <Card
+                bordered={false}
+                style={{ marginBottom: 24 }}
+                loading={currentUserLoading}
+              >
+                {currentUser && Object.keys(currentUser).length
+                  ? this.renderContent()
+                  : 'loading...'}
+              </Card>
+            </Col>
+            <Col lg={17} md={24}>
+              <Card
+                className={styles.tabsCard}
+                bordered={false}
+                tabList={operationTabList}
+                onTabChange={this.onTabChange}
+              >
+                {contentMap[this.state.key]}
+              </Card>
+            </Col>
+          </Row>
+        </div>
+      </GridContent>
     );
   }
 }

+ 31 - 37
src/routes/UserProfile/Userinfo/Info.js

@@ -3,7 +3,8 @@ import { connect } from 'dva';
 import { Route, routerRedux, Switch, Redirect } from 'dva/router';
 import { Menu } from 'antd';
 import styles from './Info.less';
-import { getRoutes } from '../../../utils/utils';
+import { getRoutes } from '../../utils/utils';
+import GridContent from '../../layouts/GridContent';
 
 const { Item } = Menu;
 
@@ -71,43 +72,36 @@ export default class Info extends Component {
       return '';
     }
     return (
-      <div
-        className={styles.main}
-        ref={(ref) => {
-          this.main = ref;
-        }}
-      >
-        <div className={styles.leftmenu}>
-          <Menu
-            mode={this.state.mode}
-            selectedKeys={[this.state.selectKey]}
-            onClick={this.selectKey}
-          >
-            {this.getmenu()}
-          </Menu>
+      <GridContent>
+        <div className={styles.main}>
+          <div className={styles.leftmenu}>
+            <Menu
+              mode={this.state.mode}
+              selectedKeys={[this.state.selectKey]}
+              onClick={this.selectKey}
+            >
+              {this.getmenu()}
+            </Menu>
+          </div>
+          <div className={styles.right}>
+            <div className={styles.title}>{this.getRightTitle()}</div>
+            <Switch>
+              {getRoutes(match.path, routerData).map(item => (
+                <Route
+                  key={item.key}
+                  path={item.path}
+                  render={props => (
+                    <item.component {...props} currentUser={currentUser} />
+                  )}
+                  exact={item.exact}
+                />
+              ))}
+              <Redirect exact from="/userinfo" to="/userinfo/base" />
+              <Redirect to="/exception/404" />
+            </Switch>
+          </div>
         </div>
-        <div className={styles.right}>
-          <div className={styles.title}>{this.getRightTitle()}</div>
-          <Switch>
-            {getRoutes(match.path, routerData).map(item => (
-              <Route
-                key={item.key}
-                path={item.path}
-                render={props => (
-                  <item.component {...props} currentUser={currentUser} />
-                )}
-                exact={item.exact}
-              />
-            ))}
-            <Redirect
-              exact
-              from="/user-profile/userinfo"
-              to="/user-profile/userinfo/base"
-            />
-            <Redirect to="/exception/404" />
-          </Switch>
-        </div>
-      </div>
+      </GridContent>
     );
   }
 }