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) {