|
|
@ -5,6 +5,7 @@ |
|
|
|
<img src="@/assets/img/AiEmotion/金轮.png" class="golden-wheel-img" alt="金轮图标" |
|
|
|
:class="{ 'rotating-image': isRotating }" /> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 消息显示区域 --> |
|
|
|
<div class="user-input-display"> |
|
|
|
<div v-for="(message, index) in messages" :key="index" class="message-container"> |
|
|
@ -118,7 +119,7 @@ |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<!-- 场景应用 --> |
|
|
|
<div class="class09"> |
|
|
|
<div class="class09" ref="scenarioApplicationRef"> |
|
|
|
<img src="@/assets/img/AiEmotion/场景应用.png" alt="场景应用标题"> |
|
|
|
<div class="bk-image"> |
|
|
|
<div class="conclusion-container" v-if="parsedConclusion"> |
|
|
@ -167,7 +168,7 @@ import { ElMessage } from 'element-plus'; |
|
|
|
import { useEmotionStore } from '@/store/emotion'; // 导入Pinia store |
|
|
|
import { useAudioStore } from '@/store/audio.js'; // 导入音频store |
|
|
|
import { Howl, Howler } from 'howler'; // 导入音频播放库 |
|
|
|
|
|
|
|
import { reactive } from 'vue'; |
|
|
|
// 使用Pinia store |
|
|
|
const emotionStore = useEmotionStore(); |
|
|
|
const audioStore = useAudioStore(); |
|
|
@ -186,6 +187,15 @@ const isRotating = ref(false);//控制旋转 |
|
|
|
const version1 = ref(2); // 版本号 |
|
|
|
const conclusionData = ref(''); // 存储第二个工作流接口返回的结论数据 |
|
|
|
|
|
|
|
// 自动滚动相关数据 |
|
|
|
const isAutoScrolling = ref(false); |
|
|
|
const currentSection = ref(0); |
|
|
|
const sectionRefs = ref([]); |
|
|
|
const scenarioApplicationRef = ref(null); // 场景应用部分的引用 |
|
|
|
const hasTriggeredAudio = ref(false); // 是否已触发音频播放 |
|
|
|
const hasTriggeredTypewriter = ref(false); // 是否已触发打字机效果 |
|
|
|
const intersectionObserver = ref(null); // 存储observer实例 |
|
|
|
|
|
|
|
// 打字机效果相关数据 |
|
|
|
const displayedTexts = ref({ |
|
|
|
one1: '', |
|
|
@ -232,10 +242,24 @@ const parsedConclusion = computed(() => { |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 监听当前股票变化,重新渲染图表 |
|
|
|
watch(currentStock, (newStock) => { |
|
|
|
if (newStock && newStock.apiData) { |
|
|
|
isPageLoaded.value = true; |
|
|
|
// 重置触发状态,允许新股票重新触发效果 |
|
|
|
hasTriggeredAudio.value = false; |
|
|
|
hasTriggeredTypewriter.value = false; |
|
|
|
// 清空之前的显示文本 |
|
|
|
displayedTexts.value = { |
|
|
|
one1: '', |
|
|
|
one2: '', |
|
|
|
two: '', |
|
|
|
three: '', |
|
|
|
four: '', |
|
|
|
disclaimer: '' |
|
|
|
}; |
|
|
|
nextTick(() => { |
|
|
|
renderCharts(newStock.apiData); |
|
|
|
}); |
|
|
@ -244,11 +268,11 @@ watch(currentStock, (newStock) => { |
|
|
|
} |
|
|
|
}, { immediate: true }); |
|
|
|
|
|
|
|
// 监听parsedConclusion变化,触发打字机效果 |
|
|
|
// 监听parsedConclusion变化,准备数据但不立即触发打字机效果 |
|
|
|
watch(parsedConclusion, (newConclusion) => { |
|
|
|
if (newConclusion) { |
|
|
|
console.log('场景应用结论数据:', newConclusion); |
|
|
|
startTypewriterEffect(newConclusion); |
|
|
|
// 不再立即开始打字机效果,等待滚动到场景应用部分时触发 |
|
|
|
|
|
|
|
// 尝试多种可能的语音URL字段名 |
|
|
|
let voiceUrl = null; |
|
|
@ -266,13 +290,13 @@ watch(parsedConclusion, (newConclusion) => { |
|
|
|
} |
|
|
|
|
|
|
|
if (voiceUrl && voiceUrl.startsWith('http')) { |
|
|
|
console.log('找到并清理后的语音URL:', voiceUrl); |
|
|
|
audioUrl.value = voiceUrl; |
|
|
|
playAudio(voiceUrl); |
|
|
|
} else { |
|
|
|
console.log('未找到有效的语音URL,原始URL:', newConclusion.url); |
|
|
|
console.log('结论数据中的所有字段:', Object.keys(newConclusion)); |
|
|
|
} |
|
|
|
console.log('找到并清理后的语音URL:', voiceUrl); |
|
|
|
audioUrl.value = voiceUrl; |
|
|
|
console.log('音频URL已准备,等待滚动触发播放'); |
|
|
|
} else { |
|
|
|
console.log('未找到有效的语音URL,原始URL:', newConclusion.url); |
|
|
|
console.log('结论数据中的所有字段:', Object.keys(newConclusion)); |
|
|
|
} |
|
|
|
} |
|
|
|
}, { immediate: true }); |
|
|
|
|
|
|
@ -293,7 +317,7 @@ function startTypewriterEffect(conclusion) { |
|
|
|
}; |
|
|
|
|
|
|
|
// 定义打字速度(毫秒) |
|
|
|
const typeSpeed = 50; |
|
|
|
const typeSpeed = 300; |
|
|
|
let totalDelay = 0; |
|
|
|
|
|
|
|
// 为每个文本创建打字机效果 |
|
|
@ -404,8 +428,6 @@ function stopAudio() { |
|
|
|
} |
|
|
|
isAudioPlaying.value = false; |
|
|
|
} |
|
|
|
//导出方法供外部使用 |
|
|
|
defineExpose({ handleSendMessage }) |
|
|
|
// 触发图片旋转的方法 |
|
|
|
function startImageRotation() { |
|
|
|
isRotating.value = true; |
|
|
@ -620,16 +642,175 @@ const scrollToBottom = async () => { |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 页面挂载完成后触发图片旋转 |
|
|
|
onMounted(() => { |
|
|
|
startImageRotation(); |
|
|
|
}); |
|
|
|
// 检查数据是否已加载完成 |
|
|
|
function isDataLoaded() { |
|
|
|
// 检查页面是否已加载 |
|
|
|
if (!isPageLoaded.value) { |
|
|
|
console.log('页面数据尚未加载完成'); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 检查当前股票数据是否存在 |
|
|
|
if (!currentStock.value || !currentStock.value.apiData) { |
|
|
|
console.log('股票数据尚未加载完成'); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// 检查图表组件是否已渲染 |
|
|
|
const requiredRefs = [ |
|
|
|
marketTemperatureRef.value, |
|
|
|
emotionDecodRef.value, |
|
|
|
emotionalBottomRadarRef.value, |
|
|
|
emoEnergyConverterRef.value |
|
|
|
]; |
|
|
|
|
|
|
|
const allRefsLoaded = requiredRefs.every(ref => ref !== null); |
|
|
|
if (!allRefsLoaded) { |
|
|
|
console.log('图表组件尚未完全加载'); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
console.log('所有数据和组件已加载完成,可以开始滚动'); |
|
|
|
return true; |
|
|
|
} |
|
|
|
|
|
|
|
// 组件卸载时清理定时器和音频 |
|
|
|
onUnmounted(() => { |
|
|
|
clearTypewriterTimers(); |
|
|
|
stopAudio(); |
|
|
|
}); |
|
|
|
// 自动滚动函数 |
|
|
|
function startAutoScroll() { |
|
|
|
if (isAutoScrolling.value) return; |
|
|
|
|
|
|
|
// 检查数据是否已加载完成 |
|
|
|
if (!isDataLoaded()) { |
|
|
|
console.log('数据尚未加载完成,延迟1秒后重试'); |
|
|
|
setTimeout(() => { |
|
|
|
startAutoScroll(); |
|
|
|
}, 1000); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
isAutoScrolling.value = true; |
|
|
|
const sections = document.querySelectorAll('.class02, .class03, .class04, .class05, .class06, .class08, .class09'); |
|
|
|
|
|
|
|
let currentIndex = 0; |
|
|
|
|
|
|
|
function scrollToNextSection() { |
|
|
|
if (currentIndex < sections.length) { |
|
|
|
const section = sections[currentIndex]; |
|
|
|
|
|
|
|
// 使用更平滑的滚动配置 |
|
|
|
section.scrollIntoView({ |
|
|
|
behavior: 'smooth', |
|
|
|
block: 'center', // 改为居中显示,视觉效果更好 |
|
|
|
inline: 'nearest' |
|
|
|
}); |
|
|
|
|
|
|
|
console.log(`滚动到第${currentIndex + 1}个部分`); |
|
|
|
currentSection.value = currentIndex; |
|
|
|
currentIndex++; |
|
|
|
|
|
|
|
// 增加滚动间隔时间,让用户有更多时间观看内容 |
|
|
|
setTimeout(scrollToNextSection, 4000); // 从2秒增加到4秒 |
|
|
|
} else { |
|
|
|
console.log('自动滚动完成'); |
|
|
|
isAutoScrolling.value = false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 开始滚动前等待2秒,给用户准备时间 |
|
|
|
console.log('自动滚动将在2秒后开始'); |
|
|
|
setTimeout(scrollToNextSection, 2000); |
|
|
|
} |
|
|
|
|
|
|
|
// 设置Intersection Observer监听场景应用部分 |
|
|
|
function setupIntersectionObserver() { |
|
|
|
if (!scenarioApplicationRef.value) return; |
|
|
|
|
|
|
|
const observer = new IntersectionObserver( |
|
|
|
(entries) => { |
|
|
|
entries.forEach((entry) => { |
|
|
|
if (entry.isIntersecting && (!hasTriggeredAudio.value || !hasTriggeredTypewriter.value)) { |
|
|
|
console.log('场景应用部分进入视口,开始打字机效果和音频播放'); |
|
|
|
|
|
|
|
// 触发打字机效果 |
|
|
|
if (!hasTriggeredTypewriter.value && parsedConclusion.value) { |
|
|
|
console.log('开始场景应用打字机效果'); |
|
|
|
hasTriggeredTypewriter.value = true; |
|
|
|
startTypewriterEffect(parsedConclusion.value); |
|
|
|
} |
|
|
|
|
|
|
|
// 触发音频播放 |
|
|
|
if (!hasTriggeredAudio.value && audioUrl.value && parsedConclusion.value) { |
|
|
|
console.log('自动触发场景应用音频播放'); |
|
|
|
hasTriggeredAudio.value = true; |
|
|
|
playAudio(audioUrl.value); |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}, |
|
|
|
{ |
|
|
|
threshold: 0.3, // 当30%的元素进入视口时触发 |
|
|
|
rootMargin: '0px 0px -100px 0px' // 提前100px触发 |
|
|
|
} |
|
|
|
); |
|
|
|
|
|
|
|
observer.observe(scenarioApplicationRef.value); |
|
|
|
intersectionObserver.value = observer; |
|
|
|
} |
|
|
|
|
|
|
|
// 手动触发自动滚动 |
|
|
|
function triggerAutoScroll() { |
|
|
|
// 检查是否正在滚动 |
|
|
|
if (isAutoScrolling.value) { |
|
|
|
console.log('自动滚动正在进行中,请稍候'); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 检查数据是否已准备好 |
|
|
|
if (!isDataLoaded()) { |
|
|
|
console.log('数据尚未准备完成,请等待数据加载后再试'); |
|
|
|
// 可以显示提示信息给用户 |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
console.log('手动触发自动滚动'); |
|
|
|
startAutoScroll(); |
|
|
|
} |
|
|
|
|
|
|
|
// 页面挂载完成后触发图片旋转和设置滚动监听 |
|
|
|
onMounted(() => { |
|
|
|
startImageRotation(); |
|
|
|
|
|
|
|
// 等待DOM完全渲染后设置监听器 |
|
|
|
nextTick(() => { |
|
|
|
setupIntersectionObserver(); |
|
|
|
|
|
|
|
// 页面加载完成后自动开始滚动 |
|
|
|
setTimeout(() => { |
|
|
|
triggerAutoScroll(); |
|
|
|
}, 1000); // 延迟1秒开始滚动,确保页面完全渲染 |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
// 组件卸载时清理定时器、音频和observer |
|
|
|
onUnmounted(() => { |
|
|
|
clearTypewriterTimers(); |
|
|
|
stopAudio(); |
|
|
|
|
|
|
|
// 重置触发状态 |
|
|
|
hasTriggeredAudio.value = false; |
|
|
|
hasTriggeredTypewriter.value = false; |
|
|
|
|
|
|
|
// 清理Intersection Observer |
|
|
|
if (intersectionObserver.value) { |
|
|
|
intersectionObserver.value.disconnect(); |
|
|
|
intersectionObserver.value = null; |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 导出方法供外部使用 |
|
|
|
defineExpose({ |
|
|
|
handleSendMessage, |
|
|
|
triggerAutoScroll |
|
|
|
}); |
|
|
|
</script> |
|
|
|
|
|
|
|
<style scoped> |
|
|
@ -683,6 +864,8 @@ onUnmounted(() => { |
|
|
|
height: auto; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* 定义旋转动画 */ |
|
|
|
@keyframes rotate { |
|
|
|
from { |
|
|
@ -1191,7 +1374,7 @@ onUnmounted(() => { |
|
|
|
/* 添加内边距,确保内容与边界有间距 */ |
|
|
|
box-sizing: border-box; |
|
|
|
/* 包括内边距在宽度和高度计算中 */ |
|
|
|
background-color: #5e81a7; |
|
|
|
background-color: #02107d; |
|
|
|
margin: 0 auto; |
|
|
|
/* 居中容器 */ |
|
|
|
margin-bottom: 10rem; |
|
|
@ -1404,7 +1587,7 @@ onUnmounted(() => { |
|
|
|
min-height: 100px; |
|
|
|
height: auto; |
|
|
|
box-sizing: border-box; |
|
|
|
background-color: #5e81a7; |
|
|
|
background-color: #02107d; |
|
|
|
margin-bottom: 10rem; |
|
|
|
} |
|
|
|
|
|
|
|