|
@ -247,6 +247,7 @@ const scenarioApplicationRef = ref(null); // 场景应用部分的引用 |
|
|
const hasTriggeredAudio = ref(false); // 是否已触发音频播放 |
|
|
const hasTriggeredAudio = ref(false); // 是否已触发音频播放 |
|
|
const hasTriggeredTypewriter = ref(false); // 是否已触发打字机效果 |
|
|
const hasTriggeredTypewriter = ref(false); // 是否已触发打字机效果 |
|
|
const intersectionObserver = ref(null); // 存储observer实例 |
|
|
const intersectionObserver = ref(null); // 存储observer实例 |
|
|
|
|
|
const isUserInitiated = ref(false); // 标记是否为用户主动搜索 |
|
|
|
|
|
|
|
|
// 显示的文本内容(用于打字机效果) |
|
|
// 显示的文本内容(用于打字机效果) |
|
|
const displayedTexts = ref({ |
|
|
const displayedTexts = ref({ |
|
@ -325,8 +326,8 @@ watch(currentStock, (newStock) => { |
|
|
stopAudio(); |
|
|
stopAudio(); |
|
|
// 清理音频URL,确保不会播放之前股票的音频 |
|
|
// 清理音频URL,确保不会播放之前股票的音频 |
|
|
audioUrl.value = ''; |
|
|
audioUrl.value = ''; |
|
|
// 只停止音频播放,不清理currentAudioUrl,让新音频能正确设置 |
|
|
|
|
|
emotionAudioStore.stop(); |
|
|
|
|
|
|
|
|
// 清理store中的音频URL,确保不会播放之前股票的音频 |
|
|
|
|
|
emotionAudioStore.resetAudioState(); |
|
|
// 清理正在进行的打字机效果定时器 |
|
|
// 清理正在进行的打字机效果定时器 |
|
|
clearTypewriterTimers(); |
|
|
clearTypewriterTimers(); |
|
|
// 重置触发状态,让每个股票都能独立触发效果 |
|
|
// 重置触发状态,让每个股票都能独立触发效果 |
|
@ -365,8 +366,7 @@ watch(currentStock, (newStock) => { |
|
|
disclaimer: true |
|
|
disclaimer: true |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// 重要:即使已经显示过,也要播放当前股票的音频 |
|
|
|
|
|
// 提取音频URL并播放 |
|
|
|
|
|
|
|
|
// 提取音频URL但不自动播放,等待用户手动点击 |
|
|
let voiceUrl = null; |
|
|
let voiceUrl = null; |
|
|
if (conclusion.url) { |
|
|
if (conclusion.url) { |
|
|
voiceUrl = conclusion.url.toString().trim().replace(/[`\s]/g, ''); |
|
|
voiceUrl = conclusion.url.toString().trim().replace(/[`\s]/g, ''); |
|
@ -381,10 +381,11 @@ watch(currentStock, (newStock) => { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (voiceUrl && voiceUrl.startsWith('http')) { |
|
|
if (voiceUrl && voiceUrl.startsWith('http')) { |
|
|
console.log('切换到已显示股票,播放对应音频:', voiceUrl); |
|
|
|
|
|
|
|
|
console.log('切换到已显示股票,准备音频URL但不自动播放:', voiceUrl); |
|
|
audioUrl.value = voiceUrl; |
|
|
audioUrl.value = voiceUrl; |
|
|
// 立即播放当前股票的音频 |
|
|
|
|
|
playAudio(voiceUrl); |
|
|
|
|
|
|
|
|
// 同时更新store中的音频URL |
|
|
|
|
|
emotionAudioStore.setCurrentAudioUrl(voiceUrl); |
|
|
|
|
|
// 不自动播放,等待用户手动点击 |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error('解析结论数据失败:', error); |
|
|
console.error('解析结论数据失败:', error); |
|
@ -413,6 +414,34 @@ watch(currentStock, (newStock) => { |
|
|
four: false, |
|
|
four: false, |
|
|
disclaimer: false |
|
|
disclaimer: false |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 即使没有显示过,也需要设置音频URL以便用户手动播放 |
|
|
|
|
|
if (newStock.conclusionData) { |
|
|
|
|
|
try { |
|
|
|
|
|
const conclusion = JSON.parse(newStock.conclusionData); |
|
|
|
|
|
let voiceUrl = null; |
|
|
|
|
|
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) { |
|
|
|
|
|
console.error('解析结论数据失败:', error); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 只有在页面已加载的情况下才渲染图表 |
|
|
// 只有在页面已加载的情况下才渲染图表 |
|
@ -523,37 +552,13 @@ watch(parsedConclusion, (newConclusion) => { |
|
|
if (voiceUrl && voiceUrl.startsWith('http')) { |
|
|
if (voiceUrl && voiceUrl.startsWith('http')) { |
|
|
console.log('找到并清理后的语音URL:', voiceUrl); |
|
|
console.log('找到并清理后的语音URL:', voiceUrl); |
|
|
audioUrl.value = voiceUrl; |
|
|
audioUrl.value = voiceUrl; |
|
|
|
|
|
// 同时更新store中的音频URL |
|
|
|
|
|
emotionAudioStore.setCurrentAudioUrl(voiceUrl); |
|
|
console.log('音频URL已准备,检查是否需要立即触发效果'); |
|
|
console.log('音频URL已准备,检查是否需要立即触发效果'); |
|
|
|
|
|
|
|
|
// 音频准备好后,如果页面已加载则立即触发效果 |
|
|
|
|
|
nextTick(() => { |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
if (currentStock.value?.stockInfo && isPageLoaded.value) { |
|
|
|
|
|
const stockCode = currentStock.value.stockInfo.code || currentStock.value.stockInfo.symbol; |
|
|
|
|
|
|
|
|
|
|
|
if (parsedConclusion.value && stockCode) { |
|
|
|
|
|
// 如果该股票已经显示过,不需要再处理 |
|
|
|
|
|
if (stockTypewriterShown.value.has(stockCode)) { |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 该股票第一次:播放音频和打字机效果 |
|
|
|
|
|
console.log('音频准备完成且页面已加载,立即触发效果'); |
|
|
|
|
|
hasTriggeredTypewriter.value = true; |
|
|
|
|
|
hasTriggeredAudio.value = true; |
|
|
|
|
|
|
|
|
|
|
|
startTypewriterEffect(parsedConclusion.value); |
|
|
|
|
|
|
|
|
|
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
|
|
|
|
console.log('立即开始音频播放'); |
|
|
|
|
|
stockAudioPlayed.value.set(stockCode, true); |
|
|
|
|
|
playAudio(audioUrl.value); |
|
|
|
|
|
} |
|
|
|
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, 100); // 短暂延迟确保DOM更新完成 |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// 音频准备好后,只有在用户主动搜索时才自动触发效果 |
|
|
|
|
|
// 数据恢复时不自动播放音频和打字机效果 |
|
|
|
|
|
console.log('音频URL已准备完成,等待用户手动触发播放'); |
|
|
} else { |
|
|
} else { |
|
|
console.log('未找到有效的语音URL,原始URL:', newConclusion.url); |
|
|
console.log('未找到有效的语音URL,原始URL:', newConclusion.url); |
|
|
console.log('结论数据中的所有字段:', Object.keys(newConclusion)); |
|
|
console.log('结论数据中的所有字段:', Object.keys(newConclusion)); |
|
@ -806,6 +811,8 @@ function startImageRotation() { |
|
|
// 发送消息方法 |
|
|
// 发送消息方法 |
|
|
async function handleSendMessage(input) { |
|
|
async function handleSendMessage(input) { |
|
|
console.log("发送内容:", input); |
|
|
console.log("发送内容:", input); |
|
|
|
|
|
// 标记为用户主动搜索 |
|
|
|
|
|
isUserInitiated.value = true; |
|
|
|
|
|
|
|
|
// 检查用户输入内容是否为空 |
|
|
// 检查用户输入内容是否为空 |
|
|
if (!input || !input.trim()) { |
|
|
if (!input || !input.trim()) { |
|
@ -931,11 +938,11 @@ async function handleSendMessage(input) { |
|
|
renderCharts(currentStock.value.apiData); |
|
|
renderCharts(currentStock.value.apiData); |
|
|
console.log('数据加载完成后开始渲染图表'); |
|
|
console.log('数据加载完成后开始渲染图表'); |
|
|
|
|
|
|
|
|
// 数据加载完成后立即触发音频和文本,不等待滚动到视口 |
|
|
|
|
|
if (parsedConclusion.value && audioUrl.value) { |
|
|
|
|
|
|
|
|
// 只有在用户主动搜索时才自动触发音频和文本 |
|
|
|
|
|
if (isUserInitiated.value && parsedConclusion.value && audioUrl.value) { |
|
|
const stockCode = currentStock.value.stockInfo?.code || currentStock.value.stockInfo?.symbol; |
|
|
const stockCode = currentStock.value.stockInfo?.code || currentStock.value.stockInfo?.symbol; |
|
|
if (stockCode && !stockTypewriterShown.value.has(stockCode)) { |
|
|
if (stockCode && !stockTypewriterShown.value.has(stockCode)) { |
|
|
console.log('数据加载完成,立即触发音频和打字机效果'); |
|
|
|
|
|
|
|
|
console.log('用户主动搜索,立即触发音频和打字机效果'); |
|
|
startTypewriterEffect(parsedConclusion.value); |
|
|
startTypewriterEffect(parsedConclusion.value); |
|
|
|
|
|
|
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
@ -945,6 +952,9 @@ async function handleSendMessage(input) { |
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 重置用户主动搜索标志 |
|
|
|
|
|
isUserInitiated.value = false; |
|
|
} |
|
|
} |
|
|
}); |
|
|
}); |
|
|
} else { |
|
|
} else { |
|
@ -1189,28 +1199,37 @@ function setupIntersectionObserver() { |
|
|
if (parsedConclusion.value && stockCode) { |
|
|
if (parsedConclusion.value && stockCode) { |
|
|
// 检查该股票是否是第一次触发 |
|
|
// 检查该股票是否是第一次触发 |
|
|
if (!stockTypewriterShown.value.has(stockCode)) { |
|
|
if (!stockTypewriterShown.value.has(stockCode)) { |
|
|
// 该股票第一次:播放音频和打字机效果 |
|
|
|
|
|
if (audioUrl.value) { |
|
|
|
|
|
console.log('该股票第一次进入场景应用,开始打字机效果和音频播放'); |
|
|
|
|
|
hasTriggeredTypewriter.value = true; |
|
|
|
|
|
hasTriggeredAudio.value = true; |
|
|
|
|
|
|
|
|
|
|
|
// 开始打字机效果 |
|
|
|
|
|
startTypewriterEffect(parsedConclusion.value); |
|
|
|
|
|
|
|
|
|
|
|
// 播放音频 |
|
|
|
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
|
|
|
|
console.log('开始音频播放'); |
|
|
|
|
|
stockAudioPlayed.value.set(stockCode, true); |
|
|
|
|
|
playAudio(audioUrl.value); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 该股票第一次进入视口:只显示文本,不自动播放音频和打字机效果 |
|
|
|
|
|
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 |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// 记录该股票已显示过 |
|
|
|
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
|
|
|
} else { |
|
|
|
|
|
console.log('音频尚未准备好,等待音频加载完成后再触发效果'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// 记录该股票已显示过,但不播放音频 |
|
|
|
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
|
|
|
stockAudioPlayed.value.set(stockCode, true); // 标记为已播放,避免后续自动播放 |
|
|
} else { |
|
|
} else { |
|
|
// 非第一次或已经触发过:直接显示完整内容,不播放音频和打字机效果 |
|
|
// 非第一次或已经触发过:直接显示完整内容,不播放音频和打字机效果 |
|
|
console.log('非第一次进入场景应用或已触发过,直接显示完整内容'); |
|
|
console.log('非第一次进入场景应用或已触发过,直接显示完整内容'); |
|
@ -1285,11 +1304,40 @@ onMounted(async () => { |
|
|
|
|
|
|
|
|
startImageRotation(); |
|
|
startImageRotation(); |
|
|
|
|
|
|
|
|
// 等待DOM完全渲染后设置监听器 |
|
|
|
|
|
nextTick(() => { |
|
|
|
|
|
setupIntersectionObserver(); |
|
|
|
|
|
// 移除自动滚动触发,改为在第二个工作流接口调用时触发 |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
// 检查是否有已保存的股票数据需要恢复 |
|
|
|
|
|
if (emotionStore.stockList.length > 0 && emotionStore.activeStock) { |
|
|
|
|
|
console.log('检测到已保存的股票数据,开始恢复页面状态(不自动播放音频)'); |
|
|
|
|
|
|
|
|
|
|
|
// 恢复页面加载状态 |
|
|
|
|
|
isPageLoaded.value = true; |
|
|
|
|
|
|
|
|
|
|
|
// 等待DOM渲染后恢复图表和数据 |
|
|
|
|
|
nextTick(() => { |
|
|
|
|
|
const currentStockData = emotionStore.activeStock; |
|
|
|
|
|
if (currentStockData && currentStockData.apiData) { |
|
|
|
|
|
console.log('恢复图表数据:', currentStockData.stockInfo.name); |
|
|
|
|
|
renderCharts(currentStockData.apiData); |
|
|
|
|
|
|
|
|
|
|
|
// 恢复结论数据但不触发音频和打字机效果 |
|
|
|
|
|
if (currentStockData.conclusionData) { |
|
|
|
|
|
conclusionData.value = currentStockData.conclusionData; |
|
|
|
|
|
// 标记该股票的打字机效果和音频已经显示过,避免后续自动触发 |
|
|
|
|
|
const stockCode = currentStockData.stockInfo?.code || currentStockData.stockInfo?.symbol; |
|
|
|
|
|
if (stockCode) { |
|
|
|
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
|
|
|
stockAudioPlayed.value.set(stockCode, true); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
setupIntersectionObserver(); |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 没有保存的数据,正常设置监听器 |
|
|
|
|
|
nextTick(() => { |
|
|
|
|
|
setupIntersectionObserver(); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
// 组件卸载时清理定时器、音频和observer |
|
|
// 组件卸载时清理定时器、音频和observer |
|
|