<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 style="font-family: none;">{{ 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: chatGPTHeadImg,
|
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: headPortrait,
|
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()
|
loading.value = false
|
// 如果有查询关键字,则模拟流式输出
|
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>
|