Browse Source

情绪大模型搜索新股票时可以堆叠;音频独立播放;历史记录显示完成;

master
宋杰 4 days ago
parent
commit
1700b394dc
  1. 548
      src/views/AiEmotion.vue
  2. 25
      src/views/components/HistoryRecord.vue
  3. 6
      src/views/components/emoEnergyConverter.vue
  4. 27
      src/views/homePage.vue

548
src/views/AiEmotion.vue

@ -1,6 +1,10 @@
<template> <template>
<!-- 顶部锚点 --> <!-- 顶部锚点 -->
<div id="top-anchor" class="top-anchor"></div> <div id="top-anchor" class="top-anchor"></div>
<!-- 主容器包含对话框和main容器 -->
<div class="page-container">
<!-- 对话框区域 -->
<div class="ai-emotion-container" ref="userInputDisplayRef"> <div class="ai-emotion-container" ref="userInputDisplayRef">
<!-- 金轮 --> <!-- 金轮 -->
<div class="golden-wheel"> <div class="golden-wheel">
@ -8,38 +12,33 @@
:class="{ 'rotating-image': isRotating }" /> :class="{ 'rotating-image': isRotating }" />
</div> </div>
<!-- 消息显示区域 -->
<div class="user-input-display">
<div v-for="(message, index) in messages" :key="index" class="message-container">
<!-- 用户输入内容 -->
<div v-if="message.sender === 'user'" class="user-message-container">
<img
:src="isVoice && emotionAudioStore.isPlaying ? voiceNoActive : voice"
class="user-message-speaker"
:class="{
'speaker-active': isVoice && emotionAudioStore.isPlaying
}"
@click="toggleVoiceForUser"
alt="喇叭"
/>
<!-- 对话消息显示区域 -->
<div class="conversation-area" v-if="messages.length > 0 && !isHistoryMode">
<div class="message-list">
<div v-for="(message, index) in messages" :key="index" class="message-item"
:class="{ 'user-message-item': message.sender === 'user', 'ai-message-item': message.sender === 'ai' }">
<!-- 用户消息 -->
<div v-if="message.sender === 'user'" class="user-message-wrapper">
<div class="message-bubble user-message"> <div class="message-bubble user-message">
{{ message.text }} {{ message.text }}
</div> </div>
</div> </div>
<!-- AI返回结果 -->
<div v-if="message.sender === 'ai'" class="ai-message-container">
<!-- 思考过程动图 -->
<!-- <img v-if="message.gif" :src="message.gif" class="thinking-gif" alt="思考动图" /> -->
<!-- AI消息包括思考过程 -->
<div v-else class="ai-message-wrapper">
<div class="ai-message-container">
<img v-if="message.gif" :src="message.gif" alt="思考过程" class="thinking-gif" />
<div class="message-bubble ai-message"> <div class="message-bubble ai-message">
<!-- 思考过程动图 -->
<img v-if="message.gif" :src="message.gif" class="thinking-gif" alt="思考动图" />
{{ message.text }} {{ message.text }}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<!-- 加载提示 --> <!-- 加载提示 -->
<div v-if="isLoading" class="loading-container"> <div v-if="isLoading" class="loading-container">
<div class="loading-content"> <div class="loading-content">
@ -47,9 +46,28 @@
<div class="loading-text">AI情绪大模型正在努力为您加载请稍候...</div> <div class="loading-text">AI情绪大模型正在努力为您加载请稍候...</div>
</div> </div>
</div> </div>
<!-- main容器区域 -->
<!-- 移除股票标签页改为对话形式展示 --> <!-- 移除股票标签页改为对话形式展示 -->
<!-- 渲染整个页面 - 遍历stockList显示所有股票 --> <!-- 渲染整个页面 - 遍历stockList显示所有股票 -->
<div v-for="(stock, stockIndex) in emotionStore.stockList" :key="`stock-${stockIndex}-${stock.timestamp}`" v-if="isPageLoaded" class="main">
<div class="master" v-for="(stock, stockIndex) in emotionStore.stockList" :key="`stock-${stockIndex}-${stock.timestamp}`" v-if="isPageLoaded">
<!-- 对应股票的消息显示区域 -->
<div class="user-input-display">
<div class="message-container">
<!-- 显示该股票对应的用户输入内容 -->
<div class="user-message-container">
<img :src="isVoice && getStockAudioState(stock).isPlaying ? voiceNoActive : voice" class="user-message-speaker"
:class="{
'speaker-active': isVoice && getStockAudioState(stock).isPlaying
}" @click="() => toggleVoiceForUser(stock)" alt="喇叭" />
<div class="message-bubble user-message">
{{ stock.queryText }}
</div>
</div>
</div>
</div>
<div class="main">
<div class="main-content-wrapper"> <div class="main-content-wrapper">
<!-- 四维矩阵图 --> <!-- 四维矩阵图 -->
<div class="matrix-header"> <div class="matrix-header">
@ -74,7 +92,8 @@
<div class="temperature-hot">市场温度{{ getStockData1(stock) }}</div> <div class="temperature-hot">市场温度{{ getStockData1(stock) }}</div>
</div> </div>
</div> </div>
<marketTemperature :ref="el => marketTemperatureRef[stockIndex] = el" :companyName="stock.stockInfo.name" :stockCode="stock.stockInfo.code" />
<marketTemperature :ref="el => marketTemperatureRef[stockIndex] = el" :companyName="stock.stockInfo.name"
:stockCode="stock.stockInfo.code" />
</div> </div>
</div> </div>
<div class="emotion-decoder-icon" v-if="chartVisibility.emotionDecod"> <div class="emotion-decoder-icon" v-if="chartVisibility.emotionDecod">
@ -152,8 +171,8 @@
<div class="scenario-application-section" ref="scenarioApplicationRef"> <div class="scenario-application-section" ref="scenarioApplicationRef">
<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">
<!-- 使用打字机效果显示结论内容 -->
<div class="conclusion-item" v-if="moduleVisibility.one"> <div class="conclusion-item" v-if="moduleVisibility.one">
<h4 class="conclusion-title">{{ displayedTitles.one }}</h4> <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.one1">{{ displayedTexts.one1 }}</p>
@ -176,7 +195,7 @@
<p class="disclaimer-text">{{ displayedTexts.disclaimer }}</p> <p class="disclaimer-text">{{ displayedTexts.disclaimer }}</p>
</div> </div>
</div> </div>
<div class="conclusion-placeholder" v-else>
<div class="conclusion-placeholder" v-if="!moduleVisibility.one && !moduleVisibility.two && !moduleVisibility.three && !moduleVisibility.four">
<p>等待股票分析结论...</p> <p>等待股票分析结论...</p>
</div> </div>
</div> </div>
@ -190,6 +209,8 @@
</svg> </svg>
</div> </div>
</div> </div>
</div>
</div>
</template> </template>
<script setup> <script setup>
@ -221,31 +242,47 @@ const emotionStore = useEmotionStore();
const emotionAudioStore = useEmotionAudioStore(); const emotionAudioStore = useEmotionAudioStore();
// //
const toggleVoiceForUser = () => {
const toggleVoiceForUser = (stock) => {
if (!emotionAudioStore.isVoiceEnabled) { if (!emotionAudioStore.isVoiceEnabled) {
// //
emotionAudioStore.toggleVoice(); emotionAudioStore.toggleVoice();
} else { } else {
// /
if (emotionAudioStore.isPlaying) {
//
emotionAudioStore.togglePlayPause();
//
const stockConclusion = getStockConclusion(stock);
const currentState = getStockAudioState(stock);
//
const isAnyAudioPlaying = emotionAudioStore.isPlaying || currentState.isPlaying;
//
if (currentState.isPlaying) {
console.log('暂停当前股票音频:', stock.stockInfo?.name);
stopAudio();
emotionAudioStore.resetAudioState();
setStockAudioState(stock, { isPlaying: false, isPaused: true });
} else { } else {
//
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();
//
if (isAnyAudioPlaying) {
console.log('停止其他正在播放的音频,准备播放新音频:', stock.stockInfo?.name);
stopAudio();
emotionAudioStore.resetAudioState();
}
//
clearAllStockAudioStates();
//
if (stockConclusion && (stockConclusion.one1_url || stockConclusion.two_url || stockConclusion.three_url || stockConclusion.four_url)) {
console.log('开始播放股票音频:', stock.stockInfo?.name);
setStockAudioState(stock, { isPlaying: true, isPaused: false });
//
playAudioQueue(stockConclusion, false, () => {
//
setStockAudioState(stock, { isPlaying: false, isPaused: false });
});
} else { } else {
//
emotionAudioStore.toggleVoice();
console.log('该股票没有可播放的音频数据');
} }
} }
} }
@ -355,12 +392,87 @@ const clearConversations = () => {
addedStocks.value.clear(); addedStocks.value.clear();
}; };
//
//
const addStock = (stockData) => {
console.log('AiEmotion组件接收到股票数据:', stockData);
//
isHistoryMode.value = true;
// 1. stockList
isPageLoaded.value = false; //
emotionStore.clearAllStocks(); // stockList
emotionStore.clearConversations(); //
messages.value = []; //
//
addedStocks.value.clear();
//
stopAudio();
audioUrl.value = '';
emotionAudioStore.resetAudioState();
clearTypewriterTimers();
hasTriggeredAudio.value = false;
hasTriggeredTypewriter.value = false;
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 = {
marketTemperature: false,
emotionDecod: false,
emotionalBottomRadar: false,
emoEnergyConverter: false
};
// 2. stockList
emotionStore.addStock(stockData);
// 3.
isPageLoaded.value = true;
// 4. 使nextTickDOM
nextTick(() => {
//
startHeightObserver();
//
scrollToBottom();
});
};
//
defineExpose({ defineExpose({
handleSendMessage, handleSendMessage,
clearConversations
clearConversations,
addStock
}); });
const isPageLoaded = ref(false); // const isPageLoaded = ref(false); //
const isHistoryMode = ref(false); //
// const isLoading = ref(false); // // const isLoading = ref(false); //
const isRotating = ref(false);// const isRotating = ref(false);//
const version1 = ref(1); // const version1 = ref(1); //
@ -415,6 +527,8 @@ const typewriterTimers = ref([]);
const stockTypewriterShown = ref(new Map()); const stockTypewriterShown = ref(new Map());
// //
const stockAudioPlayed = ref(new Map()); const stockAudioPlayed = ref(new Map());
//
const stockAudioStates = ref(new Map());
// //
const currentOnCompleteCallback = ref(null); const currentOnCompleteCallback = ref(null);
@ -422,6 +536,29 @@ const currentOnCompleteCallback = ref(null);
const audioUrl = ref(''); const audioUrl = ref('');
const isAudioPlaying = ref(false); const isAudioPlaying = ref(false);
//
const getStockAudioState = (stock) => {
const stockCode = stock.stockInfo?.code || stock.stockInfo?.symbol;
if (!stockCode) return { isPlaying: false, isPaused: false };
return stockAudioStates.value.get(stockCode) || { isPlaying: false, isPaused: false };
};
//
const setStockAudioState = (stock, state) => {
const stockCode = stock.stockInfo?.code || stock.stockInfo?.symbol;
if (!stockCode) return;
stockAudioStates.value.set(stockCode, { ...state });
};
//
const clearAllStockAudioStates = () => {
for (const [key, value] of stockAudioStates.value.entries()) {
stockAudioStates.value.set(key, { isPlaying: false, isPaused: false });
}
};
// //
const showBackToTop = ref(false); const showBackToTop = ref(false);
@ -746,7 +883,13 @@ watch(currentStock, (newStock) => {
console.log('图表数据已准备完成,开始渲染:', newStock.apiData) console.log('图表数据已准备完成,开始渲染:', newStock.apiData)
// //
setTimeout(() => { setTimeout(() => {
if (scenarioApplicationRef.value && parsedConclusion.value) {
// scenarioApplicationRef.value DOM
if (!scenarioApplicationRef.value || !(scenarioApplicationRef.value instanceof Element)) {
console.warn('scenarioApplicationRef.value 不是有效的 DOM 元素,跳过处理');
return;
}
if (parsedConclusion.value) {
const stockCode = newStock.stockInfo?.code || newStock.stockInfo?.symbol; const stockCode = newStock.stockInfo?.code || newStock.stockInfo?.symbol;
// //
@ -763,9 +906,9 @@ watch(currentStock, (newStock) => {
if (stockCode) { if (stockCode) {
// //
if (!stockTypewriterShown.value.has(stockCode)) { if (!stockTypewriterShown.value.has(stockCode)) {
//
if (audioUrl.value) {
console.log('该股票第一次进入场景应用,开始打字机效果和音频播放');
//
if (isUserInitiated.value && audioUrl.value) {
console.log('用户主动搜索,该股票第一次进入场景应用,开始打字机效果和音频播放');
hasTriggeredTypewriter.value = true; hasTriggeredTypewriter.value = true;
hasTriggeredAudio.value = true; hasTriggeredAudio.value = true;
@ -779,9 +922,38 @@ watch(currentStock, (newStock) => {
} }
stockTypewriterShown.value.set(stockCode, true); stockTypewriterShown.value.set(stockCode, true);
} else {
} else if (isUserInitiated.value && !audioUrl.value) {
console.log('音频尚未准备好,等待音频加载完成后再触发效果(股票切换后)'); console.log('音频尚未准备好,等待音频加载完成后再触发效果(股票切换后)');
return; return;
} else {
//
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);
stockAudioPlayed.value.set(stockCode, true);
} }
} else { } else {
// //
@ -1142,6 +1314,12 @@ const playNextAudio = () => {
console.log("🎉 所有音频播放完成"); console.log("🎉 所有音频播放完成");
emotionAudioStore.nowSound = null; emotionAudioStore.nowSound = null;
isCallingPlayNext = false; isCallingPlayNext = false;
//
if (audioInfo.onComplete && typeof audioInfo.onComplete === 'function') {
console.log('调用音频播放完成回调');
audioInfo.onComplete();
}
} }
}, },
onstop: () => { onstop: () => {
@ -1483,6 +1661,8 @@ async function handleSendMessage(input, onComplete) {
console.log("发送内容:", input); console.log("发送内容:", input);
// //
isUserInitiated.value = true; isUserInitiated.value = true;
// conversation-area
isHistoryMode.value = false;
// //
if (!input || !input.trim()) { if (!input || !input.trim()) {
@ -1496,7 +1676,8 @@ async function handleSendMessage(input, onComplete) {
return; return;
} }
//
//
isPageLoaded.value = false; //
isRotating.value = true; isRotating.value = true;
const previousMessages = [...messages.value]; // const previousMessages = [...messages.value]; //
messages.value = []; // messages.value = []; //
@ -1645,6 +1826,15 @@ async function handleSendMessage(input, onComplete) {
// isLoading.value = false; // isLoading.value = false;
isPageLoaded.value = true; isPageLoaded.value = true;
// 使nextTickDOM
nextTick(() => {
messages.value = [];
//
startHeightObserver();
//
scrollToBottom();
});
// //
try { try {
await chatStore.getUserCount(); await chatStore.getUserCount();
@ -1736,6 +1926,14 @@ async function handleSendMessage(input, onComplete) {
// false // false
if (emotionStore.stockList.length > 0 && emotionStore.activeStock) { if (emotionStore.stockList.length > 0 && emotionStore.activeStock) {
isPageLoaded.value = true; isPageLoaded.value = true;
// 使nextTickDOM
nextTick(() => {
messages.value = [];
//
startHeightObserver();
//
scrollToBottom();
});
console.log('请求工作流接口失败,但恢复显示之前的股票数据'); console.log('请求工作流接口失败,但恢复显示之前的股票数据');
// //
nextTick(() => { nextTick(() => {
@ -1815,6 +2013,14 @@ async function fetchData(code, market, stockName, queryText, stockId) {
// false // false
if (emotionStore.stockList.length > 0 && emotionStore.activeStock) { if (emotionStore.stockList.length > 0 && emotionStore.activeStock) {
isPageLoaded.value = true; isPageLoaded.value = true;
// 使nextTickDOM
nextTick(() => {
messages.value = [];
//
startHeightObserver();
//
scrollToBottom();
});
console.log('数据验证失败,但恢复显示之前的股票数据'); console.log('数据验证失败,但恢复显示之前的股票数据');
// //
nextTick(() => { nextTick(() => {
@ -1862,6 +2068,14 @@ async function fetchData(code, market, stockName, queryText, stockId) {
// false // false
if (emotionStore.stockList.length > 0 && emotionStore.activeStock) { if (emotionStore.stockList.length > 0 && emotionStore.activeStock) {
isPageLoaded.value = true; isPageLoaded.value = true;
// 使nextTickDOM
nextTick(() => {
messages.value = [];
//
startHeightObserver();
//
scrollToBottom();
});
console.log('API请求失败,但恢复显示之前的股票数据'); console.log('API请求失败,但恢复显示之前的股票数据');
// //
nextTick(() => { nextTick(() => {
@ -1890,6 +2104,14 @@ async function fetchData(code, market, stockName, queryText, stockId) {
// false // false
if (emotionStore.stockList.length > 0 && emotionStore.activeStock) { if (emotionStore.stockList.length > 0 && emotionStore.activeStock) {
isPageLoaded.value = true; isPageLoaded.value = true;
// 使nextTickDOM
nextTick(() => {
messages.value = [];
//
startHeightObserver();
//
scrollToBottom();
});
console.log('网络异常,但恢复显示之前的股票数据'); console.log('网络异常,但恢复显示之前的股票数据');
// //
nextTick(() => { nextTick(() => {
@ -2024,6 +2246,9 @@ async function renderChartsSequentially(clonedData, stockIndex = 0) {
if (typeof config.ref[config.method] === 'function') { if (typeof config.ref[config.method] === 'function') {
try { try {
// DOM
await new Promise(resolve => setTimeout(resolve, 100));
config.ref[config.method](...config.params); config.ref[config.method](...config.params);
console.log(`${stockIndex}个股票的${config.name}图表渲染成功`); console.log(`${stockIndex}个股票的${config.name}图表渲染成功`);
@ -2229,10 +2454,24 @@ function setupIntersectionObserver() {
if (parsedConclusion.value && stockCode) { if (parsedConclusion.value && stockCode) {
// //
if (!stockTypewriterShown.value.has(stockCode)) { if (!stockTypewriterShown.value.has(stockCode)) {
//
console.log('该股票第一次进入场景应用,直接显示完整内容,不自动播放');
//
if (isUserInitiated.value && audioUrl.value) {
console.log('用户主动搜索,该股票第一次进入场景应用,开始打字机效果和音频播放');
if (!stockAudioPlayed.value.has(stockCode)) {
console.log('开始音频播放和打字机效果');
stockAudioPlayed.value.set(stockCode, true);
playAudioQueue(parsedConclusion.value, true);
} else {
//
startTypewriterEffect(parsedConclusion.value);
}
stockTypewriterShown.value.set(stockCode, true);
} else {
//
console.log('非用户主动搜索,该股票第一次进入场景应用,直接显示完整内容');
// 使
const conclusion = parsedConclusion.value; const conclusion = parsedConclusion.value;
displayedTexts.value = { displayedTexts.value = {
one1: conclusion.one1 || '', one1: conclusion.one1 || '',
@ -2257,9 +2496,10 @@ function setupIntersectionObserver() {
disclaimer: true disclaimer: true
}; };
//
//
stockTypewriterShown.value.set(stockCode, true); stockTypewriterShown.value.set(stockCode, true);
stockAudioPlayed.value.set(stockCode, true); //
stockAudioPlayed.value.set(stockCode, true);
}
} else { } else {
// //
console.log('非第一次进入场景应用或已触发过,直接显示完整内容'); console.log('非第一次进入场景应用或已触发过,直接显示完整内容');
@ -2328,6 +2568,131 @@ const scrollToTop = () => {
}, 1000); }, 1000);
}; };
//
const heightObserver = ref(null);
const isAutoScrollEnabled = ref(false);
//
const scrollToBottom = () => {
// 使nextTickDOM
nextTick(() => {
//
const documentHeight = Math.max(
document.body.scrollHeight,
document.body.offsetHeight,
document.documentElement.clientHeight,
document.documentElement.scrollHeight,
document.documentElement.offsetHeight
);
//
window.scrollTo({
top: documentHeight,
behavior: 'smooth'
});
//
setTimeout(() => {
document.documentElement.scrollTop = documentHeight;
document.body.scrollTop = documentHeight;
}, 1000);
});
};
//
const debouncedScrollToBottom = (() => {
let timeoutId = null;
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
if (isAutoScrollEnabled.value && isPageLoaded.value) {
scrollToBottom();
}
}, 150);
};
})();
//
const startHeightObserver = () => {
//
stopHeightObserver();
isAutoScrollEnabled.value = true;
// ResizeObserver
heightObserver.value = new ResizeObserver((entries) => {
if (isAutoScrollEnabled.value && isPageLoaded.value) {
debouncedScrollToBottom();
}
});
// document.body
if (document.body) {
heightObserver.value.observe(document.body);
}
// MutationObserverDOM
const mutationObserver = new MutationObserver((mutations) => {
let shouldScroll = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
//
const hasContent = Array.from(mutation.addedNodes).some(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
return node.offsetHeight > 0 || node.scrollHeight > 0;
}
return node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0;
});
if (hasContent) {
shouldScroll = true;
}
}
});
if (shouldScroll && isAutoScrollEnabled.value && isPageLoaded.value) {
debouncedScrollToBottom();
}
});
// DOM
const mainContainer = document.querySelector('.main') || document.body;
if (mainContainer) {
mutationObserver.observe(mainContainer, {
childList: true,
subtree: true,
attributes: false,
characterData: true
});
}
// mutationObserver便
heightObserver.value.mutationObserver = mutationObserver;
console.log('页面高度监听器已启动');
};
//
const stopHeightObserver = () => {
isAutoScrollEnabled.value = false;
if (heightObserver.value) {
// ResizeObserver
heightObserver.value.disconnect();
// MutationObserver
if (heightObserver.value.mutationObserver) {
heightObserver.value.mutationObserver.disconnect();
heightObserver.value.mutationObserver = null;
}
heightObserver.value = null;
}
console.log('页面高度监听器已停止');
};
// //
const handlePageScroll = () => { const handlePageScroll = () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop; const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;
@ -2453,6 +2818,14 @@ onMounted(async () => {
// //
isPageLoaded.value = true; isPageLoaded.value = true;
// 使nextTickDOM
nextTick(() => {
messages.value = [];
//
startHeightObserver();
//
scrollToBottom();
});
// DOM // DOM
nextTick(() => { nextTick(() => {
@ -2526,6 +2899,9 @@ onUnmounted(() => {
hasTriggeredAudio.value = false; hasTriggeredAudio.value = false;
hasTriggeredTypewriter.value = false; hasTriggeredTypewriter.value = false;
//
stopHeightObserver();
// Intersection Observer // Intersection Observer
if (intersectionObserver.value) { if (intersectionObserver.value) {
intersectionObserver.value.disconnect(); intersectionObserver.value.disconnect();
@ -3150,9 +3526,11 @@ const emit = defineEmits(['updateMessage', 'sendMessage', 'ensureAIchat']);
0% { 0% {
transform: scale(1); transform: scale(1);
} }
50% { 50% {
transform: scale(1.1); transform: scale(1.1);
} }
100% { 100% {
transform: scale(1); transform: scale(1);
} }
@ -3191,9 +3569,12 @@ const emit = defineEmits(['updateMessage', 'sendMessage', 'ensureAIchat']);
} }
@keyframes float { @keyframes float {
0%, 100% {
0%,
100% {
transform: translateY(0px); transform: translateY(0px);
} }
50% { 50% {
transform: translateY(-5px); transform: translateY(-5px);
} }
@ -3836,6 +4217,43 @@ const emit = defineEmits(['updateMessage', 'sendMessage', 'ensureAIchat']);
letter-spacing: 1px; letter-spacing: 1px;
} */ } */
/* 对话区域样式 */
.conversation-area {
width: 100%;
padding: 0 20px;
}
.message-list {
display: flex;
flex-direction: column;
gap: 15px;
}
.message-item {
width: 100%;
display: flex;
}
.user-message-item {
justify-content: flex-end;
}
.ai-message-item {
justify-content: flex-start;
}
.user-message-wrapper {
display: flex;
justify-content: flex-end;
max-width: 70%;
}
.ai-message-wrapper {
display: flex;
justify-content: flex-start;
max-width: 80%;
}
/* 顶部锚点样式 */ /* 顶部锚点样式 */
.top-anchor { .top-anchor {
position: relative; position: relative;
@ -3881,6 +4299,18 @@ const emit = defineEmits(['updateMessage', 'sendMessage', 'ensureAIchat']);
transform: translateY(-1px); transform: translateY(-1px);
} }
/* 页面主容器样式 */
.page-container {
position: relative;
width: 100%;
min-height: 100vh;
}
.master:last-child {
border-bottom: none;
margin-bottom: 0;
}
/* class01容器样式 */ /* class01容器样式 */
.main { .main {
position: relative; position: relative;

25
src/views/components/HistoryRecord.vue

@ -542,12 +542,35 @@ const openDetail = (record) => {
const historyData = ref({}); const historyData = ref({});
const selectRecord = async (record) => { const selectRecord = async (record) => {
try { try {
selectedRecordId.value = record.id;
const result = await clickRecordAPI({ const result = await clickRecordAPI({
model: props.currentType == "AIchat" ? 1 : 2, model: props.currentType == "AIchat" ? 1 : 2,
parentId: record.parentId, parentId: record.parentId,
recordId: record.id, recordId: record.id,
}); });
historyData.value;
if (result && result.data) {
historyData.value = result.data;
//
const stockData = {
queryText: record.stockCode || record.stockName || '', // 使
stockInfo: {
name: result.data.stockData?.stockName || record.stockName || '',
code: record.stockCode || '',
market: record.stockMarket || 'cn'
},
apiData: result.data.stockData || {}, //
conclusionData: result.data.wokeFlowData?.One || {}, //
timestamp: new Date().toISOString()
};
// emit
emit('selectRecord', stockData);
console.log('历史记录数据已发送给父组件:', stockData);
} else {
console.error('历史记录数据格式不正确:', result);
}
} catch (e) { } catch (e) {
console.error("获取历史记录数据失败", e); console.error("获取历史记录数据失败", e);
} }

6
src/views/components/emoEnergyConverter.vue

@ -303,6 +303,12 @@ function initQXNLZHEcharts(kline, qxnlzhqData) {
} }
}); });
// DOM
if (!qxnlzhqEchartsRef.value) {
console.error('emoEnergyConverter: DOM元素未找到,无法初始化图表');
return;
}
// //
qxnlzhqEchartsInstance = echarts.init(qxnlzhqEchartsRef.value); qxnlzhqEchartsInstance = echarts.init(qxnlzhqEchartsRef.value);
let option; let option;

27
src/views/homePage.vue

@ -234,15 +234,22 @@ const enableInput = () => {
}; };
// //
const handleHistorySelect = (record) => {
//
message.value = record.question;
// tabtab
if (record.type !== activeTab.value) {
const tabIndex = record.type === "AIchat" ? 0 : 1;
setActiveTab(record.type, tabIndex);
const handleHistorySelect = (stockData) => {
console.log('接收到历史记录数据:', stockData);
// AiEmotionAiEmotion
if (activeTab.value !== 'AiEmotion') {
setActiveTab('AiEmotion', 1);
}
// addStock
nextTick(() => {
if (aiEmotionRef.value && aiEmotionRef.value.addStock) {
aiEmotionRef.value.addStock(stockData);
} else {
console.error('AiEmotion组件或addStock方法不可用');
} }
});
}; };
// //
@ -905,9 +912,9 @@ onUnmounted(() => {
/* 添加平滑滚动效果 */ /* 添加平滑滚动效果 */
} }
.pcTabContent {
/* .pcTabContent {
margin: 0 6%; margin: 0 6%;
}
} */
@media (max-width: 768px) { @media (max-width: 768px) {
.tab-container { .tab-container {

Loading…
Cancel
Save