Commit b021ef4d authored by jim's avatar jim

add top nav layout

parent 2e59803f
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>
);
}
}
import React, { PureComponent } from 'react'; 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 Debounce from 'lodash-decorators/debounce';
import { Icon, Divider } from 'antd';
import { Link } from 'dva/router'; import { Link } from 'dva/router';
import NoticeIcon from '../NoticeIcon';
import HeaderSearch from '../HeaderSearch';
import styles from './index.less'; import styles from './index.less';
import RightContent from './RightContent';
export default class GlobalHeader extends PureComponent { export default class GlobalHeader extends PureComponent {
componentWillUnmount() { componentWillUnmount() {
this.triggerResizeEvent.cancel(); 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) @Debounce(600)
triggerResizeEvent() { // eslint-disable-line triggerResizeEvent() { // eslint-disable-line
const event = document.createEvent('HTMLEvents'); const event = document.createEvent('HTMLEvents');
event.initEvent('resize', true, false); event.initEvent('resize', true, false);
window.dispatchEvent(event); window.dispatchEvent(event);
} }
toggle = () => {
const { collapsed, onCollapse } = this.props;
onCollapse(!collapsed);
this.triggerResizeEvent();
};
render() { render() {
const { const { collapsed, isMobile, logo } = this.props;
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();
return ( return (
<div className={styles.header}> <div className={styles.header}>
{isMobile && ( {isMobile && [
[
(
<Link to="/" className={styles.logo} key="logo"> <Link to="/" className={styles.logo} key="logo">
<img src={logo} alt="logo" width="32" /> <img src={logo} alt="logo" width="32" />
</Link> </Link>,
),
<Divider type="vertical" key="line" />, <Divider type="vertical" key="line" />,
] ]}
)}
<Icon <Icon
className={styles.trigger} className={styles.trigger}
type={collapsed ? 'menu-unfold' : 'menu-fold'} type={collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={this.toggle} onClick={this.toggle}
/> />
<div className={styles.right}>
<HeaderSearch <RightContent {...this.props} />
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>
</div> </div>
); );
} }
......
@import "~antd/lib/style/themes/default.less"; @import '~antd/lib/style/themes/default.less';
.header { .header {
height: 64px; height: 64px;
padding: 0 12px 0 0; padding: 0 12px 0 0;
background: #fff; 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; position: relative;
} }
...@@ -42,7 +42,7 @@ i.trigger { ...@@ -42,7 +42,7 @@ i.trigger {
font-size: 20px; font-size: 20px;
line-height: 64px; line-height: 64px;
cursor: pointer; cursor: pointer;
transition: all .3s, padding 0s; transition: all 0.3s, padding 0s;
padding: 0 24px; padding: 0 24px;
&:hover { &:hover {
background: @primary-1; background: @primary-1;
...@@ -56,7 +56,7 @@ i.trigger { ...@@ -56,7 +56,7 @@ i.trigger {
cursor: pointer; cursor: pointer;
padding: 0 12px; padding: 0 12px;
display: inline-block; display: inline-block;
transition: all .3s; transition: all 0.3s;
height: 100%; height: 100%;
> i { > i {
font-size: 16px; font-size: 16px;
...@@ -69,8 +69,7 @@ i.trigger { ...@@ -69,8 +69,7 @@ i.trigger {
} }
} }
.search { .search {
padding: 0; padding: 0 12px;
margin: 0 12px;
&:hover { &:hover {
background: transparent; background: transparent;
} }
...@@ -79,12 +78,29 @@ i.trigger { ...@@ -79,12 +78,29 @@ i.trigger {
.avatar { .avatar {
margin: 20px 8px 20px 0; margin: 20px 8px 20px 0;
color: @primary-color; color: @primary-color;
background: rgba(255, 255, 255, .85); background: rgba(255, 255, 255, 0.85);
vertical-align: middle; 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) { @media only screen and (max-width: @screen-md) {
.header { .header {
:global(.ant-divider-vertical) { :global(.ant-divider-vertical) {
......
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>
);
}
}
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Layout, Menu, Icon } from 'antd'; import { Layout } from 'antd';
import pathToRegexp from 'path-to-regexp';
import { Link } from 'dva/router'; import { Link } from 'dva/router';
import styles from './index.less'; import styles from './index.less';
import BaseMeun, { getMeunMatcheys } from './BaseMeun';
import { urlToList } from '../utils/pathTools'; 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) => { const { Sider } = Layout;
return flatMenuKeys.filter((item) => {
return pathToRegexp(item).test(path);
});
};
export default class SiderMenu extends PureComponent { export default class SiderMenu extends PureComponent {
constructor(props) { constructor(props) {
...@@ -44,19 +24,6 @@ export default class SiderMenu extends PureComponent { ...@@ -44,19 +24,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 * Recursively flatten the data
* [{path:string},{path:string}] => {path,path2} * [{path:string},{path:string}] => {path,path2}
...@@ -73,115 +40,17 @@ export default class SiderMenu extends PureComponent { ...@@ -73,115 +40,17 @@ export default class SiderMenu extends PureComponent {
return keys; return keys;
} }
/** /**
* 判断是否是http链接.返回 Link 或 a * Convert pathname to openKeys
* Judge whether it is http link.return a or Link * /list/search/articles = > ['list','/list/search']
* @memberof SiderMenu * @param props
*/
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
*/ */
getNavMenuItems = (menusData) => { getDefaultCollapsedSubMenus(props) {
if (!menusData) { const { location: { pathname } } = props || this.props;
return []; return urlToList(pathname)
}
return menusData
.filter(item => item.name && !item.hideInMenu)
.map((item) => { .map((item) => {
// make dom return getMeunMatcheys(this.flatMenuKeys, item)[0];
const ItemDom = this.getSubMenuOrItem(item);
return this.checkPermissionItem(item.authority, ItemDom);
}) })
.filter(item => item); .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) => { handleOpenChange = (openKeys) => {
const lastOpenKey = openKeys[openKeys.length - 1]; const lastOpenKey = openKeys[openKeys.length - 1];
...@@ -193,17 +62,6 @@ export default class SiderMenu extends PureComponent { ...@@ -193,17 +62,6 @@ export default class SiderMenu extends PureComponent {
render() { render() {
const { logo, collapsed, onCollapse } = this.props; const { logo, collapsed, onCollapse } = this.props;
const { openKeys } = this.state; 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 ( return (
<Sider <Sider
trigger={null} trigger={null}
...@@ -220,17 +78,16 @@ export default class SiderMenu extends PureComponent { ...@@ -220,17 +78,16 @@ export default class SiderMenu extends PureComponent {
<h1>Ant Design Pro</h1> <h1>Ant Design Pro</h1>
</Link> </Link>
</div> </div>
<Menu <BaseMeun
{...this.props}
key="Menu" key="Menu"
theme="dark" theme="dark"
mode="inline" mode="inline"
{...menuProps} handleOpenChange={this.handleOpenChange}
openKeys={collapsed ? [] : openKeys}
onOpenChange={this.handleOpenChange} onOpenChange={this.handleOpenChange}
selectedKeys={selectedKeys}
style={{ padding: '16px 0', width: '100%' }} style={{ padding: '16px 0', width: '100%' }}
> />
{this.getNavMenuItems(this.menus)}
</Menu>
</Sider> </Sider>
); );
} }
......
import { getMeunMatcheys } from './SiderMenu'; import { getMeunMatcheys } from './BaseMeun';
const meun = [ const meun = [
'/dashboard', '/dashboard',
......
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>
);
}
}
.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;
}
}
}
}
...@@ -9,6 +9,7 @@ import classNames from 'classnames'; ...@@ -9,6 +9,7 @@ import classNames from 'classnames';
import { enquireScreen } from 'enquire-js'; import { enquireScreen } from 'enquire-js';
import GlobalHeader from '../components/GlobalHeader'; import GlobalHeader from '../components/GlobalHeader';
import GlobalFooter from '../components/GlobalFooter'; import GlobalFooter from '../components/GlobalFooter';
import TopNavHeader from '../components/TopNavHeader';
import SiderMenu from '../components/SiderMenu'; import SiderMenu from '../components/SiderMenu';
import NotFound from '../routes/Exception/404'; import NotFound from '../routes/Exception/404';
import { getRoutes } from '../utils/utils'; import { getRoutes } from '../utils/utils';
...@@ -153,12 +154,14 @@ class BasicLayout extends React.PureComponent { ...@@ -153,12 +154,14 @@ class BasicLayout extends React.PureComponent {
} }
} }
render() { render() {
const isFluid = this.props.layout === 'fluid';
const { const {
currentUser, collapsed, fetchingNotices, notices, routerData, match, location, currentUser, collapsed, fetchingNotices, notices, routerData, match, location,
} = this.props; } = this.props;
const bashRedirect = this.getBashRedirect(); const bashRedirect = this.getBashRedirect();
const layout = ( const layout = (
<Layout> <Layout>
{isFluid && !isMobile ? null : (
<SiderMenu <SiderMenu
logo={logo} logo={logo}
// 不带Authorized参数的情况下如果没有权限,会强制跳到403界面 // 不带Authorized参数的情况下如果没有权限,会强制跳到403界面
...@@ -171,8 +174,25 @@ class BasicLayout extends React.PureComponent { ...@@ -171,8 +174,25 @@ class BasicLayout extends React.PureComponent {
isMobile={this.state.isMobile} isMobile={this.state.isMobile}
onCollapse={this.handleMenuCollapse} onCollapse={this.handleMenuCollapse}
/> />
)}
<Layout> <Layout>
<Header style={{ padding: 0 }}> <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 <GlobalHeader
logo={logo} logo={logo}
currentUser={currentUser} currentUser={currentUser}
...@@ -185,6 +205,7 @@ class BasicLayout extends React.PureComponent { ...@@ -185,6 +205,7 @@ class BasicLayout extends React.PureComponent {
onMenuClick={this.handleMenuClick} onMenuClick={this.handleMenuClick}
onNoticeVisibleChange={this.handleNoticeVisibleChange} onNoticeVisibleChange={this.handleNoticeVisibleChange}
/> />
)}
</Header> </Header>
<Content style={{ margin: '24px 24px 0', height: '100%' }}> <Content style={{ margin: '24px 24px 0', height: '100%' }}>
<Switch> <Switch>
...@@ -253,6 +274,7 @@ class BasicLayout extends React.PureComponent { ...@@ -253,6 +274,7 @@ class BasicLayout extends React.PureComponent {
export default connect(({ user, global, loading }) => ({ export default connect(({ user, global, loading }) => ({
currentUser: user.currentUser, currentUser: user.currentUser,
collapsed: global.collapsed, collapsed: global.collapsed,
layout: global.layout,
fetchingNotices: loading.effects['global/fetchNotices'], fetchingNotices: loading.effects['global/fetchNotices'],
notices: global.notices, notices: global.notices,
}))(BasicLayout); }))(BasicLayout);
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);
.main {
width: 100%;
height: 100%;
min-height: 100%;
&.fluid {
max-width: 1200px;
margin: 0 auto;
}
}
import React from 'react'; import React from 'react';
import { Link } from 'dva/router'; import { Link } from 'dva/router';
import PageHeader from '../components/PageHeader'; import PageHeader from '../components/PageHeader';
import GridContent from './GridContent';
import styles from './PageHeaderLayout.less'; import styles from './PageHeaderLayout.less';
export default ({ children, wrapperClassName, top, ...restProps }) => ( export default ({ children, wrapperClassName, top, ...restProps }) => (
<div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}> <div style={{ margin: '-24px -24px 0' }} className={wrapperClassName}>
{top} {top}
<PageHeader key="pageheader" {...restProps} linkElement={Link} /> <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> </div>
); );
@import "~antd/lib/style/themes/default.less"; @import '~antd/lib/style/themes/default.less';
.content { .content {
margin: 24px 24px 0; margin: 24px 24px 0;
......
...@@ -5,6 +5,7 @@ export default { ...@@ -5,6 +5,7 @@ export default {
state: { state: {
collapsed: false, collapsed: false,
layout: 'fluid',
notices: [], notices: [],
}, },
...@@ -40,6 +41,12 @@ export default { ...@@ -40,6 +41,12 @@ export default {
collapsed: payload, collapsed: payload,
}; };
}, },
changeLayout(state, { payload }) {
return {
...state,
layout: payload,
};
},
saveNotices(state, { payload }) { saveNotices(state, { payload }) {
return { return {
...state, ...state,
......
import React, { Component, Fragment } from 'react'; import React, { Component } from 'react';
import { connect } from 'dva'; import { connect } from 'dva';
import { import {
Row, Row,
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
Dropdown, Dropdown,
} from 'antd'; } from 'antd';
import numeral from 'numeral'; import numeral from 'numeral';
import GridContent from '../../layouts/GridContent';
import { import {
ChartCard, ChartCard,
yuan, yuan,
...@@ -241,7 +242,7 @@ export default class Analysis extends Component { ...@@ -241,7 +242,7 @@ export default class Analysis extends Component {
}; };
return ( return (
<Fragment> <GridContent>
<Row gutter={24}> <Row gutter={24}>
<Col {...topColResponsiveProps}> <Col {...topColResponsiveProps}>
<ChartCard <ChartCard
...@@ -482,7 +483,7 @@ export default class Analysis extends Component { ...@@ -482,7 +483,7 @@ export default class Analysis extends Component {
))} ))}
</Tabs> </Tabs>
</Card> </Card>
</Fragment> </GridContent>
); );
} }
} }
import React, { PureComponent, Fragment } from 'react'; import React, { PureComponent } from 'react';
import { connect } from 'dva'; import { connect } from 'dva';
import { Row, Col, Card, Tooltip } from 'antd'; import { Row, Col, Card, Tooltip } from 'antd';
import numeral from 'numeral'; import numeral from 'numeral';
import GridContent from '../../layouts/GridContent';
import Authorized from '../../utils/Authorized'; import Authorized from '../../utils/Authorized';
import { Pie, WaterWave, Gauge, TagCloud } from '../../components/Charts'; import { Pie, WaterWave, Gauge, TagCloud } from '../../components/Charts';
import NumberInfo from '../../components/NumberInfo'; import NumberInfo from '../../components/NumberInfo';
...@@ -35,7 +36,7 @@ export default class Monitor extends PureComponent { ...@@ -35,7 +36,7 @@ export default class Monitor extends PureComponent {
const { tags } = monitor; const { tags } = monitor;
return ( return (
<Fragment> <GridContent>
<Row gutter={24}> <Row gutter={24}>
<Col xl={18} lg={24} md={24} sm={24} xs={24} style={{ marginBottom: 24 }}> <Col xl={18} lg={24} md={24} sm={24} xs={24} style={{ marginBottom: 24 }}>
<Card title="活动实时交易情况" bordered={false}> <Card title="活动实时交易情况" bordered={false}>
...@@ -164,7 +165,7 @@ export default class Monitor extends PureComponent { ...@@ -164,7 +165,7 @@ export default class Monitor extends PureComponent {
</Card> </Card>
</Col> </Col>
</Row> </Row>
</Fragment> </GridContent>
); );
} }
} }
...@@ -3,14 +3,28 @@ import { connect } from 'dva'; ...@@ -3,14 +3,28 @@ import { connect } from 'dva';
import { Link } from 'dva/router'; import { Link } from 'dva/router';
import moment from 'moment'; import moment from 'moment';
import numeral from 'numeral'; import numeral from 'numeral';
import { List, Card, Row, Col, Icon, Dropdown, import {
Menu, Avatar, Tag, Divider, Tooltip, Spin, Input } from 'antd'; List,
Card,
Row,
Col,
Icon,
Dropdown,
Menu,
Avatar,
Tag,
Divider,
Tooltip,
Spin,
Input,
} from 'antd';
import AvatarList from '../../components/AvatarList'; import AvatarList from '../../components/AvatarList';
import { formatWan } from '../../utils/utils'; import { formatWan } from '../../utils/utils';
import styles from './UserCenter.less'; import styles from './UserCenter.less';
import stylesArticles from '../List/Articles.less';
import stylesApplications from '../List/Applications.less';
import stylesProjects from '../List/Projects.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 }) => ({ @connect(({ list, loading, user, project }) => ({
list, list,
...@@ -26,7 +40,7 @@ export default class UserCenter extends PureComponent { ...@@ -26,7 +40,7 @@ export default class UserCenter extends PureComponent {
newTags: [], newTags: [],
inputVisible: false, inputVisible: false,
inputValue: '', inputValue: '',
} };
componentDidMount() { componentDidMount() {
const { dispatch } = this.props; const { dispatch } = this.props;
...@@ -46,33 +60,39 @@ export default class UserCenter extends PureComponent { ...@@ -46,33 +60,39 @@ export default class UserCenter extends PureComponent {
onTabChange = (key) => { onTabChange = (key) => {
this.setState({ key }); this.setState({ key });
} };
showInput = () => { showInput = () => {
this.setState({ inputVisible: true }, () => this.input.focus()); this.setState({ inputVisible: true }, () => this.input.focus());
} };
saveInputRef = (input) => { saveInputRef = (input) => {
this.input = input; this.input = input;
} };
handleInputChange = (e) => { handleInputChange = (e) => {
this.setState({ inputValue: e.target.value }); this.setState({ inputValue: e.target.value });
} };
handleInputConfirm = () => { handleInputConfirm = () => {
const { state } = this; const { state } = this;
const { inputValue } = state; const { inputValue } = state;
let { newTags } = state; let { newTags } = state;
if (inputValue && newTags.filter(tag => tag.label === inputValue).length === 0) { if (
newTags = [...newTags, { key: `new-${newTags.length}`, label: inputValue }]; inputValue &&
newTags.filter(tag => tag.label === inputValue).length === 0
) {
newTags = [
...newTags,
{ key: `new-${newTags.length}`, label: inputValue },
];
} }
this.setState({ this.setState({
newTags, newTags,
inputVisible: false, inputVisible: false,
inputValue: '', inputValue: '',
}); });
} };
renderArticles = (list, loading) => { renderArticles = (list, loading) => {
const IconText = ({ type, text }) => ( const IconText = ({ type, text }) => (
...@@ -81,11 +101,14 @@ export default class UserCenter extends PureComponent { ...@@ -81,11 +101,14 @@ export default class UserCenter extends PureComponent {
{text} {text}
</span> </span>
); );
const ListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => ( const ListContent = ({
data: { content, updatedAt, avatar, owner, href },
}) => (
<div className={stylesArticles.listContent}> <div className={stylesArticles.listContent}>
<div className={stylesArticles.description}>{content}</div> <div className={stylesArticles.description}>{content}</div>
<div className={stylesArticles.extra}> <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> <em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em>
</div> </div>
</div> </div>
...@@ -108,9 +131,14 @@ export default class UserCenter extends PureComponent { ...@@ -108,9 +131,14 @@ export default class UserCenter extends PureComponent {
]} ]}
> >
<List.Item.Meta <List.Item.Meta
title={( title={
<a className={stylesArticles.listItemMetaTitle} href={item.href}>{item.title}</a> <a
)} className={stylesArticles.listItemMetaTitle}
href={item.href}
>
{item.title}
</a>
}
description={ description={
<span> <span>
<Tag>Ant Design</Tag> <Tag>Ant Design</Tag>
...@@ -124,19 +152,37 @@ export default class UserCenter extends PureComponent { ...@@ -124,19 +152,37 @@ export default class UserCenter extends PureComponent {
)} )}
/> />
); );
} };
renderApplications = (list, loading) => { renderApplications = (list, loading) => {
const itemMenu = ( const itemMenu = (
<Menu> <Menu>
<Menu.Item> <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>
<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>
<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.Item>
</Menu> </Menu>
); );
...@@ -165,10 +211,18 @@ export default class UserCenter extends PureComponent { ...@@ -165,10 +211,18 @@ export default class UserCenter extends PureComponent {
hoverable hoverable
bodyStyle={{ paddingBottom: 20 }} bodyStyle={{ paddingBottom: 20 }}
actions={[ actions={[
<Tooltip title="下载"><Icon type="download" /></Tooltip>, <Tooltip title="下载">
<Tooltip title="编辑"><Icon type="edit" /></Tooltip>, <Icon type="download" />
<Tooltip title="分享"><Icon type="share-alt" /></Tooltip>, </Tooltip>,
<Dropdown overlay={itemMenu}><Icon type="ellipsis" /></Dropdown>, <Tooltip title="编辑">
<Icon type="edit" />
</Tooltip>,
<Tooltip title="分享">
<Icon type="share-alt" />
</Tooltip>,
<Dropdown overlay={itemMenu}>
<Icon type="ellipsis" />
</Dropdown>,
]} ]}
> >
<Card.Meta <Card.Meta
...@@ -186,7 +240,7 @@ export default class UserCenter extends PureComponent { ...@@ -186,7 +240,7 @@ export default class UserCenter extends PureComponent {
)} )}
/> />
); );
} };
renderProjects = (list, loading) => { renderProjects = (list, loading) => {
return ( return (
...@@ -211,15 +265,13 @@ export default class UserCenter extends PureComponent { ...@@ -211,15 +265,13 @@ export default class UserCenter extends PureComponent {
<span>{moment(item.updatedAt).fromNow()}</span> <span>{moment(item.updatedAt).fromNow()}</span>
<div className={stylesProjects.avatarList}> <div className={stylesProjects.avatarList}>
<AvatarList size="mini"> <AvatarList size="mini">
{ {item.members.map(member => (
item.members.map(member => (
<AvatarList.Item <AvatarList.Item
key={`${item.id}-avatar-${member.id}`} key={`${item.id}-avatar-${member.id}`}
src={member.avatar} src={member.avatar}
tips={member.name} tips={member.name}
/> />
)) ))}
}
</AvatarList> </AvatarList>
</div> </div>
</div> </div>
...@@ -228,40 +280,15 @@ export default class UserCenter extends PureComponent { ...@@ -228,40 +280,15 @@ export default class UserCenter extends PureComponent {
)} )}
/> />
); );
}
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 contentMap = {
article: this.renderArticles(list, listLoading),
application: this.renderApplications(list, listLoading),
project: this.renderProjects(list, listLoading),
}; };
renderContent() {
const { newTags, inputVisible, inputValue } = this.state;
const {
currentUser,
project: { notice },
projectLoading,
} = this.props;
return ( 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>
<div className={styles.avatarHolder}> <div className={styles.avatarHolder}>
<img alt="" src={currentUser.avatar} /> <img alt="" src={currentUser.avatar} />
...@@ -269,9 +296,16 @@ export default class UserCenter extends PureComponent { ...@@ -269,9 +296,16 @@ export default class UserCenter extends PureComponent {
<div>{currentUser.signature}</div> <div>{currentUser.signature}</div>
</div> </div>
<div className={styles.detail}> <div className={styles.detail}>
<p><i className={styles.title} />{currentUser.title}</p> <p>
<p><i className={styles.group} />{currentUser.group}</p> <i className={styles.title} />
<p><i className={styles.address} /> {currentUser.title}
</p>
<p>
<i className={styles.group} />
{currentUser.group}
</p>
<p>
<i className={styles.address} />
{currentUser.geographic.province.label} {currentUser.geographic.province.label}
{currentUser.geographic.city.label} {currentUser.geographic.city.label}
</p> </p>
...@@ -279,10 +313,31 @@ export default class UserCenter extends PureComponent { ...@@ -279,10 +313,31 @@ export default class UserCenter extends PureComponent {
<Divider dashed /> <Divider dashed />
<div className={styles.tags}> <div className={styles.tags}>
<div className={styles.tagsTitle}>标签</div> <div className={styles.tagsTitle}>标签</div>
{ {currentUser.tags.map(item => <Tag key={item.key}>{item.label}</Tag>)}
currentUser.tags.concat(newTags).map(item => <Tag style={{ background: '#fff', borderStyle: 'dashed' }}>
<Tag key={item.key}>{item.label}</Tag>) <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 && ( {inputVisible && (
<Input <Input
ref={this.saveInputRef} ref={this.saveInputRef}
...@@ -309,22 +364,72 @@ export default class UserCenter extends PureComponent { ...@@ -309,22 +364,72 @@ export default class UserCenter extends PureComponent {
<div className={styles.teamTitle}>团队</div> <div className={styles.teamTitle}>团队</div>
<Spin spinning={projectLoading}> <Spin spinning={projectLoading}>
<Row gutter={36}> <Row gutter={36}>
{ {notice.map(item => (
notice.map(item => (
<Col key={item.id} lg={24} xl={12}> <Col key={item.id} lg={24} xl={12}>
<Link to={item.href}> <Link to={item.href}>
<Avatar size="small" src={item.logo} /> <Avatar size="small" src={item.logo} />
{item.member} {item.member}
</Link> </Link>
</Col> </Col>
)) ))}
}
</Row> </Row>
</Spin> </Spin>
</div> </div>
</div> </div>
) : 'loading...' );
} }
render() {
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),
project: this.renderProjects(list, listLoading),
};
return (
<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> </Card>
</Col> </Col>
<Col lg={17} md={24}> <Col lg={17} md={24}>
...@@ -334,11 +439,12 @@ export default class UserCenter extends PureComponent { ...@@ -334,11 +439,12 @@ export default class UserCenter extends PureComponent {
tabList={operationTabList} tabList={operationTabList}
onTabChange={this.onTabChange} onTabChange={this.onTabChange}
> >
{contentMap[key]} {contentMap[this.state.key]}
</Card> </Card>
</Col> </Col>
</Row> </Row>
</div> </div>
</GridContent>
); );
} }
} }
...@@ -3,7 +3,8 @@ import { connect } from 'dva'; ...@@ -3,7 +3,8 @@ import { connect } from 'dva';
import { Route, routerRedux, Switch, Redirect } from 'dva/router'; import { Route, routerRedux, Switch, Redirect } from 'dva/router';
import { Menu } from 'antd'; import { Menu } from 'antd';
import styles from './Info.less'; import styles from './Info.less';
import { getRoutes } from '../../../utils/utils'; import { getRoutes } from '../../utils/utils';
import GridContent from '../../layouts/GridContent';
const { Item } = Menu; const { Item } = Menu;
...@@ -71,12 +72,8 @@ export default class Info extends Component { ...@@ -71,12 +72,8 @@ export default class Info extends Component {
return ''; return '';
} }
return ( return (
<div <GridContent>
className={styles.main} <div className={styles.main}>
ref={(ref) => {
this.main = ref;
}}
>
<div className={styles.leftmenu}> <div className={styles.leftmenu}>
<Menu <Menu
mode={this.state.mode} mode={this.state.mode}
...@@ -99,15 +96,12 @@ export default class Info extends Component { ...@@ -99,15 +96,12 @@ export default class Info extends Component {
exact={item.exact} exact={item.exact}
/> />
))} ))}
<Redirect <Redirect exact from="/userinfo" to="/userinfo/base" />
exact
from="/user-profile/userinfo"
to="/user-profile/userinfo/base"
/>
<Redirect to="/exception/404" /> <Redirect to="/exception/404" />
</Switch> </Switch>
</div> </div>
</div> </div>
</GridContent>
); );
} }
} }
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