|
|
|
@ -1,6 +1,6 @@ |
|
|
|
<template> |
|
|
|
<view class="deepMate-page"> |
|
|
|
<!-- 顶部导航栏 --> |
|
|
|
<!-- 顶部导航栏 - 固定定位 --> |
|
|
|
<view class="header" :style="{ paddingTop: safeAreaInsets?.top + 'px' }"> |
|
|
|
<view class="header-left"> |
|
|
|
<image |
|
|
|
@ -29,6 +29,9 @@ |
|
|
|
|
|
|
|
<!-- 主要内容区域 --> |
|
|
|
<view class="main-content"> |
|
|
|
<!-- 顶部固定区域占位符 --> |
|
|
|
<view class="banner-placeholder"></view> |
|
|
|
|
|
|
|
<view class="banner-panel" |
|
|
|
:class="messages.length === 0 ? '' : 'panelShow'" |
|
|
|
> |
|
|
|
@ -102,37 +105,40 @@ |
|
|
|
|
|
|
|
<!-- 聊天区域 --> |
|
|
|
<view class="chat-container" v-if="messages.length > 0"> |
|
|
|
<view class="message-list" id="messageList"> |
|
|
|
<view |
|
|
|
v-for="(message, index) in messages" |
|
|
|
:key="index" |
|
|
|
:class=" |
|
|
|
message.isUser ? 'message user-message' : 'message bot-message' |
|
|
|
" |
|
|
|
> |
|
|
|
<!-- 会话图标 --> |
|
|
|
<text |
|
|
|
<!-- 给聊天容器添加滚动引用 --> |
|
|
|
<scroll-view class="chat-scroll-view" scroll-y="true" :scroll-top="scrollTop"> |
|
|
|
<view class="message-list" id="messageList"> |
|
|
|
<view |
|
|
|
v-for="(message, index) in messages" |
|
|
|
:key="index" |
|
|
|
:class=" |
|
|
|
message.isUser |
|
|
|
? 'fa-solid fa-user message-icon' |
|
|
|
: 'fa-solid fa-robot message-icon' |
|
|
|
message.isUser ? 'message user-message' : 'message bot-message' |
|
|
|
" |
|
|
|
></text> |
|
|
|
<!-- 会话内容 --> |
|
|
|
<view class="message-content"> |
|
|
|
<text class="message-text">{{ message.content }}</text> |
|
|
|
<!-- loading --> |
|
|
|
<view |
|
|
|
class="loading-dots" |
|
|
|
v-if="message.isThinking " |
|
|
|
> |
|
|
|
<text class="dot"></text> |
|
|
|
<text class="dot"></text> |
|
|
|
<text class="dot"></text> |
|
|
|
> |
|
|
|
<!-- 会话图标 --> |
|
|
|
<text |
|
|
|
:class=" |
|
|
|
message.isUser |
|
|
|
? 'fa-solid fa-user message-icon' |
|
|
|
: 'fa-solid fa-robot message-icon' |
|
|
|
" |
|
|
|
></text> |
|
|
|
<!-- 会话内容 --> |
|
|
|
<view class="message-content"> |
|
|
|
<text class="message-text">{{ message.content }}</text> |
|
|
|
<!-- loading --> |
|
|
|
<view |
|
|
|
class="loading-dots" |
|
|
|
v-if="message.isThinking " |
|
|
|
> |
|
|
|
<text class="dot"></text> |
|
|
|
<text class="dot"></text> |
|
|
|
<text class="dot"></text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</scroll-view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
@ -171,7 +177,7 @@ |
|
|
|
<script setup> |
|
|
|
const { safeAreaInsets } = uni.getSystemInfoSync(); |
|
|
|
|
|
|
|
import { ref, onMounted, nextTick } from "vue"; |
|
|
|
import { ref, onMounted, nextTick, watch } from "vue"; |
|
|
|
import footerBar from "../../components/footerBar-cn.vue"; |
|
|
|
|
|
|
|
|
|
|
|
@ -180,6 +186,7 @@ const inputMessage = ref(""); |
|
|
|
const isSending = ref(false); |
|
|
|
const uuid = ref(""); |
|
|
|
const messages = ref([]); |
|
|
|
const scrollTop = ref(0); // 用于控制scroll-view的滚动位置 |
|
|
|
const hotTopics = ref([ |
|
|
|
{ |
|
|
|
id: 1, |
|
|
|
@ -208,12 +215,20 @@ onMounted(() => { |
|
|
|
initUUID(); |
|
|
|
// 如果有历史消息,滚动到底部 |
|
|
|
if (messages.value.length > 0) { |
|
|
|
nextTick(() => { |
|
|
|
setTimeout(() => { |
|
|
|
scrollToBottom(); |
|
|
|
}); |
|
|
|
}, 200); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 监听消息变化,当有新消息时自动滚动到最新消息 |
|
|
|
watch(messages, (newMessages) => { |
|
|
|
// 延迟执行滚动,确保DOM更新完成 |
|
|
|
setTimeout(() => { |
|
|
|
scrollToBottom(); |
|
|
|
}, 100); |
|
|
|
}, { deep: true }); |
|
|
|
|
|
|
|
// 初始化 UUID |
|
|
|
const initUUID = () => { |
|
|
|
let storedUUID = uni.getStorageSync("user_uuid"); |
|
|
|
@ -262,9 +277,9 @@ const sendMessage = () => { |
|
|
|
inputMessage.value = ""; |
|
|
|
|
|
|
|
// 滚动到底部 |
|
|
|
nextTick(() => { |
|
|
|
setTimeout(() => { |
|
|
|
scrollToBottom(); |
|
|
|
}); |
|
|
|
}, 100); |
|
|
|
|
|
|
|
// 模拟机器人回复 |
|
|
|
simulateBotResponse(userMessage.content); |
|
|
|
@ -285,9 +300,9 @@ const sendMessageList = (listMessage) => { |
|
|
|
inputMessage.value = ""; |
|
|
|
|
|
|
|
// 滚动到底部 |
|
|
|
nextTick(() => { |
|
|
|
setTimeout(() => { |
|
|
|
scrollToBottom(); |
|
|
|
}); |
|
|
|
}, 100); |
|
|
|
|
|
|
|
// 模拟机器人回复 |
|
|
|
simulateBotResponse(userMessage.content); |
|
|
|
@ -308,9 +323,9 @@ const simulateBotResponse = (userMessage) => { |
|
|
|
messages.value.push(botMsg); |
|
|
|
|
|
|
|
// 滚动到底部 |
|
|
|
nextTick(() => { |
|
|
|
setTimeout(() => { |
|
|
|
scrollToBottom(); |
|
|
|
}); |
|
|
|
}, 100); |
|
|
|
|
|
|
|
// 模拟流式响应 |
|
|
|
let responseText = `我已经收到您的消息: "${userMessage}"。作为您的股市顾问,我可以为您提供专业的投资建议。请问您想了解哪方面的信息?`; |
|
|
|
@ -322,15 +337,19 @@ const simulateBotResponse = (userMessage) => { |
|
|
|
messages.value[messages.value.length - 1].content = responseText.substring(0, index + 1); |
|
|
|
index++; |
|
|
|
|
|
|
|
// 滚动到底部 |
|
|
|
nextTick(() => { |
|
|
|
// 滚动到底部,更频繁地触发滚动以适应文本增长 |
|
|
|
setTimeout(() => { |
|
|
|
scrollToBottom(); |
|
|
|
}); |
|
|
|
}, 20); |
|
|
|
|
|
|
|
setTimeout(typeWriter, 30); |
|
|
|
} else { |
|
|
|
messages.value[messages.value.length - 1].isTyping = false; |
|
|
|
isSending.value = false; |
|
|
|
// 最后确保滚动到底部 |
|
|
|
setTimeout(() => { |
|
|
|
scrollToBottom(); |
|
|
|
}, 100); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
@ -339,21 +358,21 @@ const simulateBotResponse = (userMessage) => { |
|
|
|
|
|
|
|
// 滚动到底部 |
|
|
|
const scrollToBottom = () => { |
|
|
|
// 使用scroll-view的scrollTop属性来控制滚动 |
|
|
|
const query = uni.createSelectorQuery(); |
|
|
|
query.select("#messageList").boundingClientRect(); |
|
|
|
query.selectViewport().scrollOffset(); |
|
|
|
|
|
|
|
query.exec((res) => { |
|
|
|
if (res[0] && res[1]) { |
|
|
|
uni.pageScrollTo({ |
|
|
|
scrollTop: res[0].height, |
|
|
|
duration: 100, |
|
|
|
}); |
|
|
|
if (res[0]) { |
|
|
|
// 设置scrollTop为消息列表的高度,实现滚动到底部 |
|
|
|
scrollTop.value = res[0].height; |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
const scrollToTop = () => { |
|
|
|
uni.pageScrollTo({ scrollTop: 0, duration: 200 }); |
|
|
|
// 滚动到顶部 |
|
|
|
scrollTop.value = 0; |
|
|
|
}; |
|
|
|
</script> |
|
|
|
|
|
|
|
@ -364,16 +383,33 @@ const scrollToTop = () => { |
|
|
|
height: 100vh; |
|
|
|
background-color: #ffffff; |
|
|
|
padding: 20rpx 0rpx; |
|
|
|
|
|
|
|
position: relative; |
|
|
|
overflow: hidden; /* 禁止页面整体滚动 */ |
|
|
|
} |
|
|
|
|
|
|
|
/* 顶部导航栏 - 固定定位 */ |
|
|
|
.header { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
padding: 20rpx 30rpx; |
|
|
|
background-color: #ffffff; |
|
|
|
position: fixed; |
|
|
|
top: 0; |
|
|
|
left: 0; |
|
|
|
right: 0; |
|
|
|
z-index: 999; |
|
|
|
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1); |
|
|
|
} |
|
|
|
|
|
|
|
/* 顶部固定区域占位符 */ |
|
|
|
.header-placeholder { |
|
|
|
height: 120rpx; /* 与header高度一致 */ |
|
|
|
} |
|
|
|
|
|
|
|
/* 顶部固定区域占位符 */ |
|
|
|
.banner-placeholder { |
|
|
|
height: 80rpx; /* 与header高度一致,防止内容被固定头部遮挡 */ |
|
|
|
} |
|
|
|
|
|
|
|
.header-left, |
|
|
|
@ -410,9 +446,9 @@ const scrollToTop = () => { |
|
|
|
.main-content { |
|
|
|
flex: 1; |
|
|
|
padding: 20rpx; |
|
|
|
overflow-y: auto; |
|
|
|
overflow-y: hidden; /* 禁止主内容区域滚动 */ |
|
|
|
margin-top: 20rpx; |
|
|
|
margin-bottom: 120rpx; |
|
|
|
margin-bottom: 250rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.robot-container { |
|
|
|
@ -578,8 +614,14 @@ const scrollToTop = () => { |
|
|
|
/* overflow-y: auto; */ |
|
|
|
} |
|
|
|
|
|
|
|
.chat-scroll-view { |
|
|
|
height: calc(80vh - 250rpx); /* 根据需要调整高度 */ |
|
|
|
margin-top: 180rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.message-list { |
|
|
|
/* padding: 20rpx; */ |
|
|
|
/* margin-top: 200rpx; */ |
|
|
|
} |
|
|
|
|
|
|
|
.message { |
|
|
|
@ -685,11 +727,12 @@ const scrollToTop = () => { |
|
|
|
|
|
|
|
.input-area { |
|
|
|
position: fixed; |
|
|
|
bottom: 0; |
|
|
|
bottom: 70rpx; |
|
|
|
left: 0; |
|
|
|
right: 0; |
|
|
|
padding: 0 40rpx 80rpx 40rpx; |
|
|
|
background-color: #ffffff; |
|
|
|
z-index: 999; |
|
|
|
} |
|
|
|
|
|
|
|
.input-wrapper { |
|
|
|
@ -762,6 +805,10 @@ const scrollToTop = () => { |
|
|
|
} |
|
|
|
.panelShow{ |
|
|
|
height: 12%; |
|
|
|
position: fixed; |
|
|
|
top: 70rpx; |
|
|
|
z-index: 999; |
|
|
|
width: 95%; |
|
|
|
} |
|
|
|
|
|
|
|
.pray-banner { |
|
|
|
@ -799,5 +846,10 @@ const scrollToTop = () => { |
|
|
|
.static-footer { |
|
|
|
position: fixed; |
|
|
|
bottom: 0; |
|
|
|
z-index: 999; |
|
|
|
} |
|
|
|
/* 顶部固定区域占位符 */ |
|
|
|
.banner-placeholder { |
|
|
|
height: 60rpx; /* 与header高度一致,防止内容被固定头部遮挡 */ |
|
|
|
} |
|
|
|
</style> |