|
@ -364,6 +364,22 @@ const showCount = () => { |
|
|
const tabContent = ref(null); |
|
|
const tabContent = ref(null); |
|
|
const isScrolling = ref(false); //判断用户是否在滚动 |
|
|
const isScrolling = ref(false); //判断用户是否在滚动 |
|
|
|
|
|
|
|
|
|
|
|
// AiEmotion页面高度监听器相关变量 |
|
|
|
|
|
const aiEmotionHeightObserver = ref(null); |
|
|
|
|
|
const isAiEmotionAutoScrollEnabled = ref(false); |
|
|
|
|
|
const isAiEmotionUserScrolling = ref(false); // 用户是否正在手动滚动 |
|
|
|
|
|
const aiEmotionScrollTimer = ref(null); // 滚动检测定时器 |
|
|
|
|
|
|
|
|
|
|
|
// 获取当前活动页面的滚动容器 |
|
|
|
|
|
const getCurrentScrollContainer = () => { |
|
|
|
|
|
if (activeTab.value === "AIchat") { |
|
|
|
|
|
return tabContentAIchat.value; |
|
|
|
|
|
} else if (activeTab.value === "AiEmotion") { |
|
|
|
|
|
return tabContentAiEmotion.value; |
|
|
|
|
|
} |
|
|
|
|
|
return null; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const smoothScrollToBottom = async () => { |
|
|
const smoothScrollToBottom = async () => { |
|
|
// console.log("调用滚动到底部的方法"); |
|
|
// console.log("调用滚动到底部的方法"); |
|
|
// await nextTick(); |
|
|
// await nextTick(); |
|
@ -387,6 +403,165 @@ const throttledSmoothScrollToBottom = _.throttle(smoothScrollToBottom, 300, { |
|
|
trailing: false, |
|
|
trailing: false, |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// AiEmotion页面自动滚动到底部的防抖函数 |
|
|
|
|
|
const debouncedAiEmotionScrollToBottom = _.debounce(() => { |
|
|
|
|
|
if ( |
|
|
|
|
|
activeTab.value === "AiEmotion" && |
|
|
|
|
|
isAiEmotionAutoScrollEnabled.value && |
|
|
|
|
|
!isAiEmotionUserScrolling.value |
|
|
|
|
|
) { |
|
|
|
|
|
const container = tabContentAiEmotion.value; |
|
|
|
|
|
if (container) { |
|
|
|
|
|
container.scrollTop = container.scrollHeight - container.offsetHeight; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, 150); |
|
|
|
|
|
|
|
|
|
|
|
// 启动AiEmotion页面高度监听器 |
|
|
|
|
|
const startAiEmotionHeightObserver = () => { |
|
|
|
|
|
// 先停止之前的监听器 |
|
|
|
|
|
stopAiEmotionHeightObserver(); |
|
|
|
|
|
|
|
|
|
|
|
isAiEmotionAutoScrollEnabled.value = true; |
|
|
|
|
|
|
|
|
|
|
|
// 创建ResizeObserver监听页面内容变化 |
|
|
|
|
|
aiEmotionHeightObserver.value = new ResizeObserver((entries) => { |
|
|
|
|
|
if (isAiEmotionAutoScrollEnabled.value && activeTab.value === "AiEmotion") { |
|
|
|
|
|
debouncedAiEmotionScrollToBottom(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 监听document.body的尺寸变化 |
|
|
|
|
|
if (document.body) { |
|
|
|
|
|
aiEmotionHeightObserver.value.observe(document.body); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建MutationObserver监听DOM结构变化 |
|
|
|
|
|
const mutationObserver = new MutationObserver((mutations) => { |
|
|
|
|
|
let shouldScroll = false; |
|
|
|
|
|
mutations.forEach((mutation) => { |
|
|
|
|
|
if (mutation.type === "childList" && mutation.addedNodes.length > 0) { |
|
|
|
|
|
// 检查新增的节点是否包含实际内容 |
|
|
|
|
|
const hasContent = Array.from(mutation.addedNodes).some((node) => { |
|
|
|
|
|
if (node.nodeType === Node.ELEMENT_NODE) { |
|
|
|
|
|
return node.offsetHeight > 0 || node.scrollHeight > 0; |
|
|
|
|
|
} |
|
|
|
|
|
return ( |
|
|
|
|
|
node.nodeType === Node.TEXT_NODE && |
|
|
|
|
|
node.textContent.trim().length > 0 |
|
|
|
|
|
); |
|
|
|
|
|
}); |
|
|
|
|
|
if (hasContent) { |
|
|
|
|
|
shouldScroll = true; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
shouldScroll && |
|
|
|
|
|
isAiEmotionAutoScrollEnabled.value && |
|
|
|
|
|
activeTab.value === "AiEmotion" |
|
|
|
|
|
) { |
|
|
|
|
|
debouncedAiEmotionScrollToBottom(); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 监听AiEmotion页面的主要内容区域的DOM变化 |
|
|
|
|
|
const aiEmotionContainer = tabContentAiEmotion.value; |
|
|
|
|
|
if (aiEmotionContainer) { |
|
|
|
|
|
mutationObserver.observe(aiEmotionContainer, { |
|
|
|
|
|
childList: true, |
|
|
|
|
|
subtree: true, |
|
|
|
|
|
attributes: false, |
|
|
|
|
|
characterData: true, |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 保存mutationObserver引用以便清理 |
|
|
|
|
|
aiEmotionHeightObserver.value.mutationObserver = mutationObserver; |
|
|
|
|
|
|
|
|
|
|
|
// 为AiEmotion页面的滚动容器添加滚动事件监听器 |
|
|
|
|
|
if (aiEmotionContainer) { |
|
|
|
|
|
aiEmotionContainer.addEventListener("scroll", handleAiEmotionUserScroll, { |
|
|
|
|
|
passive: true, |
|
|
|
|
|
}); |
|
|
|
|
|
// 保存滚动事件监听器引用以便清理 |
|
|
|
|
|
aiEmotionHeightObserver.value.scrollListener = handleAiEmotionUserScroll; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log("AiEmotion页面高度监听器已启动"); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// AiEmotion页面用户滚动检测 |
|
|
|
|
|
const handleAiEmotionUserScroll = () => { |
|
|
|
|
|
// 标记用户正在滚动 |
|
|
|
|
|
isAiEmotionUserScrolling.value = true; |
|
|
|
|
|
|
|
|
|
|
|
// 清除之前的定时器 |
|
|
|
|
|
if (aiEmotionScrollTimer.value) { |
|
|
|
|
|
clearTimeout(aiEmotionScrollTimer.value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 设置定时器,2秒后恢复自动滚动 |
|
|
|
|
|
// aiEmotionScrollTimer.value = setTimeout(() => { |
|
|
|
|
|
// isAiEmotionUserScrolling.value = false; |
|
|
|
|
|
// console.log("AiEmotion页面用户滚动检测:恢复自动滚动"); |
|
|
|
|
|
// }, 2000); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 处理AiEmotion页面的滚动请求 |
|
|
|
|
|
const handleAiEmotionScrollToBottom = () => { |
|
|
|
|
|
if (activeTab.value === "AiEmotion") { |
|
|
|
|
|
const container = tabContentAiEmotion.value; |
|
|
|
|
|
if (container) { |
|
|
|
|
|
// 使用nextTick确保DOM已更新 |
|
|
|
|
|
nextTick(() => { |
|
|
|
|
|
container.scrollTop = container.scrollHeight - container.offsetHeight; |
|
|
|
|
|
console.log("AiEmotion页面:执行容器滚动到底部"); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 停止AiEmotion页面高度监听器 |
|
|
|
|
|
const stopAiEmotionHeightObserver = () => { |
|
|
|
|
|
isAiEmotionAutoScrollEnabled.value = false; |
|
|
|
|
|
isAiEmotionUserScrolling.value = false; |
|
|
|
|
|
|
|
|
|
|
|
// 清理滚动检测定时器 |
|
|
|
|
|
if (aiEmotionScrollTimer.value) { |
|
|
|
|
|
clearTimeout(aiEmotionScrollTimer.value); |
|
|
|
|
|
aiEmotionScrollTimer.value = null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (aiEmotionHeightObserver.value) { |
|
|
|
|
|
// 清理ResizeObserver |
|
|
|
|
|
aiEmotionHeightObserver.value.disconnect(); |
|
|
|
|
|
|
|
|
|
|
|
// 清理MutationObserver |
|
|
|
|
|
if (aiEmotionHeightObserver.value.mutationObserver) { |
|
|
|
|
|
aiEmotionHeightObserver.value.mutationObserver.disconnect(); |
|
|
|
|
|
aiEmotionHeightObserver.value.mutationObserver = null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 清理滚动事件监听器 |
|
|
|
|
|
if ( |
|
|
|
|
|
aiEmotionHeightObserver.value.scrollListener && |
|
|
|
|
|
tabContentAiEmotion.value |
|
|
|
|
|
) { |
|
|
|
|
|
tabContentAiEmotion.value.removeEventListener( |
|
|
|
|
|
"scroll", |
|
|
|
|
|
aiEmotionHeightObserver.value.scrollListener |
|
|
|
|
|
); |
|
|
|
|
|
aiEmotionHeightObserver.value.scrollListener = null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
aiEmotionHeightObserver.value = null; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log("AiEmotion页面高度监听器已停止"); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
watch( |
|
|
watch( |
|
|
() => chatStore.messages.length, |
|
|
() => chatStore.messages.length, |
|
|
() => { |
|
|
() => { |
|
@ -484,7 +659,9 @@ setTimeout(() => { |
|
|
}, 800); |
|
|
}, 800); |
|
|
|
|
|
|
|
|
const heightListener = () => { |
|
|
const heightListener = () => { |
|
|
const tabContainer = tabContent.value; |
|
|
|
|
|
|
|
|
const tabContainer = getCurrentScrollContainer(); |
|
|
|
|
|
if (!tabContainer) return; |
|
|
|
|
|
|
|
|
let befortop = 0; |
|
|
let befortop = 0; |
|
|
|
|
|
|
|
|
const scrollHandler = () => { |
|
|
const scrollHandler = () => { |
|
@ -634,8 +811,10 @@ const touchmoveHandler = (e) => { |
|
|
return; |
|
|
return; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
// 判断触摸目标是否在可滚动区域内 |
|
|
|
|
|
const isScrollableArea = e.target.closest(".tab-content"); |
|
|
|
|
|
|
|
|
// 判断触摸目标是否在当前活动页面的可滚动区域内 |
|
|
|
|
|
const currentContainer = getCurrentScrollContainer(); |
|
|
|
|
|
const isScrollableArea = |
|
|
|
|
|
currentContainer && currentContainer.contains(e.target); |
|
|
|
|
|
|
|
|
// 如果不在可滚动区域,则阻止滚动 |
|
|
// 如果不在可滚动区域,则阻止滚动 |
|
|
if (!isScrollableArea) { |
|
|
if (!isScrollableArea) { |
|
@ -733,9 +912,11 @@ const changeLevelList = ref([ |
|
|
const activeLevel = ref( |
|
|
const activeLevel = ref( |
|
|
changeLevelList.value[0] || { position: 10, calculatedPosition: 10 } |
|
|
changeLevelList.value[0] || { position: 10, calculatedPosition: 10 } |
|
|
); |
|
|
); |
|
|
const gold = ref(0); |
|
|
|
|
|
|
|
|
const gold = ref(100); |
|
|
|
|
|
|
|
|
const rechargeDialogVisible = ref(false); |
|
|
const rechargeDialogVisible = ref(false); |
|
|
|
|
|
const confirmDialogVisible = ref(false); |
|
|
|
|
|
const changeSuccessDialogVisible = ref(false); |
|
|
|
|
|
|
|
|
const chooseLevel = (item) => { |
|
|
const chooseLevel = (item) => { |
|
|
activeLevel.value = item; |
|
|
activeLevel.value = item; |
|
@ -745,6 +926,8 @@ const changeToken = () => { |
|
|
if (gold.value < activeLevel.value.position) { |
|
|
if (gold.value < activeLevel.value.position) { |
|
|
rechargeDialogVisible.value = true; |
|
|
rechargeDialogVisible.value = true; |
|
|
return; |
|
|
return; |
|
|
|
|
|
} else { |
|
|
|
|
|
confirmDialogVisible.value = true; |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
@ -770,6 +953,16 @@ const goRecharge = () => { |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const goChange = () => { |
|
|
|
|
|
confirmDialogVisible.value = false; |
|
|
|
|
|
dialogVisible.value = false; |
|
|
|
|
|
changeSuccessDialogVisible.value = true; |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
changeSuccessDialogVisible.value = false; |
|
|
|
|
|
}, 3000); |
|
|
|
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// 8.18金币兑换Token end |
|
|
// 8.18金币兑换Token end |
|
|
|
|
|
|
|
|
onMounted(async () => { |
|
|
onMounted(async () => { |
|
@ -912,7 +1105,7 @@ onUnmounted(() => { |
|
|
<div class="pc-action-btn"> |
|
|
<div class="pc-action-btn"> |
|
|
<div class="pc-count-number">{{ UserCount }}次</div> |
|
|
<div class="pc-count-number">{{ UserCount }}次</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="pc-clickGetCount">点击获取次数</div> |
|
|
|
|
|
|
|
|
<div class="pc-clickGetCount">点击获取Token</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="pc-backToHomeBtn" @click="backToHome()"> |
|
|
<div class="pc-backToHomeBtn" @click="backToHome()"> |
|
|
<img |
|
|
<img |
|
@ -939,6 +1132,27 @@ onUnmounted(() => { |
|
|
@sendMessage="sendMessage" |
|
|
@sendMessage="sendMessage" |
|
|
@ensureAIchat="ensureAIchat" |
|
|
@ensureAIchat="ensureAIchat" |
|
|
@enableInput="enableInput" |
|
|
@enableInput="enableInput" |
|
|
|
|
|
/> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
<!-- AiEmotion页面的独立滚动容器 --> |
|
|
|
|
|
<div |
|
|
|
|
|
v-show="activeTab === 'AiEmotion'" |
|
|
|
|
|
class="tab-content" |
|
|
|
|
|
:class="{ |
|
|
|
|
|
pcTabContent: !isMobile, |
|
|
|
|
|
}" |
|
|
|
|
|
ref="tabContentAiEmotion" |
|
|
|
|
|
> |
|
|
|
|
|
<component |
|
|
|
|
|
v-if="activeTab === 'AiEmotion'" |
|
|
|
|
|
:is="activeComponent" |
|
|
|
|
|
:messages="messages" |
|
|
|
|
|
@updateMessage="updateMessage" |
|
|
|
|
|
@sendMessage="sendMessage" |
|
|
|
|
|
@ensureAIchat="ensureAIchat" |
|
|
|
|
|
@enableInput="enableInput" |
|
|
|
|
|
@scrollToBottom="handleAiEmotionScrollToBottom" |
|
|
ref="aiEmotionRef" |
|
|
ref="aiEmotionRef" |
|
|
/> |
|
|
/> |
|
|
</div> |
|
|
</div> |
|
@ -1145,6 +1359,37 @@ onUnmounted(() => { |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</el-dialog> |
|
|
</el-dialog> |
|
|
|
|
|
|
|
|
|
|
|
<el-dialog |
|
|
|
|
|
v-model="confirmDialogVisible" |
|
|
|
|
|
:width="isMobile ? '60%' : '40%'" |
|
|
|
|
|
:show-close="false" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="confirmDialogTitle">兑换</div> |
|
|
|
|
|
<div class="confirmDialogContent"> |
|
|
|
|
|
尊敬的用户您好!您确认要花费{{ activeLevel.position }}金币兑换{{ |
|
|
|
|
|
activeLevel.calculatedPosition |
|
|
|
|
|
}}Token吗? |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="confirmDialogBtnGroup"> |
|
|
|
|
|
<div class="confirmDialogConfirm" @click="goChange()">确认</div> |
|
|
|
|
|
<div class="confirmDialogCancel" @click="confirmDialogVisible = false"> |
|
|
|
|
|
取消 |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
|
|
|
|
<el-dialog |
|
|
|
|
|
v-model="changeSuccessDialogVisible" |
|
|
|
|
|
:width="isMobile ? '60%' : '40%'" |
|
|
|
|
|
:show-close="false" |
|
|
|
|
|
class="changeSuccessDialog" |
|
|
|
|
|
> |
|
|
|
|
|
<div class="changeSuccessDialogTitle">兑换成功</div> |
|
|
|
|
|
<div class="changeSuccessDialogContent"> |
|
|
|
|
|
尊敬的用户,恭喜您成功兑换:{{ activeLevel.calculatedPosition }}Token |
|
|
|
|
|
</div> |
|
|
|
|
|
</el-dialog> |
|
|
</div> |
|
|
</div> |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
@ -1898,4 +2143,75 @@ body { |
|
|
.rechargeDialogCancel:hover { |
|
|
.rechargeDialogCancel:hover { |
|
|
background-color: #ecf2ff; |
|
|
background-color: #ecf2ff; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.confirmDialogTitle { |
|
|
|
|
|
font-size: 1.7rem; |
|
|
|
|
|
/* font-weight: bold; */ |
|
|
|
|
|
color: #4e86fe; |
|
|
|
|
|
display: flex; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
letter-spacing: 10px; |
|
|
|
|
|
} |
|
|
|
|
|
.confirmDialogContent { |
|
|
|
|
|
padding: 20px; |
|
|
|
|
|
font-size: 1.2rem; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.confirmDialogBtnGroup { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
font-size: 1.2rem; |
|
|
|
|
|
padding: 0px 20px; |
|
|
|
|
|
justify-content: space-between; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.confirmDialogConfirm { |
|
|
|
|
|
color: white; |
|
|
|
|
|
background-color: #4e86fe; |
|
|
|
|
|
padding: 10px 20px; |
|
|
|
|
|
border-radius: 13px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
width: 20%; |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.confirmDialogConfirm:hover { |
|
|
|
|
|
background-color: #3a73e6; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.confirmDialogCancel { |
|
|
|
|
|
border: 1px solid rgb(202, 202, 202); |
|
|
|
|
|
padding: 10px 20px; |
|
|
|
|
|
border-radius: 13px; |
|
|
|
|
|
cursor: pointer; |
|
|
|
|
|
width: 20%; |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.confirmDialogCancel:hover { |
|
|
|
|
|
background-color: #ecf2ff; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.changeSuccessDialogTitle { |
|
|
|
|
|
font-size: 1.7rem; |
|
|
|
|
|
font-weight: bold; |
|
|
|
|
|
color: #de93a3; |
|
|
|
|
|
display: flex; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
letter-spacing: 10px; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.changeSuccessDialogContent { |
|
|
|
|
|
padding: 20px; |
|
|
|
|
|
font-size: 1.2rem; |
|
|
|
|
|
font-weight: bold; |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
} |
|
|
|
|
|
</style> |
|
|
|
|
|
|
|
|
|
|
|
<style> |
|
|
|
|
|
.changeSuccessDialog { |
|
|
|
|
|
background: linear-gradient(180deg, #a2dffe, #b59be1); |
|
|
|
|
|
} |
|
|
</style> |
|
|
</style> |