Browse Source

feat: 消息体的展示

qinyuyue 2 months ago
parent
commit
a598e985a2

BIN
src/assets/img/chat/image-error.png


BIN
src/assets/img/chat/image-loading.png


BIN
src/assets/img/chat/link-icon.png


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

@@ -179,11 +179,11 @@ const uploadPictures = () => {
 
 onChange(async (files) => {
   if (!files.length) return
-
+console.log(files[0])
   const formData = new FormData()
   formData.append('uploadFile', files[0])
   formData.append('asImage', true)
-  formData.append('fieldName', 'image')
+  formData.append('fieldName', 'messageContent')
   const maxSize = 10 * 1024 * 1024 // 10 MB
 
   if (files[0].size > maxSize) {

+ 62 - 0
src/pages/chat/chat-message/audio-message/index.vue

@@ -0,0 +1,62 @@
+<script setup>
+const props = defineProps({
+  messageContent: String|Number, // 消息内容
+  viewType: Number, // 0发送 1接收
+});
+
+let videoUrl = ref('')
+let loading = ref(false)
+
+const initMessage = () => {
+  switch (props.viewType) {
+    case 0: // 发送 上传
+      uploadMessage()
+      break;
+    case 1: // 接收
+      videoUrl.value = props.messageContent
+      break;
+  }
+}
+
+const uploadMessage = async () => {
+  try {
+    loading.value = true
+    const formData = new FormData()
+    formData.append('uploadFile', props.messageContent.blob)
+    formData.append('asImage', false)
+    formData.append('fieldName', 'messageContent')
+    const res = await request(`/website/tourMessage/upload`, {
+      method: 'post',
+      body: formData
+    })
+    await handleResponse(res)
+    // videoUrl.value = res.data?.fileUrl ?? '';
+    videoUrl.value = 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg';
+  } catch (e) {
+
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(() => {
+  initMessage()
+})
+
+</script>
+
+<template>
+  <div class="flex flex-row">
+    <div v-if="loading">loading...</div>
+    <template v-if="viewType">
+      <video :src="videoUrl"></video>
+    </template>
+    <template v-else>
+      <video :src="videoUrl"></video>
+    </template>
+  </div>
+</template>
+
+<style scoped lang="scss">
+
+</style>

+ 135 - 0
src/pages/chat/chat-message/image-message/index.vue

@@ -0,0 +1,135 @@
+<script setup>
+const props = defineProps({
+  messageContent: String|Number, // 消息内容
+  viewType: Number, // 0发送 1接收
+});
+
+let imgUrl = ref(null)
+let width = ref(75);
+let height = ref(75);
+let loading = ref(false)
+
+const initMessage = () => {
+  switch (props.viewType) {
+    case 0: // 发送 上传
+      sendMessage()
+      break;
+    case 1: // 接收
+      acceptMessage(props.messageContent)
+      break;
+  }
+}
+
+const resizeImageToMaxSize = (maxSize, w, h) => {
+  let scale = 1;
+  if (w > maxSize || h > maxSize) {
+    if (w > h) {
+      scale = maxSize / w;
+    } else {
+      scale = maxSize / h;
+    }
+  }
+  return {width: scale * w, height: scale * h}
+}
+
+// 处理接收到的消息
+const acceptMessage = async (url) => {
+  try {
+    const img = new Image();
+    img.onload = function() {
+      const size = resizeImageToMaxSize(250, this.width, this.height);
+      width.value = size.width;
+      height.value = size.height
+      imgUrl.value = url;
+    };
+    img.onerror = function() {
+      console.error('图片加载失败');
+    };
+    img.src = url;
+  } catch (e) {
+
+  } finally {
+
+  }
+}
+// 处理发出去的消息
+const sendMessage = async () => {
+  try {
+    loading.value = true
+    const formData = new FormData()
+    formData.append('uploadFile', props.messageContent)
+    formData.append('asImage', true)
+    formData.append('fieldName', 'messageContent')
+    const res = await request(`/website/tourMessage/upload`, {
+      method: 'post',
+      body: formData
+    })
+    await handleResponse(res)
+    const fileUrl = res.data?.fileUrl;
+    // 应该读本地图片,但是没做发送失败的情况,就先这样吧
+    const img = new Image();
+    img.onload = function() {
+      const size = resizeImageToMaxSize(250, this.width, this.height);
+      width.value = size.width;
+      height.value = size.height
+      imgUrl.value = fileUrl;
+    };
+    img.onerror = function() {
+      console.error('图片加载失败');
+    };
+    img.src = fileUrl;
+  } catch (e) {
+
+  } finally {
+    loading.value = false
+  }
+}
+
+const preview = () => {
+  showImagePreview({
+    images: [imgUrl.value]
+  });
+}
+
+onMounted(() => {
+  initMessage()
+})
+
+</script>
+
+<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="absolute w-full h-full left-0 right-0 flex items-center justify-center">
+            <van-loading type="spinner" size="20"/>
+          </div>
+          <img class="w-full h-full" src="../../../../assets/img/chat/image-loading.png" alt="loading"/>
+        </div>
+      </template>
+      <template v-slot:error>
+        <img src="../../../../assets/img/chat/image-error.png" alt="error"/>
+      </template>
+    </van-image>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.image-message {
+  display: flex;
+  //flex-direction: row;
+  position: relative;
+  .message__operate {
+    width: 16px;
+    height: 16px;
+    border-radius: 100%;
+    background: #FF476A;
+    color: #fff;
+    position: absolute;
+    right: 8px;
+  }
+}
+
+</style>

+ 38 - 0
src/pages/chat/chat-message/index.vue

@@ -0,0 +1,38 @@
+<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>
+</template>
+<script setup>
+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";
+
+defineProps({
+  message: Object
+});
+
+</script>
+<style scoped lang="scss">
+.chat-message {
+  //background: red;
+  width: max-content;
+  margin: 20px 0;
+  &.chat-message--accept {
+    //float: left;
+    margin-right: auto;
+  }
+  &.chat-message--send {
+    //float: right;
+    margin-left: auto;
+  }
+}
+</style>

+ 96 - 0
src/pages/chat/chat-message/link-message/index.vue

@@ -0,0 +1,96 @@
+<script setup>
+const props = defineProps({
+  messageContent: String | Number, // 消息内容
+  viewType: Number, // 0发送 1接收
+});
+
+
+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;
+  } catch (e) {
+    console.error(e, 'initMessage')
+  }
+}
+onMounted(() => {
+  initMessage()
+})
+</script>
+
+<template>
+  <div class="link-message" :class="viewType ? 'link-message--accept' : 'link-message--send'">
+    <div class="link-message__content">
+      <div>
+        {{ messageContent }}
+      </div>
+      <template v-if="linkInfo">
+        <NuxtLink :to="linkInfo.url" target="_blank" class="bg-[#FFF] p-12 rounded-[4px] mt-12 flex flex-row">
+          <div class="flex-1 min-w-0 mr-10">
+            <div class="text-black-3 text-base text-ellipsis text-nowrap overflow-hidden ...">
+              {{linkInfo.mainParts}}
+            </div>
+            <div class="text-black-9 text-sm text-ellipsis text-nowrap overflow-hidden ...">
+              {{linkInfo.hostname}}
+            </div>
+          </div>
+          <div class="ml-auto w-40 h-40 rounded-2 bg-[#F1F1F1] grid place-items-center">
+            <img src="../../../../assets/img/chat/link-icon.png" height="24" width="25" alt=""/>
+          </div>
+        </NuxtLink>
+      </template>
+    </div>
+    <div class="bg-[#F3F3F3] rounded-[25px] px-12 py-4 text-black-9 mt-4 text-sm w-max grid place-items-center">转自 第三方链接</div>
+  </div>
+</template>
+
+<style scoped lang="scss">
+.link-message {
+  .link-message__content {
+    max-width: 250px;
+    padding: 12px;
+    box-sizing: border-box;
+    background: #F3F3F3;
+    color: #000000;
+    color: rgba(0, 0, 0, 0.9);
+    font-size: 14px;
+    word-wrap: break-word;
+    word-break: break-all;
+  }
+
+  &.link-message--accept {
+    .link-message__content {
+      background: #F3F3F3;
+      border-radius: 0 12px 12px 12px;
+    }
+  }
+
+  &.link-message--send {
+    .link-message__content {
+      background: #FEF4E6;
+      border-radius: 12px 0 12px 12px;
+    }
+  }
+
+
+}
+</style>

+ 44 - 0
src/pages/chat/chat-message/text-message/index.vue

@@ -0,0 +1,44 @@
+<script setup>
+const props = defineProps({
+  messageContent: String, // 消息内容
+  viewType: Number, // 0发送 1接收
+});
+
+
+let textString = ref('')
+let loading = ref(false)
+const initMessage = () => {
+  textString.value = props.messageContent
+}
+
+initMessage()
+</script>
+
+<template>
+  <div class="text-message" :class="viewType ? 'text-message--accept' : 'text-message--send'">
+    {{ textString }}
+  </div>
+</template>
+
+<style scoped lang="scss">
+.text-message {
+  max-width: 250px;
+  padding: 12px;
+  box-sizing: border-box;
+  background: #F3F3F3;
+  color: #000000;
+  color: rgba(0, 0, 0, 0.9);
+  font-size: 14px;
+  word-wrap: break-word;
+  word-break: break-all;
+  &.text-message--accept {
+    background: #F3F3F3;
+    border-radius: 0 12px 12px 12px;
+  }
+
+  &.text-message--send {
+    background: #FEF4E6;
+    border-radius: 12px 0 12px 12px;
+  }
+}
+</style>

+ 84 - 8
src/pages/test/index.vue

@@ -1,11 +1,21 @@
 <template>
   <div class="test-page flex flex-col" oncontextmenu="return false;">
-    <div>
-
+    <div class="flex flex-col">
+      <template v-for="(message, i) in messageList" :key="i">
+        <ChatMessage :message="message"></ChatMessage>
+      </template>
     </div>
 
     <div class="flex mt-auto py-20 flex-col bg-[#999]">
-      <template v-if="inRecording">
+      <van-uploader
+          :preview-image="false"
+          :after-read="upLoadImg"
+          accept="image/*"
+          :multiple="false"
+          :max-count="1"
+      ><img src="~/assets/img/scan/pic.png" height="32" width="32" alt=""/>
+      </van-uploader>
+<!--      <template v-if="inRecording">
         <div class="flex flex-row px-20 justify-around">
           <van-button @click="cancelRecording">取消</van-button>
           <van-button @click="handleStopRecording" type="warning">完成并发送</van-button>
@@ -17,19 +27,60 @@
       </template>
       <template v-else>
         <van-button type="primary" @click="handleTouchstart">点击说话</van-button>
-      </template>
-
+      </template>-->
     </div>
   </div>
 </template>
 <script setup>
+import ChatMessage from '../chat/chat-message'
+import {useRecording} from "~/pages/test/useRecording";
 // import VConsole from 'vconsole';
 // new VConsole({ theme: 'dark' });
 
-import {useRecording} from "~/pages/test/useRecording";
+let messageList = ref([
+  {
+    type: 'link',
+    messageContent: '大概更.baidu.com',
+    viewType: 0
+  },
+  {
+    type: 'link',
+    messageContent: '大概更多如果更多人的人人待https://fanyi.baidu.com',
+    viewType: 0
+  },
+  {
+    type: 'link',
+    messageContent: 'rdrdgdrwrhttps://fastly.jsdelivr.net/npm/@vant/assets/logo.png',
+    viewType: 1
+  },
+  {
+    type: 'text',
+    messageContent: '发送文字消息发送文字消息发送文字消息发送文字消息发送文字消息发送文字消息发送文字消息',
+    viewType: 0
+  },
+  {
+    type: 'text',
+    messageContent: '发送文字消息发送文字消息发送文字消息发送文字消息发送文字消息发送文字消息发送文字消息',
+    viewType: 1
+  },
+  {
+    type: 'image',
+    messageContent: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
+    viewType: 0
+  },
+  {
+    type: 'image',
+    messageContent: null,
+    viewType: 1
+  },
+  {
+    type: 'image',
+    messageContent: 'https://fastly.jsdelivr.net/npm/@vant/assets/logo.png',
+    viewType: 1
+  }
+])
 
 const {inRecording, startRecording, stopRecording, cancelRecording} = useRecording()
-
 const handleTouchstart = async () => {
   try {
     await startRecording()
@@ -39,6 +90,7 @@ const handleTouchstart = async () => {
 
   }
 }
+
 const handleStopRecording = async () => {
   try {
     const {success, errorMessage, audio} = await stopRecording()
@@ -47,13 +99,35 @@ const handleStopRecording = async () => {
       return
     }
     console.log(audio, '---audio---')
-  } catch (e) {
+    const formData = new FormData()
+    formData.append('uploadFile', audio.blob)
+    formData.append('asImage', false)
+    formData.append('fieldName', 'messageContent')
+    const res = await request(`/website/tourMessage/upload`, {
+      method: 'post',
+      body: formData
+    })
+    console.log(res, 'resresres')
 
+  } catch (e) {
+    console.log(e, 'handleStopRecording')
   } finally {
 
   }
 }
 
+const upLoadImg = async (e, b) => {
+  try {
+    console.log(e.file)
+    messageList.value.push({
+      type: 'image',
+      messageContent: e.file,
+      viewType: 0
+    })
+  } catch (e) {
+
+  }
+}
 onMounted(() => {
   /*  document.addEventListener('contextmenu', function(e) {
       e.preventDefault();
@@ -68,5 +142,7 @@ onMounted(() => {
 .test-page {
   height: calc(100vh - 50px);
   width: 100%;
+  padding: 20px;
+  box-sizing: border-box;
 }
 </style>

+ 1 - 1
src/pages/test/useRecording.js

@@ -166,7 +166,7 @@ export const useRecording = () => {
   }
 
   const closeRecording = () => {
-    recorderInstance.close()
+    recorderInstance && recorderInstance.close()
   }
 
   onUnmounted(() => {