diff --git a/src/views/AiEmotion.vue b/src/views/AiEmotion.vue index 53eb003..d1af1ba 100644 --- a/src/views/AiEmotion.vue +++ b/src/views/AiEmotion.vue @@ -30,7 +30,7 @@
- 思维矩阵图片 +
{{ stockName }}{{ stockName ? '量子四维矩阵图' : '' }}
@@ -206,6 +206,8 @@ const displayedTexts = ref({ disclaimer: '' }); const typewriterTimers = ref([]); +// 记录每个股票是否已经显示过打字机效果 +const stockTypewriterShown = ref(new Map()); // 音频播放相关数据 const audioUrl = ref(''); @@ -248,18 +250,45 @@ const parsedConclusion = computed(() => { watch(currentStock, (newStock) => { if (newStock && newStock.apiData) { isPageLoaded.value = true; + // 停止当前播放的音频 + stopAudio(); // 重置触发状态,允许新股票重新触发效果 hasTriggeredAudio.value = false; hasTriggeredTypewriter.value = false; - // 清空之前的显示文本 - displayedTexts.value = { - one1: '', - one2: '', - two: '', - three: '', - four: '', - disclaimer: '' - }; + + // 获取股票代码作为唯一标识 + const stockCode = newStock.stockInfo?.code || newStock.stockInfo?.symbol; + + // 检查该股票是否已经显示过打字机效果 + if (stockCode && stockTypewriterShown.value.has(stockCode)) { + // 如果已经显示过,直接显示完整文本 + if (newStock.conclusionData) { + try { + const conclusion = JSON.parse(newStock.conclusionData); + displayedTexts.value = { + one1: conclusion.one1 || '', + one2: conclusion.one2 || '', + two: conclusion.two || '', + three: conclusion.three || '', + four: conclusion.four || '', + disclaimer: '该内容由AI内容生成,请注意甄别' + }; + } catch (error) { + console.error('解析结论数据失败:', error); + } + } + } else { + // 如果没有显示过,清空显示文本,等待打字机效果 + displayedTexts.value = { + one1: '', + one2: '', + two: '', + three: '', + four: '', + disclaimer: '' + }; + } + nextTick(() => { renderCharts(newStock.apiData); }); @@ -273,30 +302,30 @@ watch(parsedConclusion, (newConclusion) => { if (newConclusion) { console.log('场景应用结论数据:', newConclusion); // 不再立即开始打字机效果,等待滚动到场景应用部分时触发 - + // 尝试多种可能的语音URL字段名 - let voiceUrl = null; - if (newConclusion.url) { - // 清理URL字符串,去除空格、反引号等特殊字符 - voiceUrl = newConclusion.url.toString().trim().replace(/[`\s]/g, ''); - } else if (newConclusion.audioUrl) { - voiceUrl = newConclusion.audioUrl.toString().trim().replace(/[`\s]/g, ''); - } else if (newConclusion.voice_url) { - voiceUrl = newConclusion.voice_url.toString().trim().replace(/[`\s]/g, ''); - } else if (newConclusion.audio) { - voiceUrl = newConclusion.audio.toString().trim().replace(/[`\s]/g, ''); - } else if (newConclusion.tts_url) { - voiceUrl = newConclusion.tts_url.toString().trim().replace(/[`\s]/g, ''); - } - - if (voiceUrl && voiceUrl.startsWith('http')) { - console.log('找到并清理后的语音URL:', voiceUrl); - audioUrl.value = voiceUrl; - console.log('音频URL已准备,等待滚动触发播放'); - } else { - console.log('未找到有效的语音URL,原始URL:', newConclusion.url); - console.log('结论数据中的所有字段:', Object.keys(newConclusion)); - } + let voiceUrl = null; + if (newConclusion.url) { + // 清理URL字符串,去除空格、反引号等特殊字符 + voiceUrl = newConclusion.url.toString().trim().replace(/[`\s]/g, ''); + } else if (newConclusion.audioUrl) { + voiceUrl = newConclusion.audioUrl.toString().trim().replace(/[`\s]/g, ''); + } else if (newConclusion.voice_url) { + voiceUrl = newConclusion.voice_url.toString().trim().replace(/[`\s]/g, ''); + } else if (newConclusion.audio) { + voiceUrl = newConclusion.audio.toString().trim().replace(/[`\s]/g, ''); + } else if (newConclusion.tts_url) { + voiceUrl = newConclusion.tts_url.toString().trim().replace(/[`\s]/g, ''); + } + + if (voiceUrl && voiceUrl.startsWith('http')) { + console.log('找到并清理后的语音URL:', voiceUrl); + audioUrl.value = voiceUrl; + console.log('音频URL已准备,等待滚动触发播放'); + } else { + console.log('未找到有效的语音URL,原始URL:', newConclusion.url); + console.log('结论数据中的所有字段:', Object.keys(newConclusion)); + } } }, { immediate: true }); @@ -305,7 +334,7 @@ function startTypewriterEffect(conclusion) { // 清除之前的定时器 typewriterTimers.value.forEach(timer => clearTimeout(timer)); typewriterTimers.value = []; - + // 清空之前的显示文本 displayedTexts.value = { one1: '', @@ -315,41 +344,41 @@ function startTypewriterEffect(conclusion) { four: '', disclaimer: '' }; - + // 定义打字速度(毫秒) - const typeSpeed = 300; + const typeSpeed = 250; let totalDelay = 0; - + // 为每个文本创建打字机效果 const textKeys = ['one1', 'one2', 'two', 'three', 'four']; - + textKeys.forEach((key) => { if (conclusion[key]) { const text = conclusion[key]; const startDelay = totalDelay; - + for (let i = 0; i <= text.length; i++) { const timer = setTimeout(() => { displayedTexts.value[key] = text.substring(0, i); }, startDelay + i * typeSpeed); - + typewriterTimers.value.push(timer); } - + // 更新总延迟,为下一个文本留出时间 totalDelay += text.length * typeSpeed + 300; // 额外300ms间隔 } }); - + // 添加免责声明的打字机效果(在所有内容显示完成后) const disclaimerText = '该内容由AI内容生成,请注意甄别'; const disclaimerStartDelay = totalDelay + 500; // 额外500ms间隔 - + for (let i = 0; i <= disclaimerText.length; i++) { const timer = setTimeout(() => { displayedTexts.value.disclaimer = disclaimerText.substring(0, i); }, disclaimerStartDelay + i * typeSpeed); - + typewriterTimers.value.push(timer); } } @@ -363,7 +392,7 @@ function clearTypewriterTimers() { // 音频播放函数 function playAudio(url) { console.log('尝试播放音频:', url); - + if (!url) { console.warn('音频URL为空,跳过播放'); isAudioPlaying.value = false; @@ -376,7 +405,7 @@ function playAudio(url) { console.log('语音功能已关闭,跳过播放'); return; } - + console.log('开始创建音频实例...'); try { @@ -411,10 +440,10 @@ function playAudio(url) { // 保存音频实例到store audioStore.nowSound = newSound; audioStore.setAudioInstance(newSound); - + // 播放音频 newSound.play(); - + } catch (error) { console.error('创建音频实例失败:', error); isAudioPlaying.value = false; @@ -649,13 +678,13 @@ function isDataLoaded() { console.log('页面数据尚未加载完成'); return false; } - + // 检查当前股票数据是否存在 if (!currentStock.value || !currentStock.value.apiData) { console.log('股票数据尚未加载完成'); return false; } - + // 检查图表组件是否已渲染 const requiredRefs = [ marketTemperatureRef.value, @@ -663,13 +692,13 @@ function isDataLoaded() { emotionalBottomRadarRef.value, emoEnergyConverterRef.value ]; - + const allRefsLoaded = requiredRefs.every(ref => ref !== null); if (!allRefsLoaded) { console.log('图表组件尚未完全加载'); return false; } - + console.log('所有数据和组件已加载完成,可以开始滚动'); return true; } @@ -677,7 +706,7 @@ function isDataLoaded() { // 自动滚动函数 function startAutoScroll() { if (isAutoScrolling.value) return; - + // 检查数据是否已加载完成 if (!isDataLoaded()) { console.log('数据尚未加载完成,延迟1秒后重试'); @@ -686,134 +715,153 @@ function 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('开始流畅自动滚动'); + + // 获取页面总高度 + const documentHeight = document.documentElement.scrollHeight; + const windowHeight = window.innerHeight; + const maxScrollTop = documentHeight - windowHeight; + + // 滚动参数 + const scrollDuration = 15000; // 总滚动时间15秒 + const scrollStep = maxScrollTop / (scrollDuration / 50); // 每50ms滚动的距离 + let currentScrollTop = 0; + + function smoothScroll() { + if (currentScrollTop < maxScrollTop) { + currentScrollTop += scrollStep; + if (currentScrollTop > maxScrollTop) { + currentScrollTop = maxScrollTop; + } + + window.scrollTo({ + top: currentScrollTop, + behavior: 'auto' // 使用auto而不是smooth,因为我们自己控制滚动 }); - - console.log(`滚动到第${currentIndex + 1}个部分`); - currentSection.value = currentIndex; - currentIndex++; - - // 增加滚动间隔时间,让用户有更多时间观看内容 - setTimeout(scrollToNextSection, 4000); // 从2秒增加到4秒 + + // 继续滚动 + setTimeout(smoothScroll, 50); } else { - console.log('自动滚动完成'); + console.log('流畅自动滚动完成'); isAutoScrolling.value = false; } } - - // 开始滚动前等待2秒,给用户准备时间 - console.log('自动滚动将在2秒后开始'); - setTimeout(scrollToNextSection, 2000); + + // 延迟1秒开始滚动 + setTimeout(smoothScroll, 1000); } // 设置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(); - } +function setupIntersectionObserver() { + if (!scenarioApplicationRef.value) return; + + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting && (!hasTriggeredAudio.value || !hasTriggeredTypewriter.value)) { + console.log('场景应用部分进入视口,开始打字机效果和音频播放'); + + // 获取当前股票代码 + const stockCode = currentStock.value?.stockInfo?.code || currentStock.value?.stockInfo?.symbol; + + // 触发打字机效果 + if (!hasTriggeredTypewriter.value && parsedConclusion.value && stockCode) { + // 检查该股票是否已经显示过打字机效果 + if (!stockTypewriterShown.value.has(stockCode)) { + console.log('开始场景应用打字机效果'); + hasTriggeredTypewriter.value = true; + startTypewriterEffect(parsedConclusion.value); + // 记录该股票已显示过打字机效果 + stockTypewriterShown.value.set(stockCode, true); + } else { + console.log('该股票已显示过打字机效果,跳过'); + hasTriggeredTypewriter.value = true; + } + } + + // 触发音频播放 + 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 - }); +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 +}); \ No newline at end of file diff --git a/src/views/components/emotionalBottomRadar.vue b/src/views/components/emotionalBottomRadar.vue index 20f0561..83fdd1a 100644 --- a/src/views/components/emotionalBottomRadar.vue +++ b/src/views/components/emotionalBottomRadar.vue @@ -185,7 +185,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { axisLine: { // show: false, lineStyle: { - color: '#837b7b', // x轴线颜色 + color: 'white', // x轴线颜色 } }, axisTick: { @@ -212,7 +212,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { // show: false, lineStyle: { // color: '#008000' - color: '#837b7b' + color: 'white' } }, axisTick: { @@ -236,7 +236,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { gridIndex: 2, axisLine: { lineStyle: { - color: '#837b7b' + color: 'white' } }, axisTick: { @@ -249,7 +249,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { } }, axisLabel: { - color: '#000000', + color: 'white', interval: 'auto', rotate: 45 }, @@ -270,7 +270,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { splitNumber: 4, axisLine: { lineStyle: { - color: '#837b7b' // y轴坐标轴颜色 + color: 'white' // y轴坐标轴颜色 } }, axisTick: { @@ -278,7 +278,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { }, axisLabel: { width: 50, // 宽度限制 - color: '#000000', + color: 'white', formatter: function (value, index) { if (index === 0) { return '0' @@ -302,7 +302,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { splitNumber: 3, axisLine: { lineStyle: { - color: '#837b7b' + color: 'white' } }, axisTick: { @@ -311,7 +311,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { splitNumber: 5, // 刻度数量 axisLabel: { width: 50, // 宽度限制 - color: '#000000', + color: 'white', formatter: function (value, index) { // 如果没有刻度数量,其他方法获取不到y轴刻度总长 if (index === 0) { @@ -336,7 +336,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { splitNumber: 2, axisLine: { lineStyle: { - color: '#837b7b' + color: 'white' } }, axisTick: { @@ -345,7 +345,7 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) { splitNumber: 5, // 刻度数量 axisLabel: { width: 50, // 宽度限制 - color: '#000000', + color: 'white', formatter: function (value, index) { if (index === 5) { return '' diff --git a/src/views/components/marketTemperature.vue b/src/views/components/marketTemperature.vue index 33680ce..3a3a86e 100644 --- a/src/views/components/marketTemperature.vue +++ b/src/views/components/marketTemperature.vue @@ -190,14 +190,29 @@ function initChart(raw, klineDataRawValue, WDRLValue) { // 创建新的图表实例 chartInstance = echarts.init(KlineCanvs.value) chartInstance.setOption({ - tooltip: {}, + tooltip: { + formatter: function (params) { + if (params.seriesType === 'candlestick') { + const date = params.name + // 开收低高分别取参数的第2到第5个数 + const open = params.data[1] + const close = params.data[2] + const low = params.data[3] + const high = params.data[4] + return `日期: ${date}
开: ${open}
收: ${close}
低: ${low}
高: ${high}` + } + return params.value[2] + } + }, legend: { data: ['K线', '市场温度', '股票温度'], textStyle: { color: 'white' } }, xAxis: { type: 'category', data: dateLabels, - axisLine: { lineStyle: { color: '#8392A5' } } + axisLine: { lineStyle: { color: 'white' } } }, - yAxis: [{}, { + yAxis: [{ + axisLine: { lineStyle: { color: 'white' } } + }, { min: 0, max: 100, position: 'right', @@ -210,7 +225,14 @@ function initChart(raw, klineDataRawValue, WDRLValue) { name: 'K线', type: 'candlestick', data: klineData, - itemStyle: { color: '#0CF49B', color0: '#FD1050' } + itemStyle: { + normal: { + color: '#FF0000', // 阳线红色 + color0: '#00FF00', // 阴线绿色 + borderColor: '#FF0000', // 阳线边框红色 + borderColor0: '#00FF00' // 阴线边框绿色 + } + } }, { name: '市场温度', @@ -255,8 +277,6 @@ function initChart(raw, klineDataRawValue, WDRLValue) { adjustCellFontSize() } - - // 调整单元格字体大小 function adjustCellFontSize() { const table = document.querySelector('.border4 .el-table')