Commit 6f0e2473 authored by 何乐's avatar 何乐 Committed by 陈帅

[NoticeIcon] Replace `LoadMore` with `ViewMore` button (#3439)

* enhance LoadMore: Debounce

* enhance LoadMore: debounce

* use Tag instead of div

* rewrite margin-right of Tag

* hide LoadMore in NoticeList without onLoadMore

* another style

* fix a mistake

* remove local config

* fix a bug

* user-select: none

* remover local config

* replace global/fetchMoreNotices with global/fetchNotices

* replace LoadMore with ViewMore

* remove prop `name` in NoticeIcon

* fix: tab title does not show correct text
parent 4025619d
const fakeNotices = [ const getNotices = (req, res) =>
{ res.json([
id: '000000001', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', id: '000000001',
title: '你收到了 14 份新周报', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
datetime: '2017-08-09', title: '你收到了 14 份新周报',
type: 'notification', datetime: '2017-08-09',
}, type: 'notification',
{ },
id: '000000002', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', id: '000000002',
title: '你推荐的 曲妮妮 已通过第三轮面试', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
datetime: '2017-08-08', title: '你推荐的 曲妮妮 已通过第三轮面试',
type: 'notification', datetime: '2017-08-08',
}, type: 'notification',
{ },
id: '000000003', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', id: '000000003',
title: '这种模板可以区分多种通知类型', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
datetime: '2017-08-07', title: '这种模板可以区分多种通知类型',
read: true, datetime: '2017-08-07',
type: 'notification', read: true,
}, type: 'notification',
{ },
id: '000000004', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', id: '000000004',
title: '左侧图标用于区分不同的类型', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
datetime: '2017-08-07', title: '左侧图标用于区分不同的类型',
type: 'notification', datetime: '2017-08-07',
}, type: 'notification',
{ },
id: '000000005', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', id: '000000005',
title: '内容不要超过两行字,超出时自动截断', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
datetime: '2017-08-07', title: '内容不要超过两行字,超出时自动截断',
type: 'notification', datetime: '2017-08-07',
}, type: 'notification',
{ },
id: '000000006', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', id: '000000006',
title: '曲丽丽 评论了你', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
description: '描述信息描述信息描述信息', title: '曲丽丽 评论了你',
datetime: '2017-08-07', description: '描述信息描述信息描述信息',
type: 'message', datetime: '2017-08-07',
clickClose: true, type: 'message',
}, clickClose: true,
{ },
id: '000000007', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', id: '000000007',
title: '朱偏右 回复了你', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', title: '朱偏右 回复了你',
datetime: '2017-08-07', description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
type: 'message', datetime: '2017-08-07',
clickClose: true, type: 'message',
}, clickClose: true,
{ },
id: '000000008', {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', id: '000000008',
title: '标题', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', title: '标题',
datetime: '2017-08-07', description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
type: 'message', datetime: '2017-08-07',
clickClose: true, type: 'message',
}, clickClose: true,
{ },
id: '000000009', {
title: '任务名称', id: '000000009',
description: '任务需要在 2017-01-12 20:00 前启动', title: '任务名称',
extra: '未开始', description: '任务需要在 2017-01-12 20:00 前启动',
status: 'todo', extra: '未开始',
type: 'event', status: 'todo',
}, type: 'event',
{ },
id: '000000010', {
title: '第三方紧急代码变更', id: '000000010',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', title: '第三方紧急代码变更',
extra: '马上到期', description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
status: 'urgent', extra: '马上到期',
type: 'event', status: 'urgent',
}, type: 'event',
{ },
id: '000000011', {
title: '信息安全考试', id: '000000011',
description: '指派竹尔于 2017-01-09 前完成更新并发布', title: '信息安全考试',
extra: '已耗时 8 天', description: '指派竹尔于 2017-01-09 前完成更新并发布',
status: 'doing', extra: '已耗时 8 天',
type: 'event', status: 'doing',
}, type: 'event',
{ },
id: '000000012', {
title: 'ABCD 版本发布', id: '000000012',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', title: 'ABCD 版本发布',
extra: '进行中', description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
status: 'processing', extra: '进行中',
type: 'event', status: 'processing',
}, type: 'event',
]; },
]);
const getNotices = (req, res) => {
if (req.query && req.query.type) {
const startFrom = parseInt(req.query.lastItemId, 10) + 1;
const result = fakeNotices
.filter(({ type }) => type === req.query.type)
.map((notice, index) => ({
...notice,
id: `0000000${startFrom + index}`,
}));
return res.json(startFrom > 24 ? result.concat(null) : result);
}
return res.json(fakeNotices);
};
export default { export default {
'GET /api/notices': getNotices, 'GET /api/notices': getNotices,
......
...@@ -3,15 +3,11 @@ import numeral from 'numeral'; ...@@ -3,15 +3,11 @@ import numeral from 'numeral';
import ChartCard from './ChartCard'; import ChartCard from './ChartCard';
import Field from './Field'; import Field from './Field';
const getComponent = Component => { const getComponent = Component => props => (
return props => { <Suspense fallback="...">
return ( <Component {...props} />
<Suspense fallback="..."> </Suspense>
<Component {...props} /> );
</Suspense>
);
};
};
const Bar = getComponent(React.lazy(() => import('./Bar'))); const Bar = getComponent(React.lazy(() => import('./Bar')));
const Pie = getComponent(React.lazy(() => import('./Pie'))); const Pie = getComponent(React.lazy(() => import('./Pie')));
......
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { FormattedMessage, formatMessage } from 'umi/locale'; import { FormattedMessage, formatMessage } from 'umi/locale';
import { Spin, Tag, Menu, Icon, Avatar, Tooltip } from 'antd'; import { Spin, Tag, Menu, Icon, Avatar, Tooltip, message } from 'antd';
import moment from 'moment'; import moment from 'moment';
import groupBy from 'lodash/groupBy'; import groupBy from 'lodash/groupBy';
import NoticeIcon from '../NoticeIcon'; import NoticeIcon from '../NoticeIcon';
...@@ -63,30 +63,13 @@ export default class GlobalHeaderRight extends PureComponent { ...@@ -63,30 +63,13 @@ export default class GlobalHeaderRight extends PureComponent {
}); });
}; };
fetchMoreNotices = tabProps => {
const { list, name } = tabProps;
const { dispatch, notices = [] } = this.props;
const lastItemId = notices[notices.length - 1].id;
dispatch({
type: 'global/fetchMoreNotices',
payload: {
lastItemId,
type: name,
offset: list.length,
},
});
};
render() { render() {
const { const {
currentUser, currentUser,
fetchingMoreNotices,
fetchingNotices, fetchingNotices,
loadedAllNotices,
onNoticeVisibleChange, onNoticeVisibleChange,
onMenuClick, onMenuClick,
onNoticeClear, onNoticeClear,
skeletonCount,
theme, theme,
} = this.props; } = this.props;
const menu = ( const menu = (
...@@ -110,11 +93,6 @@ export default class GlobalHeaderRight extends PureComponent { ...@@ -110,11 +93,6 @@ export default class GlobalHeaderRight extends PureComponent {
</Menu.Item> </Menu.Item>
</Menu> </Menu>
); );
const loadMoreProps = {
skeletonCount,
loadedAll: loadedAllNotices,
loading: fetchingMoreNotices,
};
const noticeData = this.getNoticeData(); const noticeData = this.getNoticeData();
const unreadMsg = this.getUnreadData(noticeData); const unreadMsg = this.getUnreadData(noticeData);
let className = styles.right; let className = styles.right;
...@@ -155,44 +133,43 @@ export default class GlobalHeaderRight extends PureComponent { ...@@ -155,44 +133,43 @@ export default class GlobalHeaderRight extends PureComponent {
console.log(item, tabProps); // eslint-disable-line console.log(item, tabProps); // eslint-disable-line
this.changeReadState(item, tabProps); this.changeReadState(item, tabProps);
}} }}
loading={fetchingNotices}
locale={{ locale={{
emptyText: formatMessage({ id: 'component.noticeIcon.empty' }), emptyText: formatMessage({ id: 'component.noticeIcon.empty' }),
clear: formatMessage({ id: 'component.noticeIcon.clear' }), clear: formatMessage({ id: 'component.noticeIcon.clear' }),
loadedAll: formatMessage({ id: 'component.noticeIcon.loaded' }), viewMore: formatMessage({ id: 'component.noticeIcon.view-more' }),
loadMore: formatMessage({ id: 'component.noticeIcon.loading-more' }), notification: formatMessage({ id: 'component.globalHeader.notification' }),
message: formatMessage({ id: 'component.globalHeader.message' }),
event: formatMessage({ id: 'component.globalHeader.event' }),
}} }}
onClear={onNoticeClear} onClear={onNoticeClear}
onLoadMore={this.fetchMoreNotices}
onPopupVisibleChange={onNoticeVisibleChange} onPopupVisibleChange={onNoticeVisibleChange}
loading={fetchingNotices} onViewMore={() => message.info('Click on view more')}
clearClose clearClose
> >
<NoticeIcon.Tab <NoticeIcon.Tab
count={unreadMsg.notification} count={unreadMsg.notification}
list={noticeData.notification} list={noticeData.notification}
title={formatMessage({ id: 'component.globalHeader.notification' })} title="notification"
name="notification"
emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })} emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
{...loadMoreProps} showViewMore
/> />
<NoticeIcon.Tab <NoticeIcon.Tab
count={unreadMsg.message} count={unreadMsg.message}
list={noticeData.message} list={noticeData.message}
title={formatMessage({ id: 'component.globalHeader.message' })} title="message"
name="message"
emptyText={formatMessage({ id: 'component.globalHeader.message.empty' })} emptyText={formatMessage({ id: 'component.globalHeader.message.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
{...loadMoreProps} showViewMore
/> />
<NoticeIcon.Tab <NoticeIcon.Tab
count={unreadMsg.event} count={unreadMsg.event}
list={noticeData.event} list={noticeData.event}
title={formatMessage({ id: 'component.globalHeader.event' })} title="event"
name="event"
emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })} emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
{...loadMoreProps} showViewMore
/> />
</NoticeIcon> </NoticeIcon>
{currentUser.name ? ( {currentUser.name ? (
......
...@@ -15,12 +15,9 @@ export interface INoticeIconTabProps { ...@@ -15,12 +15,9 @@ export interface INoticeIconTabProps {
emptyText?: React.ReactNode; emptyText?: React.ReactNode;
emptyImage?: string; emptyImage?: string;
list?: INoticeIconData[]; list?: INoticeIconData[];
loadedAll?: boolean;
loading?: boolean;
name?: string; name?: string;
showClear?: boolean; showClear?: boolean;
skeletonCount?: number; showViewMore?: boolean;
skeletonProps?: SkeletonProps;
style?: React.CSSProperties; style?: React.CSSProperties;
title?: string; title?: string;
} }
......
import React from 'react'; import React from 'react';
import { Avatar, List, Skeleton } from 'antd'; import { Avatar, List } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import styles from './NoticeList.less'; import styles from './NoticeList.less';
let ListElement = null;
export default function NoticeList({ export default function NoticeList({
data = [], data = [],
onClick, onClick,
...@@ -13,14 +11,9 @@ export default function NoticeList({ ...@@ -13,14 +11,9 @@ export default function NoticeList({
locale, locale,
emptyText, emptyText,
emptyImage, emptyImage,
loading, onViewMore = null,
onLoadMore,
visible,
loadedAll = true,
scrollToLoad = true,
showClear = true, showClear = true,
skeletonCount = 5, showViewMore = false,
skeletonProps = {},
}) { }) {
if (data.length === 0) { if (data.length === 0) {
return ( return (
...@@ -30,36 +23,10 @@ export default function NoticeList({ ...@@ -30,36 +23,10 @@ export default function NoticeList({
</div> </div>
); );
} }
const loadingList = Array.from({ length: loading ? skeletonCount : 0 }).map(() => ({ loading }));
const LoadMore = loadedAll ? (
<div className={classNames(styles.loadMore, styles.loadedAll)}>
<span>{locale.loadedAll}</span>
</div>
) : (
<div className={styles.loadMore} onClick={onLoadMore}>
<span>{locale.loadMore}</span>
</div>
);
const onScroll = event => {
if (!scrollToLoad || loading || loadedAll) return;
if (typeof onLoadMore !== 'function') return;
const { currentTarget: t } = event;
if (t.scrollHeight - t.scrollTop - t.clientHeight <= 40) {
onLoadMore(event);
ListElement = t;
}
};
if (!visible && ListElement) {
try {
ListElement.scrollTo(null, 0);
} catch (err) {
ListElement = null;
}
}
return ( return (
<div> <div>
<List className={styles.list} loadMore={LoadMore} onScroll={onScroll}> <List className={styles.list}>
{[...data, ...loadingList].map((item, i) => { {data.map((item, i) => {
const itemCls = classNames(styles.item, { const itemCls = classNames(styles.item, {
[styles.read]: item.read, [styles.read]: item.read,
}); });
...@@ -74,35 +41,36 @@ export default function NoticeList({ ...@@ -74,35 +41,36 @@ export default function NoticeList({
return ( return (
<List.Item className={itemCls} key={item.key || i} onClick={() => onClick(item)}> <List.Item className={itemCls} key={item.key || i} onClick={() => onClick(item)}>
<Skeleton avatar title={false} active {...skeletonProps} loading={item.loading}> <List.Item.Meta
<List.Item.Meta className={styles.meta}
className={styles.meta} avatar={leftIcon}
avatar={leftIcon} title={
title={ <div className={styles.title}>
<div className={styles.title}> {item.title}
{item.title} <div className={styles.extra}>{item.extra}</div>
<div className={styles.extra}>{item.extra}</div> </div>
</div> }
} description={
description={ <div>
<div> <div className={styles.description} title={item.description}>
<div className={styles.description} title={item.description}> {item.description}
{item.description}
</div>
<div className={styles.datetime}>{item.datetime}</div>
</div> </div>
} <div className={styles.datetime}>{item.datetime}</div>
/> </div>
</Skeleton> }
/>
</List.Item> </List.Item>
); );
})} })}
</List> </List>
{showClear ? ( <div className={styles.bottomBar}>
<div className={styles.clear} onClick={onClear}> {showClear ? (
{locale.clear} {title} <div onClick={onClear}>
</div> {locale.clear} {locale[title] || title}
) : null} </div>
) : null}
{showViewMore ? <div onClick={onViewMore}>{locale.viewMore}</div> : null}
</div>
</div> </div>
); );
} }
...@@ -78,17 +78,28 @@ ...@@ -78,17 +78,28 @@
} }
} }
.clear { .bottomBar {
height: 46px; height: 46px;
color: @text-color; color: @text-color;
line-height: 46px; line-height: 46px;
text-align: center; text-align: center;
border-top: 1px solid @border-color-split; border-top: 1px solid @border-color-split;
border-radius: 0 0 @border-radius-base @border-radius-base; border-radius: 0 0 @border-radius-base @border-radius-base;
cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
div {
&:hover { display: inline-block;
color: @heading-color; 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;
}
} }
} }
...@@ -9,103 +9,90 @@ title: 带浮层卡片 ...@@ -9,103 +9,90 @@ title: 带浮层卡片
import NoticeIcon from 'ant-design-pro/lib/NoticeIcon'; import NoticeIcon from 'ant-design-pro/lib/NoticeIcon';
import { Tag } from 'antd'; import { Tag } from 'antd';
const data = [ const data = [{
{ id: '000000001',
id: '000000001', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', title: '你收到了 14 份新周报',
title: '你收到了 14 份新周报', datetime: '2017-08-09',
datetime: '2017-08-09', type: 'notification',
type: '通知', }, {
}, id: '000000002',
{ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
id: '000000002', title: '你推荐的 曲妮妮 已通过第三轮面试',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png', datetime: '2017-08-08',
title: '你推荐的 曲妮妮 已通过第三轮面试', type: 'notification',
datetime: '2017-08-08', }, {
type: '通知', id: '000000003',
}, avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
{ title: '这种模板可以区分多种通知类型',
id: '000000003', datetime: '2017-08-07',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png', read: true,
title: '这种模板可以区分多种通知类型', type: 'notification',
datetime: '2017-08-07', }, {
read: true, id: '000000004',
type: '通知', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
}, title: '左侧图标用于区分不同的类型',
{ datetime: '2017-08-07',
id: '000000004', type: 'notification',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png', }, {
title: '左侧图标用于区分不同的类型', id: '000000005',
datetime: '2017-08-07', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
type: '通知', title: '内容不要超过两行字,超出时自动截断',
}, datetime: '2017-08-07',
{ type: 'notification',
id: '000000005', }, {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png', id: '000000006',
title: '内容不要超过两行字,超出时自动截断', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
datetime: '2017-08-07', title: '曲丽丽 评论了你',
type: '通知', description: '描述信息描述信息描述信息',
}, datetime: '2017-08-07',
{ type: 'message',
id: '000000006', clickClose: true,
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', }, {
title: '曲丽丽 评论了你', id: '000000007',
description: '描述信息描述信息描述信息', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
datetime: '2017-08-07', title: '朱偏右 回复了你',
type: '消息', description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
clickClose: true, datetime: '2017-08-07',
}, type: 'message',
{ clickClose: true,
id: '000000007', }, {
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', id: '000000008',
title: '朱偏右 回复了你', avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', title: '标题',
datetime: '2017-08-07', description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
type: '消息', datetime: '2017-08-07',
clickClose: true, type: 'message',
}, clickClose: true,
{ }, {
id: '000000008', id: '000000009',
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg', title: '任务名称',
title: '标题', description: '任务需要在 2017-01-12 20:00 前启动',
description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像', extra: '未开始',
datetime: '2017-08-07', status: 'todo',
type: '消息', type: 'event',
clickClose: true, }, {
}, id: '000000010',
{ title: '第三方紧急代码变更',
id: '000000009', description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
title: '任务名称', extra: '马上到期',
description: '任务需要在 2017-01-12 20:00 前启动', status: 'urgent',
extra: '未开始', type: 'event',
status: 'todo', }, {
type: '待办', id: '000000011',
}, title: '信息安全考试',
{ description: '指派竹尔于 2017-01-09 前完成更新并发布',
id: '000000010', extra: '已耗时 8 天',
title: '第三方紧急代码变更', status: 'doing',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务', type: 'event',
extra: '马上到期', }, {
status: 'urgent', id: '000000012',
type: '待办', title: 'ABCD 版本发布',
}, description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
{ extra: '进行中',
id: '000000011', status: 'processing',
title: '信息安全考试', type: 'event',
description: '指派竹尔于 2017-01-09 前完成更新并发布', }];
extra: '已耗时 8 天',
status: 'doing',
type: '待办',
},
{
id: '000000012',
title: 'ABCD 版本发布',
description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
extra: '进行中',
status: 'processing',
type: '待办',
},
];
function onItemClick(item, tabProps) { function onItemClick(item, tabProps) {
console.log(item, tabProps); console.log(item, tabProps);
...@@ -163,23 +150,20 @@ const Demo = () => ( ...@@ -163,23 +150,20 @@ const Demo = () => (
> >
<NoticeIcon className="notice-icon" count={5} onItemClick={onItemClick} onClear={onClear}> <NoticeIcon className="notice-icon" count={5} onItemClick={onItemClick} onClear={onClear}>
<NoticeIcon.Tab <NoticeIcon.Tab
list={noticeData['通知']} list={noticeData.notification}
name="通知" title="notification"
title="通知"
emptyText="你已查看所有通知" emptyText="你已查看所有通知"
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
/> />
<NoticeIcon.Tab <NoticeIcon.Tab
list={noticeData['消息']} list={noticeData.message}
name="消息" title="message"
title="消息"
emptyText="您已读完所有消息" emptyText="您已读完所有消息"
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
/> />
<NoticeIcon.Tab <NoticeIcon.Tab
list={noticeData['待办']} list={noticeData.event}
name="待办" title="event"
title="待办"
emptyText="你已完成所有待办" emptyText="你已完成所有待办"
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg" emptyImage="https://gw.alipayobjects.com/zos/rmsportal/HsIsxMZiWKrNUavQUXqx.svg"
/> />
......
...@@ -8,7 +8,7 @@ export interface INoticeIconProps { ...@@ -8,7 +8,7 @@ export interface INoticeIconProps {
loading?: boolean; loading?: boolean;
onClear?: (tabName: string) => void; onClear?: (tabName: string) => void;
onItemClick?: (item: INoticeIconData, tabProps: INoticeIconProps) => void; onItemClick?: (item: INoticeIconData, tabProps: INoticeIconProps) => void;
onLoadMore?: (tabProps: INoticeIconProps) => void; onViewMore?: (tabProps: INoticeIconProps) => void;
onTabChange?: (tabTile: string) => void; onTabChange?: (tabTile: string) => void;
style?: React.CSSProperties; style?: React.CSSProperties;
onPopupVisibleChange?: (visible: boolean) => void; onPopupVisibleChange?: (visible: boolean) => void;
...@@ -16,8 +16,8 @@ export interface INoticeIconProps { ...@@ -16,8 +16,8 @@ export interface INoticeIconProps {
locale?: { locale?: {
emptyText: string; emptyText: string;
clear: string; clear: string;
loadedAll: string; viewMore: string;
loadMore: string; [key: string]: string;
}; };
clearClose?: boolean; clearClose?: boolean;
} }
......
...@@ -5,7 +5,7 @@ cols: 1 ...@@ -5,7 +5,7 @@ cols: 1
order: 9 order: 9
--- ---
用在导航工具栏上,作为整个产品统一的通知中心。 Used in navigation toolbar as a unified notification center for the entire product.
## API ## API
...@@ -16,11 +16,11 @@ bell | Change the bell Icon | ReactNode | `<Icon type='bell' />` ...@@ -16,11 +16,11 @@ bell | Change the bell Icon | ReactNode | `<Icon type='bell' />`
loading | Popup card loading status | boolean | `false` loading | Popup card loading status | boolean | `false`
onClear | Click to clear button the callback | function(tabName) | - onClear | Click to clear button the callback | function(tabName) | -
onItemClick | Click on the list item's callback | function(item, tabProps) | - onItemClick | Click on the list item's callback | function(item, tabProps) | -
onLoadMore | Callback of click for loading more | function(tabProps, event) | -
onPopupVisibleChange | Popup Card Showing or Hiding Callbacks | function(visible) | - onPopupVisibleChange | Popup Card Showing or Hiding Callbacks | function(visible) | -
onTabChange | Switching callbacks for tabs | function(tabTitle) | - onTabChange | Switching callbacks for tabs | function(tabTitle) | -
onViewMore | Callback of click for view more | function(tabProps, event) | -
popupVisible | Popup card display state | boolean | - popupVisible | Popup card display state | boolean | -
locale | Default message text | Object | `{ emptyText: 'No notifications', clear: 'Clear', loadedAll: 'Loaded', loadMore: 'Loading more' }` locale | Default message text | Object | `{ emptyText: 'No notifications', clear: 'Clear', viewMore: 'Loading more' }`
clearClose | Close menu after clear | boolean | `false` clearClose | Close menu after clear | boolean | `false`
### NoticeIcon.Tab ### NoticeIcon.Tab
...@@ -31,14 +31,9 @@ count | Unread messages count of this tab | number | list.length ...@@ -31,14 +31,9 @@ count | Unread messages count of this tab | number | list.length
emptyText | Message text when list is empty | ReactNode | - emptyText | Message text when list is empty | ReactNode | -
emptyImage | Image when list is empty | string | - emptyImage | Image when list is empty | string | -
list | List data, format refer to the following table | Array | `[]` list | List data, format refer to the following table | Array | `[]`
loadedAll | All messages have been loaded | boolean | `true`
loading | Loading status of this tab | boolean | `false`
name | identifier for message Tab | string | -
scrollToLoad | Scroll to load | boolean | `true`
skeletonCount | Number of skeleton when tab is loading | number | `5`
skeletonProps | Props of skeleton | SkeletonProps | `{}`
showClear | Clear button display status | boolean | `true` showClear | Clear button display status | boolean | `true`
title | header for message Tab | string | - showViewMore | View more button display status | boolean | `false`
title | header for message Tab, the actual text is `locale[title] || title` | string | -
### Tab data ### Tab data
......
...@@ -16,13 +16,13 @@ export default class NoticeIcon extends PureComponent { ...@@ -16,13 +16,13 @@ export default class NoticeIcon extends PureComponent {
onPopupVisibleChange: () => {}, onPopupVisibleChange: () => {},
onTabChange: () => {}, onTabChange: () => {},
onClear: () => {}, onClear: () => {},
onViewMore: () => {},
loading: false, loading: false,
clearClose: false, clearClose: false,
locale: { locale: {
emptyText: 'No notifications', emptyText: 'No notifications',
clear: 'Clear', clear: 'Clear',
loadedAll: 'Loaded', viewMore: 'More',
loadMore: 'Loading more',
}, },
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg', emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
}; };
...@@ -53,53 +53,35 @@ export default class NoticeIcon extends PureComponent { ...@@ -53,53 +53,35 @@ export default class NoticeIcon extends PureComponent {
onTabChange(tabType); onTabChange(tabType);
}; };
onLoadMore = (tabProps, event) => { onViewMore = (tabProps, event) => {
const { onLoadMore } = this.props; const { onViewMore } = this.props;
onLoadMore(tabProps, event); onViewMore(tabProps, event);
}; };
getNotificationBox() { getNotificationBox() {
const { visible } = this.state;
const { children, loading, locale } = this.props; const { children, loading, locale } = this.props;
if (!children) { if (!children) {
return null; return null;
} }
const panes = React.Children.map(children, child => { const panes = React.Children.map(children, child => {
const { const { list, title, count, emptyText, emptyImage, showClear, showViewMore } = child.props;
list,
title,
name,
count,
emptyText,
emptyImage,
showClear,
loadedAll,
scrollToLoad,
skeletonCount,
skeletonProps,
loading: tabLoading,
} = child.props;
const len = list && list.length ? list.length : 0; const len = list && list.length ? list.length : 0;
const msgCount = count || count === 0 ? count : len; const msgCount = count || count === 0 ? count : len;
const tabTitle = msgCount > 0 ? `${title} (${msgCount})` : title; const localeTitle = locale[title] || title;
const tabTitle = msgCount > 0 ? `${localeTitle} (${msgCount})` : localeTitle;
return ( return (
<TabPane tab={tabTitle} key={name}> <TabPane tab={tabTitle} key={title}>
<List <List
data={list} data={list}
emptyImage={emptyImage} emptyImage={emptyImage}
emptyText={emptyText} emptyText={emptyText}
loadedAll={loadedAll}
loading={tabLoading}
locale={locale} locale={locale}
onClear={() => this.onClear(name)} onClear={() => this.onClear(title)}
onClick={item => this.onItemClick(item, child.props)} onClick={item => this.onItemClick(item, child.props)}
onLoadMore={event => this.onLoadMore(child.props, event)} onViewMore={event => this.onViewMore(child.props, event)}
scrollToLoad={scrollToLoad}
showClear={showClear} showClear={showClear}
skeletonCount={skeletonCount} showViewMore={showViewMore}
skeletonProps={skeletonProps}
title={title} title={title}
visible={visible}
/> />
</TabPane> </TabPane>
); );
......
...@@ -16,11 +16,11 @@ bell | translate this please -> Change the bell Icon | ReactNode | `<Icon type=' ...@@ -16,11 +16,11 @@ bell | translate this please -> Change the bell Icon | ReactNode | `<Icon type='
loading | 弹出卡片加载状态 | boolean | `false` loading | 弹出卡片加载状态 | boolean | `false`
onClear | 点击清空按钮的回调 | function(tabName) | - onClear | 点击清空按钮的回调 | function(tabName) | -
onItemClick | 点击列表项的回调 | function(item, tabProps) | - onItemClick | 点击列表项的回调 | function(item, tabProps) | -
onLoadMore | 加载更多的回调 | function(tabProps, event) | -
onPopupVisibleChange | 弹出卡片显隐的回调 | function(visible) | - onPopupVisibleChange | 弹出卡片显隐的回调 | function(visible) | -
onTabChange | 切换页签的回调 | function(tabTitle) | - onTabChange | 切换页签的回调 | function(tabTitle) | -
onViewMore | 点击查看更多的回调 | function(tabProps, event) | -
popupVisible | 控制弹层显隐 | boolean | - popupVisible | 控制弹层显隐 | boolean | -
locale | 默认文案 | Object | `{ emptyText: 'No notifications', clear: 'Clear', loadedAll: 'Loaded', loadMore: 'Loading more' }` locale | 默认文案 | Object | `{ emptyText: 'No notifications', clear: 'Clear', viewMore: 'Loading more' }`
clearClose | 点击清空按钮后关闭通知菜单 | boolean | `false` clearClose | 点击清空按钮后关闭通知菜单 | boolean | `false`
### NoticeIcon.Tab ### NoticeIcon.Tab
...@@ -31,14 +31,9 @@ count | 当前 Tab 未读消息数量 | number | list.length ...@@ -31,14 +31,9 @@ count | 当前 Tab 未读消息数量 | number | list.length
emptyText | 针对每个 Tab 定制空数据文案 | ReactNode | - emptyText | 针对每个 Tab 定制空数据文案 | ReactNode | -
emptyImage | 针对每个 Tab 定制空数据图片 | string | - emptyImage | 针对每个 Tab 定制空数据图片 | string | -
list | 列表数据,格式参照下表 | Array | `[]` list | 列表数据,格式参照下表 | Array | `[]`
loadedAll | 已加载完所有消息 | boolean | `true`
loading | 当前 Tab 的加载状态 | boolean | `false`
name | 消息分类的标识符 | string | -
scrollToLoad | 允许滚动自加载 | boolean | `true`
skeletonCount | 加载时占位骨架的数量 | number | `5`
skeletonProps | 加载时占位骨架的属性 | SkeletonProps | `{}`
showClear | 是否显示清空按钮 | boolean | `true` showClear | 是否显示清空按钮 | boolean | `true`
title | 消息分类的页签标题 | string | - showViewMore | 是否显示查看更多按钮 | boolean | `false`
title | 消息分类的页签标题,实际的文案是 `locale[title] || title` | string | -
### Tab data ### Tab data
......
...@@ -155,7 +155,6 @@ export default connect(({ user, global, setting, loading }) => ({ ...@@ -155,7 +155,6 @@ export default connect(({ user, global, setting, loading }) => ({
collapsed: global.collapsed, collapsed: global.collapsed,
fetchingMoreNotices: loading.effects['global/fetchMoreNotices'], fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
fetchingNotices: loading.effects['global/fetchNotices'], fetchingNotices: loading.effects['global/fetchNotices'],
loadedAllNotices: global.loadedAllNotices,
notices: global.notices, notices: global.notices,
setting, setting,
}))(HeaderView); }))(HeaderView);
...@@ -13,6 +13,5 @@ export default { ...@@ -13,6 +13,5 @@ export default {
'component.noticeIcon.clear': 'Clear', 'component.noticeIcon.clear': 'Clear',
'component.noticeIcon.cleared': 'Cleared', 'component.noticeIcon.cleared': 'Cleared',
'component.noticeIcon.empty': 'No notifications', 'component.noticeIcon.empty': 'No notifications',
'component.noticeIcon.loaded': 'Loaded', 'component.noticeIcon.view-more': 'View more',
'component.noticeIcon.loading-more': 'Loading more',
}; };
...@@ -14,5 +14,5 @@ export default { ...@@ -14,5 +14,5 @@ export default {
'component.noticeIcon.cleared': 'Limpo', 'component.noticeIcon.cleared': 'Limpo',
'component.noticeIcon.empty': 'Sem notificações', 'component.noticeIcon.empty': 'Sem notificações',
'component.noticeIcon.loaded': 'Carregado', 'component.noticeIcon.loaded': 'Carregado',
'component.noticeIcon.loading-more': 'Carregar mais', 'component.noticeIcon.view-more': 'Veja mais',
}; };
...@@ -13,6 +13,5 @@ export default { ...@@ -13,6 +13,5 @@ export default {
'component.noticeIcon.clear': '清空', 'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了', 'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暂无数据', 'component.noticeIcon.empty': '暂无数据',
'component.noticeIcon.loaded': '加载完毕', 'component.noticeIcon.view-more': '查看更多',
'component.noticeIcon.loading-more': '加载更多',
}; };
...@@ -13,6 +13,5 @@ export default { ...@@ -13,6 +13,5 @@ export default {
'component.noticeIcon.clear': '清空', 'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了', 'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暫無資料', 'component.noticeIcon.empty': '暫無資料',
'component.noticeIcon.loaded': '加載完畢', 'component.noticeIcon.view-more': '查看更多',
'component.noticeIcon.loading-more': '加載更多',
}; };
...@@ -6,42 +6,14 @@ export default { ...@@ -6,42 +6,14 @@ export default {
state: { state: {
collapsed: false, collapsed: false,
notices: [], notices: [],
loadedAllNotices: false,
}, },
effects: { effects: {
*fetchNotices(_, { call, put, select }) { *fetchNotices(_, { call, put, select }) {
const data = yield call(queryNotices); const data = yield call(queryNotices);
const loadedAllNotices = data && data.length && data[data.length - 1] === null;
yield put({
type: 'setLoadedStatus',
payload: loadedAllNotices,
});
yield put({ yield put({
type: 'saveNotices', type: 'saveNotices',
payload: data.filter(item => item), payload: data,
});
const unreadCount = yield select(
state => state.global.notices.filter(item => !item.read).length
);
yield put({
type: 'user/changeNotifyCount',
payload: {
totalCount: data.length,
unreadCount,
},
});
},
*fetchMoreNotices({ payload }, { call, put, select }) {
const data = yield call(queryNotices, payload);
const loadedAllNotices = data && data.length && data[data.length - 1] === null;
yield put({
type: 'setLoadedStatus',
payload: loadedAllNotices,
});
yield put({
type: 'pushNotices',
payload: data.filter(item => item),
}); });
const unreadCount = yield select( const unreadCount = yield select(
state => state.global.notices.filter(item => !item.read).length state => state.global.notices.filter(item => !item.read).length
...@@ -114,18 +86,6 @@ export default { ...@@ -114,18 +86,6 @@ export default {
notices: state.notices.filter(item => item.type !== payload), notices: state.notices.filter(item => item.type !== payload),
}; };
}, },
pushNotices(state, { payload }) {
return {
...state,
notices: [...state.notices, ...payload],
};
},
setLoadedStatus(state, { payload }) {
return {
...state,
loadedAllNotices: payload,
};
},
}, },
subscriptions: { subscriptions: {
......
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