Browse Source

Merge branch 'follow' into dev_suwenjiang

suwenjiang 2 months ago
parent
commit
4f88350c2e

+ 2 - 0
src/components/Profile/News/ChatInput.vue

@@ -213,6 +213,8 @@ console.log(files[0])
       // form.image.push(data.fileUrl)
       closeToast()
       showToast('图片上传成功')
+
+      emit('onSelectImg', data.fileUrl)
     } catch (error) {
       // form.image.push({
       //   url: files[0].name,

+ 455 - 0
src/pages/chat/_single.vue

@@ -0,0 +1,455 @@
+<template>
+  <div>
+    <van-nav-bar fixed @click-left="onClickLeft" @click-right="onClickRight">
+      <template #left>
+        <div>
+          <van-icon name="arrow-left" color="black" size="18" />
+        </div>
+      </template>
+      <template #title>
+        <div class="text-2xl text-black-3 text-semibold">
+          <div class="inline-block w-220 line-clamp-1">
+            {{ title }}
+          </div>
+          <!-- <span class="ml-7 iconfont icon-close-remind text-black-9" style="font-size: 16px"></span> -->
+        </div>
+      </template>
+      <template #right>
+        <van-icon name="ellipsis" color="black" size="18" />
+      </template>
+    </van-nav-bar>
+
+    <van-pull-refresh v-model="loading" @refresh="onRefresh">
+      <div class="w-full h-[100vh] pt-55">
+        <div ref="messageBoxRef" class="w-full box-border px-12 overflow-y-auto scrollbar">
+          <van-list
+            v-model:loading="loading"
+            :finished="finished"
+            finished-text="没有更多了"
+            @load="onLoad"
+          >
+            <template v-for="(message, index) in receiveGetter" :key="index">
+                <ChatMessage :message="message"></ChatMessage>
+            </template>
+
+<!--            <template v-for="(item, index) in receiveGetter" :key="index">
+              &lt;!&ndash; 右侧是自己的消息 &ndash;&gt;
+
+              <template v-if="item.sendUserId == user.pass">
+                &lt;!&ndash; 右侧消息 图片 &ndash;&gt;
+                <template v-if="item.messageType == 1">
+                  <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
+                    {{ item.createTime }}
+                  </div>
+
+                  <div class="flex justify-end items-start">
+                    <div
+                      class="mr-10 text-left inline-block min-h-46 max-w-full break-words overflow-auto bg-[#FEF4E6] box-border text-base p-12 text-wrap"
+                    >
+                      {{ item.messageContent }}
+                    </div>
+
+                    <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
+                      <img
+                        v-if="user?.headImageUrl"
+                        class="w-full h-full object-cover"
+                        :src="user?.headImageUrl"
+                        alt=""
+                      />
+                      <img
+                        v-else
+                        class="w-full h-full object-cover"
+                        src="~/assets/img/default_avatar.png"
+                        alt=""
+                      />
+                    </div>
+                  </div>
+                </template>
+
+                &lt;!&ndash; 文字消息 &ndash;&gt;
+                <template v-if="item.messageType == 0 && item.messageContent.trim()">
+                  <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
+                    {{ formatTimestamp(item.createTime) }}
+                  </div>
+
+                  <div class="flex justify-end items-start">
+                    <div
+                      class="mr-10 text-left inline-block min-h-46 max-w-full break-words overflow-auto rounded-b-2xl rounded-tl-2xl bg-white box-border text-base p-12 text-wrap"
+                    >
+                      {{ messageContentParse(item.messageContent) }}
+                    </div>
+                  </div>
+
+                  <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
+                    <img
+                      v-if="user?.headImageUrl"
+                      class="w-full h-full object-cover"
+                      :src="user?.headImageUrl"
+                      alt=""
+                    />
+                    <img
+                      v-else
+                      class="w-full h-full object-cover"
+                      src="~/assets/img/default_avatar.png"
+                      alt=""
+                    />
+                  </div>
+                </template>
+              </template>
+
+              &lt;!&ndash; 左侧他人的消息 &ndash;&gt;
+              <template v-else>
+                &lt;!&ndash; 左侧消息 图片 &ndash;&gt;
+                <template v-if="item.messageType == 1">
+                  <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
+                    {{ item.createTime }}
+                  </div>
+
+                  <div class="flex justify-end items-start">
+                    <div
+                      class="mr-10 text-left inline-block min-h-46 max-w-full break-words overflow-auto rounded-[4px] bg-white box-border text-base p-12 text-wrap"
+                    >
+                      &lt;!&ndash; {{ item.messageContent }} &ndash;&gt;
+                    </div>
+
+                    <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
+                      <img
+                        v-if="user?.headImageUrl"
+                        class="w-full h-full object-cover"
+                        :src="user?.headImageUrl"
+                        alt=""
+                      />
+                      <img
+                        v-else
+                        class="w-full h-full object-cover"
+                        src="~/assets/img/default_avatar.png"
+                        alt=""
+                      />
+                    </div>
+                  </div>
+                </template>
+
+                &lt;!&ndash; 左侧文字 &ndash;&gt;
+                &lt;!&ndash; <template v-if="item.messageType == 0 && item.messageContent.trim()"> &ndash;&gt;
+                <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
+                  {{ formatTimestamp(item.createTime) }}
+                </div>
+
+                <div class="flex justify-start items-start">
+                  <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
+                    <img
+                      v-if="user?.headImageUrl"
+                      class="w-full h-full object-cover"
+                      :src="user?.headImageUrl"
+                      alt=""
+                    />
+                    <img
+                      v-else
+                      class="w-full h-full object-cover"
+                      src="~/assets/img/default_avatar.png"
+                      alt=""
+                    />
+                  </div>
+
+                  <div
+                    class="ml-8 inline-block rounded-b-2xl rounded-tr-2xl min-h-46 max-w-full break-words overflow-auto bg-[#F3F3F3] box-border text-base p-12 text-wrap"
+                  >
+                    {{ messageContentParse(item.messageContent) }}
+                  </div>
+                </div>
+                &lt;!&ndash; </template> &ndash;&gt;
+              </template>
+            </template>-->
+
+            <div ref="msgBottomRef"></div>
+          </van-list>
+        </div>
+      </div>
+    </van-pull-refresh>
+
+    <ProfileNewsChatInput
+      :shareGroup="false"
+      @on-select-Img="selectImg"
+      @on-send-message="sendMessage"
+    ></ProfileNewsChatInput>
+  </div>
+</template>
+<script setup>
+import ChatMessage from './chat-message'
+import { messageContentParse, formatTimestamp } from '~/utils/detalTime'
+import {findHyperlinks} from "~/pages/chat/chat-message/link-message/handle";
+const route = useRoute()
+const router = useRouter()
+
+const uploadUrl = `${import.meta.env.VITE_APP_BASE_URL}/website/tourMessage/upload`
+const chatStore = useChatStore()
+const { ws, curConversiton, receive, receiveGetter, connectSta, onNewMessage } =
+  storeToRefs(chatStore)
+const user = computed(() => chatStore.user)
+
+// 消息接收者的用户id
+const getUserId = computed(() => curConversiton.value.toUserId)
+
+// 消息发送者:当前登录用户的加密id
+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(() => route.query.sendUserId ?? '')
+})
+
+// 本地生成一个唯一消息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 = () => {}
+
+const onClickLeft = () => router.back()
+
+const onClickRight = () => {
+  navigateTo({
+    path: '/chat/set-single',
+    query: {
+      toUserId: route.query.getUserId
+    }
+  })
+}
+
+// 发送消息的方法
+// 发送文本消息
+function sendMessage(messageParams) {
+  console.log(messageParams, 'messageParams')
+  console.log(getUserId.value, '5555')
+  console.log(noticeType, '5555')
+  if (!messageParams.trim()) return
+  let msg = {
+    // getUserId: getUserId.value,
+    // sendUserId: sendUserId.value,
+    // groupId: groupId.value,
+    ...querySwParams,
+    specialUserId: specialUserId.value,
+    messageContent: messageParams,
+    messageType: 0,
+    noticeType: 1,
+    object: {
+      id: getLocalId()
+    }
+  }
+  const isLink = !!findHyperlinks(messageParams)
+  if(isLink) {
+    msg.messageType = 4
+  }
+
+  receive.value.push({
+    ...msg,
+    messageContent: JSON.stringify({ messageContent: msg.messageContent })
+  })
+  messageContent.value = ''
+  messageBoxRef.value.scrollTop = messageBoxRef.value.scrollHeight
+
+  msg = JSON.stringify(msg)
+  ws.value.send(msg)
+}
+
+const pageSize = ref(10)
+
+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
+
+  if (receive.value.length && !messageId) return
+
+  const query = {
+    pageSize: pageSize.value,
+    groupId: groupId.value,
+    messageId
+  }
+
+  const {
+    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(() => {
+        messageBoxRef.value && messageBoxRef.value.scrollTo({ top: msgBottomRef.value.offsetTop })
+      }, 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()
+          sendMessage()
+        }
+      })
+    }
+  })
+}
+
+// 监听消息列表的滚动事件
+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)
+        }
+      })
+    }
+  })
+}
+
+// 选择发送图片
+function selectImg(e) {
+  const file = e.target.files[0]
+
+  const { size, type, name } = file
+  const IMIETypes = ['image/jpeg', 'image/png', 'image/gif']
+
+  if (!IMIETypes.includes(type)) {
+    showToast('请上传图片')
+
+    return
+  }
+
+  const maxSize = 1024 * 1024 * 20
+
+  if (size > maxSize) {
+    showFailToast('图片大小不能超过10M')
+    return
+  }
+
+  const formData = new FormData()
+
+  formData.append('uploadFile', file)
+  formData.append('fieldName', 'messageContent')
+  formData.append('asImage', true)
+
+  request(uploadUrl, { method: 'post', body: formData }).then((res) => {
+    const {
+      data: { fileUrl }
+    } = res
+    if (fileUrl) {
+      const msg = {
+        getUserId: getUserId.value,
+        sendUserId: sendUserId.value,
+        specialUserId: '',
+        groupId: groupId.value,
+        messageContent: fileUrl,
+        messageType: 1,
+        noticeType: noticeType.value,
+        object: {
+          id: getLocalId()
+        }
+      }
+      ws.value.send(JSON.stringify(msg))
+    }
+  })
+}
+
+// 获取我与对方的关注情况
+async function isFollow() {
+  if (noticeType.value !== 1) return //只有单聊中才需要获取关注情况
+
+  const query = {
+    userId: curConversiton.value.toUserId
+  }
+  const { data: status = 0 } = await request('/website/tourGroup/isFollow', { query })
+  followStatus.value = status
+  console.log('关注情况:', status)
+}
+
+watch(groupId, async () => {
+  // 消息置空
+  receive.value = []
+
+  // 监听消息输入框键盘enter事件
+  addEventListenerTextarea()
+
+  // 监听聊天框消息滚动事件
+  addEventListenerMessage()
+
+  // 获取与当前会话用户的关注状态
+  isFollow()
+
+  // 获取前会话用户的聊天记录
+  getChatHistory()
+})
+
+watch(onNewMessage, getUserId, sendUserId, () => {})
+
+onMounted(() => {
+  // 获取前会话用户的聊天记录
+  getChatHistory()
+
+  addEventListenerTextarea()
+  addEventListenerMessage()
+
+  // 获取与当前会话用户的关注状态
+  isFollow()
+})
+
+definePageMeta({
+  layout: false
+})
+</script>
+<style lang="scss" scoped></style>

+ 7 - 7
src/pages/chat/chat-message/image-message/index.vue

@@ -1,6 +1,6 @@
 <script setup>
 const props = defineProps({
-  messageContent: String|Number, // 消息内容
+  messageContent: String, // 消息内容
   viewType: Number, // 0发送 1接收
 });
 
@@ -9,7 +9,7 @@ let width = ref(75);
 let height = ref(75);
 let loading = ref(false)
 
-const initMessage = () => {
+/*const initMessage = () => {
   switch (props.viewType) {
     case 0: // 发送 上传
       sendMessage()
@@ -18,7 +18,7 @@ const initMessage = () => {
       acceptMessage(props.messageContent)
       break;
   }
-}
+}*/
 
 const resizeImageToMaxSize = (maxSize, w, h) => {
   let scale = 1;
@@ -33,8 +33,9 @@ const resizeImageToMaxSize = (maxSize, w, h) => {
 }
 
 // 处理接收到的消息
-const acceptMessage = async (url) => {
+const initMessage = async () => {
   try {
+    const url = props.messageContent;
     const img = new Image();
     img.onload = function() {
       const size = resizeImageToMaxSize(250, this.width, this.height);
@@ -99,10 +100,9 @@ onMounted(() => {
 
 <template>
   <div class="image-message">
-<!--    <div v-if="!viewType" class="message__operate">!</div>-->
     <van-image :src="imgUrl" :width="width" :height="height" @click="preview">
       <template v-slot:loading>
-        <div class="w-75 h-75 relative">
+        <div class="relative">
           <div class="absolute w-full h-full left-0 right-0 flex items-center justify-center">
             <van-loading type="spinner" size="20"/>
           </div>
@@ -119,8 +119,8 @@ onMounted(() => {
 <style scoped lang="scss">
 .image-message {
   display: flex;
-  //flex-direction: row;
   position: relative;
+
   .message__operate {
     width: 16px;
     height: 16px;

+ 72 - 16
src/pages/chat/chat-message/index.vue

@@ -1,38 +1,94 @@
 <template v-if="message">
-  <div class="chat-message" :class="message.viewType ? 'chat-message--accept' : 'chat-message--send'">
-    <TextMessage v-if="message.type === 'text'" :message-content="message.messageContent"
-                 :view-type="message.viewType"></TextMessage>
-    <ImageMessage v-if="message.type === 'image'" :message-content="message.messageContent"
-                  :view-type="message.viewType"></ImageMessage>
-    <AudioMessage v-if="message.type === 'audio'" :message-content="message.messageContent"
-                  :view-type="message.viewType"></AudioMessage>
-    <LinkMessage v-if="message.type === 'link'" :message-content="message.messageContent"
-                  :view-type="message.viewType"></LinkMessage>
+  <div class="chat-message" :class="msg.viewType ? 'chat-message--accept' : 'chat-message--send'">
+    <div class="text-center text-sm text-[#000]/40 mb-16">{{ message.createTime }}</div>
+
+    <div class="chat-message__content">
+      <van-image
+          v-if="msg.viewType === 1"
+          :src="curConversiton?.headImage || defaultAvatar"
+          width="40px"
+          height="40px"
+          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>
+<!--      <div class="self-center text-sm mx-5 text-black-9">发送中</div>-->
+      <van-image
+          v-if="msg.viewType === 0"
+          :src="user?.headImageUrl || defaultAvatar"
+          width="40px"
+          height="40px"
+          radius="100%"
+          class="ml-8"
+      ></van-image>
+    </div>
   </div>
 </template>
 <script setup>
+import defaultAvatar from "~/assets/img/default_avatar.png";
 import TextMessage from "~/pages/chat/chat-message/text-message/index.vue";
 import ImageMessage from "~/pages/chat/chat-message/image-message/index.vue";
 import AudioMessage from "~/pages/chat/chat-message/audio-message/index.vue";
 import LinkMessage from "~/pages/chat/chat-message/link-message/index.vue";
+import {isValidJson} from "~/utils";
 
-defineProps({
+const chatStore = useChatStore()
+const user = computed(() => chatStore.user)
+const {curConversiton} = storeToRefs(chatStore)
+const props = defineProps({
   message: Object
 });
 
+let msg = ref(null)
+const initMsg = () => {
+  try {
+    console.log(props.message, 'messagemessage')
+    if (!isValidJson(props.message?.messageContent)) return
+    const {createTime, getUserId, sendUserId, messageContent} = JSON.parse(props.message?.messageContent)
+    msg.value = {
+      messageContent: messageContent,
+      type: getMessageType(props.message?.messageType),
+      viewType: getUserId === sendUserId ? 0 : 1,
+      createTime: createTime
+    }
+  } catch (e) {
+
+  }
+}
+
+const getMessageType = (messageType) => {
+  const types = ['text', 'image', 'audio', 'video', 'link']
+  return types[messageType]
+}
+initMsg()
 </script>
 <style scoped lang="scss">
 .chat-message {
-  //background: red;
-  width: max-content;
   margin: 20px 0;
+
+  .chat-message__content {
+    width: max-content;
+    display: flex;
+  }
   &.chat-message--accept {
-    //float: left;
-    margin-right: auto;
+    .chat-message__content {
+      margin-right: auto;
+    }
   }
+
   &.chat-message--send {
-    //float: right;
-    margin-left: auto;
+    .chat-message__content {
+      margin-left: auto;
+    }
   }
 }
 </style>

+ 20 - 0
src/pages/chat/chat-message/link-message/handle.js

@@ -0,0 +1,20 @@
+
+const IN_STATION_LINK = []// 站内链接
+export const findHyperlinks = (text) => {
+  try {
+    const urlPattern = /https?:\/\/[^\s]+/g;
+    if (!text.match(urlPattern)) return null
+    const maybeUrl = text.match(urlPattern)[0];
+    const url = new URL(maybeUrl);
+    let hostname = url.hostname;
+    const mainParts = hostname.split('.').slice(-2).join('.');
+    return {
+      mainParts,
+      hostname: hostname,
+      isInStation: IN_STATION_LINK.includes(hostname),
+      url: maybeUrl
+    }
+  } catch (e) {
+    return null
+  }
+}

+ 3 - 19
src/pages/chat/chat-message/link-message/index.vue

@@ -1,4 +1,6 @@
 <script setup>
+import {findHyperlinks} from "~/pages/chat/chat-message/link-message/handle";
+
 const props = defineProps({
   messageContent: String | Number, // 消息内容
   viewType: Number, // 0发送 1接收
@@ -6,25 +8,7 @@ const props = defineProps({
 
 
 let linkInfo = ref(null) // hostname、mainParts、isInStation、url
-const IN_STATION_LINK = []// 站内链接
-const findHyperlinks = (text) => {
-  try {
-    const urlPattern = /https?:\/\/[^\s]+/g;
-    if (!text.match(urlPattern)) return null
-    const maybeUrl = text.match(urlPattern)[0];
-    const url = new URL(maybeUrl);
-    let hostname = url.hostname;
-    const mainParts = hostname.split('.').slice(-2).join('.');
-    return {
-      mainParts,
-      hostname: hostname,
-      isInStation: IN_STATION_LINK.includes(hostname),
-      url: maybeUrl
-    }
-  } catch (e) {
-    return null
-  }
-}
+
 const initMessage = () => {
   try {
     linkInfo.value = findHyperlinks(props.messageContent) ?? null;

+ 76 - 210
src/pages/chat/single.vue

@@ -1,9 +1,9 @@
 <template>
-  <div>
-    <van-nav-bar fixed @click-left="onClickLeft" @click-right="onClickRight">
+  <div class="single-page h-full w-full" style="height: 100vh">
+    <van-nav-bar @click-left="onClickLeft" @click-right="onClickRight">
       <template #left>
         <div>
-          <van-icon name="arrow-left" color="black" size="18" />
+          <van-icon name="arrow-left" color="black" size="18"/>
         </div>
       </template>
       <template #title>
@@ -15,170 +15,40 @@
         </div>
       </template>
       <template #right>
-        <van-icon name="ellipsis" color="black" size="18" />
+        <van-icon name="ellipsis" color="black" size="18"/>
       </template>
     </van-nav-bar>
-
-    <van-pull-refresh v-model="loading" @refresh="onRefresh">
-      <div class="w-full h-[100vh] pt-55">
-        <div ref="messageBoxRef" class="w-full box-border px-12 overflow-y-auto scrollbar">
-          <van-list
-            v-model:loading="loading"
-            :finished="finished"
-            finished-text="没有更多了"
-            @load="onLoad"
-          >
-            <template v-for="(item, index) in receiveGetter" :key="index">
-              <!-- 右侧是自己的消息 -->
-
-              <template v-if="item.sendUserId == user.pass">
-                <!-- 右侧消息 图片 -->
-                <template v-if="item.messageType == 1">
-                  <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
-                    {{ item.createTime }}
-                  </div>
-
-                  <div class="flex justify-end items-start">
-                    <div
-                      class="mr-10 text-left inline-block min-h-46 max-w-full break-words overflow-auto bg-[#FEF4E6] box-border text-base p-12 text-wrap"
-                    >
-                      {{ item.messageContent }}
-                    </div>
-
-                    <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
-                      <img
-                        v-if="user?.headImageUrl"
-                        class="w-full h-full object-cover"
-                        :src="user?.headImageUrl"
-                        alt=""
-                      />
-                      <img
-                        v-else
-                        class="w-full h-full object-cover"
-                        src="~/assets/img/default_avatar.png"
-                        alt=""
-                      />
-                    </div>
-                  </div>
-                </template>
-
-                <!-- 文字消息 -->
-                <template v-if="item.messageType == 0 && item.messageContent.trim()">
-                  <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
-                    {{ formatTimestamp(item.createTime) }}
-                  </div>
-
-                  <div class="flex justify-end items-start">
-                    <div
-                      class="mr-10 text-left inline-block min-h-46 max-w-full break-words overflow-auto rounded-b-2xl rounded-tl-2xl bg-white box-border text-base p-12 text-wrap"
-                    >
-                      {{ messageContentParse(item.messageContent) }}
-                    </div>
-                  </div>
-
-                  <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
-                    <img
-                      v-if="user?.headImageUrl"
-                      class="w-full h-full object-cover"
-                      :src="user?.headImageUrl"
-                      alt=""
-                    />
-                    <img
-                      v-else
-                      class="w-full h-full object-cover"
-                      src="~/assets/img/default_avatar.png"
-                      alt=""
-                    />
-                  </div>
-                </template>
-              </template>
-
-              <!-- 左侧他人的消息 -->
-              <template v-else>
-                <!-- 左侧消息 图片 -->
-                <template v-if="item.messageType == 1">
-                  <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
-                    {{ item.createTime }}
-                  </div>
-
-                  <div class="flex justify-end items-start">
-                    <div
-                      class="mr-10 text-left inline-block min-h-46 max-w-full break-words overflow-auto rounded-[4px] bg-white box-border text-base p-12 text-wrap"
-                    >
-                      <!-- {{ item.messageContent }} -->
-                    </div>
-
-                    <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
-                      <img
-                        v-if="user?.headImageUrl"
-                        class="w-full h-full object-cover"
-                        :src="user?.headImageUrl"
-                        alt=""
-                      />
-                      <img
-                        v-else
-                        class="w-full h-full object-cover"
-                        src="~/assets/img/default_avatar.png"
-                        alt=""
-                      />
-                    </div>
-                  </div>
-                </template>
-
-                <!-- 左侧文字 -->
-                <!-- <template v-if="item.messageType == 0 && item.messageContent.trim()"> -->
-                <div class="w-full text-center text-black/[0.4] mt-24 text-sm">
-                  {{ formatTimestamp(item.createTime) }}
-                </div>
-
-                <div class="flex justify-start items-start">
-                  <div class="w-40 h-40 rounded-full overflow-hidden shrink-0">
-                    <img
-                      v-if="user?.headImageUrl"
-                      class="w-full h-full object-cover"
-                      :src="user?.headImageUrl"
-                      alt=""
-                    />
-                    <img
-                      v-else
-                      class="w-full h-full object-cover"
-                      src="~/assets/img/default_avatar.png"
-                      alt=""
-                    />
-                  </div>
-
-                  <div
-                    class="ml-8 inline-block rounded-b-2xl rounded-tr-2xl min-h-46 max-w-full break-words overflow-auto bg-[#F3F3F3] box-border text-base p-12 text-wrap"
-                  >
-                    {{ messageContentParse(item.messageContent) }}
-                  </div>
-                </div>
-                <!-- </template> -->
-              </template>
-            </template>
-
-            <div ref="msgBottomRef"></div>
-          </van-list>
-        </div>
-      </div>
-    </van-pull-refresh>
-
+    <van-list
+        ref="messageBoxRef"
+        class="flex-1 overflow-y-auto px-12"
+        :finished="true"
+        finished-text=""
+    >
+      <template v-for="(message, index) in receiveGetter" :key="index">
+        <ChatMessage :message="message"></ChatMessage>
+      </template>
+    </van-list>
+    <div class="h-88 w-full bg-[#333]"></div>
     <ProfileNewsChatInput
-      :shareGroup="false"
-      @on-select-Img="selectImg"
-      @on-send-message="sendMessage"
+        :shareGroup="false"
+        @on-select-Img="selectImg"
+        @on-send-message="sendMessage"
     ></ProfileNewsChatInput>
   </div>
 </template>
 <script setup>
-import { messageContentParse, formatTimestamp } from '~/utils/detalTime'
+import ChatMessage from './chat-message'
+import {messageContentParse, formatTimestamp} from '~/utils/detalTime'
+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, receiveGetter, connectSta, onNewMessage } =
-  storeToRefs(chatStore)
+const {ws, curConversiton, receive, receiveGetter, connectSta, onNewMessage} =
+    storeToRefs(chatStore)
+
 const user = computed(() => chatStore.user)
 
 // 消息接收者的用户id
@@ -230,7 +100,9 @@ const messageList = ref([])
 const title = computed(() => route.query.groupRemark)
 
 // 刷新
-const onRefresh = () => {}
+const onRefresh = () => {
+  refreshing.value = false
+}
 
 const onClickLeft = () => router.back()
 
@@ -249,7 +121,6 @@ function sendMessage(messageParams) {
   console.log(messageParams, 'messageParams')
   console.log(getUserId.value, '5555')
   console.log(noticeType, '5555')
-
   if (!messageParams.trim()) return
   let msg = {
     // getUserId: getUserId.value,
@@ -264,10 +135,14 @@ function sendMessage(messageParams) {
       id: getLocalId()
     }
   }
+  const isLink = !!findHyperlinks(messageParams)
+  if (isLink) {
+    msg.messageType = 4
+  }
 
   receive.value.push({
     ...msg,
-    messageContent: JSON.stringify({ messageContent: msg.messageContent })
+    messageContent: JSON.stringify({messageContent: msg.messageContent})
   })
   messageContent.value = ''
   messageBoxRef.value.scrollTop = messageBoxRef.value.scrollHeight
@@ -297,8 +172,8 @@ 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
 
@@ -310,7 +185,7 @@ async function getChatHistory(messageId = '') {
   if (!messageId) {
     nextTick(() => {
       setTimeout(() => {
-        messageBoxRef.value && messageBoxRef.value.scrollTo({ top: msgBottomRef.value.offsetTop })
+        messageBoxRef.value && messageBoxRef.value.scrollTo({top: msgBottomRef.value.offsetTop})
       }, 100)
     })
   }
@@ -321,7 +196,8 @@ async function getChatHistory(messageId = '') {
 function addEventListenerTextarea() {
   nextTick(() => {
     if (inputBoxRef.value) {
-      inputBoxRef.value.removeEventListener('keydown', () => {})
+      inputBoxRef.value.removeEventListener('keydown', () => {
+      })
       inputBoxRef.value.addEventListener('keydown', function (event) {
         if (event.key === 'Enter') {
           event.preventDefault()
@@ -336,7 +212,8 @@ function addEventListenerTextarea() {
 function addEventListenerMessage() {
   nextTick(() => {
     if (messageBoxRef.value) {
-      messageBoxRef.value.removeEventListener('scroll', () => {})
+      messageBoxRef.value.removeEventListener('scroll', () => {
+      })
 
       messageBoxRef.value.addEventListener('scroll', (e) => {
         if (messageBoxRef.value.scrollTop == 0 && receive.value[0]?.id) {
@@ -350,51 +227,32 @@ function addEventListenerMessage() {
 }
 
 // 选择发送图片
-function selectImg(e) {
-  const file = e.target.files[0]
-
-  const { size, type, name } = file
-  const IMIETypes = ['image/jpeg', 'image/png', 'image/gif']
-
-  if (!IMIETypes.includes(type)) {
-    showToast('请上传图片')
-
-    return
-  }
-
-  const maxSize = 1024 * 1024 * 20
-
-  if (size > maxSize) {
-    showFailToast('图片大小不能超过10M')
-    return
-  }
-
-  const formData = new FormData()
-
-  formData.append('uploadFile', file)
-  formData.append('fieldName', 'messageContent')
-  formData.append('asImage', true)
-
-  request(uploadUrl, { method: 'post', body: formData }).then((res) => {
-    const {
-      data: { fileUrl }
-    } = res
-    if (fileUrl) {
-      const msg = {
-        getUserId: getUserId.value,
-        sendUserId: sendUserId.value,
-        specialUserId: '',
-        groupId: groupId.value,
-        messageContent: fileUrl,
-        messageType: 1,
-        noticeType: noticeType.value,
-        object: {
-          id: getLocalId()
-        }
+function selectImg(fileUrl) {
+  try {
+    console.log(fileUrl, 'fileUrl')
+    const msg = {
+      getUserId: getUserId.value,
+      sendUserId: sendUserId.value,
+      specialUserId: '',
+      groupId: groupId.value,
+      messageContent: JSON.stringify({messageContent: fileUrl}),
+      messageType: 1,
+      noticeType: noticeType.value,
+      object: {
+        id: getLocalId()
       }
-      ws.value.send(JSON.stringify(msg))
     }
-  })
+    receive.value.push({
+      ...msg,
+      messageContent: JSON.stringify({messageContent: fileUrl})
+    })
+    messageBoxRef.value.scrollTop = messageBoxRef.value.scrollHeight
+    ws.value.send(JSON.stringify(msg))
+  } catch (e) {
+    console.error(e, '??')
+  } finally {
+
+  }
 }
 
 // 获取我与对方的关注情况
@@ -404,7 +262,7 @@ async function isFollow() {
   const query = {
     userId: curConversiton.value.toUserId
   }
-  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)
 }
@@ -426,7 +284,8 @@ watch(groupId, async () => {
   getChatHistory()
 })
 
-watch(onNewMessage, getUserId, sendUserId, () => {})
+watch(onNewMessage, getUserId, sendUserId, () => {
+})
 
 onMounted(() => {
   // 获取前会话用户的聊天记录
@@ -443,4 +302,11 @@ definePageMeta({
   layout: false
 })
 </script>
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.single-page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  min-height: 0;
+}
+</style>

+ 11 - 1
src/utils/index.js

@@ -50,4 +50,14 @@ const formatNumber = (n) => {
     }
 }
 
-export { setIntervalImmediately, formatImgSrc, formatNumber, isEmptyValue };
+
+const isValidJson = (jsonString) => {
+    try {
+        JSON.parse(jsonString);
+        return true;
+    } catch (e) {
+        return false;
+    }
+}
+
+export { setIntervalImmediately, formatImgSrc, formatNumber, isEmptyValue, isValidJson };