Browse Source

解决新股票结论覆盖旧股票结论的问题;

dev
宋杰 2 days ago
parent
commit
e6be67c018
  1. 553
      src/views/AiEmotion.vue

553
src/views/AiEmotion.vue

@ -277,41 +277,76 @@
<img src="@/assets/img/AiEmotion/场景应用.png" alt="场景应用标题" />
<div class="bk-image">
<div class="conclusion-container">
<!-- 使用打字机效果显示结论内容 -->
<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 }}
<!-- 使用当前股票项的结论数据显示内容支持打字机效果 -->
<!-- L1: 情绪监控 -->
<div class="conclusion-item" v-if="(getStockTypewriterVisibility(stock) && getStockTypewriterVisibility(stock).one) || (getStockConclusion(stock) && !getStockTypewriterTexts(stock) && (getStockConclusion(stock).one1 || getStockConclusion(stock).one2))">
<h4 class="conclusion-title">L1: 情绪监控</h4>
<!-- 打字机效果文本 -->
<p class="conclusion-text" v-if="getStockTypewriterTexts(stock) && getStockTypewriterTexts(stock).one1">
{{ getStockTypewriterTexts(stock).one1 }}
</p>
<p class="conclusion-text" v-if="displayedTexts.one2">
{{ displayedTexts.one2 }}
<p class="conclusion-text" v-if="getStockTypewriterTexts(stock) && getStockTypewriterTexts(stock).one2">
{{ getStockTypewriterTexts(stock).one2 }}
</p>
<!-- 完整文本 -->
<p class="conclusion-text" v-if="!getStockTypewriterTexts(stock) && getStockConclusion(stock) && getStockConclusion(stock).one1">
{{ getStockConclusion(stock).one1 }}
</p>
<p class="conclusion-text" v-if="!getStockTypewriterTexts(stock) && getStockConclusion(stock) && getStockConclusion(stock).one2">
{{ getStockConclusion(stock).one2 }}
</p>
</div>
<div class="conclusion-item" v-if="moduleVisibility.two">
<h4 class="conclusion-title">{{ displayedTitles.two }}</h4>
<p class="conclusion-text">{{ displayedTexts.two }}</p>
<!-- L2: 情绪解码 -->
<div class="conclusion-item" v-if="(getStockTypewriterVisibility(stock) && getStockTypewriterVisibility(stock).two) || (getStockConclusion(stock) && !getStockTypewriterTexts(stock) && getStockConclusion(stock).two)">
<h4 class="conclusion-title">L2: 情绪解码</h4>
<!-- 打字机效果文本 -->
<p class="conclusion-text" v-if="getStockTypewriterTexts(stock) && getStockTypewriterTexts(stock).two">
{{ getStockTypewriterTexts(stock).two }}
</p>
<!-- 完整文本 -->
<p class="conclusion-text" v-if="!getStockTypewriterTexts(stock) && getStockConclusion(stock) && getStockConclusion(stock).two">
{{ getStockConclusion(stock).two }}
</p>
</div>
<div class="conclusion-item" v-if="moduleVisibility.three">
<h4 class="conclusion-title">{{ displayedTitles.three }}</h4>
<p class="conclusion-text">{{ displayedTexts.three }}</p>
<!-- L3: 情绪推演 -->
<div class="conclusion-item" v-if="(getStockTypewriterVisibility(stock) && getStockTypewriterVisibility(stock).three) || (getStockConclusion(stock) && !getStockTypewriterTexts(stock) && getStockConclusion(stock).three)">
<h4 class="conclusion-title">L3: 情绪推演</h4>
<!-- 打字机效果文本 -->
<p class="conclusion-text" v-if="getStockTypewriterTexts(stock) && getStockTypewriterTexts(stock).three">
{{ getStockTypewriterTexts(stock).three }}
</p>
<!-- 完整文本 -->
<p class="conclusion-text" v-if="!getStockTypewriterTexts(stock) && getStockConclusion(stock) && getStockConclusion(stock).three">
{{ getStockConclusion(stock).three }}
</p>
</div>
<div class="conclusion-item" v-if="moduleVisibility.four">
<h4 class="conclusion-title">{{ displayedTitles.four }}</h4>
<p class="conclusion-text">{{ displayedTexts.four }}</p>
<!-- L4: 情绪套利 -->
<div class="conclusion-item" v-if="(getStockTypewriterVisibility(stock) && getStockTypewriterVisibility(stock).four) || (getStockConclusion(stock) && !getStockTypewriterTexts(stock) && getStockConclusion(stock).four)">
<h4 class="conclusion-title">L4: 情绪套利</h4>
<!-- 打字机效果文本 -->
<p class="conclusion-text" v-if="getStockTypewriterTexts(stock) && getStockTypewriterTexts(stock).four">
{{ getStockTypewriterTexts(stock).four }}
</p>
<!-- 完整文本 -->
<p class="conclusion-text" v-if="!getStockTypewriterTexts(stock) && getStockConclusion(stock) && getStockConclusion(stock).four">
{{ getStockConclusion(stock).four }}
</p>
</div>
<!-- AI生成内容免责声明 -->
<div class="disclaimer-item" v-if="moduleVisibility.disclaimer">
<p class="disclaimer-text">{{ displayedTexts.disclaimer }}</p>
<div class="disclaimer-item" v-if="(getStockTypewriterVisibility(stock) && getStockTypewriterVisibility(stock).disclaimer) || (getStockConclusion(stock) && !getStockTypewriterTexts(stock))">
<!-- 打字机效果文本 -->
<p class="disclaimer-text" v-if="getStockTypewriterTexts(stock) && getStockTypewriterTexts(stock).disclaimer">
{{ getStockTypewriterTexts(stock).disclaimer }}
</p>
<!-- 完整文本 -->
<p class="disclaimer-text" v-if="!getStockTypewriterTexts(stock)">
该内容由AI生成请注意甄别
</p>
</div>
</div>
<div
class="conclusion-placeholder"
v-if="
!moduleVisibility.one &&
!moduleVisibility.two &&
!moduleVisibility.three &&
!moduleVisibility.four
"
v-if="!getStockConclusion(stock) || (!getStockConclusion(stock).one1 && !getStockConclusion(stock).one2 && !getStockConclusion(stock).two && !getStockConclusion(stock).three && !getStockConclusion(stock).four)"
>
<p>等待股票分析结论...</p>
</div>
@ -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);
}
console.log("历史记录股票已标记为已显示");
}
// 5. 使nextTickDOM
@ -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,9 +922,7 @@ watch(
//
const stockCode = newStock.stockInfo?.code || newStock.stockInfo?.symbol;
//
if (stockCode && stockTypewriterShown.value.has(stockCode)) {
//
// URL
if (newStock.conclusionData) {
try {
// conclusionData使JSON
@ -978,28 +930,10 @@ watch(
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,
};
//
if (stockCode && stockTypewriterShown.value.has(stockCode)) {
//
// URL
let voiceUrl = null;
@ -1063,103 +997,15 @@ 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_urlURL
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;
// storeURL
emotionAudioStore.setCurrentAudioUrl(voiceUrl);
}
} catch (error) {
console.error("解析股票结论数据失败:", error);
}
}
} else {
// URL
audioUrl.value = "";
emotionAudioStore.resetAudioState();
console.log("当前股票没有结论数据,已清空音频");
}
//
@ -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,
});
}
displayedTitles.value = {
one: "",
//
stockTypewriterTexts.value.set(stockId, {
one1: "",
one2: "",
two: "",
three: "",
four: "",
};
disclaimer: "",
});
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 =

Loading…
Cancel
Save