diff --git a/mock/notices.js b/mock/notices.js
index 505088e0da17ba556961e2d1f3ed1159d561cae2..e07565dfe603c73b348c908a201d20422da3bf52 100644
--- a/mock/notices.js
+++ b/mock/notices.js
@@ -1,101 +1,114 @@
-const getNotices = (req, res) =>
- res.json([
- {
- id: '000000001',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
- title: '你收到了 14 份新周报',
- datetime: '2017-08-09',
- type: 'notification',
- },
- {
- id: '000000002',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
- title: '你推荐的 曲妮妮 已通过第三轮面试',
- datetime: '2017-08-08',
- type: 'notification',
- },
- {
- id: '000000003',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
- title: '这种模板可以区分多种通知类型',
- datetime: '2017-08-07',
- read: true,
- type: 'notification',
- },
- {
- id: '000000004',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
- title: '左侧图标用于区分不同的类型',
- datetime: '2017-08-07',
- type: 'notification',
- },
- {
- id: '000000005',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
- title: '内容不要超过两行字,超出时自动截断',
- datetime: '2017-08-07',
- type: 'notification',
- },
- {
- id: '000000006',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
- title: '曲丽丽 评论了你',
- description: '描述信息描述信息描述信息',
- datetime: '2017-08-07',
- type: 'message',
- clickClose: true,
- },
- {
- id: '000000007',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
- title: '朱偏右 回复了你',
- description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
- datetime: '2017-08-07',
- type: 'message',
- clickClose: true,
- },
- {
- id: '000000008',
- avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
- title: '标题',
- description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
- datetime: '2017-08-07',
- type: 'message',
- clickClose: true,
- },
- {
- id: '000000009',
- title: '任务名称',
- description: '任务需要在 2017-01-12 20:00 前启动',
- extra: '未开始',
- status: 'todo',
- type: 'event',
- },
- {
- id: '000000010',
- title: '第三方紧急代码变更',
- description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
- extra: '马上到期',
- status: 'urgent',
- type: 'event',
- },
- {
- id: '000000011',
- title: '信息安全考试',
- description: '指派竹尔于 2017-01-09 前完成更新并发布',
- extra: '已耗时 8 天',
- status: 'doing',
- type: 'event',
- },
- {
- id: '000000012',
- title: 'ABCD 版本发布',
- description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
- extra: '进行中',
- status: 'processing',
- type: 'event',
- },
- ]);
+const fakeNotices = [
+ {
+ id: '000000001',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+ title: '你收到了 14 份新周报',
+ datetime: '2017-08-09',
+ type: 'notification',
+ },
+ {
+ id: '000000002',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/OKJXDXrmkNshAMvwtvhu.png',
+ title: '你推荐的 曲妮妮 已通过第三轮面试',
+ datetime: '2017-08-08',
+ type: 'notification',
+ },
+ {
+ id: '000000003',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/kISTdvpyTAhtGxpovNWd.png',
+ title: '这种模板可以区分多种通知类型',
+ datetime: '2017-08-07',
+ read: true,
+ type: 'notification',
+ },
+ {
+ id: '000000004',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/GvqBnKhFgObvnSGkDsje.png',
+ title: '左侧图标用于区分不同的类型',
+ datetime: '2017-08-07',
+ type: 'notification',
+ },
+ {
+ id: '000000005',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ThXAXghbEsBCCSDihZxY.png',
+ title: '内容不要超过两行字,超出时自动截断',
+ datetime: '2017-08-07',
+ type: 'notification',
+ },
+ {
+ id: '000000006',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+ title: '曲丽丽 评论了你',
+ description: '描述信息描述信息描述信息',
+ datetime: '2017-08-07',
+ type: 'message',
+ clickClose: true,
+ },
+ {
+ id: '000000007',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+ title: '朱偏右 回复了你',
+ description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+ datetime: '2017-08-07',
+ type: 'message',
+ clickClose: true,
+ },
+ {
+ id: '000000008',
+ avatar: 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg',
+ title: '标题',
+ description: '这种模板用于提醒谁与你发生了互动,左侧放『谁』的头像',
+ datetime: '2017-08-07',
+ type: 'message',
+ clickClose: true,
+ },
+ {
+ id: '000000009',
+ title: '任务名称',
+ description: '任务需要在 2017-01-12 20:00 前启动',
+ extra: '未开始',
+ status: 'todo',
+ type: 'event',
+ },
+ {
+ id: '000000010',
+ title: '第三方紧急代码变更',
+ description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+ extra: '马上到期',
+ status: 'urgent',
+ type: 'event',
+ },
+ {
+ id: '000000011',
+ title: '信息安全考试',
+ description: '指派竹尔于 2017-01-09 前完成更新并发布',
+ extra: '已耗时 8 天',
+ status: 'doing',
+ type: 'event',
+ },
+ {
+ id: '000000012',
+ title: 'ABCD 版本发布',
+ description: '冠霖提交于 2017-01-06,需在 2017-01-07 前完成代码变更任务',
+ extra: '进行中',
+ 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 {
'GET /api/notices': getNotices,
diff --git a/src/components/GlobalHeader/RightContent.js b/src/components/GlobalHeader/RightContent.js
index 80ccba0b4aaccd2d79c6f548a47ce1874ee5180a..dad360ff08299518c7d3fdc7026c2b3b289b883f 100644
--- a/src/components/GlobalHeader/RightContent.js
+++ b/src/components/GlobalHeader/RightContent.js
@@ -63,13 +63,30 @@ 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() {
const {
currentUser,
+ fetchingMoreNotices,
fetchingNotices,
+ loadedAllNotices,
onNoticeVisibleChange,
onMenuClick,
onNoticeClear,
+ skeletonCount,
theme,
} = this.props;
const menu = (
@@ -93,6 +110,11 @@ export default class GlobalHeaderRight extends PureComponent {
);
+ const loadMoreProps = {
+ skeletonCount,
+ loadedAll: loadedAllNotices,
+ loading: fetchingMoreNotices,
+ };
const noticeData = this.getNoticeData();
const unreadMsg = this.getUnreadData(noticeData);
let className = styles.right;
@@ -136,8 +158,11 @@ export default class GlobalHeaderRight extends PureComponent {
locale={{
emptyText: formatMessage({ id: 'component.noticeIcon.empty' }),
clear: formatMessage({ id: 'component.noticeIcon.clear' }),
+ loadedAll: formatMessage({ id: 'component.noticeIcon.loaded' }),
+ loadMore: formatMessage({ id: 'component.noticeIcon.loading-more' }),
}}
onClear={onNoticeClear}
+ onLoadMore={this.fetchMoreNotices}
onPopupVisibleChange={onNoticeVisibleChange}
loading={fetchingNotices}
clearClose
@@ -149,6 +174,7 @@ export default class GlobalHeaderRight extends PureComponent {
name="notification"
emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })}
emptyImage="https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg"
+ {...loadMoreProps}
/>
{currentUser.name ? (
diff --git a/src/components/NoticeIcon/NoticeIconTab.d.ts b/src/components/NoticeIcon/NoticeIconTab.d.ts
index a44ecd9cb8627d0a8eb86c27d22a258fee651e61..0f8037cbbf30a863b3f13512fd75196bfd48c351 100644
--- a/src/components/NoticeIcon/NoticeIconTab.d.ts
+++ b/src/components/NoticeIcon/NoticeIconTab.d.ts
@@ -1,4 +1,6 @@
+import { SkeletonProps } from 'antd/lib/skeleton';
import * as React from 'react';
+
export interface INoticeIconData {
avatar?: string | React.ReactNode;
title?: React.ReactNode;
@@ -9,14 +11,18 @@ export interface INoticeIconData {
}
export interface INoticeIconTabProps {
- list?: INoticeIconData[];
count?: number;
- title?: string;
- name?: string;
emptyText?: React.ReactNode;
emptyImage?: string;
- style?: React.CSSProperties;
+ list?: INoticeIconData[];
+ loadedAll?: boolean;
+ loading?: boolean;
+ name?: string;
showClear?: boolean;
+ skeletonCount?: number;
+ skeletonProps: SkeletonProps;
+ style?: React.CSSProperties;
+ title?: string;
}
export default class NoticeIconTab extends React.Component {}
diff --git a/src/components/NoticeIcon/NoticeList.js b/src/components/NoticeIcon/NoticeList.js
index d0de9af5ef16f3f6cff34da035a4f60e672dc09d..6b73e6e0de9987fcc11a8df296b43e5cda5822bf 100644
--- a/src/components/NoticeIcon/NoticeList.js
+++ b/src/components/NoticeIcon/NoticeList.js
@@ -1,8 +1,10 @@
import React from 'react';
-import { Avatar, List } from 'antd';
+import { Avatar, List, Skeleton } from 'antd';
import classNames from 'classnames';
import styles from './NoticeList.less';
+let ListElement = null;
+
export default function NoticeList({
data = [],
onClick,
@@ -11,7 +13,14 @@ export default function NoticeList({
locale,
emptyText,
emptyImage,
+ loading,
+ onLoadMore,
+ visible,
+ loadedAll = true,
+ scrollToLoad = true,
showClear = true,
+ skeletonCount = 5,
+ skeletonProps = {},
}) {
if (data.length === 0) {
return (
@@ -21,10 +30,36 @@ export default function NoticeList({
);
}
+ const loadingList = Array.from({ length: loading ? skeletonCount : 0 }).map(() => ({ loading }));
+ const LoadMore = loadedAll ? (
+
+ {locale.loadedAll}
+
+ ) : (
+
+ {locale.loadMore}
+
+ );
+ 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 (
-
- {data.map((item, i) => {
+
+ {[...data, ...loadingList].map((item, i) => {
const itemCls = classNames(styles.item, {
[styles.read]: item.read,
});
@@ -33,30 +68,32 @@ export default function NoticeList({
typeof item.avatar === 'string' ? (
) : (
- item.avatar
+ {item.avatar}
)
) : null;
return (
onClick(item)}>
- {leftIcon}}
- title={
-
- {item.title}
-
{item.extra}
-
- }
- description={
-
-
- {item.description}
+
+
+ {item.title}
+ {item.extra}
+
+ }
+ description={
+
+
+ {item.description}
+
+
{item.datetime}
-
{item.datetime}
-
- }
- />
+ }
+ />
+
);
})}
diff --git a/src/components/NoticeIcon/NoticeList.less b/src/components/NoticeIcon/NoticeList.less
index e34efdc9da182b0530544ef3162200583ba21906..8435414a23e724846f5b2d8f6679f834cf8cd79e 100644
--- a/src/components/NoticeIcon/NoticeList.less
+++ b/src/components/NoticeIcon/NoticeList.less
@@ -3,6 +3,9 @@
.list {
max-height: 400px;
overflow: auto;
+ &::-webkit-scrollbar {
+ display: none;
+ }
.item {
transition: all 0.3s;
overflow: hidden;
@@ -52,6 +55,16 @@
margin-top: -1.5px;
}
}
+ .loadMore {
+ padding: 8px 0;
+ cursor: pointer;
+ color: @primary-6;
+ text-align: center;
+ &.loadedAll {
+ cursor: unset;
+ color: rgba(0, 0, 0, 0.25);
+ }
+ }
}
.notFound {
diff --git a/src/components/NoticeIcon/index.d.ts b/src/components/NoticeIcon/index.d.ts
index 79803fe7853b5a60fd972e8339f656ccb5e79b17..f7d6479aa89d8584d37e0f7360b1ddc705a71ca8 100644
--- a/src/components/NoticeIcon/index.d.ts
+++ b/src/components/NoticeIcon/index.d.ts
@@ -8,11 +8,17 @@ export interface INoticeIconProps {
loading?: boolean;
onClear?: (tabName: string) => void;
onItemClick?: (item: INoticeIconData, tabProps: INoticeIconProps) => void;
+ onLoadMore?: (tabProps: INoticeIconProps) => void;
onTabChange?: (tabTile: string) => void;
style?: React.CSSProperties;
onPopupVisibleChange?: (visible: boolean) => void;
popupVisible?: boolean;
- locale?: { emptyText: string; clear: string };
+ locale?: {
+ emptyText: string;
+ clear: string;
+ loadedAll: string;
+ loadMore: string;
+ };
clearClose?: boolean;
}
diff --git a/src/components/NoticeIcon/index.en-US.md b/src/components/NoticeIcon/index.en-US.md
index c4990b64483f16e4f888cdcb3288c22203959c9c..a5a5f214b1043ea09c200ea70d839128df87484b 100644
--- a/src/components/NoticeIcon/index.en-US.md
+++ b/src/components/NoticeIcon/index.en-US.md
@@ -13,32 +13,40 @@ Property | Description | Type | Default
----|------|-----|------
count | Total number of messages | number | -
bell | Change the bell Icon | ReactNode | ``
-loading | Popup card loading status | boolean | false
-onClear | Click to clear button the callback | function(tabName) | -
+loading | Popup card loading status | boolean | `false`
+onClear | Click to clear button the callback | function(tabName) | -
onItemClick | Click on the list item's callback | function(item, tabProps) | -
-onTabChange | Switching callbacks for tabs | function(tabTitle) | -
+onLoadMore | Callback of click for loading more | function(tabProps, event) | -
onPopupVisibleChange | Popup Card Showing or Hiding Callbacks | function(visible) | -
+onTabChange | Switching callbacks for tabs | function(tabTitle) | -
popupVisible | Popup card display state | boolean | -
-locale | Default message text | Object | `{ emptyText: '暂无数据', clear: '清空' }`
+locale | Default message text | Object | `{ emptyText: 'No notifications', clear: 'Clear', loadedAll: 'Loaded', loadMore: 'Loading more' }`
+clearClose | Close menu after clear | boolean | `false`
### NoticeIcon.Tab
Property | Description | Type | Default
----|------|-----|------
-title | header for message Tab | string | -
-name | identifier for message Tab | string | -
+count | Unread messages count of this tab | number | list.length
+emptyText | Message text when list is empty | ReactNode | -
+emptyImage | Image when list is empty | string | -
list | List data, format refer to the following table | Array | `[]`
-showClear | Clear button display status | boolean | true
-emptyText | message text when list is empty | ReactNode | -
-emptyImage | image when list is empty | string | -
-
+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`
+title | header for message Tab | string | -
### Tab data
Property | Description | Type | Default
----|------|-----|------
-avatar | avatar img url | string \| ReactNode | -
+avatar | avatar img url | string \| ReactNode | -
title | title | ReactNode | -
description | description info | ReactNode | -
datetime | Timestamps | ReactNode | -
-extra |Additional information in the upper right corner of the list item | ReactNode | -
+extra | Additional information in the upper right corner of the list item | ReactNode | -
+clickClose | Close menu after clicking list item | boolean | `false`
diff --git a/src/components/NoticeIcon/index.js b/src/components/NoticeIcon/index.js
index ad21287cd9f7c9c200cb59b89108852a03b28c49..133819bb1e580f88020b5609c6d4afd3aae35883 100644
--- a/src/components/NoticeIcon/index.js
+++ b/src/components/NoticeIcon/index.js
@@ -21,6 +21,8 @@ export default class NoticeIcon extends PureComponent {
locale: {
emptyText: 'No notifications',
clear: 'Clear',
+ loadedAll: 'Loaded',
+ loadMore: 'Loading more',
},
emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
};
@@ -51,25 +53,53 @@ export default class NoticeIcon extends PureComponent {
onTabChange(tabType);
};
+ onLoadMore = (tabProps, event) => {
+ const { onLoadMore } = this.props;
+ onLoadMore(tabProps, event);
+ };
+
getNotificationBox() {
+ const { visible } = this.state;
const { children, loading, locale } = this.props;
if (!children) {
return null;
}
const panes = React.Children.map(children, child => {
- const { list, title, name, count } = child.props;
+ const {
+ list,
+ title,
+ name,
+ count,
+ emptyText,
+ emptyImage,
+ showClear,
+ loadedAll,
+ scrollToLoad,
+ skeletonCount,
+ skeletonProps,
+ loading: tabLoading,
+ } = child.props;
const len = list && list.length ? list.length : 0;
const msgCount = count || count === 0 ? count : len;
const tabTitle = msgCount > 0 ? `${title} (${msgCount})` : title;
return (
this.onItemClick(item, child.props)}
+ emptyImage={emptyImage}
+ emptyText={emptyText}
+ loadedAll={loadedAll}
+ loading={tabLoading}
+ locale={locale}
onClear={() => this.onClear(name)}
+ onClick={item => this.onItemClick(item, child.props)}
+ onLoadMore={event => this.onLoadMore(child.props, event)}
+ scrollToLoad={scrollToLoad}
+ showClear={showClear}
+ skeletonCount={skeletonCount}
+ skeletonProps={skeletonProps}
title={title}
- locale={locale}
+ visible={visible}
/>
);
diff --git a/src/components/NoticeIcon/index.less b/src/components/NoticeIcon/index.less
index 55eb9294e59a5c2cfb9e82494128cf42cc0d5038..3ca7c5d1e7126893c6044603f06e5c76cabed104 100644
--- a/src/components/NoticeIcon/index.less
+++ b/src/components/NoticeIcon/index.less
@@ -20,7 +20,7 @@
text-align: center;
}
.ant-tabs-bar {
- margin-bottom: 4px;
+ margin-bottom: 0;
}
}
}
diff --git a/src/components/NoticeIcon/index.zh-CN.md b/src/components/NoticeIcon/index.zh-CN.md
index 65b2db714cbc0de295e0fa1821caf801130a4483..23dab2203ed8c6fb3db427359c0ad3b15533a089 100644
--- a/src/components/NoticeIcon/index.zh-CN.md
+++ b/src/components/NoticeIcon/index.zh-CN.md
@@ -13,26 +13,32 @@ order: 9
----|------|-----|------
count | 图标上的消息总数 | number | -
bell | translate this please -> Change the bell Icon | ReactNode | ``
-loading | 弹出卡片加载状态 | boolean | false
+loading | 弹出卡片加载状态 | boolean | `false`
onClear | 点击清空按钮的回调 | function(tabName) | -
onItemClick | 点击列表项的回调 | function(item, tabProps) | -
-onTabChange | 切换页签的回调 | function(tabTitle) | -
+onLoadMore | 加载更多的回调 | function(tabProps, event) | -
onPopupVisibleChange | 弹出卡片显隐的回调 | function(visible) | -
+onTabChange | 切换页签的回调 | function(tabTitle) | -
popupVisible | 控制弹层显隐 | boolean | -
-locale | 默认文案 | Object | `{ emptyText: '暂无数据', clear: '清空' }`
-clearClose | 点击清空按钮后关闭通知菜单 | boolean | false
+locale | 默认文案 | Object | `{ emptyText: 'No notifications', clear: 'Clear', loadedAll: 'Loaded', loadMore: 'Loading more' }`
+clearClose | 点击清空按钮后关闭通知菜单 | boolean | `false`
### NoticeIcon.Tab
参数 | 说明 | 类型 | 默认值
----|------|-----|------
-title | 消息分类的页签标题 | string | -
-name | 消息分类的标识符 | string | -
-list | 列表数据,格式参照下表 | Array | `[]`
-showClear | 是否显示清空按钮 | boolean | true
+count | 当前 Tab 未读消息数量 | number | list.length
emptyText | 针对每个 Tab 定制空数据文案 | ReactNode | -
emptyImage | 针对每个 Tab 定制空数据图片 | string | -
-
+list | 列表数据,格式参照下表 | Array | `[]`
+loadedAll | 已加载完所有消息 | boolean | `true`
+loading | 当前 Tab 的加载状态 | boolean | `false`
+name | 消息分类的标识符 | string | -
+scrollToLoad | 允许滚动自加载 | boolean | `true`
+skeletonCount | 加载时占位骨架的数量 | number | `5`
+skeletonProps | 加载时占位骨架的属性 | SkeletonProps | `{}`
+showClear | 是否显示清空按钮 | boolean | `true`
+title | 消息分类的页签标题 | string | -
### Tab data
@@ -43,4 +49,4 @@ title | 标题 | ReactNode | -
description | 描述信息 | ReactNode | -
datetime | 时间戳 | ReactNode | -
extra | 额外信息,在列表项右上角 | ReactNode | -
-clickClose | 点击列表项关闭通知菜单 | boolean | false
+clickClose | 点击列表项关闭通知菜单 | boolean | `false`
diff --git a/src/layouts/Header.js b/src/layouts/Header.js
index 9763982e71a73a9b82256f645af6d9a56505c69a..3c22510da00ee2317e3140d18474bbcfa20b0fc6 100644
--- a/src/layouts/Header.js
+++ b/src/layouts/Header.js
@@ -153,7 +153,9 @@ class HeaderView extends PureComponent {
export default connect(({ user, global, setting, loading }) => ({
currentUser: user.currentUser,
collapsed: global.collapsed,
+ fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
fetchingNotices: loading.effects['global/fetchNotices'],
+ loadedAllNotices: global.loadedAllNotices,
notices: global.notices,
setting,
}))(HeaderView);
diff --git a/src/locales/en-US/globalHeader.js b/src/locales/en-US/globalHeader.js
index 4e95bca85d6bd9373e1599a43817c37d791a032c..29f21d7d264c280642637ee533d762dc92e64423 100644
--- a/src/locales/en-US/globalHeader.js
+++ b/src/locales/en-US/globalHeader.js
@@ -13,4 +13,6 @@ export default {
'component.noticeIcon.clear': 'Clear',
'component.noticeIcon.cleared': 'Cleared',
'component.noticeIcon.empty': 'No notifications',
+ 'component.noticeIcon.loaded': 'Loaded',
+ 'component.noticeIcon.loading-more': 'Loading more',
};
diff --git a/src/locales/pt-BR/globalHeader.js b/src/locales/pt-BR/globalHeader.js
index dbb8c6d98e08b6cd55857eb4c20d17679baccadc..eac034d50bd65844b3d5246011344f7c3fdab008 100644
--- a/src/locales/pt-BR/globalHeader.js
+++ b/src/locales/pt-BR/globalHeader.js
@@ -13,4 +13,6 @@ export default {
'component.noticeIcon.clear': 'Limpar',
'component.noticeIcon.cleared': 'Limpo',
'component.noticeIcon.empty': 'Sem notificações',
+ 'component.noticeIcon.loaded': 'Carregado',
+ 'component.noticeIcon.loading-more': 'Carregar mais',
};
diff --git a/src/locales/zh-CN/globalHeader.js b/src/locales/zh-CN/globalHeader.js
index abd2d2a22ba37376e2f55a89d793e3272580109c..204538294cedfe4d37dfa7436dadf9dc8f6b73ef 100644
--- a/src/locales/zh-CN/globalHeader.js
+++ b/src/locales/zh-CN/globalHeader.js
@@ -13,4 +13,6 @@ export default {
'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暂无数据',
+ 'component.noticeIcon.loaded': '加载完毕',
+ 'component.noticeIcon.loading-more': '加载更多',
};
diff --git a/src/locales/zh-TW/globalHeader.js b/src/locales/zh-TW/globalHeader.js
index 9166c4c8dad6f3dea4ed3fee43b0ae15e4145edb..0ab49d62770deb384787ddcda4a677efca46d6cd 100644
--- a/src/locales/zh-TW/globalHeader.js
+++ b/src/locales/zh-TW/globalHeader.js
@@ -13,4 +13,6 @@ export default {
'component.noticeIcon.clear': '清空',
'component.noticeIcon.cleared': '清空了',
'component.noticeIcon.empty': '暫無數據',
+ 'component.noticeIcon.loaded': '加載完畢',
+ 'component.noticeIcon.loading-more': '加載更多',
};
diff --git a/src/models/global.js b/src/models/global.js
index 34cff599b1072d2df8615f09e634a5e2548de0bf..42895b5929507c5c31609bc8ef226f25095d648a 100644
--- a/src/models/global.js
+++ b/src/models/global.js
@@ -6,14 +6,42 @@ export default {
state: {
collapsed: false,
notices: [],
+ loadedAllNotices: false,
},
effects: {
*fetchNotices(_, { call, put, select }) {
const data = yield call(queryNotices);
+ const loadedAllNotices = data && data.length && data[data.length - 1] === null;
+ yield put({
+ type: 'setLoadedStatus',
+ payload: loadedAllNotices,
+ });
yield put({
type: 'saveNotices',
- payload: data,
+ payload: data.filter(item => item),
+ });
+ 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(
state => state.global.notices.filter(item => !item.read).length
@@ -86,6 +114,18 @@ export default {
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: {
diff --git a/src/services/api.js b/src/services/api.js
index 431f2ccd00c5b75cefb91262734ea62d5c92a032..8cdf4fa889b434f4f8fe2b8c43d0831c7a1d42d6 100644
--- a/src/services/api.js
+++ b/src/services/api.js
@@ -117,8 +117,8 @@ export async function fakeRegister(params) {
});
}
-export async function queryNotices() {
- return request('/api/notices');
+export async function queryNotices(params = {}) {
+ return request(`/api/notices?${stringify(params)}`);
}
export async function getFakeCaptcha(mobile) {