RightContent.tsx 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import { ConnectProps, ConnectState } from '@/models/connect';
  2. import { NoticeItem } from '@/models/global';
  3. import { CurrentUser } from '@/models/user';
  4. import React, { Component } from 'react';
  5. import { Spin, Tag, Menu, Icon, Avatar, Tooltip, message } from 'antd';
  6. import { ClickParam } from 'antd/es/menu';
  7. import { FormattedMessage, formatMessage } from 'umi-plugin-react/locale';
  8. import moment from 'moment';
  9. import groupBy from 'lodash/groupBy';
  10. import NoticeIcon from '../NoticeIcon';
  11. import HeaderSearch from '../HeaderSearch';
  12. import HeaderDropdown from '../HeaderDropdown';
  13. import SelectLang from '../SelectLang';
  14. import styles from './index.less';
  15. import { connect } from 'dva';
  16. export type SiderTheme = 'light' | 'dark';
  17. export interface GlobalHeaderRightProps extends ConnectProps {
  18. notices?: NoticeItem[];
  19. currentUser?: CurrentUser;
  20. fetchingNotices?: boolean;
  21. onNoticeVisibleChange?: (visible: boolean) => void;
  22. onMenuClick?: (param: ClickParam) => void;
  23. onNoticeClear?: (tabName?: string) => void;
  24. theme?: SiderTheme;
  25. }
  26. class GlobalHeaderRight extends Component<GlobalHeaderRightProps> {
  27. getNoticeData = (): { [key: string]: NoticeItem[] } => {
  28. const { notices = [] } = this.props;
  29. if (notices.length === 0) {
  30. return {};
  31. }
  32. const newNotices = notices.map(notice => {
  33. const newNotice = { ...notice };
  34. if (newNotice.datetime) {
  35. newNotice.datetime = moment(notice.datetime as string).fromNow();
  36. }
  37. if (newNotice.id) {
  38. newNotice.key = newNotice.id;
  39. }
  40. if (newNotice.extra && newNotice.status) {
  41. const color = {
  42. todo: '',
  43. processing: 'blue',
  44. urgent: 'red',
  45. doing: 'gold',
  46. }[newNotice.status];
  47. newNotice.extra = (
  48. <Tag color={color} style={{ marginRight: 0 }}>
  49. {newNotice.extra}
  50. </Tag>
  51. );
  52. }
  53. return newNotice;
  54. });
  55. return groupBy(newNotices, 'type');
  56. };
  57. getUnreadData = (noticeData: { [key: string]: NoticeItem[] }) => {
  58. const unreadMsg: { [key: string]: number } = {};
  59. Object.entries(noticeData).forEach(([key, value]) => {
  60. if (!unreadMsg[key]) {
  61. unreadMsg[key] = 0;
  62. }
  63. if (Array.isArray(value)) {
  64. unreadMsg[key] = value.filter(item => !item.read).length;
  65. }
  66. });
  67. return unreadMsg;
  68. };
  69. changeReadState = (clickedItem: NoticeItem) => {
  70. const { id } = clickedItem;
  71. const { dispatch } = this.props;
  72. dispatch!({
  73. type: 'global/changeNoticeReadState',
  74. payload: id,
  75. });
  76. };
  77. componentDidMount() {
  78. const { dispatch } = this.props;
  79. dispatch!({
  80. type: 'global/fetchNotices',
  81. });
  82. }
  83. handleNoticeClear = (title: string, key: string) => {
  84. const { dispatch } = this.props;
  85. message.success(`${formatMessage({ id: 'component.noticeIcon.cleared' })} ${title}`);
  86. if (dispatch) {
  87. dispatch({
  88. type: 'global/clearNotices',
  89. payload: key,
  90. });
  91. }
  92. };
  93. render() {
  94. const { currentUser, fetchingNotices, onNoticeVisibleChange, onMenuClick, theme } = this.props;
  95. const menu = (
  96. <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
  97. <Menu.Item key="userCenter">
  98. <Icon type="user" />
  99. <FormattedMessage id="menu.account.center" defaultMessage="account center" />
  100. </Menu.Item>
  101. <Menu.Item key="userinfo">
  102. <Icon type="setting" />
  103. <FormattedMessage id="menu.account.settings" defaultMessage="account settings" />
  104. </Menu.Item>
  105. <Menu.Item key="triggerError">
  106. <Icon type="close-circle" />
  107. <FormattedMessage id="menu.account.trigger" defaultMessage="Trigger Error" />
  108. </Menu.Item>
  109. <Menu.Divider />
  110. <Menu.Item key="logout">
  111. <Icon type="logout" />
  112. <FormattedMessage id="menu.account.logout" defaultMessage="logout" />
  113. </Menu.Item>
  114. </Menu>
  115. );
  116. const noticeData = this.getNoticeData();
  117. const unreadMsg = this.getUnreadData(noticeData);
  118. let className = styles.right;
  119. if (theme === 'dark') {
  120. className = `${styles.right} ${styles.dark}`;
  121. }
  122. return (
  123. <div className={className}>
  124. <HeaderSearch
  125. className={`${styles.action} ${styles.search}`}
  126. placeholder={formatMessage({ id: 'component.globalHeader.search' })}
  127. dataSource={[
  128. formatMessage({ id: 'component.globalHeader.search.example1' }),
  129. formatMessage({ id: 'component.globalHeader.search.example2' }),
  130. formatMessage({ id: 'component.globalHeader.search.example3' }),
  131. ]}
  132. onSearch={value => {
  133. console.log('input', value); // tslint:disable-line no-console
  134. }}
  135. onPressEnter={value => {
  136. console.log('enter', value); // tslint:disable-line no-console
  137. }}
  138. />
  139. <Tooltip title={formatMessage({ id: 'component.globalHeader.help' })}>
  140. <a
  141. target="_blank"
  142. href="https://pro.ant.design/docs/getting-started"
  143. rel="noopener noreferrer"
  144. className={styles.action}
  145. >
  146. <Icon type="question-circle-o" />
  147. </a>
  148. </Tooltip>
  149. <NoticeIcon
  150. className={styles.action}
  151. count={currentUser && currentUser.unreadCount}
  152. onItemClick={item => {
  153. this.changeReadState(item as NoticeItem);
  154. }}
  155. loading={fetchingNotices}
  156. clearText={formatMessage({ id: 'component.noticeIcon.clear' })}
  157. viewMoreText={formatMessage({ id: 'component.noticeIcon.view-more' })}
  158. onClear={this.handleNoticeClear}
  159. onPopupVisibleChange={onNoticeVisibleChange}
  160. onViewMore={() => message.info('Click on view more')}
  161. clearClose
  162. >
  163. <NoticeIcon.Tab
  164. tabKey="notification"
  165. count={unreadMsg.notification}
  166. list={noticeData.notification}
  167. title={formatMessage({ id: 'component.globalHeader.notification' })}
  168. emptyText={formatMessage({ id: 'component.globalHeader.notification.empty' })}
  169. showViewMore
  170. />
  171. <NoticeIcon.Tab
  172. tabKey="message"
  173. count={unreadMsg.message}
  174. list={noticeData.message}
  175. title={formatMessage({ id: 'component.globalHeader.message' })}
  176. emptyText={formatMessage({ id: 'component.globalHeader.message.empty' })}
  177. showViewMore
  178. />
  179. <NoticeIcon.Tab
  180. tabKey="event"
  181. title={formatMessage({ id: 'component.globalHeader.event' })}
  182. emptyText={formatMessage({ id: 'component.globalHeader.event.empty' })}
  183. count={unreadMsg.event}
  184. list={noticeData.event}
  185. showViewMore
  186. />
  187. </NoticeIcon>
  188. {currentUser && currentUser.name ? (
  189. <HeaderDropdown overlay={menu}>
  190. <span className={`${styles.action} ${styles.account}`}>
  191. <Avatar
  192. size="small"
  193. className={styles.avatar}
  194. src={currentUser.avatar}
  195. alt="avatar"
  196. />
  197. <span className={styles.name}>{currentUser.name}</span>
  198. </span>
  199. </HeaderDropdown>
  200. ) : (
  201. <Spin size="small" style={{ marginLeft: 8, marginRight: 8 }} />
  202. )}
  203. <SelectLang className={styles.action} />
  204. </div>
  205. );
  206. }
  207. }
  208. export default connect(({ user, global, loading }: ConnectState) => ({
  209. currentUser: user.currentUser,
  210. collapsed: global.collapsed,
  211. fetchingMoreNotices: loading.effects['global/fetchMoreNotices'],
  212. fetchingNotices: loading.effects['global/fetchNotices'],
  213. notices: global.notices,
  214. }))(GlobalHeaderRight);