diff --git a/src/views/AiEmotion.vue b/src/views/AiEmotion.vue index 446af76..08afd97 100644 --- a/src/views/AiEmotion.vue +++ b/src/views/AiEmotion.vue @@ -277,41 +277,76 @@ 场景应用标题
- -
-

{{ displayedTitles.one }}

-

- {{ displayedTexts.one1 }} + + +

+

L1: 情绪监控

+ +

+ {{ getStockTypewriterTexts(stock).one1 }}

-

- {{ displayedTexts.one2 }} +

+ {{ getStockTypewriterTexts(stock).one2 }} +

+ +

+ {{ getStockConclusion(stock).one1 }} +

+

+ {{ getStockConclusion(stock).one2 }}

-
-

{{ displayedTitles.two }}

-

{{ displayedTexts.two }}

+ +
+

L2: 情绪解码

+ +

+ {{ getStockTypewriterTexts(stock).two }} +

+ +

+ {{ getStockConclusion(stock).two }} +

-
-

{{ displayedTitles.three }}

-

{{ displayedTexts.three }}

+ +
+

L3: 情绪推演

+ +

+ {{ getStockTypewriterTexts(stock).three }} +

+ +

+ {{ getStockConclusion(stock).three }} +

-
-

{{ displayedTitles.four }}

-

{{ displayedTexts.four }}

+ +
+

L4: 情绪套利

+ +

+ {{ getStockTypewriterTexts(stock).four }} +

+ +

+ {{ getStockConclusion(stock).four }} +

-
-

{{ displayedTexts.disclaimer }}

+
+ +

+ {{ getStockTypewriterTexts(stock).disclaimer }} +

+ +

+ 该内容由AI生成,请注意甄别 +

等待股票分析结论...

@@ -565,30 +600,7 @@ const addStock = (stockData) => { stockTypewriterShown.value.clear(); stockAudioPlayed.value.clear(); - // 清理显示的文本和标题 - displayedTexts.value = { - one1: "", - one2: "", - two: "", - three: "", - four: "", - disclaimer: "", - }; - displayedTitles.value = { - one: "", - two: "", - three: "", - four: "", - }; - - // 隐藏所有模块 - moduleVisibility.value = { - one: false, - two: false, - three: false, - four: false, - disclaimer: false, - }; + // 清理状态变量(保留用于其他功能的变量) // 隐藏所有图表组件 chartVisibility.value = { @@ -603,50 +615,15 @@ const addStock = (stockData) => { // 3. 设置页面为已加载状态,重新渲染页面 isPageLoaded.value = true; - // 4. 立即显示历史记录的结论文本 + // 4. 标记历史记录股票已显示过,避免重复触发 if (stockData.conclusionData) { - try { - const conclusion = - typeof stockData.conclusionData === "object" - ? stockData.conclusionData - : JSON.parse(stockData.conclusionData); - - displayedTexts.value = { - one1: conclusion.one1 || "", - one2: conclusion.one2 || "", - two: conclusion.two || "", - three: conclusion.three || "", - four: conclusion.four || "", - disclaimer: "该内容由AI生成,请注意甄别", - }; - - displayedTitles.value = { - one: "L1: 情绪监控", - two: "L2: 情绪解码", - three: "L3: 情绪推演", - four: "L4: 情绪套利", - }; - - moduleVisibility.value = { - one: !!(conclusion.one1 || conclusion.one2), - two: !!conclusion.two, - three: !!conclusion.three, - four: !!conclusion.four, - disclaimer: true, - }; - - // 标记该股票已显示过,避免重复触发 - const stockCode = - stockData.stockInfo?.code || stockData.stockInfo?.symbol; - if (stockCode) { - stockTypewriterShown.value.set(stockCode, true); - stockAudioPlayed.value.set(stockCode, true); - } - - console.log("历史记录结论文本已立即显示:", conclusion); - } catch (error) { - console.error("解析历史记录结论数据失败:", error); + const stockCode = + stockData.stockInfo?.code || stockData.stockInfo?.symbol; + if (stockCode) { + stockTypewriterShown.value.set(stockCode, true); + stockAudioPlayed.value.set(stockCode, true); } + console.log("历史记录股票已标记为已显示"); } // 5. 使用nextTick确保DOM更新后启动高度监听器并滚动到底部 @@ -681,32 +658,7 @@ const hasTriggeredTypewriter = ref(false); // 是否已触发打字机效果 const intersectionObserver = ref(null); // 存储observer实例 const isUserInitiated = ref(false); // 标记是否为用户主动搜索 -// 显示的文本内容(用于打字机效果) -const displayedTexts = ref({ - one1: "", - one2: "", - two: "", - three: "", - four: "", - disclaimer: "", -}); - -// 显示的标题内容(用于打字机效果) -const displayedTitles = ref({ - one: "", - two: "", - three: "", - four: "", -}); - -// 模块显示状态 -const moduleVisibility = ref({ - one: false, - two: false, - three: false, - four: false, - disclaimer: false, -}); +// 打字机效果相关的变量已移除,现在直接使用parsedConclusion显示内容 // 图表组件显示状态 const chartVisibility = ref({ @@ -718,6 +670,10 @@ const chartVisibility = ref({ const typewriterTimers = ref([]); // 记录每个股票是否已经显示过打字机效果 const stockTypewriterShown = ref(new Map()); +// 记录每个股票的打字机显示状态 +const stockTypewriterTexts = ref(new Map()); +// 记录每个股票的打字机模块可见性 +const stockTypewriterVisibility = ref(new Map()); // 记录每个股票是否已经播放过音频 const stockAudioPlayed = ref(new Map()); // 跟踪每个股票的音频播放状态 @@ -887,6 +843,27 @@ const getStockConclusion = (stock) => { } }; +// 辅助函数:获取股票的打字机文本状态 +const getStockTypewriterTexts = (stock) => { + const stockCode = stock.stockInfo?.code || stock.stockInfo?.symbol; + if (!stockCode) return null; + return stockTypewriterTexts.value.get(stockCode) || null; +}; + +// 辅助函数:获取股票的打字机可见性状态 +const getStockTypewriterVisibility = (stock) => { + const stockCode = stock.stockInfo?.code || stock.stockInfo?.symbol; + if (!stockCode) return null; + return stockTypewriterVisibility.value.get(stockCode) || null; +}; + +// 辅助函数:检查股票是否正在进行打字机效果 +const isStockTypewriting = (stock) => { + const stockCode = stock.stockInfo?.code || stock.stockInfo?.symbol; + if (!stockCode) return false; + return stockTypewriterShown.value.has(stockCode) && !stockTypewriterTexts.value.has(stockCode); +}; + // 监听股票列表变化,当列表为空时隐藏页面数据 watch( () => emotionStore.stockList, @@ -908,29 +885,6 @@ watch( stockAudioPlayed.value.clear(); // 清理已添加股票的记录 addedStocks.value.clear(); - // 清理显示的文本和标题 - displayedTexts.value = { - one1: "", - one2: "", - two: "", - three: "", - four: "", - disclaimer: "", - }; - displayedTitles.value = { - one: "", - two: "", - three: "", - four: "", - }; - // 隐藏所有模块 - moduleVisibility.value = { - one: false, - two: false, - three: false, - four: false, - disclaimer: false, - }; // 隐藏所有图表组件 chartVisibility.value = { marketTemperature: false, @@ -947,7 +901,7 @@ watch( { deep: true } ); -// 监听当前股票变化,重新渲染图表 +// 监听当前股票变化,重新渲染图表和显示对应结论 watch( currentStock, (newStock) => { @@ -968,38 +922,18 @@ watch( // 获取股票代码作为唯一标识 const stockCode = newStock.stockInfo?.code || newStock.stockInfo?.symbol; - // 检查该股票是否已经显示过打字机效果 - if (stockCode && stockTypewriterShown.value.has(stockCode)) { - // 如果已经显示过,直接显示完整文本和标题 - if (newStock.conclusionData) { - try { - // 如果conclusionData已经是对象,直接使用;否则解析JSON - const conclusion = - typeof newStock.conclusionData === "object" - ? newStock.conclusionData - : JSON.parse(newStock.conclusionData); - displayedTexts.value = { - one1: conclusion.one1 || "", - one2: conclusion.one2 || "", - two: conclusion.two || "", - three: conclusion.three || "", - four: conclusion.four || "", - disclaimer: "该内容由AI生成,请注意甄别", - }; - displayedTitles.value = { - one: "L1: 情绪监控", - two: "L2: 情绪解码", - three: "L3: 情绪推演", - four: "L4: 情绪套利", - }; - // 显示所有有内容的模块 - moduleVisibility.value = { - one: !!(conclusion.one1 || conclusion.one2), - two: !!conclusion.two, - three: !!conclusion.three, - four: !!conclusion.four, - disclaimer: true, - }; + // 处理当前股票的音频URL + if (newStock.conclusionData) { + try { + // 如果conclusionData已经是对象,直接使用;否则解析JSON + const conclusion = + typeof newStock.conclusionData === "object" + ? newStock.conclusionData + : JSON.parse(newStock.conclusionData); + + // 检查该股票是否已经显示过打字机效果 + if (stockCode && stockTypewriterShown.value.has(stockCode)) { + // 如果已经显示过,直接显示完整内容,不需要打字机效果 // 提取音频URL但不自动播放,等待用户手动点击 let voiceUrl = null; @@ -1063,104 +997,16 @@ watch( emotionAudioStore.setCurrentAudioUrl(voiceUrl); // 不自动播放,等待用户手动点击 } - } catch (error) { - console.error("解析股票结论数据失败:", error); } - } - } else { - // 如果没有显示过,清空显示文本,等待打字机效果 - displayedTexts.value = { - one1: "", - one2: "", - two: "", - three: "", - four: "", - disclaimer: "", - }; - displayedTitles.value = { - one: "", - two: "", - three: "", - four: "", - }; - moduleVisibility.value = { - one: false, - two: false, - three: false, - four: false, - disclaimer: false, - }; - - // 即使没有显示过,也需要设置音频URL以便用户手动播放 - if (newStock.conclusionData) { - try { - // 如果conclusionData已经是对象,直接使用;否则解析JSON - const conclusion = - typeof newStock.conclusionData === "object" - ? newStock.conclusionData - : JSON.parse(newStock.conclusionData); - let voiceUrl = null; - // 优先使用one1_url,如果没有则尝试其他音频URL - if (conclusion.one1_url) { - voiceUrl = conclusion.one1_url - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.one2_url) { - voiceUrl = conclusion.one2_url - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.two_url) { - voiceUrl = conclusion.two_url - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.three_url) { - voiceUrl = conclusion.three_url - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.four_url) { - voiceUrl = conclusion.four_url - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.url) { - voiceUrl = conclusion.url.toString().trim().replace(/[`\s]/g, ""); - } else if (conclusion.audioUrl) { - voiceUrl = conclusion.audioUrl - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.voice_url) { - voiceUrl = conclusion.voice_url - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.audio) { - voiceUrl = conclusion.audio - .toString() - .trim() - .replace(/[`\s]/g, ""); - } else if (conclusion.tts_url) { - voiceUrl = conclusion.tts_url - .toString() - .trim() - .replace(/[`\s]/g, ""); - } - - if (voiceUrl && voiceUrl.startsWith("http")) { - console.log("切换到未显示股票,准备音频URL:", voiceUrl); - audioUrl.value = voiceUrl; - // 同时更新store中的音频URL - emotionAudioStore.setCurrentAudioUrl(voiceUrl); - } - } catch (error) { + } catch (error) { console.error("解析股票结论数据失败:", error); } + } else { + // 如果没有结论数据,清空音频URL + audioUrl.value = ""; + emotionAudioStore.resetAudioState(); + console.log("当前股票没有结论数据,已清空音频"); } - } // 只有在页面已加载的情况下才渲染图表 if (isPageLoaded.value) { @@ -1213,7 +1059,7 @@ watch( playAudioQueue(parsedConclusion.value, true); } else { // 如果音频已播放过,只启动打字机效果 - startTypewriterEffect(parsedConclusion.value); + startTypewriterEffect(parsedConclusion.value, stockCode); } stockTypewriterShown.value.set(stockCode, true); @@ -1229,27 +1075,7 @@ watch( ); const conclusion = parsedConclusion.value; - displayedTexts.value = { - one1: conclusion.one1 || "", - one2: conclusion.one2 || "", - two: conclusion.two || "", - three: conclusion.three || "", - four: conclusion.four || "", - disclaimer: "该内容由AI生成,请注意甄别", - }; - displayedTitles.value = { - one: "L1: 情绪监控", - two: "L2: 情绪解码", - three: "L3: 情绪推演", - four: "L4: 情绪套利", - }; - moduleVisibility.value = { - one: !!(conclusion.one1 || conclusion.one2), - two: !!conclusion.two, - three: !!conclusion.three, - four: !!conclusion.four, - disclaimer: true, - }; + // 结论内容现在直接通过parsedConclusion计算属性显示 stockTypewriterShown.value.set(stockCode, true); stockAudioPlayed.value.set(stockCode, true); @@ -1381,8 +1207,19 @@ watch( ); // 打字机效果函数 -function startTypewriterEffect(conclusion, onComplete) { - console.log("开始打字机效果,结论数据:", conclusion); +function startTypewriterEffect(conclusion, stockId, onComplete) { + // 如果没有传入stockId,使用当前活跃股票 + if (!stockId && emotionStore.activeStock) { + const stock = emotionStore.activeStock; + stockId = stock.stockInfo?.code || stock.stockInfo?.symbol; + } + + if (!stockId) { + console.warn("无法确定股票ID,跳过打字机效果"); + return; + } + + console.log("开始打字机效果,结论数据:", conclusion, "股票ID:", stockId); // 保存当前的完成回调函数 currentOnCompleteCallback.value = onComplete; @@ -1398,30 +1235,44 @@ function startTypewriterEffect(conclusion, onComplete) { typewriterTimers.value.forEach((timer) => clearTimeout(timer)); typewriterTimers.value = []; - // 重置显示文本和状态 - displayedTexts.value = { + // 初始化该股票的打字机状态 + if (!stockTypewriterTexts.value.has(stockId)) { + stockTypewriterTexts.value.set(stockId, { + one1: "", + one2: "", + two: "", + three: "", + four: "", + disclaimer: "", + }); + } + if (!stockTypewriterVisibility.value.has(stockId)) { + stockTypewriterVisibility.value.set(stockId, { + one: false, + two: false, + three: false, + four: false, + disclaimer: false, + }); + } + + // 重置该股票的显示文本和状态 + stockTypewriterTexts.value.set(stockId, { one1: "", one2: "", two: "", three: "", four: "", disclaimer: "", - }; - - displayedTitles.value = { - one: "", - two: "", - three: "", - four: "", - }; + }); - moduleVisibility.value = { + stockTypewriterVisibility.value.set(stockId, { one: false, two: false, three: false, four: false, disclaimer: false, - }; + }); // 定义打字速度(毫秒) const typeSpeed = 200; @@ -1469,35 +1320,32 @@ function startTypewriterEffect(conclusion, onComplete) { if (!hasContent) return; console.log(`开始显示模块 ${module.key}`); + // 显示模块 const showModuleTimer = setTimeout(() => { - moduleVisibility.value[module.key] = true; - console.log(`模块 ${module.key} 已设置为可见`); + const visibility = stockTypewriterVisibility.value.get(stockId); + if (visibility) { + visibility[module.key] = true; + stockTypewriterVisibility.value.set(stockId, { ...visibility }); + } }, totalDelay); typewriterTimers.value.push(showModuleTimer); totalDelay += 100; - // 打字机效果显示标题 - const title = module.title; - for (let i = 0; i <= title.length; i++) { - const timer = setTimeout(() => { - displayedTitles.value[module.key] = title.substring(0, i); - }, totalDelay + i * typeSpeed); - typewriterTimers.value.push(timer); - } - totalDelay += title.length * typeSpeed + 300; // 标题完成后间隔 - - // 打字机效果显示内容 + // 为每个内容项添加打字机效果 module.contents.forEach((content) => { if (content.text && content.text.trim()) { - const text = content.text; - for (let i = 0; i <= text.length; i++) { + for (let i = 0; i <= content.text.length; i++) { const timer = setTimeout(() => { - displayedTexts.value[content.key] = text.substring(0, i); + const texts = stockTypewriterTexts.value.get(stockId); + if (texts) { + texts[content.key] = content.text.substring(0, i); + stockTypewriterTexts.value.set(stockId, { ...texts }); + } }, totalDelay + i * typeSpeed); typewriterTimers.value.push(timer); } - totalDelay += text.length * typeSpeed + 500; // 内容完成后间隔 + totalDelay += content.text.length * typeSpeed + 200; // 内容间间隔 } }); @@ -1509,7 +1357,11 @@ function startTypewriterEffect(conclusion, onComplete) { // 显示免责声明模块 const showDisclaimerTimer = setTimeout(() => { - moduleVisibility.value.disclaimer = true; + const visibility = stockTypewriterVisibility.value.get(stockId); + if (visibility) { + visibility.disclaimer = true; + stockTypewriterVisibility.value.set(stockId, { ...visibility }); + } }, totalDelay); typewriterTimers.value.push(showDisclaimerTimer); totalDelay += 100; @@ -1517,7 +1369,11 @@ function startTypewriterEffect(conclusion, onComplete) { // 打字机效果显示免责声明 for (let i = 0; i <= disclaimerText.length; i++) { const timer = setTimeout(() => { - displayedTexts.value.disclaimer = disclaimerText.substring(0, i); + const texts = stockTypewriterTexts.value.get(stockId); + if (texts) { + texts.disclaimer = disclaimerText.substring(0, i); + stockTypewriterTexts.value.set(stockId, { ...texts }); + } // 在免责声明打字机效果完成后调用回调函数 if (i === disclaimerText.length) { console.log("打字机效果完成,调用onComplete回调"); @@ -1628,7 +1484,9 @@ const playNextAudio = () => { parsedConclusion.value ) { console.log("🎬 第一个音频开始播放,同时启动打字机效果"); - startTypewriterEffect(parsedConclusion.value, audioInfo.onComplete); + const currentStock = emotionStore.activeStock; + const stockId = currentStock?.stockInfo?.code || currentStock?.stockInfo?.symbol; + startTypewriterEffect(parsedConclusion.value, stockId, audioInfo.onComplete); } }, onpause: () => { @@ -1854,7 +1712,9 @@ function playAudioQueue( // 如果没有音频但需要启动打字机效果,直接启动 if (shouldStartTypewriter) { console.log("没有音频但需要启动打字机效果"); - startTypewriterEffect(conclusion, onComplete); + const currentStock = emotionStore.activeStock; + const stockId = currentStock?.stockInfo?.code || currentStock?.stockInfo?.symbol; + startTypewriterEffect(conclusion, stockId, onComplete); } } else { console.log(`总共找到 ${audioQueue.value.length} 个音频,准备播放`); @@ -2295,7 +2155,8 @@ async function handleSendMessage(input, onComplete) { playAudioQueue(parsedConclusion.value, true, onComplete); } else { // 如果音频已播放过,只启动打字机效果 - startTypewriterEffect(parsedConclusion.value, onComplete); + const stockCode = currentStock.value?.stockInfo?.code || currentStock.value?.stockInfo?.symbol; + startTypewriterEffect(parsedConclusion.value, stockCode, onComplete); } stockTypewriterShown.value.set(stockCode, true); } else { @@ -2975,7 +2836,7 @@ function setupIntersectionObserver() { playAudioQueue(parsedConclusion.value, true); } else { // 如果音频已播放过,只启动打字机效果 - startTypewriterEffect(parsedConclusion.value); + startTypewriterEffect(parsedConclusion.value, stockCode); } stockTypewriterShown.value.set(stockCode, true); @@ -2985,29 +2846,7 @@ function setupIntersectionObserver() { "非用户主动搜索,该股票第一次进入场景应用,直接显示完整内容" ); - const conclusion = parsedConclusion.value; - displayedTexts.value = { - one1: conclusion.one1 || "", - one2: conclusion.one2 || "", - two: conclusion.two || "", - three: conclusion.three || "", - four: conclusion.four || "", - disclaimer: "该内容由AI生成,请注意甄别", - }; - displayedTitles.value = { - one: "L1: 情绪监控", - two: "L2: 情绪解码", - three: "L3: 情绪推演", - four: "L4: 情绪套利", - }; - // 显示所有有内容的模块 - moduleVisibility.value = { - one: !!(conclusion.one1 || conclusion.one2), - two: !!conclusion.two, - three: !!conclusion.three, - four: !!conclusion.four, - disclaimer: true, - }; + // 结论内容现在直接通过parsedConclusion计算属性显示 // 记录该股票已显示过 stockTypewriterShown.value.set(stockCode, true); @@ -3017,30 +2856,7 @@ function setupIntersectionObserver() { // 非第一次或已经触发过:直接显示完整内容,不播放音频和打字机效果 console.log("非第一次进入场景应用或已触发过,直接显示完整内容"); - // 直接显示完整内容 - const conclusion = parsedConclusion.value; - displayedTexts.value = { - one1: conclusion.one1 || "", - one2: conclusion.one2 || "", - two: conclusion.two || "", - three: conclusion.three || "", - four: conclusion.four || "", - disclaimer: "该内容由AI生成,请注意甄别", - }; - displayedTitles.value = { - one: "L1: 情绪监控", - two: "L2: 情绪解码", - three: "L3: 情绪推演", - four: "L4: 情绪套利", - }; - // 显示所有有内容的模块 - moduleVisibility.value = { - one: !!(conclusion.one1 || conclusion.one2), - two: !!conclusion.two, - three: !!conclusion.three, - four: !!conclusion.four, - disclaimer: true, - }; + // 结论内容现在直接通过parsedConclusion计算属性显示 } } } @@ -3358,31 +3174,8 @@ onMounted(async () => { if (currentStockData.conclusionData) { conclusionData.value = currentStockData.conclusionData; - // 直接显示所有内容,不使用打字机效果 - const conclusion = currentStockData.conclusionData; - displayedTexts.value = { - one1: conclusion.one1 || "", - one2: conclusion.one2 || "", - two: conclusion.two || "", - three: conclusion.three || "", - four: conclusion.four || "", - disclaimer: "该内容由AI生成,请注意甄别", - }; - - displayedTitles.value = { - one: conclusion.one1 || conclusion.one2 ? "L1: 情绪监控" : "", - two: conclusion.two ? "L2: 情绪解码" : "", - three: conclusion.three ? "L3: 情绪推演" : "", - four: conclusion.four ? "L4: 情绪套利" : "", - }; - - moduleVisibility.value = { - one: !!(conclusion.one1 || conclusion.one2), - two: !!conclusion.two, - three: !!conclusion.three, - four: !!conclusion.four, - disclaimer: true, - }; + // 结论内容现在直接通过parsedConclusion计算属性显示 + conclusionData.value = currentStockData.conclusionData; // 标记该股票的打字机效果和音频已经显示过,避免后续自动触发 const stockCode =