|
|
@@ -1,142 +1,152 @@
|
|
|
-import { BellOutlined } from '@ant-design/icons';
|
|
|
-import { Badge, Spin, Tabs } from 'antd';
|
|
|
-import useMergedState from 'rc-util/es/hooks/useMergedState';
|
|
|
-import React from 'react';
|
|
|
-import classNames from 'classnames';
|
|
|
-import type { NoticeIconTabProps } from './NoticeList';
|
|
|
-import NoticeList from './NoticeList';
|
|
|
-
|
|
|
-import HeaderDropdown from '../HeaderDropdown';
|
|
|
+import { useEffect, useState } from 'react';
|
|
|
+import { Tag, message } from 'antd';
|
|
|
+import { groupBy } from 'lodash';
|
|
|
+import moment from 'moment';
|
|
|
+import { useModel } from 'umi';
|
|
|
+import { getNotices } from '@/services/ant-design-pro/api';
|
|
|
+
|
|
|
+import NoticeIcon from './NoticeIcon';
|
|
|
import styles from './index.less';
|
|
|
|
|
|
-const { TabPane } = Tabs;
|
|
|
-
|
|
|
-export type 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 type GlobalHeaderRightProps = {
|
|
|
+ fetchingNotices?: boolean;
|
|
|
+ onNoticeVisibleChange?: (visible: boolean) => void;
|
|
|
+ onNoticeClear?: (tabName?: string) => void;
|
|
|
};
|
|
|
|
|
|
-export type 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;
|
|
|
- emptyImage?: string;
|
|
|
- children: React.ReactElement<NoticeIconTabProps>[];
|
|
|
+const getNoticeData = (notices: API.NoticeIconItem[]): Record<string, API.NoticeIconItem[]> => {
|
|
|
+ if (!notices || notices.length === 0 || !Array.isArray(notices)) {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+
|
|
|
+ const newNotices = notices.map((notice) => {
|
|
|
+ const newNotice = { ...notice };
|
|
|
+
|
|
|
+ if (newNotice.datetime) {
|
|
|
+ newNotice.datetime = moment(notice.datetime as string).fromNow();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newNotice.id) {
|
|
|
+ newNotice.key = newNotice.id;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newNotice.extra && newNotice.status) {
|
|
|
+ const color = {
|
|
|
+ todo: '',
|
|
|
+ processing: 'blue',
|
|
|
+ urgent: 'red',
|
|
|
+ doing: 'gold',
|
|
|
+ }[newNotice.status];
|
|
|
+ newNotice.extra = (
|
|
|
+ <Tag
|
|
|
+ color={color}
|
|
|
+ style={{
|
|
|
+ marginRight: 0,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {newNotice.extra}
|
|
|
+ </Tag>
|
|
|
+ ) as any;
|
|
|
+ }
|
|
|
+
|
|
|
+ return newNotice;
|
|
|
+ });
|
|
|
+ return groupBy(newNotices, 'type');
|
|
|
};
|
|
|
|
|
|
-const NoticeIcon: React.FC<NoticeIconProps> & {
|
|
|
- Tab: typeof NoticeList;
|
|
|
-} = (props) => {
|
|
|
- const getNotificationBox = (): React.ReactNode => {
|
|
|
- const {
|
|
|
- children,
|
|
|
- loading,
|
|
|
- onClear,
|
|
|
- onTabChange,
|
|
|
- onItemClick,
|
|
|
- onViewMore,
|
|
|
- clearText,
|
|
|
- viewMoreText,
|
|
|
- } = props;
|
|
|
- if (!children) {
|
|
|
- return null;
|
|
|
+const getUnreadData = (noticeData: Record<string, API.NoticeIconItem[]>) => {
|
|
|
+ const unreadMsg: Record<string, number> = {};
|
|
|
+ Object.keys(noticeData).forEach((key) => {
|
|
|
+ const value = noticeData[key];
|
|
|
+
|
|
|
+ if (!unreadMsg[key]) {
|
|
|
+ unreadMsg[key] = 0;
|
|
|
}
|
|
|
- const panes: React.ReactNode[] = [];
|
|
|
- React.Children.forEach(children, (child: React.ReactElement<NoticeIconTabProps>): void => {
|
|
|
- if (!child) {
|
|
|
- return;
|
|
|
- }
|
|
|
- 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;
|
|
|
- panes.push(
|
|
|
- <TabPane tab={tabTitle} key={tabKey}>
|
|
|
- <NoticeList
|
|
|
- {...child.props}
|
|
|
- clearText={clearText}
|
|
|
- viewMoreText={viewMoreText}
|
|
|
- data={list}
|
|
|
- onClear={(): void => {
|
|
|
- onClear?.(title, tabKey);
|
|
|
- }}
|
|
|
- onClick={(item): void => {
|
|
|
- onItemClick?.(item, child.props);
|
|
|
- }}
|
|
|
- onViewMore={(event): void => {
|
|
|
- onViewMore?.(child.props, event);
|
|
|
- }}
|
|
|
- showClear={showClear}
|
|
|
- showViewMore={showViewMore}
|
|
|
- title={title}
|
|
|
- />
|
|
|
- </TabPane>,
|
|
|
- );
|
|
|
- });
|
|
|
- return (
|
|
|
- <Spin spinning={loading} delay={300}>
|
|
|
- <Tabs className={styles.tabs} onChange={onTabChange}>
|
|
|
- {panes}
|
|
|
- </Tabs>
|
|
|
- </Spin>
|
|
|
+
|
|
|
+ if (Array.isArray(value)) {
|
|
|
+ unreadMsg[key] = value.filter((item) => !item.read).length;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ return unreadMsg;
|
|
|
+};
|
|
|
+
|
|
|
+const NoticeIconView = () => {
|
|
|
+ const { initialState } = useModel('@@initialState');
|
|
|
+ const { currentUser } = initialState || {};
|
|
|
+ const [notices, setNotices] = useState<API.NoticeIconItem[]>([]);
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ getNotices().then(({ data }) => setNotices(data || []));
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const noticeData = getNoticeData(notices);
|
|
|
+ const unreadMsg = getUnreadData(noticeData || {});
|
|
|
+
|
|
|
+ const changeReadState = (id: string) => {
|
|
|
+ setNotices(
|
|
|
+ notices.map((item) => {
|
|
|
+ const notice = { ...item };
|
|
|
+ if (notice.id === id) {
|
|
|
+ notice.read = true;
|
|
|
+ }
|
|
|
+ return notice;
|
|
|
+ }),
|
|
|
);
|
|
|
};
|
|
|
|
|
|
- const { className, count, bell } = props;
|
|
|
-
|
|
|
- const [visible, setVisible] = useMergedState<boolean>(false, {
|
|
|
- value: props.popupVisible,
|
|
|
- onChange: props.onPopupVisibleChange,
|
|
|
- });
|
|
|
- const noticeButtonClass = classNames(className, styles.noticeButton);
|
|
|
- const notificationBox = getNotificationBox();
|
|
|
- const NoticeBellIcon = bell || <BellOutlined 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 clearReadState = (title: string, key: string) => {
|
|
|
+ setNotices(
|
|
|
+ notices.map((item) => {
|
|
|
+ const notice = { ...item };
|
|
|
+ if (notice.type === key) {
|
|
|
+ notice.read = true;
|
|
|
+ }
|
|
|
+ return notice;
|
|
|
+ }),
|
|
|
+ );
|
|
|
+ message.success(`${'清空了'} ${title}`);
|
|
|
+ };
|
|
|
|
|
|
return (
|
|
|
- <HeaderDropdown
|
|
|
- placement="bottomRight"
|
|
|
- overlay={notificationBox}
|
|
|
- overlayClassName={styles.popover}
|
|
|
- trigger={['click']}
|
|
|
- visible={visible}
|
|
|
- onVisibleChange={setVisible}
|
|
|
+ <NoticeIcon
|
|
|
+ className={styles.action}
|
|
|
+ count={currentUser && currentUser.unreadCount}
|
|
|
+ onItemClick={(item) => {
|
|
|
+ changeReadState(item.id!);
|
|
|
+ }}
|
|
|
+ onClear={(title: string, key: string) => clearReadState(title, key)}
|
|
|
+ loading={false}
|
|
|
+ clearText="清空"
|
|
|
+ viewMoreText="查看更多"
|
|
|
+ onViewMore={() => message.info('Click on view more')}
|
|
|
+ clearClose
|
|
|
>
|
|
|
- {trigger}
|
|
|
- </HeaderDropdown>
|
|
|
+ <NoticeIcon.Tab
|
|
|
+ tabKey="notification"
|
|
|
+ count={unreadMsg.notification}
|
|
|
+ list={noticeData.notification}
|
|
|
+ title="通知"
|
|
|
+ emptyText="你已查看所有通知"
|
|
|
+ showViewMore
|
|
|
+ />
|
|
|
+ <NoticeIcon.Tab
|
|
|
+ tabKey="message"
|
|
|
+ count={unreadMsg.message}
|
|
|
+ list={noticeData.message}
|
|
|
+ title="消息"
|
|
|
+ emptyText="您已读完所有消息"
|
|
|
+ showViewMore
|
|
|
+ />
|
|
|
+ <NoticeIcon.Tab
|
|
|
+ tabKey="event"
|
|
|
+ title="待办"
|
|
|
+ emptyText="你已完成所有待办"
|
|
|
+ count={unreadMsg.event}
|
|
|
+ list={noticeData.event}
|
|
|
+ showViewMore
|
|
|
+ />
|
|
|
+ </NoticeIcon>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
-NoticeIcon.defaultProps = {
|
|
|
- emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
|
|
|
-};
|
|
|
-
|
|
|
-NoticeIcon.Tab = NoticeList;
|
|
|
-
|
|
|
-export default NoticeIcon;
|
|
|
+export default NoticeIconView;
|