yaowanxin
2025-08-12 a0dd2bd1be4e97a93443a48b86c719930d0a268a
src/views/chatHome/chatHomeIndex/MobileChat.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,461 @@
<template>
  <div class="mobile-chat-wrapper" style="height: 91vh;">
    <div class="chat-history">
      <div class="chat-content" ref="chatContent">
        <div class="chat-wrapper" v-for="(item, index) in chatList" :key="index">
          <div class="chat-friend" v-if="item.uid !== '1001'">
            <div class="info-time">
              <img :src="item.headImg" alt="" />
              <span>{{ item.name }}</span>
              <span>{{ item.time }}</span>
            </div>
            <div class="chat-text" v-if="item.chatType == 0">
              <template v-if="isSend && index === chatList.length - 1">
                <span class="flash_cursor"></span>
              </template>
              <template v-else>
                <pre>{{ item.msg }}</pre>
              </template>
            </div>
            <div class="chat-img" v-if="item.chatType == 1">
              <img :src="item.msg" alt="表情" v-if="item.extend.imgType == 1" style="width: 100px; height: 100px" />
              <el-image :src="item.msg" :preview-src-list="srcImgList" v-else> </el-image>
            </div>
            <div class="chat-img" v-if="item.chatType == 2">
              <div class="word-file">
                <FileCard :fileType="item.extend.fileType" :file="item.msg"></FileCard>
              </div>
            </div>
          </div>
          <div class="chat-me" v-else>
            <div class="info-time">
              <span>{{ item.name }}</span>
              <span>{{ item.time }}</span>
              <img :src="item.headImg" alt="" />
            </div>
            <div class="chat-text" v-if="item.chatType == 0">
              {{ item.msg }}
            </div>
            <div class="chat-img" v-if="item.chatType == 1">
              <img :src="item.msg" alt="表情" v-if="item.extend.imgType == 1" style="width: 100px; height: 100px" />
              <el-image style="max-width: 300px; border-radius: 10px" :src="item.msg" :preview-src-list="srcImgList" v-else> </el-image>
            </div>
            <div class="chat-img" v-if="item.chatType == 2">
              <div class="word-file">
                <FileCard :fileType="item.extend.fileType" :file="item.msg"></FileCard>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="chat-input-wrapper">
      <div style="display: flex; align-items: center">
          <input v-model="inputMsg" @change="sendText" :disabled="loading" class="input-text" autofocus placeholder="给小智发送消息" />
          <img class="send-icon" src="@/assets/img/emoji/rocket.png" alt="" @click="sendText" />
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick,onActivated } from 'vue'
import { useRoute } from 'vue-router'
import { animation } from '@/utils/util'
import chatGPTHeadImg from '@/assets/img/head_portrait1.png'
import headPortrait from '@/assets/img/head_portrait.jpg'
import FileCard from '@/components/FileCard.vue'
import { ElMessage } from "element-plus"
import {checking} from './ai-wd.js'
// å®šä¹‰å“åº”式数据
const route = useRoute()
const chatContent = ref(null)
const ws = ref(null)
const chatList = ref([
  {
    headImg: chatGPTHeadImg,
    name: '小智',
    time: new Date().toLocaleTimeString(),
    msg: ' å°æ™ºä¸ºæ‚¨æœåŠ¡',
    chatType: 0,
    uid: '1002'
  }
])
const inputMsg = ref('')
const isSend = ref(false)
const fileList = ref([])
const isProcessing = ref(false)
const loading = ref(true)
const srcImgList = ref([])
// åˆ é™¤å›¾ç‰‡
const deleteImg = (index) => {
  if (index >= 0 && index < fileList.value.length) {
    fileList.value.splice(index, 1)
  }
}
// WebSocket消息接收
const websocketonmessage = (e) => {
  const redata = JSON.parse(e.data)
  //数据接收
  let chatGPT = {
    headImg: headPortrait,
    name: 'DeepSeek',
    time: new Date().toLocaleTimeString(),
    msg: redata[0].text,
    chatType: 0, //信息类型,0文字,1图片
    uid: '1002' //uid
  }
  sendMsg(chatGPT)
  isSend.value = false
}
// WebSocket发送消息
const websocketsend = (Data) => {
  console.log("即将发送消息", Data)
  if (ws.value && ws.value.readyState === WebSocket.OPEN) {
    console.log("发送消息", ws.value)
    console.log("发送消息", Data)
    let fileUrls = fileList.value.map(item => item.file.fileUrl)
    //数据发送
    ws.value.send(Data + ":" + fileUrls.join(","))
    fileList.value = []
    inputMsg.value = ''
  }
}
// å‘送文本消息
const sendText = () => {
  if (inputMsg.value) {
    let chatMsg = {
      headImg: headPortrait,
      name: '卧龙',
      time: new Date().toLocaleTimeString(),
      msg: inputMsg.value,
      chatType: 0, //信息类型,0文字,1图片
      uid: '1001' //uid
    }
    chatList.value.push(chatMsg)
    let chatGPT = {
      headImg: headPortrait,
      name: '小智',
      time: new Date().toLocaleTimeString(),
      msg: "",
      chatType: 0, //信息类型,0文字,1图片
      uid: '1002' //uid
    }
    chatList.value.push(chatGPT) // å°†æŽ¥æ”¶åˆ°çš„æ¶ˆæ¯å­˜å‚¨åˆ° messages æ•°ç»„
    simulateStreamingOutput(chatGPT, inputMsg.value)
    inputMsg.value = ''
  } else {
    ElMessage({
      message: '消息不能为空哦~',
      type: 'warning'
    })
  }
}
// å‘送信息
const sendMsg = (msgList) => {
  chatList.value.push(msgList)
  scrollBottom()
}
// èŽ·å–çª—å£é«˜åº¦å¹¶æ»šåŠ¨è‡³æœ€åº•å±‚
const scrollBottom = () => {
  nextTick(() => {
    const scrollDom = chatContent.value
    animation(scrollDom, scrollDom.scrollHeight - scrollDom.offsetHeight)
  })
}
// ç»„件挂载时执行
onActivated(() => {
  chatList.value = []
  chatList.value.push({
    headImg: chatGPTHeadImg,
    name: '小智',
    time: new Date().toLocaleTimeString(),
    msg: '小智为您服务',
    chatType: 0,
    uid: '1002'
  })
  chatList.value.push({
    headImg: chatGPTHeadImg,
    name: '卧龙',
    time: new Date().toLocaleTimeString(),
    msg: route.query.keyWord,
    chatType: 0,
    uid: '1001'
  })
  // æ·»åŠ ä¸€ä¸ªç©ºçš„å›žå¤æ¶ˆæ¯å ä½
  const replyMsg = {
    headImg: chatGPTHeadImg,
    name: '小智',
    time: new Date().toLocaleTimeString(),
    msg: '',
    chatType: 0,
    uid: '1002'
  }
  chatList.value.push(replyMsg)
  scrollBottom()
  // å¦‚果有查询关键字,则模拟流式输出
  if (route.query.keyWord) {
    simulateStreamingOutput(replyMsg, route.query.keyWord)
  }
})
// æ¨¡æ‹Ÿæµå¼è¾“出
const simulateStreamingOutput = async (msgObj, keyWord) => {
  loading.value = true
  // ç”Ÿæˆ0.8-1.3秒之间的随机延迟
  const delay = Math.random() * 500 + 800
  // æ¨¡æ‹Ÿå›žå¤å†…容(实际应用中应从API获取)
  const responseText = `关于"${keyWord}"的问题,我来为您解答:\n` + checking(keyWord)
  isSend.value = true
  let index = 0
  setTimeout(() => {
    const interval = setInterval(() => {
      isSend.value = true
      if (index < responseText.length) {
        msgObj.msg += responseText.charAt(index)
        index++
        isSend.value = false
        scrollBottom()
      } else {
        clearInterval(interval)
        isSend.value = false
        loading.value = false
      }
    }, 50) // æ¯50ms输出一个字符,模拟流式效果
  }, delay)
}
</script>
<style lang="scss" scoped>
.mobile-chat-wrapper {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 91vh;
  position: relative;
  background-color: white;
  .chat-history {
    flex: 1 1 0;
    overflow-y: auto;
  }
  .chat-input-wrapper {
    padding: 8px 16px 8px 8px;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
      .file-tt{
          flex-direction: column;
          width: 200px;
          display: flex;
          padding: 5px;
          border-radius: 5px;
          margin-right: 5px;
          background: #cacaca;
          .file-item{
              width: 200px;
              overflow:hidden;
              word-wrap: break-word;
              text-overflow:ellipsis;
              display:-webkit-box;
              -webkit-box-orient:vertical;
              -webkit-line-clamp:2;
          }
      }
    .send-icon {
      height: 40px;
      margin-left: 16px;
    }
      .input-text{
          font-size: 18px;
          width: 100%;
          border-radius: 20px;
          height: 80px;
          padding-left: 10px;
          //padding-top: 10px;
          border: none;
          color: black;                 /* ä¿®æ”¹æ–‡æœ¬é¢œè‰²ä¸ºç™½è‰² */
          background-color: #f5f4f4;   /* ä¿®æ”¹èƒŒæ™¯é¢œè‰²ä¸ºæ·±ç°è‰² */
      }
  }
  .chat-content {
    width: 100%;
    height: 80%;
    overflow-y: scroll;
    padding: 20px;
    box-sizing: border-box;
    &::-webkit-scrollbar {
      width: 0;
      /* Safari,Chrome éšè—æ»šåŠ¨æ¡ */
      height: 0;
      /* Safari,Chrome éšè—æ»šåŠ¨æ¡ */
      display: none;
      /* ç§»åŠ¨ç«¯ã€pad ä¸ŠSafari,Chrome,隐藏滚动条 */
    }
    .chat-wrapper {
      position: relative;
      word-break: break-all;
      .chat-friend {
        width: 100%;
        float: left;
        margin-bottom: 20px;
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
        align-items: flex-start;
        .chat-text {
          max-width: 90%;
          padding: 20px;
          border-radius: 20px 20px 20px 5px;
          background-color: rgb(245, 248, 248);
          color: black;
          &:hover {
            background-color: rgb(232, 232, 232);
          }
          pre {
            white-space: break-spaces;
          }
        }
        .chat-img {
          img {
            width: 100px;
            height: 100px;
          }
        }
        .info-time {
          margin: 10px 0;
          color: black;
          font-size: 14px;
          img {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            vertical-align: middle;
            margin-right: 10px;
          }
          span:last-child {
            color: rgb(101, 104, 115);
            margin-left: 10px;
            vertical-align: middle;
          }
        }
      }
      .chat-me {
        width: 100%;
        float: right;
        margin-bottom: 20px;
        position: relative;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        align-items: flex-end;
        .chat-text {
          float: right;
          max-width: 90%;
          padding: 20px;
          border-radius: 20px 20px 5px 20px;
          background-color: rgb(29, 144, 245);
          color: #fff;
          &:hover {
            background-color: rgb(26, 129, 219);
          }
        }
        .chat-img {
          img {
            max-width: 300px;
            max-height: 200px;
            border-radius: 10px;
          }
        }
        .info-time {
          margin: 10px 0;
          color: black;
          font-size: 14px;
          display: flex;
          justify-content: flex-end;
          img {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            vertical-align: middle;
            margin-left: 10px;
          }
          span {
            line-height: 30px;
          }
          span:first-child {
            color: rgb(101, 104, 115);
            margin-right: 10px;
            vertical-align: middle;
          }
        }
      }
    }
  }
  .flash_cursor {
    width: 20px;
    height: 30px;
    display: inline-block;
    background: #d6e3f5;
    opacity: 1;
    animation: glow 800ms ease-out infinite alternate;
  }
  @keyframes glow {
    0% {
      opacity: 1;
    }
    25% {
      opacity: 0.5;
    }
    50% {
      opacity: 0;
    }
    75% {
      opacity: 0.5;
    }
    100% {
      opacity: 1;
    }
  }
}
</style>