chenhao 2 лет назад
Родитель
Сommit
264d925ab1

+ 9 - 2
src/api/message.ts

@@ -1,9 +1,16 @@
 import { DefineApiModule, Message } from 'api-type'
 
 const MessageApi: DefineApiModule<Message.ApiMap> = {
-  getUserHistory: '/api/message/history',
-  getHistory: '/api/message/list',
+  /** 历史消息 */
+  getMessageHistory: '/api/message/history',
+  /** 消息列表 */
+  getMessageList: '/api/message/list',
+  /** 发送/回复消息 */
   sendMessage: '/api/message/send',
+  /** 未读消息 - 最新5条 */
+  getUnReadMessage: '/api/message/new',
+  /** 已读 */
+  handleReadMessage: '/api/message/read',
 }
 
 export default MessageApi

+ 1 - 0
src/assets/styles/var.scss

@@ -35,6 +35,7 @@ $LargeGapSpace: map-get($cst-gap-space, 'large');
 $BaseFont: map-get($ep-font-size, 'base');
 $SmallFont: map-get($ep-font-size, 'extra-small');
 $MediumFont: map-get($ep-font-size, 'medium');
+$LargeFont: map-get($ep-font-size, 'large');
 
 /** color */
 $NormalColor: map-get($ep-text-color, 'primary');

+ 70 - 3
src/components/shared/Message.vue

@@ -1,18 +1,85 @@
 <template>
-  <div class="message-icon">
+  <div ref="messageIcon" class="message-icon">
+    <span v-show="unReadMessages?.newCount" class="un-read-num"></span>
     <svg-icon name="message"></svg-icon>
   </div>
+  <el-popover
+    placement="bottom-start"
+    :show-arrow="false"
+    :virtual-ref="messageIcon"
+    trigger="click"
+    virtual-triggering
+  >
+    <div class="message-popover-content">
+      <div class="title">
+        <span class="unread-count">{{ unReadMessages?.newCount || 0 }}</span>
+        <span>条消息</span>
+      </div>
+      <div class="message-list"></div>
+      <confirm-button
+        ok-text="收消息"
+        cancel-text="发消息"
+        @confirm="onReceiveMessage"
+        @cancel="onSendMessage"
+      ></confirm-button>
+    </div>
+  </el-popover>
 </template>
 
 <script setup lang="ts" name="Message">
 import { reactive, ref } from 'vue'
-import SvgIcon from '../common/SvgIcon.vue'
+import { ElPopover } from 'element-plus'
+import SvgIcon from '@/components/common/SvgIcon.vue'
+import useMessageLoop from '@/hooks/useMessageLoop'
+import ConfirmButton from '../common/ConfirmButton.vue'
+
+const messageIcon = ref<HTMLDivElement>()
+
+const unReadMessages = useMessageLoop()
+
+/** 收消息 */
+const onReceiveMessage = () => {
+  console.log('收消息')
+}
+
+/** 发消息 */
+const onSendMessage = () => {
+  console.log('发消息')
+}
 </script>
 
 <style scoped lang="scss">
 .message-icon {
   display: grid;
   place-items: center;
-  font-size: $MediumFont;
+  font-size: $LargeFont;
+  position: relative;
+  .un-read-num {
+    position: absolute;
+    width: 8px;
+    height: 8px;
+    right: -2px;
+    top: -2px;
+    border-radius: 50%;
+    background-color: $DangerColor;
+  }
+}
+.message-popover-content {
+  .title {
+    .unread-count {
+      font-size: $MediumFont;
+      color: $DangerColor;
+      margin-right: 4px;
+    }
+  }
+  .message-list {
+    .message-row {
+      padding: 10px 4px;
+      .message-send-user {
+      }
+      .message-content {
+      }
+    }
+  }
 }
 </style>

+ 39 - 0
src/hooks/useMessageLoop.ts

@@ -0,0 +1,39 @@
+import { effectScope, onScopeDispose } from 'vue'
+import { useIntervalFn } from '@vueuse/core'
+import uesFetch from '@/hooks/useFetch'
+
+import type { EffectScope, ShallowRef } from 'vue'
+import type { ExtractApiResponse } from 'api-type'
+import type {} from 'global-type'
+
+const useMessageLoop = () => {
+  const { fetch: getUnReadMessage, result: unReadMessage } = uesFetch('getUnReadMessage')
+  useIntervalFn(getUnReadMessage, 300 * 1000)
+
+  return unReadMessage
+}
+
+function createSharedComposable(composable: typeof useMessageLoop) {
+  let subscribers = 0
+  let state: ShallowRef<ExtractApiResponse<'getUnReadMessage'>> | null | undefined
+  let scope: EffectScope | null
+
+  const dispose = () => {
+    if (scope && --subscribers <= 0) {
+      scope.stop()
+      state = scope = null
+    }
+  }
+
+  return () => {
+    subscribers++
+    if (!state) {
+      scope = effectScope(true)
+      state = scope.run<ShallowRef<ExtractApiResponse<'getUnReadMessage'>>>(composable)
+    }
+    onScopeDispose(dispose)
+    return state
+  }
+}
+
+export default createSharedComposable(useMessageLoop)

+ 3 - 2
src/modules/analysis/monitoring/index.vue

@@ -229,6 +229,7 @@ const getColumns = (
     {
       label: '老师ID',
       formatter(row) {
+        console.log(row)
         return row.markerId === 0 ? (
           '全体'
         ) : (
@@ -263,7 +264,7 @@ const interval = computed(() => fetchModel.refresh * 60 * 1000)
 const { fetch, result } = useFetch('getStatistics')
 
 const getData = (data: ExtractRecordValue<ExtractApiResponse<'getStatistics'>>) => {
-  return data?.slice(0, 4).concat(data?.slice(4).slice(-3))
+  return data?.slice(0, 4).concat(data?.slice(4).slice(-3)) || []
 }
 
 const sortableResult = computed<typeof result.value>(() => {
@@ -273,7 +274,7 @@ const sortableResult = computed<typeof result.value>(() => {
     const arr = [...result.value[k]]
     const totalIndex = arr.findIndex((v) => v.markerId === 0)
     const [total] = arr.splice(totalIndex, 1)
-    arr.unshift(total)
+    total && arr.unshift(total)
     sortable[k] = arr
   }
   return sortable

+ 1 - 1
src/modules/analysis/statistics/index.vue

@@ -226,7 +226,7 @@ const sortableResult = computed<typeof result.value>(() => {
     const arr = [...result.value[k]]
     const totalIndex = arr.findIndex((v) => v.markerId === 0)
     const [total] = arr.splice(totalIndex, 1)
-    arr.unshift(total)
+    total && arr.unshift(total)
     sortable[k] = arr
   }
   return sortable

+ 1 - 1
src/router/index.ts

@@ -29,7 +29,7 @@ if (import.meta.env.DEV) {
 }
 
 const router = createRouter({
-  history: createWebHistory(),
+  history: createWebHistory('/'),
   routes: routes,
 })
 

+ 18 - 7
types/api.d.ts

@@ -237,18 +237,29 @@ declare module 'api-type' {
       sendTime: string
       sendUserId: number
       sendUserName: string
-      taskId: number
+      unReadCount: number
     }
     /** 历史消息 */
-    type GetUserHistory = BaseDefine<{ sendUserId: number }, BaseMessageResponse[]>
-    /** 历史消息列表 */
-    type GetHistory = BaseDefine<null, BaseMessageResponse[]>
+    type GetMessageHistory = BaseDefine<{ sendUserId: number }, BaseMessageResponse[]>
+    /** 消息列表 */
+    type GetMessageList = BaseDefine<null, BaseMessageResponse[]>
     /** 发送/回复消息 */
-    type SendMessage = BaseDefine<{ taskId?: number; content: string; receiveUserIds: number[] }>
+    type SendMessage = BaseDefine<{ content: string; receiveUserIds: number[] }>
+    /** 未读消息 - 最新5条 */
+    type GetUnReadMessage = BaseDefine<null, { newCount: number; messages: BaseMessageResponse[] }>
+    /** 已读 */
+    type HandleReadMessage = BaseDefine<{ id: number }>
     export interface ApiMap {
-      getUserHistory: GetUserHistory
-      getHistory: GetHistory
+      /** 历史消息 */
+      getMessageHistory: GetMessageHistory
+      /** 消息列表 */
+      getMessageList: GetMessageList
+      /** 发送/回复消息 */
       sendMessage: SendMessage
+      /** 未读消息 - 最新5条 */
+      getUnReadMessage: GetUnReadMessage
+      /** 已读 */
+      handleReadMessage: HandleReadMessage
     }
   }