|
@ -24,6 +24,7 @@ |
|
|
/> |
|
|
/> |
|
|
<div class="message-bubble user-message"> |
|
|
<div class="message-bubble user-message"> |
|
|
{{ message.text }} |
|
|
{{ message.text }} |
|
|
|
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<!-- AI返回结果 --> |
|
|
<!-- AI返回结果 --> |
|
@ -152,26 +153,27 @@ |
|
|
<img src="@/assets/img/AiEmotion/场景应用.png" alt="场景应用标题"> |
|
|
<img src="@/assets/img/AiEmotion/场景应用.png" alt="场景应用标题"> |
|
|
<div class="bk-image"> |
|
|
<div class="bk-image"> |
|
|
<div class="conclusion-container" v-if="getStockConclusion(stock)"> |
|
|
<div class="conclusion-container" v-if="getStockConclusion(stock)"> |
|
|
<div class="conclusion-item" v-if="(getStockConclusion(stock).one1 || getStockConclusion(stock).one2)"> |
|
|
|
|
|
<h4 class="conclusion-title">L1: 情绪监控</h4> |
|
|
|
|
|
<p class="conclusion-text" v-if="getStockConclusion(stock).one1">{{ getStockConclusion(stock).one1 }}</p> |
|
|
|
|
|
<p class="conclusion-text" v-if="getStockConclusion(stock).one2">{{ getStockConclusion(stock).one2 }}</p> |
|
|
|
|
|
|
|
|
<!-- 打字机效果显示的内容 --> |
|
|
|
|
|
<div class="conclusion-item" v-if="moduleVisibility.one"> |
|
|
|
|
|
<h4 class="conclusion-title">{{ displayedTitles.one }}</h4> |
|
|
|
|
|
<p class="conclusion-text" v-if="displayedTexts.one1">{{ displayedTexts.one1 }}</p> |
|
|
|
|
|
<p class="conclusion-text" v-if="displayedTexts.one2">{{ displayedTexts.one2 }}</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="conclusion-item" v-if="getStockConclusion(stock).two"> |
|
|
|
|
|
<h4 class="conclusion-title">L2: 情绪解码</h4> |
|
|
|
|
|
<p class="conclusion-text">{{ getStockConclusion(stock).two }}</p> |
|
|
|
|
|
|
|
|
<div class="conclusion-item" v-if="moduleVisibility.two"> |
|
|
|
|
|
<h4 class="conclusion-title">{{ displayedTitles.two }}</h4> |
|
|
|
|
|
<p class="conclusion-text">{{ displayedTexts.two }}</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="conclusion-item" v-if="getStockConclusion(stock).three"> |
|
|
|
|
|
<h4 class="conclusion-title">L3: 情绪推演</h4> |
|
|
|
|
|
<p class="conclusion-text">{{ getStockConclusion(stock).three }}</p> |
|
|
|
|
|
|
|
|
<div class="conclusion-item" v-if="moduleVisibility.three"> |
|
|
|
|
|
<h4 class="conclusion-title">{{ displayedTitles.three }}</h4> |
|
|
|
|
|
<p class="conclusion-text">{{ displayedTexts.three }}</p> |
|
|
</div> |
|
|
</div> |
|
|
<div class="conclusion-item" v-if="getStockConclusion(stock).four"> |
|
|
|
|
|
<h4 class="conclusion-title">L4: 情绪套利</h4> |
|
|
|
|
|
<p class="conclusion-text">{{ getStockConclusion(stock).four }}</p> |
|
|
|
|
|
|
|
|
<div class="conclusion-item" v-if="moduleVisibility.four"> |
|
|
|
|
|
<h4 class="conclusion-title">{{ displayedTitles.four }}</h4> |
|
|
|
|
|
<p class="conclusion-text">{{ displayedTexts.four }}</p> |
|
|
</div> |
|
|
</div> |
|
|
<!-- AI生成内容免责声明 --> |
|
|
<!-- AI生成内容免责声明 --> |
|
|
<div class="disclaimer-item" v-if="getStockConclusion(stock)"> |
|
|
|
|
|
<p class="disclaimer-text">该内容由AI生成,请注意甄别</p> |
|
|
|
|
|
|
|
|
<div class="disclaimer-item" v-if="moduleVisibility.disclaimer"> |
|
|
|
|
|
<p class="disclaimer-text">{{ displayedTexts.disclaimer }}</p> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="conclusion-placeholder" v-else> |
|
|
<div class="conclusion-placeholder" v-else> |
|
@ -212,6 +214,7 @@ import { Howl, Howler } from 'howler'; // 导入音频播放库 |
|
|
import { reactive } from 'vue'; |
|
|
import { reactive } from 'vue'; |
|
|
import { marked } from 'marked'; // 引入marked库 |
|
|
import { marked } from 'marked'; // 引入marked库 |
|
|
import { useUserStore } from "../store/userPessionCode"; |
|
|
import { useUserStore } from "../store/userPessionCode"; |
|
|
|
|
|
const APIurl = import.meta.env.VITE_APP_API_BASE_URL; |
|
|
|
|
|
|
|
|
// 使用Pinia store |
|
|
// 使用Pinia store |
|
|
const emotionStore = useEmotionStore(); |
|
|
const emotionStore = useEmotionStore(); |
|
@ -224,12 +227,26 @@ const toggleVoiceForUser = () => { |
|
|
emotionAudioStore.toggleVoice(); |
|
|
emotionAudioStore.toggleVoice(); |
|
|
} else { |
|
|
} else { |
|
|
// 如果语音功能开启,则切换播放/暂停状态 |
|
|
// 如果语音功能开启,则切换播放/暂停状态 |
|
|
if (emotionAudioStore.currentAudioUrl || emotionAudioStore.ttsUrl) { |
|
|
|
|
|
// 有音频时切换播放/暂停 |
|
|
|
|
|
|
|
|
if (emotionAudioStore.isPlaying) { |
|
|
|
|
|
// 如果正在播放,则暂停 |
|
|
emotionAudioStore.togglePlayPause(); |
|
|
emotionAudioStore.togglePlayPause(); |
|
|
} else { |
|
|
} else { |
|
|
// 没有音频时关闭语音功能 |
|
|
|
|
|
emotionAudioStore.toggleVoice(); |
|
|
|
|
|
|
|
|
// 如果没有在播放,先检查是否处于暂停状态 |
|
|
|
|
|
if (emotionAudioStore.isPaused && (emotionAudioStore.currentAudioUrl || emotionAudioStore.ttsUrl)) { |
|
|
|
|
|
// 如果处于暂停状态且有音频,则继续播放 |
|
|
|
|
|
console.log('从暂停状态继续播放'); |
|
|
|
|
|
emotionAudioStore.togglePlayPause(); |
|
|
|
|
|
} else if (parsedConclusion.value && (parsedConclusion.value.one1_url || parsedConclusion.value.two_url || parsedConclusion.value.three_url || parsedConclusion.value.four_url)) { |
|
|
|
|
|
// 有结论数据时,重新播放整个音频队列 |
|
|
|
|
|
console.log('用户点击播放,重新播放音频队列'); |
|
|
|
|
|
playAudioQueue(parsedConclusion.value, false); // 不启动打字机效果,因为内容已经显示 |
|
|
|
|
|
} else if (emotionAudioStore.currentAudioUrl || emotionAudioStore.ttsUrl) { |
|
|
|
|
|
// 有单个音频URL时切换播放/暂停 |
|
|
|
|
|
emotionAudioStore.togglePlayPause(); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 没有音频时关闭语音功能 |
|
|
|
|
|
emotionAudioStore.toggleVoice(); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
@ -444,6 +461,13 @@ const currentConclusion = computed(() => { |
|
|
}); |
|
|
}); |
|
|
const parsedConclusion = computed(() => { |
|
|
const parsedConclusion = computed(() => { |
|
|
if (!currentConclusion.value) return null; |
|
|
if (!currentConclusion.value) return null; |
|
|
|
|
|
|
|
|
|
|
|
// 如果conclusionData已经是对象,直接返回 |
|
|
|
|
|
if (typeof currentConclusion.value === 'object') { |
|
|
|
|
|
return currentConclusion.value; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果是字符串,尝试解析JSON |
|
|
try { |
|
|
try { |
|
|
return JSON.parse(currentConclusion.value); |
|
|
return JSON.parse(currentConclusion.value); |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
@ -488,6 +512,13 @@ const getStockData2 = (stock) => { |
|
|
// 辅助函数:获取股票的结论数据 |
|
|
// 辅助函数:获取股票的结论数据 |
|
|
const getStockConclusion = (stock) => { |
|
|
const getStockConclusion = (stock) => { |
|
|
if (!stock?.conclusionData) return null; |
|
|
if (!stock?.conclusionData) return null; |
|
|
|
|
|
|
|
|
|
|
|
// 如果conclusionData已经是对象,直接返回 |
|
|
|
|
|
if (typeof stock.conclusionData === 'object') { |
|
|
|
|
|
return stock.conclusionData; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 如果是字符串,尝试解析JSON |
|
|
try { |
|
|
try { |
|
|
return JSON.parse(stock.conclusionData); |
|
|
return JSON.parse(stock.conclusionData); |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
@ -578,7 +609,10 @@ watch(currentStock, (newStock) => { |
|
|
// 如果已经显示过,直接显示完整文本和标题 |
|
|
// 如果已经显示过,直接显示完整文本和标题 |
|
|
if (newStock.conclusionData) { |
|
|
if (newStock.conclusionData) { |
|
|
try { |
|
|
try { |
|
|
const conclusion = JSON.parse(newStock.conclusionData); |
|
|
|
|
|
|
|
|
// 如果conclusionData已经是对象,直接使用;否则解析JSON |
|
|
|
|
|
const conclusion = typeof newStock.conclusionData === 'object' |
|
|
|
|
|
? newStock.conclusionData |
|
|
|
|
|
: JSON.parse(newStock.conclusionData); |
|
|
displayedTexts.value = { |
|
|
displayedTexts.value = { |
|
|
one1: conclusion.one1 || '', |
|
|
one1: conclusion.one1 || '', |
|
|
one2: conclusion.one2 || '', |
|
|
one2: conclusion.one2 || '', |
|
@ -604,7 +638,18 @@ watch(currentStock, (newStock) => { |
|
|
|
|
|
|
|
|
// 提取音频URL但不自动播放,等待用户手动点击 |
|
|
// 提取音频URL但不自动播放,等待用户手动点击 |
|
|
let voiceUrl = null; |
|
|
let voiceUrl = null; |
|
|
if (conclusion.url) { |
|
|
|
|
|
|
|
|
// 优先使用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, ''); |
|
|
voiceUrl = conclusion.url.toString().trim().replace(/[`\s]/g, ''); |
|
|
} else if (conclusion.audioUrl) { |
|
|
} else if (conclusion.audioUrl) { |
|
|
voiceUrl = conclusion.audioUrl.toString().trim().replace(/[`\s]/g, ''); |
|
|
voiceUrl = conclusion.audioUrl.toString().trim().replace(/[`\s]/g, ''); |
|
@ -624,7 +669,7 @@ watch(currentStock, (newStock) => { |
|
|
// 不自动播放,等待用户手动点击 |
|
|
// 不自动播放,等待用户手动点击 |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error('解析结论数据失败:', error); |
|
|
|
|
|
|
|
|
console.error('解析股票结论数据失败:', error); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
@ -654,9 +699,23 @@ watch(currentStock, (newStock) => { |
|
|
// 即使没有显示过,也需要设置音频URL以便用户手动播放 |
|
|
// 即使没有显示过,也需要设置音频URL以便用户手动播放 |
|
|
if (newStock.conclusionData) { |
|
|
if (newStock.conclusionData) { |
|
|
try { |
|
|
try { |
|
|
const conclusion = JSON.parse(newStock.conclusionData); |
|
|
|
|
|
|
|
|
// 如果conclusionData已经是对象,直接使用;否则解析JSON |
|
|
|
|
|
const conclusion = typeof newStock.conclusionData === 'object' |
|
|
|
|
|
? newStock.conclusionData |
|
|
|
|
|
: JSON.parse(newStock.conclusionData); |
|
|
let voiceUrl = null; |
|
|
let voiceUrl = null; |
|
|
if (conclusion.url) { |
|
|
|
|
|
|
|
|
// 优先使用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, ''); |
|
|
voiceUrl = conclusion.url.toString().trim().replace(/[`\s]/g, ''); |
|
|
} else if (conclusion.audioUrl) { |
|
|
} else if (conclusion.audioUrl) { |
|
|
voiceUrl = conclusion.audioUrl.toString().trim().replace(/[`\s]/g, ''); |
|
|
voiceUrl = conclusion.audioUrl.toString().trim().replace(/[`\s]/g, ''); |
|
@ -675,7 +734,7 @@ watch(currentStock, (newStock) => { |
|
|
emotionAudioStore.setCurrentAudioUrl(voiceUrl); |
|
|
emotionAudioStore.setCurrentAudioUrl(voiceUrl); |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
console.error('解析结论数据失败:', error); |
|
|
|
|
|
|
|
|
console.error('解析股票结论数据失败:', error); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
@ -710,12 +769,13 @@ watch(currentStock, (newStock) => { |
|
|
hasTriggeredTypewriter.value = true; |
|
|
hasTriggeredTypewriter.value = true; |
|
|
hasTriggeredAudio.value = true; |
|
|
hasTriggeredAudio.value = true; |
|
|
|
|
|
|
|
|
startTypewriterEffect(parsedConclusion.value); |
|
|
|
|
|
|
|
|
|
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
|
console.log('开始音频播放'); |
|
|
|
|
|
|
|
|
console.log('开始音频播放和打字机效果'); |
|
|
stockAudioPlayed.value.set(stockCode, true); |
|
|
stockAudioPlayed.value.set(stockCode, true); |
|
|
playAudio(audioUrl.value); |
|
|
|
|
|
|
|
|
playAudioQueue(parsedConclusion.value, true); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 如果音频已播放过,只启动打字机效果 |
|
|
|
|
|
startTypewriterEffect(parsedConclusion.value); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
@ -777,9 +837,19 @@ watch(parsedConclusion, (newConclusion) => { |
|
|
console.log('场景应用结论数据:', newConclusion); |
|
|
console.log('场景应用结论数据:', newConclusion); |
|
|
// 不再立即开始打字机效果,等待滚动到场景应用部分时触发 |
|
|
// 不再立即开始打字机效果,等待滚动到场景应用部分时触发 |
|
|
|
|
|
|
|
|
// 尝试多种可能的语音URL字段名 |
|
|
|
|
|
|
|
|
// 尝试多种可能的语音URL字段名,优先使用新的数据结构 |
|
|
let voiceUrl = null; |
|
|
let voiceUrl = null; |
|
|
if (newConclusion.url) { |
|
|
|
|
|
|
|
|
if (newConclusion.one1_url) { |
|
|
|
|
|
voiceUrl = newConclusion.one1_url.toString().trim().replace(/[`\s]/g, ''); |
|
|
|
|
|
} else if (newConclusion.one2_url) { |
|
|
|
|
|
voiceUrl = newConclusion.one2_url.toString().trim().replace(/[`\s]/g, ''); |
|
|
|
|
|
} else if (newConclusion.two_url) { |
|
|
|
|
|
voiceUrl = newConclusion.two_url.toString().trim().replace(/[`\s]/g, ''); |
|
|
|
|
|
} else if (newConclusion.three_url) { |
|
|
|
|
|
voiceUrl = newConclusion.three_url.toString().trim().replace(/[`\s]/g, ''); |
|
|
|
|
|
} else if (newConclusion.four_url) { |
|
|
|
|
|
voiceUrl = newConclusion.four_url.toString().trim().replace(/[`\s]/g, ''); |
|
|
|
|
|
} else if (newConclusion.url) { |
|
|
// 清理URL字符串,去除空格、反引号等特殊字符 |
|
|
// 清理URL字符串,去除空格、反引号等特殊字符 |
|
|
voiceUrl = newConclusion.url.toString().trim().replace(/[`\s]/g, ''); |
|
|
voiceUrl = newConclusion.url.toString().trim().replace(/[`\s]/g, ''); |
|
|
} else if (newConclusion.audioUrl) { |
|
|
} else if (newConclusion.audioUrl) { |
|
@ -966,7 +1036,258 @@ function clearTypewriterTimers() { |
|
|
typewriterTimers.value = []; |
|
|
typewriterTimers.value = []; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 音频播放函数 |
|
|
|
|
|
|
|
|
// 音频队列管理 |
|
|
|
|
|
const audioQueue = ref([]); |
|
|
|
|
|
const isPlayingQueueAudio = ref(false); |
|
|
|
|
|
let currentPlayIndex = 0; |
|
|
|
|
|
let isCallingPlayNext = false; |
|
|
|
|
|
|
|
|
|
|
|
// 音频队列顺序管理 |
|
|
|
|
|
const audioQueueOrder = { |
|
|
|
|
|
"one1_url": 1, |
|
|
|
|
|
"one2_url": 2, |
|
|
|
|
|
"two_url": 3, |
|
|
|
|
|
"three_url": 4, |
|
|
|
|
|
"four_url": 5, |
|
|
|
|
|
"url": 6, |
|
|
|
|
|
"audioUrl": 7, |
|
|
|
|
|
"voice_url": 8, |
|
|
|
|
|
"audio": 9, |
|
|
|
|
|
"tts_url": 10 |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 播放音频队列中的下一个音频 |
|
|
|
|
|
const playNextAudio = () => { |
|
|
|
|
|
console.log("=== playNextAudio 被调用 ==="); |
|
|
|
|
|
console.log("当前队列状态:", { |
|
|
|
|
|
queueLength: audioQueue.value.length, |
|
|
|
|
|
queueItems: audioQueue.value.map((item) => item.name), |
|
|
|
|
|
currentPlayIndex: currentPlayIndex, |
|
|
|
|
|
isPlayingQueueAudio: isPlayingQueueAudio.value, |
|
|
|
|
|
isCallingPlayNext: isCallingPlayNext, |
|
|
|
|
|
audioStoreIsPlaying: emotionAudioStore.isPlaying, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
audioQueue.value.length === 0 || |
|
|
|
|
|
isPlayingQueueAudio.value || |
|
|
|
|
|
isCallingPlayNext |
|
|
|
|
|
) { |
|
|
|
|
|
console.log("❌ 播放条件不满足 - 队列长度:", audioQueue.value.length, "正在播放:", isPlayingQueueAudio.value, "正在调用:", isCallingPlayNext); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已播放完所有音频 |
|
|
|
|
|
if (currentPlayIndex >= audioQueue.value.length && audioQueue.value.length > 0) { |
|
|
|
|
|
console.log("🔄 所有音频播放完成,重置索引从第一个开始"); |
|
|
|
|
|
currentPlayIndex = 0; |
|
|
|
|
|
isCallingPlayNext = false; // 重置调用标志 |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
isCallingPlayNext = true; |
|
|
|
|
|
isPlayingQueueAudio.value = true; |
|
|
|
|
|
const audioInfo = audioQueue.value[currentPlayIndex]; |
|
|
|
|
|
|
|
|
|
|
|
console.log(`✅ 开始播放${audioInfo.name}音频 (索引:${currentPlayIndex}),队列总长度:`, audioQueue.value.length); |
|
|
|
|
|
|
|
|
|
|
|
// 停止之前的音频 |
|
|
|
|
|
if (emotionAudioStore.nowSound && emotionAudioStore.nowSound.playing()) { |
|
|
|
|
|
emotionAudioStore.nowSound.stop(); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 创建新的音频实例 |
|
|
|
|
|
const audio = new Howl({ |
|
|
|
|
|
src: [audioInfo.url], |
|
|
|
|
|
html5: true, |
|
|
|
|
|
format: ['mp3', 'wav'], |
|
|
|
|
|
onplay: () => { |
|
|
|
|
|
isAudioPlaying.value = true; |
|
|
|
|
|
isPlayingQueueAudio.value = true; |
|
|
|
|
|
emotionAudioStore.isPlaying = true; |
|
|
|
|
|
console.log(`开始播放${audioInfo.name}音频`); |
|
|
|
|
|
|
|
|
|
|
|
// 如果是第一个音频且需要启动打字机效果,则启动 |
|
|
|
|
|
if (currentPlayIndex === 0 && audioInfo.shouldStartTypewriter && parsedConclusion.value) { |
|
|
|
|
|
console.log('🎬 第一个音频开始播放,同时启动打字机效果'); |
|
|
|
|
|
startTypewriterEffect(parsedConclusion.value, audioInfo.onComplete); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
onpause: () => { |
|
|
|
|
|
emotionAudioStore.isPaused = true; |
|
|
|
|
|
console.log(`${audioInfo.name}音频暂停播放`); |
|
|
|
|
|
}, |
|
|
|
|
|
onresume: () => { |
|
|
|
|
|
emotionAudioStore.isPaused = false; |
|
|
|
|
|
console.log(`${audioInfo.name}音频继续播放`); |
|
|
|
|
|
}, |
|
|
|
|
|
onend: () => { |
|
|
|
|
|
console.log(`${audioInfo.name}音频播放完成,准备播放下一个`); |
|
|
|
|
|
emotionAudioStore.isPlaying = false; |
|
|
|
|
|
emotionAudioStore.isPaused = false; |
|
|
|
|
|
emotionAudioStore.playbackPosition = 0; |
|
|
|
|
|
isAudioPlaying.value = false; |
|
|
|
|
|
isPlayingQueueAudio.value = false; |
|
|
|
|
|
|
|
|
|
|
|
// 移动到下一个音频索引 |
|
|
|
|
|
currentPlayIndex++; |
|
|
|
|
|
|
|
|
|
|
|
// 确保只有在音频真正播放完成时才播放下一个 |
|
|
|
|
|
if (currentPlayIndex < audioQueue.value.length) { |
|
|
|
|
|
console.log(`队列中还有音频,500ms后播放下一个 (索引:${currentPlayIndex})`); |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
isCallingPlayNext = false; |
|
|
|
|
|
playNextAudio(); |
|
|
|
|
|
}, 500); |
|
|
|
|
|
} else { |
|
|
|
|
|
console.log("🎉 所有音频播放完成"); |
|
|
|
|
|
emotionAudioStore.nowSound = null; |
|
|
|
|
|
isCallingPlayNext = false; |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
onstop: () => { |
|
|
|
|
|
console.log(`${audioInfo.name}音频被停止`); |
|
|
|
|
|
emotionAudioStore.isPlaying = false; |
|
|
|
|
|
emotionAudioStore.isPaused = false; |
|
|
|
|
|
emotionAudioStore.playbackPosition = 0; |
|
|
|
|
|
isAudioPlaying.value = false; |
|
|
|
|
|
isPlayingQueueAudio.value = false; |
|
|
|
|
|
}, |
|
|
|
|
|
onerror: (error) => { |
|
|
|
|
|
console.error(`${audioInfo.name}音频播放失败:`, error); |
|
|
|
|
|
isAudioPlaying.value = false; |
|
|
|
|
|
isPlayingQueueAudio.value = false; |
|
|
|
|
|
isCallingPlayNext = false; |
|
|
|
|
|
// 播放下一个音频 |
|
|
|
|
|
setTimeout(() => { |
|
|
|
|
|
playNextAudio(); |
|
|
|
|
|
}, 100); |
|
|
|
|
|
}, |
|
|
|
|
|
onload: () => { |
|
|
|
|
|
emotionAudioStore.duration = audio.duration(); |
|
|
|
|
|
console.log(`${audioInfo.name}音频加载完成,时长:`, emotionAudioStore.duration); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// 设置当前音频URL到store |
|
|
|
|
|
emotionAudioStore.setCurrentAudioUrl(audioInfo.url); |
|
|
|
|
|
emotionAudioStore.nowSound = audio; |
|
|
|
|
|
emotionAudioStore.setAudioInstance(audio); |
|
|
|
|
|
|
|
|
|
|
|
console.log(`尝试播放${audioInfo.name}音频`); |
|
|
|
|
|
audio.play(); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 添加音频到播放队列 |
|
|
|
|
|
const addToAudioQueue = (url, name, shouldStartTypewriter = false, onComplete = null) => { |
|
|
|
|
|
console.log(`=== 添加音频到队列 ===`); |
|
|
|
|
|
console.log("URL:", url); |
|
|
|
|
|
console.log("Name:", name); |
|
|
|
|
|
console.log("是否启动打字机效果:", shouldStartTypewriter); |
|
|
|
|
|
console.log("音频启用状态:", emotionAudioStore.isVoiceEnabled); |
|
|
|
|
|
|
|
|
|
|
|
if (url && emotionAudioStore.isVoiceEnabled) { |
|
|
|
|
|
const audioItem = { |
|
|
|
|
|
url, |
|
|
|
|
|
name, |
|
|
|
|
|
order: audioQueueOrder[name] || 999, |
|
|
|
|
|
shouldStartTypewriter, // 添加打字机效果标志 |
|
|
|
|
|
onComplete, // 添加完成回调 |
|
|
|
|
|
}; |
|
|
|
|
|
audioQueue.value.push(audioItem); |
|
|
|
|
|
|
|
|
|
|
|
// 按顺序排序队列 |
|
|
|
|
|
audioQueue.value.sort((a, b) => a.order - b.order); |
|
|
|
|
|
|
|
|
|
|
|
console.log(`音频${name}已添加到播放队列,顺序:${audioItem.order}`); |
|
|
|
|
|
console.log("当前队列顺序:", audioQueue.value.map((item) => `${item.name}(${item.order})`)); |
|
|
|
|
|
|
|
|
|
|
|
// 只有在确实没有音频在播放且这是第一个音频时才开始播放 |
|
|
|
|
|
if (!isPlayingQueueAudio.value && !emotionAudioStore.isPlaying && audioQueue.value.length === 1) { |
|
|
|
|
|
console.log("✅ 条件满足:没有音频在播放且这是第一个音频,立即开始播放"); |
|
|
|
|
|
playNextAudio(); |
|
|
|
|
|
} else { |
|
|
|
|
|
console.log("⏳ 等待条件:", { |
|
|
|
|
|
isPlayingQueueAudio: isPlayingQueueAudio.value, |
|
|
|
|
|
audioStoreIsPlaying: emotionAudioStore.isPlaying, |
|
|
|
|
|
queueLength: audioQueue.value.length, |
|
|
|
|
|
reason: audioQueue.value.length > 1 ? "队列中已有其他音频" : "有音频正在播放", |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
console.log("❌ 跳过添加音频:", { |
|
|
|
|
|
hasUrl: !!url, |
|
|
|
|
|
voiceEnabled: emotionAudioStore.isVoiceEnabled, |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// 修改后的音频播放函数 - 支持多音频播放和同步打字机效果 |
|
|
|
|
|
function playAudioQueue(conclusionData, shouldStartTypewriter = false, onComplete = null) { |
|
|
|
|
|
if (!conclusionData) { |
|
|
|
|
|
console.log('没有结论数据,跳过播放'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 检查是否启用了语音功能 |
|
|
|
|
|
console.log('语音功能状态:', emotionAudioStore.isVoiceEnabled); |
|
|
|
|
|
if (!emotionAudioStore.isVoiceEnabled) { |
|
|
|
|
|
console.log('语音功能已关闭,跳过播放'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log('开始处理多音频播放...', shouldStartTypewriter ? '同时启动打字机效果' : ''); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
// 解析结论数据 |
|
|
|
|
|
const conclusion = typeof conclusionData === 'object' ? conclusionData : JSON.parse(conclusionData); |
|
|
|
|
|
|
|
|
|
|
|
// 清空之前的音频队列 |
|
|
|
|
|
audioQueue.value = []; |
|
|
|
|
|
currentPlayIndex = 0; |
|
|
|
|
|
isCallingPlayNext = false; |
|
|
|
|
|
isPlayingQueueAudio.value = false; |
|
|
|
|
|
|
|
|
|
|
|
// 按优先级顺序检查并添加所有可用的音频URL |
|
|
|
|
|
const audioSources = [ |
|
|
|
|
|
{ key: 'one1_url', name: 'one1_url' }, |
|
|
|
|
|
{ key: 'one2_url', name: 'one2_url' }, |
|
|
|
|
|
{ key: 'two_url', name: 'two_url' }, |
|
|
|
|
|
{ key: 'three_url', name: 'three_url' }, |
|
|
|
|
|
{ key: 'four_url', name: 'four_url' }, |
|
|
|
|
|
{ key: 'url', name: 'url' }, |
|
|
|
|
|
{ key: 'audioUrl', name: 'audioUrl' }, |
|
|
|
|
|
{ key: 'voice_url', name: 'voice_url' }, |
|
|
|
|
|
{ key: 'audio', name: 'audio' }, |
|
|
|
|
|
{ key: 'tts_url', name: 'tts_url' } |
|
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
|
|
audioSources.forEach(source => { |
|
|
|
|
|
if (conclusion[source.key]) { |
|
|
|
|
|
const voiceUrl = conclusion[source.key].toString().trim().replace(/[`\s]/g, ''); |
|
|
|
|
|
if (voiceUrl && voiceUrl.startsWith('http')) { |
|
|
|
|
|
console.log(`找到音频URL: ${source.name} = ${voiceUrl}`); |
|
|
|
|
|
addToAudioQueue(voiceUrl, source.name, shouldStartTypewriter && audioQueue.value.length === 0, onComplete); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (audioQueue.value.length === 0) { |
|
|
|
|
|
console.log('未找到有效的音频URL'); |
|
|
|
|
|
// 如果没有音频但需要启动打字机效果,直接启动 |
|
|
|
|
|
if (shouldStartTypewriter) { |
|
|
|
|
|
console.log('没有音频但需要启动打字机效果'); |
|
|
|
|
|
startTypewriterEffect(conclusion, onComplete); |
|
|
|
|
|
} |
|
|
|
|
|
} else { |
|
|
|
|
|
console.log(`总共找到 ${audioQueue.value.length} 个音频,准备播放`); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
console.error('处理音频播放失败:', error); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 原有的单音频播放函数(保持兼容性) |
|
|
function playAudio(url) { |
|
|
function playAudio(url) { |
|
|
console.log('尝试播放音频:', url); |
|
|
console.log('尝试播放音频:', url); |
|
|
|
|
|
|
|
@ -1217,37 +1538,35 @@ async function handleSendMessage(input, onComplete) { |
|
|
|
|
|
|
|
|
// 开始思考过程(不带股票名称) |
|
|
// 开始思考过程(不带股票名称) |
|
|
const thinkingMessageRef = await showThinkingProcess(); |
|
|
const thinkingMessageRef = await showThinkingProcess(); |
|
|
|
|
|
let thinkingMessage3Ref = null; |
|
|
|
|
|
|
|
|
try { |
|
|
try { |
|
|
// 第一步:调用第一个接口验证用户输入内容是否合法 |
|
|
// 第一步:调用第一个接口验证用户输入内容是否合法 |
|
|
const params = { |
|
|
|
|
|
content: userMessage.text, |
|
|
|
|
|
userData: { |
|
|
|
|
|
token: localStorage.getItem('localToken'), |
|
|
|
|
|
language: "cn", |
|
|
|
|
|
// brainPrivilegeState: userStore.brainPerssion, |
|
|
|
|
|
// swordPrivilegeState: userStore.swordPerssion, |
|
|
|
|
|
// stockForecastPrivilegeState: userStore.pricePerssion, |
|
|
|
|
|
// spaceForecastPrivilegeState: userStore.timePerssion, |
|
|
|
|
|
// aibullPrivilegeState: userStore.aibullPerssion, |
|
|
|
|
|
// aigoldBullPrivilegeState: userStore.aiGnbullPerssion, |
|
|
|
|
|
// airadarPrivilegeState: userStore.airadarPerssion, |
|
|
|
|
|
// marketList: userStore.aiGoldMarketList, |
|
|
|
|
|
brainPrivilegeState: '1', |
|
|
|
|
|
swordPrivilegeState: '1', |
|
|
|
|
|
stockForecastPrivilegeState: '1', |
|
|
|
|
|
spaceForecastPrivilegeState: '1', |
|
|
|
|
|
aibullPrivilegeState: '1', |
|
|
|
|
|
aigoldBullPrivilegeState: '1', |
|
|
|
|
|
airadarPrivilegeState: '1', |
|
|
|
|
|
marketList: "hk,cn,usa,my,sg,vi,in,gb", |
|
|
|
|
|
}, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const result = await getReplyAPI(params); |
|
|
|
|
|
const response = await result.json(); |
|
|
|
|
|
const parsedData = JSON.parse(response.data); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// const params = { |
|
|
|
|
|
// content: userMessage.text, |
|
|
|
|
|
// userData: { |
|
|
|
|
|
// token: localStorage.getItem('localToken'), |
|
|
|
|
|
// language: "cn", |
|
|
|
|
|
// brainPrivilegeState: '1', |
|
|
|
|
|
// swordPrivilegeState: '1', |
|
|
|
|
|
// stockForecastPrivilegeState: '1', |
|
|
|
|
|
// spaceForecastPrivilegeState: '1', |
|
|
|
|
|
// aibullPrivilegeState: '1', |
|
|
|
|
|
// aigoldBullPrivilegeState: '1', |
|
|
|
|
|
// airadarPrivilegeState: '1', |
|
|
|
|
|
// marketList: "hk,cn,usa,my,sg,vi,in,gb", |
|
|
|
|
|
// }, |
|
|
|
|
|
// }; |
|
|
|
|
|
|
|
|
|
|
|
const result = await getReplyAPI({ |
|
|
|
|
|
"token": localStorage.getItem("localToken"), |
|
|
|
|
|
"language": "cn", |
|
|
|
|
|
"marketList": "hk,cn,usa,my,sg,vi,in,gb", |
|
|
|
|
|
"content": userMessage.text |
|
|
|
|
|
}); |
|
|
|
|
|
const response = result; |
|
|
|
|
|
const parsedData = response.data; |
|
|
|
|
|
console.log('第一个接口返回的完整数据:', parsedData); |
|
|
// 检查用户输入是否合法 |
|
|
// 检查用户输入是否合法 |
|
|
if (!parsedData || !parsedData.market || !parsedData.code) { |
|
|
if (!parsedData || !parsedData.market || !parsedData.code) { |
|
|
// 输入不合法,先清理思考过程消息 |
|
|
// 输入不合法,先清理思考过程消息 |
|
@ -1283,11 +1602,9 @@ async function handleSendMessage(input, onComplete) { |
|
|
|
|
|
|
|
|
// 输入合法,继续执行后续处理 |
|
|
// 输入合法,继续执行后续处理 |
|
|
// 获取到股票名称后,继续思考过程 |
|
|
// 获取到股票名称后,继续思考过程 |
|
|
let thinkingMessage3Ref = null; |
|
|
|
|
|
if (thinkingMessageRef && parsedData.name) { |
|
|
if (thinkingMessageRef && parsedData.name) { |
|
|
thinkingMessage3Ref = await continueThinkingProcess(thinkingMessageRef, parsedData.name); |
|
|
thinkingMessage3Ref = await continueThinkingProcess(thinkingMessageRef, parsedData.name); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 设置加载状态,隐藏图表页面 |
|
|
// 设置加载状态,隐藏图表页面 |
|
|
// isLoading.value = true; |
|
|
// isLoading.value = true; |
|
|
|
|
|
|
|
@ -1295,24 +1612,22 @@ async function handleSendMessage(input, onComplete) { |
|
|
|
|
|
|
|
|
// 调用第二个工作流接口 |
|
|
// 调用第二个工作流接口 |
|
|
const conclusionParams = { |
|
|
const conclusionParams = { |
|
|
content: input.trim(), |
|
|
|
|
|
userData: { |
|
|
|
|
|
token: localStorage.getItem('localToken'), |
|
|
|
|
|
language: "cn", |
|
|
|
|
|
marketList: "hk,cn,usa,my,sg,vi,in,gb", |
|
|
|
|
|
}, |
|
|
|
|
|
code: parsedData.code, |
|
|
|
|
|
market: parsedData.market, |
|
|
|
|
|
|
|
|
recordId: parsedData.recordId, |
|
|
|
|
|
parentId: parsedData.parentId, |
|
|
|
|
|
stockId: parsedData.stockId, |
|
|
|
|
|
token: localStorage.getItem('localToken'), |
|
|
|
|
|
language: "cn", |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
console.log('第二个接口参数:', conclusionParams); |
|
|
// 同时调用第二个数据流接口和fetchData方法 |
|
|
// 同时调用第二个数据流接口和fetchData方法 |
|
|
const [conclusionResult, fetchDataResult] = await Promise.all([ |
|
|
const [conclusionResult, fetchDataResult] = await Promise.all([ |
|
|
getConclusionAPI(conclusionParams), |
|
|
getConclusionAPI(conclusionParams), |
|
|
fetchData(parsedData.code, parsedData.market, parsedData.name || "未知股票", input.trim()) |
|
|
|
|
|
|
|
|
fetchData(parsedData.code, parsedData.market, parsedData.name || "未知股票", input.trim(), parsedData.stockId) |
|
|
]); |
|
|
]); |
|
|
|
|
|
|
|
|
// 处理结论接口返回的数据 |
|
|
// 处理结论接口返回的数据 |
|
|
const conclusionResponse = await conclusionResult.json(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const conclusionResponse = conclusionResult; |
|
|
// 检查所有数据是否都加载成功 |
|
|
// 检查所有数据是否都加载成功 |
|
|
if (conclusionResponse && conclusionResponse.data && fetchDataResult) { |
|
|
if (conclusionResponse && conclusionResponse.data && fetchDataResult) { |
|
|
// 第二个工作流接口成功,完成思考过程 |
|
|
// 第二个工作流接口成功,完成思考过程 |
|
@ -1322,6 +1637,7 @@ async function handleSendMessage(input, onComplete) { |
|
|
|
|
|
|
|
|
// 将结论数据存储到响应式变量和store中 |
|
|
// 将结论数据存储到响应式变量和store中 |
|
|
conclusionData.value = conclusionResponse.data; |
|
|
conclusionData.value = conclusionResponse.data; |
|
|
|
|
|
console.log('第二个接口返回的完整数据结构:', conclusionResponse.data); |
|
|
// 将结论数据存储到store中的当前激活股票 |
|
|
// 将结论数据存储到store中的当前激活股票 |
|
|
emotionStore.updateActiveStockConclusion(conclusionResponse.data); |
|
|
emotionStore.updateActiveStockConclusion(conclusionResponse.data); |
|
|
|
|
|
|
|
@ -1347,11 +1663,12 @@ async function handleSendMessage(input, onComplete) { |
|
|
if (isUserInitiated.value && 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)) { |
|
|
startTypewriterEffect(parsedConclusion.value, onComplete); |
|
|
|
|
|
|
|
|
|
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
|
if (!stockAudioPlayed.value.has(stockCode)) { |
|
|
stockAudioPlayed.value.set(stockCode, true); |
|
|
stockAudioPlayed.value.set(stockCode, true); |
|
|
playAudio(audioUrl.value); |
|
|
|
|
|
|
|
|
playAudioQueue(parsedConclusion.value, true, onComplete); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 如果音频已播放过,只启动打字机效果 |
|
|
|
|
|
startTypewriterEffect(parsedConclusion.value, onComplete); |
|
|
} |
|
|
} |
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
stockTypewriterShown.value.set(stockCode, true); |
|
|
} else { |
|
|
} else { |
|
@ -1382,7 +1699,7 @@ async function handleSendMessage(input, onComplete) { |
|
|
// 如果 fetchDataResult 为 false,说明数据不完整的错误信息已经在 fetchData 中添加到 messages |
|
|
// 如果 fetchDataResult 为 false,说明数据不完整的错误信息已经在 fetchData 中添加到 messages |
|
|
// 只有在 conclusionResponse 有问题时才添加通用错误信息 |
|
|
// 只有在 conclusionResponse 有问题时才添加通用错误信息 |
|
|
if (!conclusionResponse || !conclusionResponse.data) { |
|
|
if (!conclusionResponse || !conclusionResponse.data) { |
|
|
const aiMessage = reactive({ sender: 'ai', text: '数据加载失败,请重试' }); |
|
|
|
|
|
|
|
|
const aiMessage = reactive({ sender: 'ai', text: '网络加载失败,请重试' }); |
|
|
messages.value.push(aiMessage); |
|
|
messages.value.push(aiMessage); |
|
|
} |
|
|
} |
|
|
isRotating.value = false; |
|
|
isRotating.value = false; |
|
@ -1467,23 +1784,17 @@ async function handleSendMessage(input, onComplete) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 请求数据接口 |
|
|
// 请求数据接口 |
|
|
async function fetchData(code, market, stockName, queryText) { |
|
|
|
|
|
|
|
|
async function fetchData(code, market, stockName, queryText, stockId) { |
|
|
try { |
|
|
try { |
|
|
const stockDataParams = { |
|
|
const stockDataParams = { |
|
|
// token: '+XgqsgdW0RLIbIG2pxnnbZi0+fEeMx8pywnIlrmTxtkSaPZ9xjSOWrxq+s0rL3RrfNhXPvGtz9srFfjwu8A', |
|
|
|
|
|
token: localStorage.getItem('localToken'), |
|
|
|
|
|
market: market, |
|
|
|
|
|
code: code, |
|
|
|
|
|
language: 'cn', |
|
|
|
|
|
version: version1.value |
|
|
|
|
|
|
|
|
"stockId": stockId |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const stockDataResult = await axios.post( |
|
|
const stockDataResult = await axios.post( |
|
|
// "http://39.101.133.168:8828/link/api/aiEmotion/client/getAiEmotionData", |
|
|
// "http://39.101.133.168:8828/link/api/aiEmotion/client/getAiEmotionData", |
|
|
'https://api.homilychart.com/link/api/aiEmotion/client/getAiEmotionData', |
|
|
|
|
|
|
|
|
`${APIurl}/api/workflow/getStockData`, |
|
|
stockDataParams, |
|
|
stockDataParams, |
|
|
{ |
|
|
{ |
|
|
headers: { |
|
|
|
|
|
|
|
|
headers: { |
|
|
"Content-Type": "application/json", |
|
|
"Content-Type": "application/json", |
|
|
}, |
|
|
}, |
|
|
} |
|
|
} |
|
@ -2150,9 +2461,36 @@ onMounted(async () => { |
|
|
console.log('恢复图表数据:', currentStockData.stockInfo.name); |
|
|
console.log('恢复图表数据:', currentStockData.stockInfo.name); |
|
|
renderCharts(currentStockData.apiData); |
|
|
renderCharts(currentStockData.apiData); |
|
|
|
|
|
|
|
|
// 恢复结论数据但不触发音频和打字机效果 |
|
|
|
|
|
|
|
|
// 恢复结论数据并显示内容 |
|
|
if (currentStockData.conclusionData) { |
|
|
if (currentStockData.conclusionData) { |
|
|
conclusionData.value = 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 |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
// 标记该股票的打字机效果和音频已经显示过,避免后续自动触发 |
|
|
// 标记该股票的打字机效果和音频已经显示过,避免后续自动触发 |
|
|
const stockCode = currentStockData.stockInfo?.code || currentStockData.stockInfo?.symbol; |
|
|
const stockCode = currentStockData.stockInfo?.code || currentStockData.stockInfo?.symbol; |
|
|
if (stockCode) { |
|
|
if (stockCode) { |
|
|