Browse Source

feat: 封装socket类

qinyuyue 2 months ago
parent
commit
0118d6dd95

+ 2 - 0
package.json

@@ -24,11 +24,13 @@
     "html5-qrcode": "^2.3.8",
     "jsqr": "^1.4.0",
     "lodash-es": "^4.17.21",
+    "mitt": "^3.0.1",
     "mockjs": "^1.1.0",
     "nuxt": "^3.13.0",
     "nuxt-swiper": "^2.0.0",
     "pinia": "^2.2.2",
     "pinyin-pro": "^3.26.0",
+    "reconnecting-websocket": "^4.4.0",
     "recorder-core": "^1.3.25011100",
     "vconsole": "^3.15.1",
     "vue": "latest",

+ 12 - 1
pnpm-lock.yaml

@@ -41,6 +41,9 @@ importers:
       lodash-es:
         specifier: ^4.17.21
         version: 4.17.21
+      mitt:
+        specifier: ^3.0.1
+        version: 3.0.1
       mockjs:
         specifier: ^1.1.0
         version: 1.1.0
@@ -56,6 +59,9 @@ importers:
       pinyin-pro:
         specifier: ^3.26.0
         version: 3.26.0
+      reconnecting-websocket:
+        specifier: ^4.4.0
+        version: 4.4.0
       recorder-core:
         specifier: ^1.3.25011100
         version: 1.3.25011100
@@ -2481,7 +2487,7 @@ packages:
     engines: {node: '>= 8'}
 
   mitt@3.0.1:
-    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
+    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==, tarball: https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz}
 
   mkdirp@1.0.4:
     resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
@@ -3082,6 +3088,9 @@ packages:
     resolution: {integrity: sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==}
     engines: {node: '>= 14.16.0'}
 
+  reconnecting-websocket@4.4.0:
+    resolution: {integrity: sha512-D2E33ceRPga0NvTDhJmphEgJ7FUYF0v4lr1ki0csq06OdlxKfugGzN0dSkxM/NfqCxYELK4KcaTOUOjTV6Dcng==, tarball: https://registry.npmmirror.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz}
+
   recorder-core@1.3.25011100:
     resolution: {integrity: sha512-trXsCH0zurhoizT4Z22C0OsM0SDOW+2OvtgRxeLQFwxoFeqFjDjYZsbZEZUiKMJLhBvamI4K7Ic+qZ2LBo74TA==, tarball: https://registry.npmmirror.com/recorder-core/-/recorder-core-1.3.25011100.tgz}
 
@@ -7055,6 +7064,8 @@ snapshots:
 
   readdirp@4.0.1: {}
 
+  reconnecting-websocket@4.4.0: {}
+
   recorder-core@1.3.25011100: {}
 
   redis-errors@1.2.0: {}

+ 47 - 0
src/middleware/02.auth.global.js

@@ -0,0 +1,47 @@
+import {useChatsStore} from "~/stores/useChats";
+
+
+export default defineNuxtRouteMiddleware((to, from) => {
+  if (import.meta.server) return;
+
+  const authStore = useAuthStore();
+  const { token } = storeToRefs(authStore);
+  const userInfoStore = useUserInfoStore();
+  const {userInfo} = storeToRefs(userInfoStore);
+
+  const chatsStore = useChatsStore()
+
+  if (token.value) {
+    const config = useRuntimeConfig()
+    const baseIM = config.public.baseIM
+    // 俏的
+/*
+    const chatStore = useChatStore()
+    chatStore.createConnection(baseIM + '?token=' + userInfo.value.pass)
+*/
+
+     userInfoStore.getUserInfo().then(() => {
+      chatsStore.initWebSocket(baseIM + '?token=' + userInfo.value.pass)
+    })
+    return
+  }
+
+  if (to.fullPath.includes("/profile")) {
+    return navigateTo("/login", {
+      replace: true,
+      query: {
+        redirect: to.fullPath,
+      },
+    });
+  }
+
+  if (to.fullPath.includes("/note-create")) {
+    return navigateTo("/login", {
+      replace: true,
+      query: {
+        redirect: to.fullPath,
+      },
+    });
+  }
+  return;
+});

+ 0 - 48
src/middleware/auth.global.js

@@ -1,48 +0,0 @@
-export default defineNuxtRouteMiddleware((to, from) => {
-  if (import.meta.server) return
-
-  const authStore = useAuthStore()
-  const { token } = storeToRefs(authStore)
-  const chatStore = useChatStore()
-  const { user } = storeToRefs(chatStore)
-
-  // 建立链接
-  async function getUserInfo() {
-    const { data } = await request('/website/tourism/user/view')
-    chatStore.user = data
-    user.value = data
-    console.log(data, 'createConnection')
-
-    await chatStore.createConnection(data.pass)
-
-    // chatStore.reqChatList()
-
-    // console.log('用户信息:', chatStore.user)
-
-    // console.log('会话列表:', chatStore.chatList.value)
-  }
-
-  if (token.value) {
-    getUserInfo()
-    return
-  }
-
-  if (to.fullPath.includes('/profile')) {
-    return navigateTo('/login', {
-      replace: true,
-      query: {
-        redirect: to.fullPath
-      }
-    })
-  }
-
-  if (to.fullPath.includes('/note-create')) {
-    return navigateTo('/login', {
-      replace: true,
-      query: {
-        redirect: to.fullPath
-      }
-    })
-  }
-  return
-})

+ 483 - 0
src/pages/profile/my-news/_index.vue

@@ -0,0 +1,483 @@
+<template>
+  <div class="w-full">
+<!--    <audio
+      class="fixed top-0 left-0"
+      ref="audioRef"
+      :src="audioTips"
+      style="opacity: 0; z-index: -1"
+    ></audio>-->
+
+    <!-- <van-pull-refresh v-model="refreshing" @refresh="onRefresh"> -->
+    <van-sticky :offset-top="50">
+      <div class="w-full text-xl font-semibold px-16 h-48 flex justify-start items-center bg-white">
+        <span
+          @click="showPopover = true"
+          v-if="showPopover"
+          class="iconfont icon-plus text-[#FF9300]"
+          style="font-size: 24px"
+        ></span>
+        <span v-else class="iconfont icon-plus text-black" style="font-size: 24px"></span>
+
+        <van-popover
+          v-model:show="showPopover"
+          placement="bottom"
+          theme="dark"
+          offset="[5,20]"
+          :actions="actionsList"
+          @select="onSelect"
+        >
+          <template #reference>
+            <span :class="`${showPopover ? 'text-[#FF9300]' : 'text-black-3'}`">添加</span>
+          </template>
+        </van-popover>
+      </div>
+    </van-sticky>
+
+    <div
+      @click="onChatPage('/profile/system-message', {})"
+      class="w-full relative h-82 flex justify-start items-center px-16"
+    >
+      <van-badge
+        v-if="chatList[0]?.unreadMessageCount"
+        v-bind="messageNumber(chatList[0]?.unreadMessageCount)"
+        max="99"
+      >
+        <div
+          class="w-48 h-48 bg-[#0052D9] rounded-full overflow-hidden flex justify-center items-center"
+        >
+          <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/remind.svg" alt="" />
+        </div>
+      </van-badge>
+
+      <div
+        v-else
+        class="w-48 h-48 bg-[#0052D9] rounded-full overflow-hidden flex justify-center items-center"
+      >
+        <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/remind.svg" alt="" />
+      </div>
+
+      <div class="h-48 w-245 shrink-0 ml-12 flex flex-wrap items-start">
+        <h1 class="line-clamp-1 w-full text-base text-black-3 font-semibold mb-4">
+          {{ chatList[0]?.groupName }}
+        </h1>
+        <p class="line-clamp-1 h-20 text-sm text-black-6 leading-3xl">
+          {{
+            chatList[0]?.lastMessage
+              ? messageContentParse(chatList[0]?.lastMessage?.messageContent)
+              : ''
+          }}
+        </p>
+      </div>
+
+      <div class="w-35 h-48 shrink-0">
+        <p class="text-black/[0.6] text-sm text-end">
+          {{
+            chatList[0]?.lastMessage ? createTimeSplit(chatList[0]?.lastMessage?.createTime) : ''
+          }}
+        </p>
+      </div>
+      <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+    </div>
+
+    <div
+      @click="navigateTo('/follow?listType=fans')"
+      class="w-full relative h-82 flex justify-start items-center px-16"
+    >
+      <van-badge
+        v-if="chatList[1]?.unreadMessageCount"
+        v-bind="messageNumber(chatList[1]?.unreadMessageCount)"
+        max="99"
+      >
+        <div
+          class="w-48 h-48 bg-[#FF9300] rounded-full overflow-hidden flex justify-center items-center"
+        >
+          <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/user.svg" alt="" />
+        </div>
+      </van-badge>
+
+      <div
+        v-else
+        @click="navigateTo('/follow?listType=fans')"
+        class="w-48 h-48 bg-[#0052D9] rounded-full overflow-hidden flex justify-center items-center"
+      >
+        <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/user.svg" alt="" />
+      </div>
+
+      <div class="h-48 w-245 shrink-0 ml-12 flex flex-wrap items-start">
+        <h1 class="line-clamp-1 w-full text-base text-black-3 font-semibold mb-4">
+          {{ chatList[1]?.groupName }}
+        </h1>
+        <p class="line-clamp-1 h-20 text-sm text-black-6 leading-3xl">
+          {{
+            chatList[1]?.lastMessage
+              ? messageContentParse(chatList[1]?.lastMessage?.messageContent)
+              : ''
+          }}
+        </p>
+      </div>
+
+      <div class="w-35 h-48 shrink-0">
+        <p class="text-black/[0.6] text-sm text-end">
+          {{
+            chatList[1]?.lastMessage ? createTimeSplit(chatList[1]?.lastMessage?.createTime) : ''
+          }}
+        </p>
+      </div>
+      <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+    </div>
+    <div
+      @click="onChatPage('/profile/interaction-message', {})"
+      class="w-full relative h-82 flex justify-start items-center px-16"
+    >
+      <van-badge v-if="messagesNumber > 0" v-bind="messageNumber(messagesNumber)" max="99">
+        <div
+          class="w-48 h-48 bg-[#FF476A] rounded-full overflow-hidden flex justify-center items-center"
+        >
+          <img
+            class="w-24 h-24 shrink-0 object-cover"
+            src="~/assets/img/chat/weixin-shake.svg"
+            alt=""
+          />
+        </div>
+      </van-badge>
+
+      <div
+        v-else
+        class="w-48 h-48 bg-[#FF476A] rounded-full overflow-hidden flex justify-center items-center"
+      >
+        <img
+          class="w-24 h-24 shrink-0 object-cover"
+          src="~/assets/img/chat/weixin-shake.svg"
+          alt=""
+        />
+      </div>
+
+      <div class="h-48 w-245 shrink-0 ml-12 flex flex-wrap items-start">
+        <h1 class="line-clamp-1 w-full text-base text-black-3 font-semibold mb-4">
+          {{ chatList[2]?.groupName }}
+        </h1>
+        <p class="line-clamp-1 h-20 text-sm text-black-6 leading-3xl">
+          {{
+            chatList[2]?.lastMessage
+              ? messageContentParse(chatList[2]?.lastMessage?.messageContent)
+              : ''
+          }}
+        </p>
+      </div>
+
+      <div class="w-35 h-48 shrink-0">
+        <p class="text-black/[0.6] text-sm text-end">
+          {{
+            chatList[2]?.lastMessage ? createTimeSplit(chatList[2]?.lastMessage?.createTime) : ''
+          }}
+        </p>
+      </div>
+      <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+    </div>
+
+    <!-- 置顶绘画列表  "-->
+    <div v-if="isTopList?.length && activeNames" class="w-full">
+      <template v-for="(item, index) in isTopList" :key="item?.id">
+        <template v-if="index > 2">
+          <!-- 单聊会话 -->
+          <ProfileNewsGroupChat
+            v-if="item?.noticeType == 2"
+            :item-data="{
+              ...item,
+              updateTime: item?.lastMessage ? createTimeSplit(item?.lastMessage?.updateTime) : ''
+            }"
+            @on-chat-page="
+              onChatPage('/chat/group', { userId: user.userId, groupId: item?.groupId })
+            "
+            @on-no-bother="noBother(item)"
+            @on-is-top="onIsTop(item)"
+            @on-conv-delete="onIsShow(item)"
+          />
+
+          <!-- 群聊会话 -->
+          <ProfileNewsSingleChat
+            v-if="item?.noticeType == 1"
+            :item-data="{
+              ...item,
+              updateTime: item?.lastMessage ? createTimeSplit(item?.lastMessage?.updateTime) : ''
+            }"
+            @on-chat-page="
+              onChatPage('/chat/single', {
+                sendUserId: userInfo.userId,
+                groupId: item?.groupId,
+                getUserId: item?.toUserId,
+                groupRemark: item?.groupRemark
+              })
+            "
+            @on-no-bother="noBother(item)"
+            @on-is-top="onIsTop(item)"
+            @on-conv-delete="onIsShow(item)"
+          />
+        </template>
+        <template v-else></template>
+      </template>
+    </div>
+
+    <div
+      v-if="isTopList.length >= 6"
+      @click="activeNames = !activeNames"
+      class="flex justify-between border items-center h-20 w-full p-16 box-border"
+    >
+      <div class="shrink-0 flex items-center">
+        <van-icon name="bars" size="16" class="mr-5" />
+        <span class="text-sm">{{ collapseTitle }}</span>
+      </div>
+      <div class="w-16 h-16 shrink-0">
+        <van-icon name="arrow" size="16" />
+      </div>
+    </div>
+
+    <!-- 不置顶的会话列表 -->
+    <template v-for="(item, index) in pinnedList" :key="item?.id">
+      <ProfileNewsGroupChat
+        v-if="item?.noticeType == 2"
+        :item-data="{
+          ...item,
+          updateTime: item?.lastMessage ? createTimeSplit(item?.lastMessage?.updateTime) : ''
+        }"
+        @on-chat-page="
+          onChatPage('/chat/group', {
+            userId: userInfo.userId,
+            groupId: item?.groupId
+          })
+        "
+        @on-no-bother="noBother(item)"
+        @on-is-top="onIsTop(item)"
+        @on-conv-delete="onIsShow(item)"
+      />
+      <ProfileNewsSingleChat
+        v-if="item?.noticeType == 1"
+        :item-data="{
+          ...item,
+          updateTime: item?.lastMessage ? createTimeSplit(item?.lastMessage?.updateTime) : ''
+        }"
+        @on-chat-page="
+          onChatPage('/chat/single', {
+            sendUserId: userInfo.userId,
+            groupId: item?.groupId,
+            getUserId: item?.toUserId,
+            groupRemark: item?.groupRemark
+          })
+        "
+        @on-no-bother="noBother(item)"
+        @on-is-top="onIsTop(item)"
+        @on-conv-delete="onIsShow(item)"
+      />
+    </template>
+    <!-- </van-pull-refresh> -->
+  </div>
+</template>
+
+<script setup>
+import comments from '~/assets/img/chat/comments-white.svg'
+import plaza from '~/assets/img/chat/guangchang.svg'
+import userAdd from '~/assets/img/chat/user-add.svg'
+
+import { messageContentParse } from '~/utils/detalTime.js'
+
+const actionsList = [
+  { text: '创建群聊', icon: comments },
+  { text: '群聊广场', icon: plaza },
+  { text: '加入群聊', icon: plaza },
+  { text: '添加用户', icon: userAdd }
+]
+const userInfoStore = useUserInfoStore()
+const { userInfo } = storeToRefs(userInfoStore)
+// const uploadUrl = `${import.meta.env.VITE_APP_BASE_URL}/website/tourMessage/upload`
+const chatStore = useChatStore()
+const { messages, connectSta, chatList, curConversiton, onNewMessage } = storeToRefs(chatStore)
+
+// 链接的状态
+const wsConnect = computed(() => connectSta.value)
+
+const chatLists = computed(() => chatList.value)
+
+const audioRef = ref(null)
+
+const finished = ref(false)
+
+const error = ref(false)
+
+const loading = ref(false)
+
+const showPopover = ref(false)
+
+const messagesNumber = ref(10)
+
+// 置顶列表
+const isTopList = computed(() => chatList.value.filter((el) => el.isTop != 1))
+const pinnedList = computed(() => chatList.value.filter((el) => el?.isTop == 1) ?? [])
+const pinnedSystemList = ref([]) //三个固定的消息
+
+// 个置顶聊天
+const collapse_text = '折叠置顶聊天'
+
+const activeNames = ref(true)
+// collapse
+const collapseTitle = ref(collapse_text)
+
+// 刷新
+const refreshing = ref(false)
+
+// 建立链接
+// async function getUserInfo() {
+//   const { data } = await request('/website/tourism/user/view')
+//   chatStore.user = data
+//   user.value = data
+//   console.log(data, 'createConnection')
+
+//   await chatStore.createConnection(data.pass)
+//   chatStore.reqChatList()
+
+//   console.log('用户信息:', chatStore.user)
+
+//   // console.log('会话列表:', chatStore.chatList.value)
+// }
+
+useSeoMeta({
+  title: '我的消息'
+})
+
+// 时间的转换
+const createTimeSplit = (timer) => {
+  if (timer) {
+    return timer.split(' ')[1]
+  }
+}
+// 下拉刷新
+const onRefresh = () => {
+  refreshing.value = true
+  chatStore.reqChatList()
+  refreshing.value = false
+}
+
+// 打扰和免打扰得
+const noBother = (parmas) => {
+  handleBoolean({ groupId: parmas.groupId, isNotDisturb: parmas.isNotDisturb })
+}
+
+// 是否置顶
+const onIsTop = (parmas) => {
+  handleBoolean({ groupId: parmas.groupId, isTop: parmas.isTop })
+}
+
+// 是否显示会话
+const onIsShow = (parmas) => {
+  handleBoolean({ groupId: parmas.groupId, isShow: parmas.isShow })
+}
+
+// 是否免打扰和 是否置顶 公共
+const handleBoolean = async (body) => {
+  try {
+    let { data } = await request('/website/tourMember/updateSingleTourMember', {
+      method: 'post',
+      body
+    })
+    if (data) {
+      chatStore.reqChatList()
+    }
+  } catch (error) {}
+}
+
+// 消息数量通知的展示  需要动态的展示
+const messageNumber = (content) => {
+  let messageNumberObj = {}
+
+  if (content != null || content != undefined || content != '') {
+    if (content <= 1) {
+      messageNumberObj = {
+        offset: [-5, 4],
+        dot: true,
+        content
+      }
+    }
+    if (content > 1) {
+      messageNumberObj = {
+        offset: [-10, 7],
+        content
+      }
+    }
+  } else {
+    content = 0
+    messageNumberObj = {
+      offset: [-5, 4],
+      dot: true,
+      content
+    }
+  }
+
+  return messageNumberObj
+}
+
+// 跳转聊天页面
+const onChatPage = async (path, query) => {
+  const res = await request('/website/tourMessage/updateRead', {
+    query: {
+      groupId: query.groupId
+    }
+  })
+
+  if (res && res?.success) {
+    navigateTo({
+      path,
+      query
+    })
+  }
+}
+
+// 选中的是那个页面
+const onSelect = (action) => {
+  if (action.text == '创建群聊') onChatPage('/chat/create-group', {})
+  if (action.text == '群聊广场') onChatPage('/chat/group-square', {})
+  if (action.text == '加入群聊') onChatPage('/scan', {})
+  if (action.text == '添加用户') onChatPage('/chat/user-add', {})
+}
+
+onMounted(() => {
+  if (wsConnect == 0) {
+    showToast('聊天网络连接中...')
+  } else if (wsConnect == 1) {
+    showToast(' 聊天正在连接中...')
+  } else if (wsConnect == 3) {
+    showToast(' 聊天连接已断开,请刷新页面重新连接,或稍后重试!')
+  } else if (wsConnect == 2) {
+  }
+  chatStore.reqChatList()
+  // chatStore.reqChatList()
+})
+
+// watch(
+//   wsConnect,
+//   async (newValue, oldValue) => {
+//     console.log(newValue, 'wsConnect')
+//     console.log(chatLists.value, 'chatLists')
+
+//     if (newValue == 2) {
+//       pinnedList.value = chatList.value.filter((el) => el.isTop != 1)
+//       isTopList.value = chatList.value.filter((el) => el.isTop == 1)
+//     }
+//   },
+//   { immediate: true }
+// )
+// watch(
+//   chatList.value,
+//   (newList, oldList) => {
+//     // pinnedSystemList.value = newList.filter((el) => el.noticeType == 3)
+//     pinnedList.value = newList.filter((el) => el.isTop != 1)
+//     isTopList.value = newList.filter((el) => el.isTop == 1)
+
+//     console.log(pinnedList.value, 'pinnedList')
+//     console.log(isTopList.value, 'isTopList')
+//     // console.log(newList, 'newList')
+//     console.log(oldList, 'oldList')
+//   },
+//   { immediate: true }
+// )
+</script>
+
+<style lang="scss" scoped></style>

+ 19 - 75
src/pages/profile/my-news/index.vue

@@ -1,11 +1,11 @@
 <template>
   <div class="w-full">
-    <audio
+<!--    <audio
       class="fixed top-0 left-0"
       ref="audioRef"
       :src="audioTips"
       style="opacity: 0; z-index: -1"
-    ></audio>
+    ></audio>-->
 
     <!-- <van-pull-refresh v-model="refreshing" @refresh="onRefresh"> -->
     <van-sticky :offset-top="50">
@@ -279,6 +279,8 @@ import plaza from '~/assets/img/chat/guangchang.svg'
 import userAdd from '~/assets/img/chat/user-add.svg'
 
 import { messageContentParse } from '~/utils/detalTime.js'
+import {useChatsStore} from "~/stores/useChats";
+import {XYWebSocket} from "~/utils/XYWebSocket";
 
 const actionsList = [
   { text: '创建群聊', icon: comments },
@@ -288,22 +290,9 @@ const actionsList = [
 ]
 const userInfoStore = useUserInfoStore()
 const { userInfo } = storeToRefs(userInfoStore)
-// const uploadUrl = `${import.meta.env.VITE_APP_BASE_URL}/website/tourMessage/upload`
-const chatStore = useChatStore()
-const { messages, connectSta, chatList, curConversiton, onNewMessage } = storeToRefs(chatStore)
 
-// 链接的状态
-const wsConnect = computed(() => connectSta.value)
-
-const chatLists = computed(() => chatList.value)
-
-const audioRef = ref(null)
-
-const finished = ref(false)
-
-const error = ref(false)
-
-const loading = ref(false)
+const chatsStore = useChatsStore();
+const { chatList, chatListLoading } = storeToRefs(chatsStore)
 
 const showPopover = ref(false)
 
@@ -324,21 +313,6 @@ const collapseTitle = ref(collapse_text)
 // 刷新
 const refreshing = ref(false)
 
-// 建立链接
-// async function getUserInfo() {
-//   const { data } = await request('/website/tourism/user/view')
-//   chatStore.user = data
-//   user.value = data
-//   console.log(data, 'createConnection')
-
-//   await chatStore.createConnection(data.pass)
-//   chatStore.reqChatList()
-
-//   console.log('用户信息:', chatStore.user)
-
-//   // console.log('会话列表:', chatStore.chatList.value)
-// }
-
 useSeoMeta({
   title: '我的消息'
 })
@@ -352,7 +326,7 @@ const createTimeSplit = (timer) => {
 // 下拉刷新
 const onRefresh = () => {
   refreshing.value = true
-  chatStore.reqChatList()
+  chatsStore.getChatList()
   refreshing.value = false
 }
 
@@ -379,7 +353,7 @@ const handleBoolean = async (body) => {
       body
     })
     if (data) {
-      chatStore.reqChatList()
+      // chatStore.reqChatList()
     }
   } catch (error) {}
 }
@@ -432,52 +406,22 @@ const onChatPage = async (path, query) => {
 
 // 选中的是那个页面
 const onSelect = (action) => {
-  if (action.text == '创建群聊') onChatPage('/chat/create-group', {})
-  if (action.text == '群聊广场') onChatPage('/chat/group-square', {})
-  if (action.text == '加入群聊') onChatPage('/scan', {})
-  if (action.text == '添加用户') onChatPage('/chat/user-add', {})
+  if (action.text === '创建群聊') onChatPage('/chat/create-group', {})
+  if (action.text === '群聊广场') onChatPage('/chat/group-square', {})
+  if (action.text === '加入群聊') onChatPage('/scan', {})
+  if (action.text === '添加用户') onChatPage('/chat/user-add', {})
 }
 
 onMounted(() => {
-  if (wsConnect == 0) {
-    showToast('聊天网络连接中...')
-  } else if (wsConnect == 1) {
-    showToast(' 聊天正在连接中...')
-  } else if (wsConnect == 3) {
-    showToast(' 聊天连接已断开,请刷新页面重新连接,或稍后重试!')
-  } else if (wsConnect == 2) {
-  }
-  chatStore.reqChatList()
-  // chatStore.reqChatList()
+  chatsStore.getChatList()
+  XYWebSocket.SocketEventsBus.on(XYWebSocket.SocketEvents.chatEvent, (chat) => {
+    chatsStore.getChatList()
+  })
 })
 
-// watch(
-//   wsConnect,
-//   async (newValue, oldValue) => {
-//     console.log(newValue, 'wsConnect')
-//     console.log(chatLists.value, 'chatLists')
-
-//     if (newValue == 2) {
-//       pinnedList.value = chatList.value.filter((el) => el.isTop != 1)
-//       isTopList.value = chatList.value.filter((el) => el.isTop == 1)
-//     }
-//   },
-//   { immediate: true }
-// )
-// watch(
-//   chatList.value,
-//   (newList, oldList) => {
-//     // pinnedSystemList.value = newList.filter((el) => el.noticeType == 3)
-//     pinnedList.value = newList.filter((el) => el.isTop != 1)
-//     isTopList.value = newList.filter((el) => el.isTop == 1)
-
-//     console.log(pinnedList.value, 'pinnedList')
-//     console.log(isTopList.value, 'isTopList')
-//     // console.log(newList, 'newList')
-//     console.log(oldList, 'oldList')
-//   },
-//   { immediate: true }
-// )
+onUnmounted(() => {
+  XYWebSocket.SocketEventsBus.off(XYWebSocket.SocketEvents.chatEvent)
+})
 </script>
 
 <style lang="scss" scoped></style>

+ 3 - 3
src/stores/chat.js

@@ -101,11 +101,11 @@ export const useChatStore = defineStore('chat', () => {
     ws,
     conversations,
     curConversiton,
-    receive,
+    receive, // 可删
     receiveGetter,
-    user,
+    user, // 可删
     chatList,
-    isConnect,
+    isConnect, // 可删
     connectSta,
     onNewMessage
   }

+ 66 - 0
src/stores/useChats.js

@@ -0,0 +1,66 @@
+import {XYWebSocket} from "~/utils/XYWebSocket";
+
+export const useChatsStore = defineStore('chats', () => {
+  let socket = null;
+  const initWebSocket = (url) => {
+    if (!socket) socket = new XYWebSocket.WebSocketClass(url);
+  }
+
+  // 当前会话管理
+  const currConversation = ref(null);
+  const setCurrConversation = (conversation) => {
+    currConversation.value = conversation
+  }
+
+  // 会话列表
+  let chatList = ref([]);
+  let chatListLoading = ref(false);
+  const getChatList = async () => {
+    try {
+      const res= await request('/website/tourism/fans/getTourMemberList');
+      await handleResponse(res)
+      const { list } = res.data;
+      chatList.value = list;
+    } catch (e) {
+
+    } finally {
+      chatListLoading.value = false
+    }
+  }
+
+  // socket状态管理
+  const STATUS_TEXT = ['连接关闭', '连接成功', '连接出错']
+  let socketStatus = ref(0)
+  let socketStatusText = computed(() => STATUS_TEXT[socketStatus.value])
+  XYWebSocket.SocketEventsBus.on(XYWebSocket.SocketEvents.socketStatusChange, (status) => {
+    socketStatus.value = status
+  })
+
+  const MESSAGE_TYPE = {
+    'text': 0,
+    'image': 1,
+    'audio': 2,
+    'video': 3,
+    'link': 4
+  }
+  const NOTICE_TYPE = []
+
+  return {
+    // 注册socket
+    initWebSocket,
+    // socket状态
+    socketStatus,
+    socketStatusText,
+
+    // 当前会话
+    currConversation,
+    setCurrConversation,
+
+    // 会话列表
+    chatList,
+    chatListLoading,
+    getChatList,
+
+
+  }
+})

+ 171 - 0
src/utils/XYWebSocket.ts

@@ -0,0 +1,171 @@
+import ReconnectingWebSocket, {type Options, type Event} from 'reconnecting-websocket';
+import mitt from "mitt"
+import {uuid} from "~/utils/index";
+
+export namespace XYWebSocket {
+    export class WebSocketClass {
+        public socket: ReconnectingWebSocket;
+        public socketUrl: string;
+        public options: SocketOptions = {
+            apiTimeout: 10, // 接口请求超时 s
+            connectionTimeout: 10 * 1000, // 超时连接 ms
+            maxRetries: 5, // 最大重连次数
+        };
+        private eventPoll: Map<string, SocketResponseType> = new Map();
+        public SocketStatus: SocketStatusType = SocketStatusType.close;
+
+        constructor(url: string, options?: SocketOptions) {
+            this.socketUrl = url;
+            this.options = {
+                ...this.options,
+                ...(options || {})
+            };
+            this.socket = new ReconnectingWebSocket(this.socketUrl, [], this.options);
+            this.initWebSocket();
+        }
+
+        public initWebSocket() {
+            console.info(this.socketUrl, '正在连接websocket........');
+            this.socket.addEventListener('open', this.websocketOnopen.bind(this));
+            this.socket.addEventListener('message', this.websocketOnmessage.bind(this));
+            this.socket.addEventListener('error', this.websocketOnerror.bind(this));
+            this.socket.addEventListener('close', this.websocketOnclose.bind(this));
+            return this.socket;
+        }
+
+        private websocketOnopen(e: Event) {
+            console.info('WebSocket连接成功', e);
+            this.changeSocketStatus(SocketStatusType.open)
+        }
+
+        // 收到服务端消息
+        private websocketOnmessage(e: MessageEvent) {
+            try {
+                // console.error(e, '收到服务端消息')
+                if (!e) return;
+                if (!e.data) return;
+                const messagePackage = JSON.parse(e.data) || {};
+                console.log('---------接收---------', messagePackage, '-----------消息包------');
+                const {web_request_id} = messagePackage;
+
+                /**
+                 * 1、回复客户端消息
+                 */
+                if (web_request_id && this.eventPoll.has(web_request_id)) {
+                    const eventItem = this.eventPoll.get(web_request_id)!;
+                    eventItem.response = messagePackage;
+                    eventItem.status = 'end'; // todo status属性的修改最好写在最后,写在前面好像也没啥问题
+                    return;
+                }
+                /**
+                 * 2、 服务端主动发起消息
+                 */
+                if (!web_request_id) {
+                    SocketEventsBus.emit(SocketEvents.chatEvent, messagePackage)
+                }
+            } catch (e) {
+
+            }
+        }
+
+        private websocketOnerror(e: Event) {
+            console.info(this.socket, 'WebSocket连接出错', e);
+            this.changeSocketStatus(SocketStatusType.close)
+        }
+
+        private websocketOnclose(e: Event) {
+            console.info(this.socket, 'WebSocket连接关闭', e);
+            this.changeSocketStatus(SocketStatusType.close)
+        }
+
+        private changeSocketStatus(status: SocketStatusType) {
+            this.SocketStatus = status;
+            SocketEventsBus.emit(SocketEvents.socketStatusChange, this.SocketStatus)
+        }
+
+        public destroyWebsocket() {
+            this.socket.removeEventListener('open', this.websocketOnopen.bind(this));
+            this.socket.removeEventListener('message', this.websocketOnmessage.bind(this));
+            this.socket.removeEventListener('error', this.websocketOnerror.bind(this));
+            this.socket.removeEventListener('close', this.websocketOnclose.bind(this));
+        }
+
+        public sendMessage(path: string, data?: any): Promise<any> { // string | ArrayBuffer | Blob | ArrayBufferView
+            const requestId = uuid();
+
+            let timer: any = 0;
+            return new Promise((resolve, reject) => {
+                // 整理好请求,记录到本地请求池
+                const eventItem: SocketResponseType = {
+                    requestId: requestId,
+                    status: 'waiting',
+                    response: null,
+                };
+                const eventItemProxy = new Proxy(eventItem, {
+                    set(target: SocketResponseType, p: keyof SocketResponseType, value, receiver): boolean {
+                        // @ts-ignore
+                        target[p] = value;
+                        if (target.status === 'end') {
+                            // console.warn('------Proxy 检测到状态变化 end---------')
+                            resolve(target.response);
+                        }
+                        return true;
+                    }
+                });
+                this.eventPoll.set(requestId, eventItemProxy);
+
+                const request = {
+                    web_request_id: requestId,
+                    ...data
+                };
+                console.warn('---------发送---------', request, '-----------消息包------');
+                console.warn('---------发送---------', JSON.stringify(request), '-----------消息包------');
+                this.socket.send(JSON.stringify(request) + 'AD8C0A5F-3CF1-BEE2-3FE1-84209A3E9BD2');
+
+                // 判断超时
+                let loop = 0;
+                timer = setInterval(() => {
+                    if (this.eventPoll.has(requestId)) {
+                        const item = this.eventPoll.get(requestId)!;
+                        if (item.status === 'end') {
+                            clearInterval(timer);
+                        } else {
+                            loop++;
+                            if (loop > this.options.apiTimeout!) {
+                                clearInterval(timer);
+                                reject('请求超时' + 'requestId:' + requestId)
+                            }
+                        }
+                    }
+                }, 1000)
+            })
+                .finally(() => {
+                    this.eventPoll.delete(requestId);
+                });
+        }
+
+    }
+
+    export enum SocketStatusType {
+        close,
+        open,
+        error,
+    }
+
+    export const SocketEventsBus = mitt();
+
+    interface SocketResponseType {
+        requestId: string;
+        status: 'waiting' | 'end';
+        response: any
+    }
+
+    interface SocketOptions extends Options {
+        apiTimeout?: number
+    }
+
+    export const SocketEvents = {
+        chatEvent: 'chat-event',
+        socketStatusChange: 'socket-status-change'
+    }
+}

+ 10 - 1
src/utils/index.js

@@ -60,4 +60,13 @@ const isValidJson = (jsonString) => {
     }
 }
 
-export { setIntervalImmediately, formatImgSrc, formatNumber, isEmptyValue, isValidJson };
+
+const uuid = function () {
+    function S4() {
+        return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
+    }
+    return (S4() + S4() + "" + S4() + "" + S4() + "" + S4() + "" + S4() + S4() + S4());
+};
+
+
+export { setIntervalImmediately, formatImgSrc, formatNumber, isEmptyValue, isValidJson, uuid };