Browse Source

Merge branch 'follow' into dev_suwenjiang

# Conflicts:
#	src/pages/chat/single.vue
#	src/pages/profile/my-news/index.vue
suwenjiang 2 months ago
parent
commit
9a706261f6

+ 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
-})

+ 19 - 15
src/pages/chat/chat-message/index.vue

@@ -10,15 +10,18 @@
           radius="100%"
           class="mr-8"
       ></van-image>
-      <div>
-        <TextMessage v-if="msg.type === 'text'" :message-content="msg.messageContent"
-                     :view-type="msg.viewType"></TextMessage>
-        <ImageMessage v-if="msg.type === 'image'" :message-content="msg.messageContent"
-                      :view-type="msg.viewType"></ImageMessage>
-        <AudioMessage v-if="msg.type === 'audio'" :message-content="msg.messageContent"
-                      :view-type="msg.viewType"></AudioMessage>
-        <LinkMessage v-if="msg.type === 'link'" :message-content="msg.messageContent"
-                     :view-type="msg.viewType"></LinkMessage>
+      <div class="flex flex-col" :class=" msg.viewType ?  'items-start' : 'items-end'">
+        <div v-if="showName && msg.viewType === 1" class="text-black-9 text-sm mb-2">{{msg.showName}}</div>
+        <div class="flex-grow-0 w-fit">
+          <TextMessage v-if="msg.type === 'text'" :message-content="msg.messageContent"
+                       :view-type="msg.viewType"></TextMessage>
+          <ImageMessage v-if="msg.type === 'image'" :message-content="msg.messageContent"
+                        :view-type="msg.viewType"></ImageMessage>
+          <AudioMessage v-if="msg.type === 'audio'" :message-content="msg.messageContent"
+                        :view-type="msg.viewType"></AudioMessage>
+          <LinkMessage v-if="msg.type === 'link'" :message-content="msg.messageContent"
+                       :view-type="msg.viewType"></LinkMessage>
+        </div>
       </div>
 <!--      <div class="self-center text-sm mx-5 text-black-9">发送中</div>-->
       <van-image
@@ -45,7 +48,11 @@ const user = computed(() => chatStore.user)
 const {curConversiton} = storeToRefs(chatStore)
 
 const props = defineProps({
-  message: Object
+  message: Object,
+  showName: {
+    type: Boolean,
+    default: false
+  }
 });
 
 let msg = ref(null)
@@ -53,14 +60,12 @@ const initMsg = () => {
   try {
     if (!isValidJson(props.message?.messageContent)) return
     const {createTime, getUserId, sendUserId, messageContent} = JSON.parse(props.message?.messageContent)
-    console.log(props.message, 'messagemessage', user.value)
-    console.warn(props.message?.sendUserId, user.value.pass, 'initMsg')
-
     msg.value = {
       messageContent: messageContent,
       type: getMessageType(props.message?.messageType),
       viewType: props.message?.sendUserId === user.value.pass ? 0 : 1,
-      createTime: createTime
+      createTime: createTime,
+      showName: props.message?.showName
     }
   } catch (e) {
 
@@ -76,7 +81,6 @@ initMsg()
 <style scoped lang="scss">
 .chat-message {
   margin: 20px 0;
-
   .chat-message__content {
     width: max-content;
     display: flex;

+ 65 - 145
src/pages/chat/single.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="single-page h-full w-full" style="height: 100vh">
-    <van-nav-bar @click-left="onClickLeft" @click-right="onClickRight">
+    <van-nav-bar @click-left="router.back()" @click-right="onClickRight">
       <template #left>
         <div>
           <van-icon name="arrow-left" color="black" size="18" />
@@ -19,91 +19,61 @@
       </template>
     </van-nav-bar>
     <van-list
-      ref="messageBoxRef"
-      class="flex-1 overflow-y-auto px-12 flex flex-col"
-      :finished="true"
-      finished-text=""
+        ref="chatListRef"
+        class="flex-1 overflow-y-auto px-12 flex flex-col"
+        :finished="true"
+        finished-text=""
     >
       <template v-for="(message, index) in receiveGetter" :key="index">
         <ChatMessage :message="message"></ChatMessage>
       </template>
-      <div v-if="false" class="text-[#979797] text-sm text-center mt-auto mb-10">
-        在对方关注或回复前,最多只能发送1条信息
-      </div>
+      <div v-if="false" class="text-[#979797] text-sm text-center mt-auto mb-10">{{followStatus}}在对方关注或回复前,最多只能发送1条信息</div>
     </van-list>
-    <ChatInput :operates="['image']" @focus="scrollToBottom" @send="handleSendMessage"></ChatInput>
+    <ChatInput :operates="['image']"  @focus="scrollToBottom" @send="handleSendMessage"></ChatInput>
   </div>
 </template>
 <script setup>
 import ChatMessage from './chat-message'
-import ChatInput from './chat-input'
-import { messageContentParse, formatTimestamp } from '~/utils/detalTime'
-import { findHyperlinks } from '~/pages/chat/chat-message/link-message/handle'
+import ChatInput from "./chat-input";
+import {findHyperlinks} from "~/pages/chat/chat-message/link-message/handle";
 
 const route = useRoute()
 const router = useRouter()
 const refreshing = ref(false)
-const uploadUrl = `${import.meta.env.VITE_APP_BASE_URL}/website/tourMessage/upload`
-const chatStore = useChatStore()
-const { ws, curConversiton, receive, conversations, receiveGetter, connectSta, onNewMessage } =
-  storeToRefs(chatStore)
-
+const chatStore = useChatStore();
 const user = computed(() => chatStore.user)
+const {ws, curConversiton, receive, conversations, receiveGetter, connectSta, onNewMessage} = storeToRefs(chatStore)
+console.warn(curConversiton, 'curConversitoncurConversiton')
+// 单聊的标题
+const title = computed(() => route.query.groupRemark)
 
-// 消息接收者的用户id
-const getUserId = computed(() => curConversiton.value.toUserId)
+// 聊天列表
+const chatListRef = ref(null)
 
+// 当前websocket连接状态 0: 未连接 1: 连接中 2: 已连接 3: 已断开
+// const wsConnect = computed(() => connectSta?.value)
+
+// 消息接收者的用户id
+const getUserId = computed(() => curConversiton?.value.toUserId)
 // 消息发送者:当前登录用户的加密id
-const sendUserId = computed(() => user.value.pass)
+const sendUserId = computed(() => user?.value.pass)
 
 // 会话id
 const groupId = computed(() => route.query.groupId)
 
-// 用户在群聊中艾特的人
-const specialUserId = ref('')
-
-// 用户输入的文本消息
-const messageContent = ref('')
-
-// 聊天类型 1单聊 2群聊 3系统消息 4关注信息
-const noticeType = computed(() => curConversiton.value.noticeType)
-
-const messageBoxRef = ref(null)
-const msgBottomRef = ref(null)
-const inputBoxRef = ref(null)
-
-// 当前websocket连接状态 0: 未连接 1: 连接中 2: 已连接 3: 已断开
-const wsConnect = computed(() => connectSta.value)
-
 const followStatus = ref(0)
 
-const querySwParams = reactive({
-  getUserId: computed(() => route.query.getUserId ?? ''),
-  groupId: computed(() => route.query.groupId ?? ''),
-  sendUserId: computed(() => user.value?.pass)
-})
-
 // 本地生成一个唯一消息id
 function getLocalId() {
   const random = Math.floor(Math.random() * 10000)
   return Date.now() + '' + random
 }
 
-// 刷新次数
-const count = ref(0)
-const loading = ref(false)
-const finished = ref(false)
-const messageList = ref([])
-
-// 单聊的标题
-const title = computed(() => route.query.groupRemark)
-
 // 刷新
 const onRefresh = () => {
-  refreshing.value = true
+  refreshing.value = false
 }
 
-const onClickLeft = () => router.back()
 
 const onClickRight = () => {
   navigateTo({
@@ -120,6 +90,8 @@ const messageCount = ref(0)
 
 // 获取聊天记录
 async function getChatHistory(messageId = '') {
+  if (curConversiton.value.isLocal) return
+
   if (!groupId.value) return
 
   if (receive.value.length >= messageCount.value && receive.value.length > 0) return
@@ -133,79 +105,37 @@ async function getChatHistory(messageId = '') {
   }
 
   const {
-    data: { data = [], count = [] }
-  } = await request('/website/tourMessage/getMessageByGroupId', { query })
+    data: {data = [], count = []}
+  } = await request('/website/tourMessage/getMessageByGroupId', {query})
 
   messageCount.value = count || 0
 
   if (Array.isArray(data)) {
     receive.value = [...data, ...receive.value]
   }
-
-  // 获取到数据后,滚动到底部
-  if (!messageId) {
-    nextTick(() => {
-      setTimeout(() => {
-        mscrollToBottom()
-      }, 100)
-    })
-  }
   console.log('历史记录:', data)
-}
-
-// 监听输入框的enter事件
-function addEventListenerTextarea() {
-  nextTick(() => {
-    if (inputBoxRef.value) {
-      inputBoxRef.value.removeEventListener('keydown', () => {})
-      inputBoxRef.value.addEventListener('keydown', function (event) {
-        if (event.key === 'Enter') {
-          event.preventDefault()
-          sendTextMessage()
-        }
-      })
-    }
-  })
-}
-
-// 监听消息列表的滚动事件
-function addEventListenerMessage() {
-  nextTick(() => {
-    if (messageBoxRef.value) {
-      messageBoxRef.value.removeEventListener('scroll', () => {})
-
-      messageBoxRef.value.addEventListener('scroll', (e) => {
-        if (messageBoxRef.value.scrollTop == 0 && receive.value[0]?.id) {
-          console.log('滚动到顶部了')
-          console.log(receive.value[0].id)
-          getChatHistory(receive.value[0].id)
-        }
-      })
-    }
-  })
+  // 获取到数据后,滚动到底部
+  if (!messageId) await scrollToBottom()
 }
 
 // 获取我与对方的关注情况
-async function isFollow() {
-  if (noticeType.value !== 1) return //只有单聊中才需要获取关注情况
-
+async function getFollowStatus() {
   const query = {
-    userId: curConversiton.value.toUserId
+    userId: getUserId.value
   }
-  const { data: status = 0 } = await request('/website/tourGroup/isFollow', { query })
+  const {data: status = 0} = await request('/website/tourGroup/isFollow', {query})
   followStatus.value = status
   console.log('关注情况:', status)
 }
 
 // 发送文本消息
 const sendTextMessage = (text) => {
-  if (!text) return
+  if(!text) return
   let msg = {
-    // getUserId: getUserId.value,
-    // sendUserId: sendUserId.value,
-    // groupId: groupId.value,
-    ...querySwParams,
-    specialUserId: specialUserId.value,
+    groupId: groupId.value,
+    getUserId: getUserId.value,
+    sendUserId: sendUserId.value,
+    specialUserId: '',
     messageContent: text,
     messageType: 0,
     noticeType: 1,
@@ -214,11 +144,11 @@ const sendTextMessage = (text) => {
     }
   }
   const isLink = !!findHyperlinks(text)
-  if (isLink) msg.messageType = 4
+  if (isLink) msg.messageType = 4;
 
   receive.value.push({
     ...msg,
-    messageContent: JSON.stringify({ messageContent: msg.messageContent })
+    messageContent: JSON.stringify({messageContent: msg.messageContent})
   })
   ws.value.send(JSON.stringify(msg))
 }
@@ -226,83 +156,76 @@ const sendTextMessage = (text) => {
 // 选择发送图片
 const sendImageMessage = async (file) => {
   try {
-    console.log(file, 'file')
     const formData = new FormData()
     formData.append('uploadFile', file)
     formData.append('asImage', true)
     formData.append('fieldName', 'messageContent')
-    const { data } = await request('/website/tourMessage/upload', {
+    const {data} = await request('/website/tourMessage/upload', {
       method: 'post',
       body: formData
     })
 
     const msg = {
+      groupId: groupId.value,
       getUserId: getUserId.value,
       sendUserId: sendUserId.value,
       specialUserId: '',
-      groupId: groupId.value,
-      messageContent: JSON.stringify({ messageContent: data.fileUrl }),
+      messageContent: JSON.stringify({messageContent: data.fileUrl}),
       messageType: 1,
-      noticeType: noticeType.value,
+      noticeType: 1,
       object: {
         id: getLocalId()
       }
     }
     receive.value.push({
       ...msg,
-      messageContent: JSON.stringify({ messageContent: data.fileUrl })
+      messageContent: JSON.stringify({messageContent: data.fileUrl})
     })
 
     ws.value.send(JSON.stringify(msg))
   } catch (e) {
     console.error(e, '??')
   } finally {
+
   }
 }
 
-const handleSendMessage = async ({ type, messageContent }) => {
+const handleSendMessage = async ({type, messageContent}) => {
   try {
     switch (type) {
       case 'text':
         sendTextMessage(messageContent)
-        break
+        break;
       case 'image':
         await sendImageMessage(messageContent)
-        break
+        break;
     }
     await scrollToBottom()
   } catch (e) {
+
   } finally {
+
   }
+
 }
 
+
 const scrollToBottom = async () => {
-  await nextTick() // 确保DOM已经更新
-  const listElement = messageBoxRef.value?.$el
+  await nextTick(); // 确保DOM已经更新
+  const listElement = chatListRef.value?.$el;
   if (listElement) {
-    const scrollContainer = listElement
-    scrollContainer.scrollTop = scrollContainer.scrollHeight
+    const scrollContainer = listElement;
+    scrollContainer.scrollTop = scrollContainer.scrollHeight;
   }
-}
-
-watch(groupId, async () => {
-  // 消息置空
-  receive.value = []
-
-  // 监听消息输入框键盘enter事件
-  // addEventListenerTextarea()
+};
 
-  // 监听聊天框消息滚动事件
-  addEventListenerMessage()
 
-  // 获取与当前会话用户的关注状态
-  isFollow()
-
-  // 获取前会话用户的聊天记录
-  getChatHistory()
+watch(groupId, () => {
+  // 消息置空
+  // receive.value = []
 })
 
-watch(onNewMessage, () => {
+watchEffect(onNewMessage, () => {
   scrollToBottom()
 })
 
@@ -310,15 +233,12 @@ onMounted(() => {
   // 获取前会话用户的聊天记录
   getChatHistory()
 
-  // addEventListenerTextarea()
-  addEventListenerMessage()
-
   // 获取与当前会话用户的关注状态
-  isFollow()
+  getFollowStatus()
 
-  setTimeout(() => {
-    scrollToBottom()
-  }, 500)
+  // setTimeout(() => {
+  //   scrollToBottom()
+  // }, 500)
 })
 
 definePageMeta({

+ 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>

+ 21 - 47
src/pages/profile/my-news/index.vue

@@ -1,11 +1,13 @@
 <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">
       <div class="w-full text-xl font-semibold px-16 h-48 flex justify-start items-center bg-white">
         <span
@@ -334,6 +336,7 @@ import plaza from '~/assets/img/chat/guangchang.svg'
 import userAdd from '~/assets/img/chat/user-add.svg'
 import audioTips from '@/assets/audio/message.mp3'
 import { messageContentParse } from '~/utils/detalTime.js'
+import {XYWebSocket} from '~/utils/XYWebSocket.ts'
 
 const actionsList = [
   { text: '创建群聊', icon: comments },
@@ -343,21 +346,9 @@ const actionsList = [
 ]
 const userInfoStore = useUserInfoStore()
 const { userInfo } = storeToRefs(userInfoStore)
-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)
 
@@ -392,7 +383,7 @@ const createTimeSplit = (timer) => {
 // 下拉刷新
 const onRefresh = () => {
   refreshing.value = true
-  chatStore.reqChatList()
+  chatsStore.getChatList()
   refreshing.value = false
 }
 
@@ -419,7 +410,7 @@ const handleBoolean = async (body) => {
       body
     })
     if (data) {
-      chatStore.reqChatList()
+      chatsStore.getChatList()
     }
   } catch (error) {}
 }
@@ -465,7 +456,7 @@ const onChatPage = async (path, query, itemParams) => {
   })
 
   if (res && res?.success) {
-    chatStore.reqChatList()
+    chatsStore.getChatList()
     navigateTo({
       path,
       query
@@ -475,39 +466,22 @@ const onChatPage = async (path, query, itemParams) => {
 
 // 选中的是那个页面
 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, () => {
+    chatsStore.getChatList()
+  })
 })
 
-// 消息的内容转换
-function messageShowName(messageContent) {
-  try {
-    let content = {}
-    if (messageContent) {
-      content = JSON.parse(messageContent)
-      return content.showName
-    } else {
-      return ''
-    }
-  } catch (error) {
-    console.log(error)
-  }
-}
+onUnmounted(() => {
+  XYWebSocket.SocketEventsBus.off(XYWebSocket.SocketEvents.chatEvent)
+})
 </script>
 
 <style lang="scss" scoped></style>

+ 3 - 3
src/stores/chat.js

@@ -155,11 +155,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 };