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 { 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
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 && (
[
(
{isMobile && [
<Link to="/" className={styles.logo} key="logo">
<img src={logo} alt="logo" width="32" />
</Link>
),
</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>
);
}
......
@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) {
......
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 { 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) {
......@@ -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
* [{path:string},{path:string}] => {path,path2}
......@@ -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>
);
}
......
import { getMeunMatcheys } from './SiderMenu';
import { getMeunMatcheys } from './BaseMeun';
const meun = [
'/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';
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,12 +154,14 @@ 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>
{isFluid && !isMobile ? null : (
<SiderMenu
logo={logo}
// 不带Authorized参数的情况下如果没有权限,会强制跳到403界面
......@@ -171,8 +174,25 @@ class BasicLayout extends React.PureComponent {
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);
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 { 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>
);
@import "~antd/lib/style/themes/default.less";
@import '~antd/lib/style/themes/default.less';
.content {
margin: 24px 24px 0;
......
......@@ -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,
......
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>
);
}
}
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>
);
}
}
......@@ -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 => (
{item.members.map(member => (
<AvatarList.Item
key={`${item.id}-avatar-${member.id}`}
src={member.avatar}
tips={member.name}
/>
))
}
))}
</AvatarList>
</div>
</div>
......@@ -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 (
<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} />
......@@ -269,9 +296,16 @@ export default class UserCenter extends PureComponent {
<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} />
<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>
......@@ -279,10 +313,31 @@ export default class UserCenter extends PureComponent {
<Divider dashed />
<div className={styles.tags}>
<div className={styles.tagsTitle}>标签</div>
{
currentUser.tags.concat(newTags).map(item =>
<Tag key={item.key}>{item.label}</Tag>)
}
{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}
......@@ -309,22 +364,72 @@ export default class UserCenter extends PureComponent {
<div className={styles.teamTitle}>团队</div>
<Spin spinning={projectLoading}>
<Row gutter={36}>
{
notice.map(item => (
{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...'
);
}
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>
</Col>
<Col lg={17} md={24}>
......@@ -334,11 +439,12 @@ export default class UserCenter extends PureComponent {
tabList={operationTabList}
onTabChange={this.onTabChange}
>
{contentMap[key]}
{contentMap[this.state.key]}
</Card>
</Col>
</Row>
</div>
</GridContent>
);
}
}
......@@ -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,12 +72,8 @@ export default class Info extends Component {
return '';
}
return (
<div
className={styles.main}
ref={(ref) => {
this.main = ref;
}}
>
<GridContent>
<div className={styles.main}>
<div className={styles.leftmenu}>
<Menu
mode={this.state.mode}
......@@ -99,15 +96,12 @@ export default class Info extends Component {
exact={item.exact}
/>
))}
<Redirect
exact
from="/user-profile/userinfo"
to="/user-profile/userinfo/base"
/>
<Redirect exact from="/userinfo" to="/userinfo/base" />
<Redirect to="/exception/404" />
</Switch>
</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