Prechádzať zdrojové kódy

fix: 右上角无消息通知提示

sun-chaochao 3 rokov pred
rodič
commit
412e5b750b

+ 1 - 1
src/components/NoticeIcon/NoticeIcon.tsx

@@ -26,7 +26,7 @@ export type NoticeIconProps = {
   viewMoreText?: string;
   clearClose?: boolean;
   emptyImage?: string;
-  children?: React.ReactElement<NoticeIconTabProps>[];
+  children?: React.ReactElement<NoticeIconTabProps>[] | React.ReactElement<NoticeIconTabProps>;
 };
 
 const NoticeIcon: React.FC<NoticeIconProps> & {

+ 2 - 6
src/components/NoticeIcon/NoticeList.tsx

@@ -23,7 +23,7 @@ const NoticeList: React.FC<NoticeIconTabProps> = ({
   list = [],
   onClick,
   onClear,
-  title,
+  // title,
   onViewMore,
   emptyText,
   showClear = true,
@@ -89,11 +89,7 @@ const NoticeList: React.FC<NoticeIconTabProps> = ({
         }}
       />
       <div className={styles.bottomBar}>
-        {showClear ? (
-          <div onClick={onClear}>
-            {clearText} {title}
-          </div>
-        ) : null}
+        {showClear ? <div onClick={onClear}>{clearText}</div> : null}
         {showViewMore ? (
           <div
             onClick={(e) => {

+ 134 - 90
src/components/NoticeIcon/index.tsx

@@ -1,12 +1,16 @@
 import { useEffect, useState } from 'react';
-import { Tag, message } from 'antd';
+import { Button, message, notification } from 'antd';
 import { groupBy } from 'lodash';
 import moment from 'moment';
-import { useRequest } from 'umi';
-import { getNotices } from '@/services/ant-design-pro/api';
-
+import Service from '@/services/notice';
 import NoticeIcon from './NoticeIcon';
 import styles from './index.less';
+import encodeQuery from '@/utils/encodeQuery';
+import { getMenuPathByCode, MENUS_CODE } from '@/utils/menu';
+import useHistory from '@/hooks/route/useHistory';
+import { throttleTime } from 'rxjs/operators';
+import Icon from '@ant-design/icons';
+import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 
 export type GlobalHeaderRightProps = {
   fetchingNotices?: boolean;
@@ -22,130 +26,170 @@ const getNoticeData = (notices: API.NoticeIconItem[]): Record<string, API.Notice
   const newNotices = notices.map((notice) => {
     const newNotice = { ...notice };
 
-    if (newNotice.datetime) {
-      newNotice.datetime = moment(notice.datetime as string).fromNow();
+    if (newNotice.notifyTime) {
+      newNotice.notifyTime = moment(notice.notifyTime 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;
-    }
-
+    // newNotice.avatar = 'https://gw.alipayobjects.com/zos/rmsportal/fcHMVNCjPOsbUGdEduuv.jpeg';
+    newNotice.title = notice.topicName;
+    newNotice.description = notice.message;
     return newNotice;
   });
-  return groupBy(newNotices, 'type');
+  return groupBy(
+    newNotices.map((item) => ({ ...item, state: item.state.value })),
+    'state',
+  );
 };
 
-const getUnreadData = (noticeData: Record<string, API.NoticeIconItem[]>) => {
-  const unreadMsg: Record<string, number> = {};
-  Object.keys(noticeData).forEach((key) => {
-    const value = noticeData[key];
+// 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;
-    }
+//     if (!unreadMsg[key]) {
+//       unreadMsg[key] = 0;
+//     }
 
-    if (Array.isArray(value)) {
-      unreadMsg[key] = value.filter((item) => !item.read).length;
-    }
-  });
-  return unreadMsg;
-};
+//     if (Array.isArray(value)) {
+//       // unreadMsg[key] = value.filter((item) => !item.read).length;
+//     }
+//   });
+//   return unreadMsg;
+// };
+
+export const service = new Service('notifications');
 
 const NoticeIconView = () => {
   // const { initialState } = useModel('@@initialState');
   // const { currentUser } = initialState || {};
   const [notices, setNotices] = useState<API.NoticeIconItem[]>([]);
-  const { data } = useRequest(getNotices);
+  const [unreadCount, setUnreadCount] = useState<number>(0);
+  const [visible, setVisible] = useState<boolean>(false);
+  const [loading, setLoading] = useState<boolean>(true);
+  // const { data } = useRequest(getNotices);
+
+  const history = useHistory();
+  const [subscribeTopic] = useSendWebsocketMessage();
+
+  const getUnread = () => {
+    setLoading(true);
+    service
+      .fetchNotices(
+        encodeQuery({
+          terms: { state: 'unread' },
+          sorts: { notifyTime: 'desc' },
+        }),
+      )
+      .then((resp) => {
+        if (resp.status === 200) {
+          setNotices(resp.result?.data || []);
+          setUnreadCount(resp.result?.total || 0);
+        }
+        setLoading(false);
+      });
+  };
+
+  const subscribeNotice = () => {
+    const id = `notification`;
+    const topic = `/notifications`;
+    subscribeTopic!(id, topic, {})
+      ?.pipe(throttleTime(2000))
+      .subscribe((resp: any) => {
+        getUnread();
+        notification.open({
+          message: resp?.payload?.topicName,
+          description: resp?.payload?.message,
+          key: resp.payload.id,
+          top: 60,
+          btn: (
+            <Button
+              type="primary"
+              onClick={() => {
+                service.changeNoticeReadState(resp.payload.id).then((response) => {
+                  if (response.status === 200) {
+                    notification.close(resp.payload.id);
+                    getUnread();
+                  }
+                });
+              }}
+            >
+              标记已读
+            </Button>
+          ),
+          icon: <Icon type="exclamation-circle" style={{ color: '#E23D38' }} />,
+        });
+      });
+  };
 
   useEffect(() => {
-    setNotices(data || []);
-  }, [data]);
+    getUnread();
+    subscribeNotice();
+  }, []);
 
   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 unreadMsg = getUnreadData(noticeData || {});
+
+  const changeReadState = async (item: any) => {
+    const resp = await service.changeNoticeReadState(item.id);
+    if (resp.status === 200) {
+      getUnread();
+    }
+    const url = getMenuPathByCode(MENUS_CODE['account/NotificationRecord']);
+    history.push(url, { ...item });
+    setVisible(false);
   };
 
-  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}`);
+  const clearReadState = async (title: string) => {
+    const clearIds = (getNoticeData(notices).unread || []).map((item) => item.id) || [];
+    const resp = await service.clearNotices(clearIds);
+    if (resp.status === 200) {
+      message.success(`${'清空了'} ${title}`);
+      getUnread();
+    }
   };
 
   return (
     <NoticeIcon
       className={styles.action}
-      count={10}
+      count={unreadCount}
       onItemClick={(item) => {
-        changeReadState(item.id!);
+        changeReadState(item!);
       }}
-      onClear={(title: string, key: string) => clearReadState(title, key)}
-      loading={false}
-      clearText="清空"
+      onClear={(title: string) => clearReadState(title)}
+      loading={loading}
+      clearText="当前标记为已读"
       viewMoreText="查看更多"
-      onViewMore={() => message.info('Click on view more')}
+      onViewMore={() => {
+        const url = getMenuPathByCode(MENUS_CODE['account/NotificationRecord']);
+        history.push(url);
+        setVisible(false);
+      }}
+      popupVisible={visible}
+      onPopupVisibleChange={(see: boolean) => {
+        setVisible(see);
+      }}
       clearClose
     >
       <NoticeIcon.Tab
-        tabKey="notification"
-        count={unreadMsg.notification}
-        list={noticeData.notification}
-        title="通知"
-        emptyText="你已查看所有通知"
-        showViewMore
-      />
-      <NoticeIcon.Tab
-        tabKey="message"
-        count={unreadMsg.message}
-        list={noticeData.message}
-        title="消息"
+        tabKey="read"
+        count={0}
+        list={noticeData.unread}
+        title="未读消息"
         emptyText="您已读完所有消息"
         showViewMore
       />
-      <NoticeIcon.Tab
-        tabKey="event"
-        title="待办"
-        emptyText="你已完成所有待办"
-        count={unreadMsg.event}
-        list={noticeData.event}
+      {/* <NoticeIcon.Tab
+        tabKey="handle"
+        title="待办消息"
+        emptyText="暂无消息"
+        count={0}
+        list={noticeData.handle}
         showViewMore
-      />
+      /> */}
     </NoticeIcon>
   );
 };

+ 4 - 0
src/components/RightContent/index.tsx

@@ -6,6 +6,7 @@ import Avatar from './AvatarDropdown';
 import styles from './index.less';
 import useSendWebsocketMessage from '@/hooks/websocket/useSendWebsocketMessage';
 import { Store } from 'jetlinks-store';
+import NoticeIcon from '../NoticeIcon';
 
 // export type SiderTheme = 'light' | 'dark';
 
@@ -60,6 +61,9 @@ const GlobalHeaderRight: React.FC = () => {
       >
         <QuestionCircleOutlined />
       </span>
+      <span>
+        <NoticeIcon />
+      </span>
       <Avatar menu={true} />
       <SelectLang className={styles.action} />
     </Space>

+ 11 - 3
src/pages/account/NotificationRecord/index.tsx

@@ -13,10 +13,11 @@ import encodeQuery from '@/utils/encodeQuery';
 import { useDomFullHeight } from '@/hooks';
 import { onlyMessage } from '@/utils/util';
 import type { CustomIconComponentProps } from '@ant-design/icons/lib/components/Icon';
-
+import { historyStateModel } from '@/hooks/route/useHistory';
+import { observer } from '@formily/reactive-react';
 export const service = new Service('notifications');
 
-const NotificationRecord = () => {
+const NotificationRecord = observer(() => {
   const intl = useIntl();
   const actionRef = useRef<ActionType>();
   const [param, setParam] = useState({});
@@ -35,6 +36,13 @@ const NotificationRecord = () => {
     });
   }, []);
 
+  useEffect(() => {
+    if (historyStateModel.state.id) {
+      setVisible(true);
+      setCurrent(historyStateModel.state);
+    }
+  }, [historyStateModel.state]);
+
   const ReadSvg = () => (
     <svg
       width="1em"
@@ -189,6 +197,6 @@ const NotificationRecord = () => {
       )}
     </PageContainer>
   );
-};
+});
 
 export default NotificationRecord;

+ 21 - 2
src/pages/device/Product/Detail/Access/AccessConfig/index.tsx

@@ -29,7 +29,10 @@ const AccessConfig = (props: Props) => {
     pageIndex: 0,
     total: 0,
   });
-  const [param, setParam] = useState<any>({ pageSize: 4, terms: [] });
+  const [param, setParam] = useState<any>({
+    pageSize: 4,
+    terms: [],
+  });
 
   const [currrent, setCurrrent] = useState<any>({
     id: productModel.current?.accessId,
@@ -56,10 +59,26 @@ const AccessConfig = (props: Props) => {
                     termType: 'eq',
                     value: 'child-device',
                   },
+                  {
+                    column: 'state',
+                    termType: 'eq',
+                    value: 'enabled',
+                  },
                 ],
               },
             ]
-          : [...params?.terms],
+          : [
+              ...params?.terms,
+              {
+                terms: [
+                  {
+                    column: 'state',
+                    termType: 'eq',
+                    value: 'enabled',
+                  },
+                ],
+              },
+            ],
     };
     service.queryList({ ...temp, sorts: [{ name: 'createTime', order: 'desc' }] }).then((resp) => {
       setDataSource(resp?.result);

+ 1 - 1
src/pages/link/AccessConfig/Detail/Cloud/OneNet/index.tsx

@@ -16,7 +16,7 @@ const OneNet = (props: Props) => {
   useEffect(() => {
     form.setFieldsValue({
       ...props.data,
-      apiAddress: 'https://ag-api.ctwing.cn/',
+      apiAddress: 'https://api.heclouds.com/',
     });
   }, [props.data]);
 

+ 1 - 0
src/pages/link/Protocol/FileUpload/index.tsx

@@ -33,6 +33,7 @@ const FileUpload = connect((props: Props) => {
       <Upload
         accept={props?.accept || '*'}
         listType={'text'}
+        disabled={props?.disabled}
         action={`/${SystemConst.API_BASE}/file/static`}
         headers={{
           'X-Access-Token': Token.get(),

+ 11 - 1
src/services/ant-design-pro/typings.d.ts

@@ -84,7 +84,7 @@ declare namespace API {
     success?: boolean;
   };
 
-  type NoticeIconItemType = 'notification' | 'message' | 'event';
+  type NoticeIconItemType = 'read' | 'handle';
 
   type NoticeIconItem = {
     id?: string;
@@ -97,5 +97,15 @@ declare namespace API {
     datetime?: string;
     description?: string;
     type?: NoticeIconItemType;
+
+    state: any;
+    message: string;
+    dataId: string;
+    notifyTime: number | string;
+    subscribeId: string;
+    subscriber: string;
+    subscriberType: string;
+    topicName: any;
+    topicProvider: string;
   };
 }

+ 24 - 0
src/services/notice/index.ts

@@ -0,0 +1,24 @@
+import BaseService from '@/utils/BaseService';
+import { request } from 'umi';
+import SystemConst from '@/utils/const';
+
+class Service extends BaseService<API.NoticeIconItem> {
+  public fetchNotices = (params?: any) =>
+    request(`/${SystemConst.API_BASE}/notifications/_query`, {
+      method: 'GET',
+      params,
+    });
+
+  public clearNotices = (data?: any[]) =>
+    request(`/${SystemConst.API_BASE}/notifications/_read`, {
+      method: 'POST',
+      data,
+    });
+
+  public changeNoticeReadState = (id: string) =>
+    request(`/${SystemConst.API_BASE}/notifications/${id}/read`, {
+      method: 'GET',
+    });
+}
+
+export default Service;