-
{{ 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 =