Bläddra i källkod

feat: 新增ws实例

lanjianrong 4 år sedan
förälder
incheckning
7cec9f4b35

+ 1 - 1
package.json

@@ -50,7 +50,7 @@
     "@ant-design/charts": "^1.1.20",
     "@ant-design/icons": "^4.0.0",
     "@ant-design/pro-descriptions": "^1.0.19",
-    "@ant-design/pro-form": "1.31.2",
+    "@ant-design/pro-form": "1.42.1",
     "@ant-design/pro-layout": "6.19.3",
     "@ant-design/pro-table": "2.43.4",
     "@icon-park/react": "^1.3.3",

+ 18 - 2
src/app.tsx

@@ -4,6 +4,8 @@ import RightContent from '@/components/RightContent'
 import { message } from 'antd'
 import logo from '../public/logo.svg'
 import BasicModal, { BasicDrawer } from '@/components/Modal'
+import { AddressBook } from '@icon-park/react'
+import ws, { onMessage } from '@/utils/ws'
 
 const loginPath = '/user/login'
 
@@ -30,14 +32,21 @@ export async function getInitialState() {
   }
   // 如果是登录页面,不执行
   if (history.location.pathname !== loginPath) {
-    const currentUser = (await fetchUserInfo()) || {}
+    const userInfo = (await fetchUserInfo()) || {}
     const permData = (await fetchPermData()) || {}
 
+    if (userInfo?.currentUser?.staffId) {
+      const { username, staffId: id, wsToken: token } = userInfo?.currentUser
+      ws.init(`ws://cld2qa.com/summon/v1/chat/link?username=${username}&id=${id}&token=${token}`, {
+        onMessage
+      })
+    }
+
     return {
       fetchUserInfo,
       fetchPermData,
       permData,
-      ...currentUser,
+      ...userInfo,
       settings: {}
     }
   }
@@ -62,6 +71,7 @@ export const layout = ({ initialState }) => {
       if (!initialState?.currentUser?.staffId && location.pathname !== loginPath) {
         history.replcae('/user/login')
       }
+
       // if (
       //   initialState?.currentUser?.staffId &&
       //   location.pathname !== loginPath &&
@@ -79,6 +89,12 @@ export const layout = ({ initialState }) => {
         <BasicModal />
       </>
     ),
+    links: [
+      <div className="ant-menu-title-content">
+        <AddressBook theme="outline" className="anticon" />
+        <span className="ant-pro-menu-item-title">通讯录</span>
+      </div>
+    ],
     // menuHeaderRender: false,
     ...initialState?.settings
   }

+ 7 - 0
src/components/RightContent/Book/index.tsx

@@ -0,0 +1,7 @@
+// import { MessageType } from '@/types/typing'
+
+const Book = () => {
+  return <></>
+}
+
+export default Book

+ 38 - 0
src/components/RightContent/Book/wsNotice.tsx

@@ -0,0 +1,38 @@
+import { notification } from 'antd'
+
+enum noticeType {
+  Notification = 'notification',
+  Message = 'message'
+}
+
+enum mediaType {
+  VersionUpdate = 1,
+  HolidayNotice = 2
+}
+// 版本发布
+function versionPublish(msg: WebSocketEventMap['message']) {
+  console.log('11')
+
+  const { refresh, title } = msg
+  notification.warning({
+    message: '有新的版本更新, 请及时刷新页面',
+    description: title,
+    duration: null,
+    btn: refresh && (
+      <span
+        className="text-primary cursor-pointer hover:text-hex-967bbd"
+        onClick={() => window.location.reload()}
+      >
+        点击刷新
+      </span>
+    )
+  })
+}
+
+export default (msg: WebSocketEventMap['message']) => {
+  const { media, notice } = msg
+  // 通知
+  if (notice === noticeType.Notification) {
+    media === mediaType.VersionUpdate && versionPublish(msg)
+  }
+}

+ 0 - 2
src/components/RightContent/NoticeIcon/index.tsx

@@ -8,7 +8,6 @@ import styles from './index.less'
 import dayjs from 'dayjs'
 import { queryNoticeList, updateNotice } from '@/services/login'
 import consts from '@/consts'
-import useWebSocketFn from '@/hooks/core/useWebSocketFn'
 
 export type GlobalHeaderRightProps = {
   fetchingNotices?: boolean
@@ -76,7 +75,6 @@ const NoticeIconView = () => {
   const { initialState } = useModel('@@initialState')
   const { currentUser } = initialState || {}
   const [notices, setNotices] = useState([])
-  useWebSocketFn()
   const getNotices = useCallback(async () => {
     const { code = -1, data } = await queryNoticeList()
     if (code === consts.RET_CODE.SUCCESS) {

+ 0 - 124
src/components/RightContent/NotifyDropdown.tsx

@@ -1,124 +0,0 @@
-import consts from '@/consts'
-import { queryNoticeList, queryUnreadNotice, updateNotice } from '@/services/login'
-import { BellOutlined } from '@ant-design/icons'
-import { Badge, Tabs, List, Typography, Popover, Tag } from 'antd'
-import { useState, useCallback, useEffect } from 'react'
-import styles from './index.less'
-
-const DropdownContent = ({ noticeCount }) => {
-  const [state, setState] = useState({
-    noticesTotal: 0,
-    notices: []
-  })
-  const getNotices = useCallback(async () => {
-    const {
-      code = -1,
-      data: { message, total }
-    } = await queryNoticeList()
-    if (code === consts.RET_CODE.SUCCESS) {
-      setState({ ...state, noticesTotal: total, notices: message })
-    }
-  }, [])
-  useEffect(() => {
-    getNotices()
-  }, [])
-  const toggleNotice = id => {
-    const nNotices = state.notices.map(item => {
-      if (item.id === id) {
-        return { ...item, isRead: 1 }
-      }
-      return item
-    })
-    setState({ ...state, notices: nNotices })
-  }
-
-  const handleTitleClick = async item => {
-    // 未读
-    if (!item.isRead) {
-      const { code = -1 } = await updateNotice({ id: item.id })
-      if (code === consts.RET_CODE.SUCCESS) {
-        toggleNotice(item.id)
-      }
-    }
-  }
-  return (
-    <Tabs>
-      <Tabs.TabPane
-        key="1"
-        tab={
-          <div>
-            系统信息
-            {noticeCount ? <span> ({noticeCount}) </span> : null}
-          </div>
-        }
-      >
-        <List
-          bordered
-          dataSource={state.notices}
-          renderItem={item => (
-            <List.Item>
-              <List.Item.Meta
-                // avatar={item.title}
-                description={
-                  <div className="flex justify-between items-center">
-                    <span>
-                      <Tag>{item.title}</Tag>
-                    </span>
-                    <span>{item.createTime}</span>
-                  </div>
-                }
-                title={
-                  <Typography.Paragraph
-                    onClick={() => handleTitleClick(item)}
-                    ellipsis={{
-                      rows: 2
-                    }}
-                    delete={!!item.isRead}
-                    style={{
-                      cursor: !item.isRead ? 'pointer' : '',
-                      width: '100%',
-                      marginBottom: 0
-                    }}
-                  >
-                    {item.content}
-                  </Typography.Paragraph>
-                }
-              />
-            </List.Item>
-          )}
-        />
-      </Tabs.TabPane>
-      <Tabs.TabPane tab="消息通知" key="2" />
-      <Tabs.TabPane tab="代办事项" key="3" />
-    </Tabs>
-  )
-}
-
-const NotifyDropdown = () => {
-  const [count, setCount] = useState(0)
-  useEffect(() => {
-    async function getCount() {
-      const { code = -1, data = 0 } = await queryUnreadNotice()
-      if (code === consts.RET_CODE.SUCCESS) {
-        setCount(data)
-      }
-    }
-    getCount()
-  }, [])
-  return (
-    <Popover
-      arrow
-      trigger="click"
-      overlayClassName="!min-w-360px"
-      content={<DropdownContent noticeCount={count} />}
-    >
-      <span className={`${styles.action} ${styles.notice}`}>
-        <Badge dot count={count}>
-          <BellOutlined />
-        </Badge>
-      </span>
-    </Popover>
-  )
-}
-
-export default NotifyDropdown

+ 1 - 1
src/components/RightContent/index.tsx

@@ -60,7 +60,7 @@ const GlobalHeaderRight: React.FC = () => {
       >
         <QuestionCircleOutlined />
       </span> */}
-      {/* <Notify /> */}
+      <Notify />
       <Avatar />
       {REACT_APP_ENV && REACT_APP_ENV !== 'prod' && (
         <span>

+ 0 - 81
src/hooks/core/useWebSocketFn.tsx

@@ -1,81 +0,0 @@
-/* eslint-disable react-hooks/rules-of-hooks */
-import { useWebSocket } from 'ahooks'
-import { notification } from 'antd'
-import { useEffect } from 'react'
-import { useModel } from 'umi'
-
-export enum cmdType {
-  Single = 10,
-  Group = 11,
-  OnlineList = 12,
-  Notice = 13
-}
-
-export enum noticeType {
-  Notice = 1,
-  Message = 2
-}
-
-// function noticeHandler (op) {}
-
-// function cmdReducer(cmd: cmdType, op: any) {
-//   switch (cmd) {
-//     case 13:
-
-//       break
-
-//     default:
-//       break
-//   }
-// }
-
-export default function useWebSocketFn() {
-  const { initialState } = useModel('@@initialState')
-
-  const { currentUser } = initialState
-
-  const { disconnect, connect, webSocketIns, readyState } = useWebSocket(
-    `ws://cld2qa.com/summon/v1/chat/link?username=${currentUser?.username}&id=${currentUser?.staffId}`,
-    {
-      manual: true,
-      reconnectLimit: 3,
-      reconnectInterval: 2000,
-      onMessage: (message: WebSocketEventMap['message']) => {
-        const data = message && JSON.parse(message.data)
-        // console.log('data', data)
-        // cmdReducer(data.cmd)
-        if (data.cmd === cmdType.Notice) {
-          notification.warning({
-            message: '有新的版本更新, 请及时刷新页面',
-            description: data.title,
-            duration: null,
-            btn: (
-              <span
-                className="text-primary cursor-pointer hover:text-hex-967bbd"
-                onClick={() => window.location.reload()}
-              >
-                点击刷新
-              </span>
-            )
-          })
-        }
-      }
-    }
-  )
-  useEffect(() => {
-    if (currentUser && currentUser.staffId) {
-      connect()
-      console.log('readyState', readyState)
-    }
-    return () => {
-      disconnect()
-    }
-  }, [])
-
-  function sendMsg(cmd: cmdType, dstid?: string | null, media?: number | null, options: any) {
-    webSocketIns?.send(
-      JSON.stringify({ cmd, dstid, media, userid: currentUser.staffId, ...options })
-    )
-  }
-  return { webSocketIns, sendMsg }
-}

+ 4 - 7
src/types/typing.d.ts

@@ -129,11 +129,8 @@ export type BasicTableProps<T, U extends ParamsType, ValueType = 'text'> = {
   debounceTime?: number
 } & Omit<TableProps<T>, 'columns' | 'rowSelection'>
 
-declare const ProviderWarp: {
-  <T extends Record<string, any>, U extends ParamsType = ParamsType, ValueType = 'text'>(
-    props: BasicTableProps<T, U, ValueType>
-  ): JSX.Element
-  // eslint-disable-next-line @typescript-eslint/consistent-type-imports
-  Summary: typeof import('rc-table/lib/Footer/Summary').default
+export type MessageType = {
+  cmd: number
+  userid: string
+  [key: string]: any
 }
-export default ProviderWarp

+ 102 - 0
src/utils/ws.ts

@@ -0,0 +1,102 @@
+import { isDevMode } from '@/utils/env'
+import { message } from 'antd'
+import type { MessageType } from '@/types/typing'
+import wsNotice from '@/components/RightContent/Book/wsNotice'
+
+enum ReadyState {
+  Connecting = 0,
+  Open = 1,
+  Closing = 2,
+  Closed = 3
+}
+
+enum CmdType {
+  NoticeOrMessage = 13
+}
+
+interface Options {
+  // reconnectLimit?: number;
+  // reconnectInterval?: number;
+  onOpen?: (event: WebSocketEventMap['open']) => void
+  onClose?: (event: WebSocketEventMap['close']) => void
+  onMessage?: (message: WebSocketEventMap['message']) => void
+  onError?: (event: WebSocketEventMap['error']) => void
+}
+
+class Ws {
+  public uri: string // websocket 链接地址
+  public opts?: Options // websocket 链接相关配置
+  private isLock: boolean // 防止多次重连
+  public timeout?: number // ♥跳间隔时长
+  public wsIns: WebSocket | undefined // websocket实例
+
+  // 初始化socket,一般在应用启动时初始化一次就好了,或者需要更换wsUrl
+  public init(uri: string, opts?: Options) {
+    this.uri = uri
+    this.opts = opts
+    this.connectWs()
+  }
+
+  // 利用promise封装,解决ws可能没有实例化
+  public getInstance(): Promise<WebSocket> {
+    return new Promise((resolve, reject) => {
+      if (this.wsIns?.readyState === ReadyState.Open) {
+        resolve(this.wsIns)
+      } else if (this.wsIns) {
+        this.connectWs()
+        resolve(this.wsIns)
+      } else {
+        reject()
+      }
+    })
+  }
+
+  private connectWs() {
+    try {
+      this.wsIns = new WebSocket(this.uri)
+      this.wsIns.onmessage = msg => {
+        this.opts?.onMessage?.(JSON.parse(msg.data))
+      }
+      this.wsIns.onclose = event => {
+        this.opts?.onClose?.(event)
+      }
+      this.wsIns.onerror = err => {
+        this.opts?.onError?.(err)
+      }
+    } catch (error) {
+      // 未来要做心跳机制
+      if (isDevMode()) {
+        message.error(error.toString())
+      }
+      this.wsIns = undefined
+    }
+  }
+
+  public sendMessage(map: MessageType) {
+    if (this.wsIns?.readyState === ReadyState.Open) {
+      this.wsIns.send(JSON.stringify(map))
+    }
+  }
+
+  public disconnect() {
+    if (this.wsIns?.readyState === ReadyState.Open) {
+      this.wsIns.close()
+    }
+  }
+}
+export function onMessage(msg: MessageType) {
+  // 这里执行所有的message监听事件
+
+  // console.log(msg)
+
+  switch (msg.cmd) {
+    case CmdType.NoticeOrMessage:
+      wsNotice(msg)
+      break
+
+    default:
+      break
+  }
+}
+
+export default new Ws()