Commit 56a5bcf5 authored by ι™ˆεΈ…'s avatar ι™ˆεΈ…

use layout components

parent d1b6e0fe
......@@ -57,7 +57,6 @@
],
"dependencies": {
"@ant-design/pro-layout": "^4.0.3",
"ant-design-pro": "^2.3.0",
"antd": "^3.15.0",
"classnames": "^2.2.6",
"dva": "^2.4.0",
......
import { ConnectProps } from '@/models/connect';
import { ConnectProps, ConnectState } from '@/models/connect';
import { NoticeItem } from '@/models/global';
import { CurrentUser } from '@/models/user';
import React, { Component } from 'react';
......@@ -7,11 +7,12 @@ import { ClickParam } from 'antd/es/menu';
import { FormattedMessage, formatMessage } from 'umi-plugin-react/locale';
import moment from 'moment';
import groupBy from 'lodash/groupBy';
import { NoticeIcon } from 'ant-design-pro';
import NoticeIcon from '../NoticeIcon';
import HeaderSearch from '../HeaderSearch';
import HeaderDropdown from '../HeaderDropdown';
import SelectLang from '../SelectLang';
import styles from './index.less';
import { connect } from 'dva';
export type SiderTheme = 'light' | 'dark';
......@@ -21,11 +22,11 @@ export interface GlobalHeaderRightProps extends ConnectProps {
fetchingNotices?: boolean;
onNoticeVisibleChange?: (visible: boolean) => void;
onMenuClick?: (param: ClickParam) => void;
onNoticeClear?: (tabName: string) => void;
onNoticeClear?: (tabName?: string) => void;
theme?: SiderTheme;
}
export default class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
getNoticeData = (): { [key: string]: NoticeItem[] } => {
const { notices = [] } = this.props;
if (notices.length === 0) {
......@@ -78,16 +79,24 @@ export default class GlobalHeaderRight extends Component<GlobalHeaderRightProps>
payload: id,
});
};
componentDidMount() {
const { dispatch } = this.props;
dispatch!({
type: 'global/fetchNotices',
});
}
handleNoticeClear = (title: string, key: string) => {
const { dispatch } = this.props;
message.success(`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${title}`);
if (dispatch) {
dispatch({
type: 'global/clearNotices',
payload: key,
});
}
};
render() {
const {
currentUser,
fetchingNotices,
onNoticeVisibleChange,
onMenuClick,
onNoticeClear,
theme,
} = this.props;
const { currentUser, fetchingNotices, onNoticeVisibleChange, onMenuClick, theme } = this.props;
const menu = (
<Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
<Menu.Item key="userCenter">
......@@ -146,46 +155,39 @@ export default class GlobalHeaderRight extends Component<GlobalHeaderRightProps>
<NoticeIcon
className={styles.action}
count={currentUser && currentUser.unreadCount}
onItemClick={(item: NoticeItem, tabProps: any) => {
console.log(item, tabProps); // tslint:disable-line no-console
onItemClick={item => {
this.changeReadState(item as NoticeItem);
}}
loading={fetchingNotices}
locale={{
emptyText: formatMessage({ id: 'component.noticeIcon.empty' }),
clear: formatMessage({ id: 'component.noticeIcon.clear' }),
viewMore: formatMessage({ id: 'component.noticeIcon.view-more' }),
notification: formatMessage({ id: 'component.globalHeader.notification' }),
message: formatMessage({ id: 'component.globalHeader.message' }),
event: formatMessage({ id: 'component.globalHeader.event' }),
}}
onClear={onNoticeClear}
clearText={formatMessage({ id: 'component.noticeIcon.clear' })}
viewMoreText={formatMessage({ id: 'component.noticeIcon.view-more' })}
onClear={this.handleNoticeClear}
onPopupVisibleChange={onNoticeVisibleChange}
onViewMore={() => message.info('Click on view more')}
clearClose
>
<NoticeIcon.Tab
tabKey="notification"
count={unreadMsg.notification}
list={noticeData.notification}
title="notification"
title={formatMessage({ id: 'component.globalHeader.notification' })}
emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
showViewMore
/>
<NoticeIcon.Tab
tabKey="message"
count={unreadMsg.message}
list={noticeData.message}
title="message"
title={formatMessage({ id: 'component.globalHeader.message' })}
emptyText={formatMessage({ id: 'component.globalHeader.message.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
showViewMore
/>
<NoticeIcon.Tab
tabKey="event"
title={formatMessage({ id: 'component.globalHeader.event' })}
emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })}
count={unreadMsg.event}
list={noticeData.event}
title="event"
emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
showViewMore
/>
</NoticeIcon>
......@@ -209,3 +211,11 @@ export default class GlobalHeaderRight extends Component<GlobalHeaderRightProps>
);
}
}
export default connect(({ user, global, loading }: ConnectState) => ({
currentUser: user.currentUser,
collapsed: global.collapsed,
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
fetchingNotices: loading.effects['global/fetchNotices'],
notices: global.notices,
}))(GlobalHeaderRight);
@import '~antd/lib/style/themes/default.less';
.list {
max-height: 400px;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
.item {
padding-right: 24px;
padding-left: 24px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s;
.meta {
width: 100%;
}
.avatar {
margin-top: 4px;
background: #fff;
}
.iconElement {
font-size: 32px;
}
&.read {
opacity: 0.4;
}
&:last-child {
border-bottom: 0;
}
&:hover {
background: @primary-1;
}
.title {
margin-bottom: 8px;
font-weight: normal;
}
.description {
font-size: 12px;
line-height: @line-height-base;
}
.datetime {
margin-top: 4px;
font-size: 12px;
line-height: @line-height-base;
}
.extra {
float: right;
margin-top: -1.5px;
margin-right: 0;
color: @text-color-secondary;
font-weight: normal;
}
}
.loadMore {
padding: 8px 0;
color: @primary-6;
text-align: center;
cursor: pointer;
&.loadedAll {
color: rgba(0, 0, 0, 0.25);
cursor: unset;
}
}
}
.notFound {
padding: 73px 0 88px 0;
color: @text-color-secondary;
text-align: center;
img {
display: inline-block;
height: 76px;
margin-bottom: 16px;
}
}
.bottomBar {
height: 46px;
color: @text-color;
line-height: 46px;
text-align: center;
border-top: 1px solid @border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base;
transition: all 0.3s;
div {
display: inline-block;
width: 50%;
cursor: pointer;
transition: all 0.3s;
user-select: none;
&:hover {
color: @heading-color;
}
&:only-child {
width: 100%;
}
&:not(:only-child):last-child {
border-left: 1px solid @border-color-split;
}
}
}
import React from 'react';
import { Avatar, List } from 'antd';
import classNames from 'classnames';
import styles from './NoticeList.less';
import { NoticeIconData } from './index';
export interface NoticeIconTabProps {
count?: number;
list?: NoticeIconData[];
name?: string;
showClear?: boolean;
showViewMore?: boolean;
style?: React.CSSProperties;
title: string;
tabKey: string;
data?: any[];
onClick?: (item: any) => void;
onClear?: (item: any) => void;
emptyText?: string;
clearText?: string;
viewMoreText?: string;
onViewMore?: (e: any) => void;
}
const NoticeList: React.SFC<NoticeIconTabProps> = ({
data = [],
onClick,
onClear,
title,
onViewMore,
emptyText,
showClear = true,
clearText,
viewMoreText,
showViewMore = false,
}) => {
if (data.length === 0) {
return (
<div className={styles.notFound}>
<img
src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
alt="not found"
/>
<div>{emptyText}</div>
</div>
);
}
return (
<div>
<List<NoticeIconData>
className={styles.list}
dataSource={data}
renderItem={(item, i) => {
const itemCls = classNames(styles.item, {
[styles.read]: item.read,
});
// eslint-disable-next-line no-nested-ternary
const leftIcon = item.avatar ? (
typeof item.avatar === 'string' ? (
<Avatar className={styles.avatar} src={item.avatar} />
) : (
<span className={styles.iconElement}>{item.avatar}</span>
)
) : null;
return (
<List.Item
className={itemCls}
key={item.key || i}
onClick={() => onClick && onClick(item)}
>
<List.Item.Meta
className={styles.meta}
avatar={leftIcon}
title={
<div className={styles.title}>
{item.title}
<div className={styles.extra}>{item.extra}</div>
</div>
}
description={
<div>
<div className={styles.description}>{item.description}</div>
<div className={styles.datetime}>{item.datetime}</div>
</div>
}
/>
</List.Item>
);
}}
/>
<div className={styles.bottomBar}>
{showClear ? (
<div onClick={onClear}>
{clearText} {title}
</div>
) : null}
{showViewMore ? (
<div
onClick={e => {
if (onViewMore) {
onViewMore(e);
}
}}
>
{viewMoreText}
</div>
) : null}
</div>
</div>
);
};
export default NoticeList;
@import '~antd/lib/style/themes/default.less';
.popover {
position: relative;
width: 336px;
}
.noticeButton {
display: inline-block;
cursor: pointer;
transition: all 0.3s;
}
.icon {
padding: 4px;
vertical-align: middle;
}
.badge {
font-size: 16px;
}
.tabs {
:global {
.ant-tabs-nav-scroll {
text-align: center;
}
.ant-tabs-bar {
margin-bottom: 0;
}
}
}
import React, { Component } from 'react';
import { Icon, Tabs, Badge, Spin } from 'antd';
import classNames from 'classnames';
import HeaderDropdown from '../HeaderDropdown';
import NoticeList, { NoticeIconTabProps } from './NoticeList';
import styles from './index.less';
const { TabPane } = Tabs;
export interface NoticeIconData {
avatar?: string | React.ReactNode;
title?: React.ReactNode;
description?: React.ReactNode;
datetime?: React.ReactNode;
extra?: React.ReactNode;
style?: React.CSSProperties;
key?: string | number;
read?: boolean;
}
export interface NoticeIconProps {
count?: number;
bell?: React.ReactNode;
className?: string;
loading?: boolean;
onClear?: (tabName: string, tabKey: string) => void;
onItemClick?: (item: NoticeIconData, tabProps: NoticeIconTabProps) => void;
onViewMore?: (tabProps: NoticeIconTabProps, e: MouseEvent) => void;
onTabChange?: (tabTile: string) => void;
style?: React.CSSProperties;
onPopupVisibleChange?: (visible: boolean) => void;
popupVisible?: boolean;
clearText?: string;
viewMoreText?: string;
clearClose?: boolean;
children: React.ReactElement<NoticeIconTabProps>[];
}
export default class NoticeIcon extends Component<NoticeIconProps> {
public static Tab: typeof NoticeList = NoticeList;
static defaultProps = {
onItemClick: () => {},
onPopupVisibleChange: () => {},
onTabChange: () => {},
onClear: () => {},
onViewMore: () => {},
loading: false,
clearClose: false,
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
};
state = {
visible: false,
};
onItemClick = (item: NoticeIconData, tabProps: NoticeIconTabProps) => {
const { onItemClick } = this.props;
if (onItemClick) {
onItemClick(item, tabProps);
}
};
onClear = (name: string, key: string) => {
const { onClear } = this.props;
if (onClear) {
onClear(name, key);
}
};
onTabChange = (tabType: string) => {
const { onTabChange } = this.props;
if (onTabChange) {
onTabChange(tabType);
}
};
onViewMore = (tabProps: NoticeIconTabProps, event: MouseEvent) => {
const { onViewMore } = this.props;
if (onViewMore) {
onViewMore(tabProps, event);
}
};
getNotificationBox() {
const { children, loading, clearText, viewMoreText } = this.props;
if (!children) {
return null;
}
const panes = React.Children.map(children, (child: React.ReactElement<NoticeIconTabProps>) => {
if (!child) {
return null;
}
const { list, title, count, tabKey, showClear, showViewMore } = child.props;
const len = list && list.length ? list.length : 0;
const msgCount = count || count === 0 ? count : len;
const tabTitle: string = msgCount > 0 ? `${title} (${msgCount})` : title;
return (
<TabPane tab={tabTitle} key={title}>
<NoticeList
clearText={clearText}
viewMoreText={viewMoreText}
data={list}
onClear={() => this.onClear(title, tabKey)}
onClick={item => this.onItemClick(item, child.props)}
onViewMore={event => this.onViewMore(child.props, event)}
showClear={showClear}
showViewMore={showViewMore}
title={title}
{...child.props}
/>
</TabPane>
);
});
return (
<Spin spinning={loading} delay={0}>
<Tabs className={styles.tabs} onChange={this.onTabChange}>
{panes}
</Tabs>
</Spin>
);
}
handleVisibleChange = (visible: boolean) => {
const { onPopupVisibleChange } = this.props;
this.setState({ visible });
if (onPopupVisibleChange) {
onPopupVisibleChange(visible);
}
};
render() {
const { className, count, popupVisible, bell } = this.props;
const { visible } = this.state;
const noticeButtonClass = classNames(className, styles.noticeButton);
const notificationBox = this.getNotificationBox();
const NoticeBellIcon = bell || <Icon type="bell" className={styles.icon} />;
const trigger = (
<span className={classNames(noticeButtonClass, { opened: visible })}>
<Badge count={count} style={{ boxShadow: 'none' }} className={styles.badge}>
{NoticeBellIcon}
</Badge>
</span>
);
if (!notificationBox) {
return trigger;
}
const popoverProps: {
visible?: boolean;
} = {};
if ('popupVisible' in this.props) {
popoverProps.visible = popupVisible;
}
return (
<HeaderDropdown
placement="bottomRight"
overlay={notificationBox}
overlayClassName={styles.popover}
trigger={['click']}
visible={visible}
onVisibleChange={this.handleVisibleChange}
{...popoverProps}
>
{trigger}
</HeaderDropdown>
);
}
}
import { ConnectState, ConnectProps } from '@/models/connect';
import RightContent from '@/components/GlobalHeader/RightContent';
import { formatMessage } from 'umi-plugin-react/locale';
import { connect } from 'dva';
import React, { useState } from 'react';
......@@ -48,6 +49,7 @@ const BasicLayout: React.FC<BasicLayoutProps> = props => {
})
}
onChangeLayoutCollapsed={handleMenuCollapse}
renderRightContent={RightProps => <RightContent {...RightProps} />}
{...props}
>
{children}
......
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