Browse Source

Merge branch 'songjie/feature-20250628160649-上线前优化' into milestone-20250710-上线前优化

master
宋杰 1 week ago
parent
commit
58428f2137
  1. 276
      src/views/AiEmotion.vue
  2. 6
      src/views/components/emoEnergyConverter.vue
  3. 9
      src/views/components/emotionDecod.vue
  4. 13
      src/views/components/emotionalBottomRadar.vue

276
src/views/AiEmotion.vue

@ -46,18 +46,17 @@
<div class="loading-text">AI情绪大模型正在努力为您加载请稍候...</div> <div class="loading-text">AI情绪大模型正在努力为您加载请稍候...</div>
</div> </div>
</div> </div>
<!-- 股票标签页 -->
<StockTabs />
<!-- 渲染整个页面 -->
<div v-if="isPageLoaded" class="main">
<!-- 移除股票标签页改为对话形式展示 -->
<!-- 渲染整个页面 - 遍历stockList显示所有股票 -->
<div v-for="(stock, stockIndex) in emotionStore.stockList" :key="`stock-${stockIndex}-${stock.timestamp}`" v-if="isPageLoaded" class="main">
<div class="main-content-wrapper"> <div class="main-content-wrapper">
<!-- 四维矩阵图 --> <!-- 四维矩阵图 -->
<div class="matrix-header"> <div class="matrix-header">
<!-- <img class="item" :src="item" alt="思维矩阵图片" /> --> <!-- <img class="item" :src="item" alt="思维矩阵图片" /> -->
<div class="market-temperature-label"> <div class="market-temperature-label">
{{ stockName }}{{ stockName ? '量子四维矩阵图' : '' }}
{{ stock.stockInfo.name }}{{ stock.stockInfo.name ? '量子四维矩阵图' : '' }}
</div> </div>
<div class="market-temperature-value">{{ displayDate }}</div>
<div class="market-temperature-value">{{ getDisplayDate(stock) }}</div>
</div> </div>
<div class="market-temperature-icon" v-if="chartVisibility.marketTemperature"> <div class="market-temperature-icon" v-if="chartVisibility.marketTemperature">
<img src="@/assets/img/AiEmotion/L1.png" alt="情绪监控图标"> <img src="@/assets/img/AiEmotion/L1.png" alt="情绪监控图标">
@ -70,11 +69,11 @@
<span class="matrix-main-title">股市温度计</span> <span class="matrix-main-title">股市温度计</span>
</div> </div>
<div class="temperature-display"> <div class="temperature-display">
<div class="temperature-cold">股票温度{{ data2 ?? "NA" }}</div>
<div class="temperature-hot">市场温度{{ data1 }}</div>
<div class="temperature-cold">股票温度{{ getStockData2(stock) ?? "NA" }}</div>
<div class="temperature-hot">市场温度{{ getStockData1(stock) }}</div>
</div> </div>
</div> </div>
<marketTemperature ref="marketTemperatureRef" :companyName="stockName" :stockCode="stockCode" />
<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">
@ -87,7 +86,7 @@
<span class="emotion-decoder-text">情绪解码器</span> <span class="emotion-decoder-text">情绪解码器</span>
</div> </div>
<div class="emotion-decoder-content"> <div class="emotion-decoder-content">
<emotionDecod ref="emotionDecodRef"></emotionDecod>
<emotionDecod :ref="el => emotionDecodRef[stockIndex] = el"></emotionDecod>
</div> </div>
</div> </div>
<div class="bottom-radar-icon" v-if="chartVisibility.emotionalBottomRadar"> <div class="bottom-radar-icon" v-if="chartVisibility.emotionalBottomRadar">
@ -100,7 +99,7 @@
<span class="bottom-radar-text">情绪探底雷达</span> <span class="bottom-radar-text">情绪探底雷达</span>
</div> </div>
<div class="bottom-radar-content"> <div class="bottom-radar-content">
<emotionalBottomRadar ref="emotionalBottomRadarRef"></emotionalBottomRadar>
<emotionalBottomRadar :ref="el => emotionalBottomRadarRef[stockIndex] = el"></emotionalBottomRadar>
</div> </div>
</div> </div>
<div class="energy-converter-icon" v-if="chartVisibility.emoEnergyConverter"> <div class="energy-converter-icon" v-if="chartVisibility.emoEnergyConverter">
@ -113,7 +112,7 @@
<span class="energy-converter-text">情绪能量转化器</span> <span class="energy-converter-text">情绪能量转化器</span>
</div> </div>
<div class="energy-converter-content"> <div class="energy-converter-content">
<emoEnergyConverter ref="emoEnergyConverterRef"></emoEnergyConverter>
<emoEnergyConverter :ref="el => emoEnergyConverterRef[stockIndex] = el"></emoEnergyConverter>
</div> </div>
</div> </div>
<!-- 核心看点 --> <!-- 核心看点 -->
@ -152,27 +151,27 @@
<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="parsedConclusion">
<div class="conclusion-item" v-if="(parsedConclusion.one1 || parsedConclusion.one2) && moduleVisibility.one">
<h4 class="conclusion-title">{{ displayedTitles.one }}</h4>
<p class="conclusion-text" v-if="parsedConclusion.one1">{{ displayedTexts.one1 }}</p>
<p class="conclusion-text" v-if="parsedConclusion.one2">{{ displayedTexts.one2 }}</p>
<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> </div>
<div class="conclusion-item" v-if="parsedConclusion.two && moduleVisibility.two">
<h4 class="conclusion-title">{{ displayedTitles.two }}</h4>
<p class="conclusion-text">{{ displayedTexts.two }}</p>
<div class="conclusion-item" v-if="getStockConclusion(stock).two">
<h4 class="conclusion-title">L2: 情绪解码</h4>
<p class="conclusion-text">{{ getStockConclusion(stock).two }}</p>
</div> </div>
<div class="conclusion-item" v-if="parsedConclusion.three && moduleVisibility.three">
<h4 class="conclusion-title">{{ displayedTitles.three }}</h4>
<p class="conclusion-text">{{ displayedTexts.three }}</p>
<div class="conclusion-item" v-if="getStockConclusion(stock).three">
<h4 class="conclusion-title">L3: 情绪推演</h4>
<p class="conclusion-text">{{ getStockConclusion(stock).three }}</p>
</div> </div>
<div class="conclusion-item" v-if="parsedConclusion.four && moduleVisibility.four">
<h4 class="conclusion-title">{{ displayedTitles.four }}</h4>
<p class="conclusion-text">{{ displayedTexts.four }}</p>
<div class="conclusion-item" v-if="getStockConclusion(stock).four">
<h4 class="conclusion-title">L4: 情绪套利</h4>
<p class="conclusion-text">{{ getStockConclusion(stock).four }}</p>
</div> </div>
<!-- AI生成内容免责声明 --> <!-- AI生成内容免责声明 -->
<div class="disclaimer-item" v-if="parsedConclusion && moduleVisibility.disclaimer">
<p class="disclaimer-text">{{ displayedTexts.disclaimer }}</p>
<div class="disclaimer-item" v-if="getStockConclusion(stock)">
<p class="disclaimer-text">该内容由AI生成请注意甄别</p>
</div> </div>
</div> </div>
<div class="conclusion-placeholder" v-else> <div class="conclusion-placeholder" v-else>
@ -271,11 +270,11 @@ function processRefuseMessage(refuseData) {
} }
} }
//
const marketTemperatureRef = ref(null); //
const emoEnergyConverterRef = ref(null)
const emotionDecodRef = ref(null)
const emotionalBottomRadarRef = ref(null)
// -
const marketTemperatureRef = ref([]); //
const emoEnergyConverterRef = ref([])
const emotionDecodRef = ref([])
const emotionalBottomRadarRef = ref([])
const userInputDisplayRef = ref(null);// const userInputDisplayRef = ref(null);//
// //
@ -290,10 +289,53 @@ const loadConversationsFromStore = () => {
})); }));
}; };
//
const addedStocks = ref(new Set());
// stockList
const loadConversationsFromStockList = () => {
//
emotionStore.stockList.forEach(stock => {
const stockKey = `${stock.stockInfo.code}_${stock.timestamp}`;
//
if (!addedStocks.value.has(stockKey)) {
// messages
const existingMessage = messages.value.find(msg =>
msg.sender === 'user' && msg.text === stock.queryText
);
// messages
if (!existingMessage) {
// AI
const userMessage = {
sender: 'user',
text: stock.queryText
};
messages.value.push(userMessage);
// emotion storestore
const storeConversations = emotionStore.getConversations();
const existingInStore = storeConversations.find(conv =>
conv.sender === 'user' && conv.text === stock.queryText
);
if (!existingInStore) {
emotionStore.addConversation(userMessage);
}
}
//
addedStocks.value.add(stockKey);
}
});
};
// //
const clearConversations = () => { const clearConversations = () => {
messages.value = []; messages.value = [];
emotionStore.clearConversations(); emotionStore.clearConversations();
//
addedStocks.value.clear();
}; };
// //
@ -346,10 +388,10 @@ const moduleVisibility = ref({
// //
const chartVisibility = ref({ const chartVisibility = ref({
marketTemperature: false,
emotionDecod: false,
emotionalBottomRadar: false,
emoEnergyConverter: false
marketTemperature: true,
emotionDecod: true,
emotionalBottomRadar: true,
emoEnergyConverter: true
}); });
const typewriterTimers = ref([]); const typewriterTimers = ref([]);
// //
@ -410,6 +452,50 @@ const parsedConclusion = computed(() => {
} }
}); });
//
const getDisplayDate = (stock) => {
if (!stock?.apiData) return "";
const lastData = stock.apiData.GSWDJ?.at(-1);
if (!lastData || !lastData[0]) return "";
const dateStr = lastData[0];
// YYYY-MM-DD YYYY/MM/DD
const dateMatch = dateStr.match(/(\d{4})[\-\/](\d{1,2})[\-\/](\d{1,2})/);
if (dateMatch) {
const [, year, month, day] = dateMatch;
// DD/MM/YYYY
return `更新时间:${day.padStart(2, '0')}/${month.padStart(2, '0')}/${year}`;
}
//
return dateStr;
};
//
const getStockData1 = (stock) => {
if (!stock?.apiData) return null;
const lastData = stock.apiData.GSWDJ?.at(-1);
return lastData ? Math.round(lastData[1]) : null;
};
//
const getStockData2 = (stock) => {
if (!stock?.apiData) return null;
const lastData = stock.apiData.GSWDJ?.at(-1);
return lastData ? Math.round(lastData[2]) : null;
};
//
const getStockConclusion = (stock) => {
if (!stock?.conclusionData) return null;
try {
return JSON.parse(stock.conclusionData);
} catch (error) {
console.error('解析股票结论数据失败:', error);
return null;
}
};
// //
@ -429,6 +515,8 @@ watch(() => emotionStore.stockList, (newStockList) => {
hasTriggeredTypewriter.value = false; hasTriggeredTypewriter.value = false;
stockTypewriterShown.value.clear(); stockTypewriterShown.value.clear();
stockAudioPlayed.value.clear(); stockAudioPlayed.value.clear();
//
addedStocks.value.clear();
// //
displayedTexts.value = { displayedTexts.value = {
one1: '', one1: '',
@ -460,6 +548,9 @@ watch(() => emotionStore.stockList, (newStockList) => {
emoEnergyConverter: false emoEnergyConverter: false
}; };
console.log('股票列表已清空,页面数据已隐藏'); console.log('股票列表已清空,页面数据已隐藏');
} else {
// stockList
loadConversationsFromStockList();
} }
}, { deep: true }); }, { deep: true });
@ -1089,11 +1180,20 @@ async function handleSendMessage(input, onComplete) {
const previousMessages = [...messages.value]; // const previousMessages = [...messages.value]; //
messages.value = []; // messages.value = []; //
//
const userMessage = reactive({ sender: 'user', text: input });
messages.value.push(userMessage);
// emotion store
emotionStore.addConversation({
sender: 'user',
text: input,
timestamp: new Date().toISOString()
});
// //
await chatStore.getUserCount(); // await chatStore.getUserCount(); //
if (chatStore.UserCount <= 0) { if (chatStore.UserCount <= 0) {
const userMessage = reactive({ sender: 'user', text: input });
messages.value.push(userMessage);
const aiMessage = reactive({ sender: 'ai', text: '您的剩余次数为0,无法使用情绪大模型,请联系客服或购买服务包。' }); const aiMessage = reactive({ sender: 'ai', text: '您的剩余次数为0,无法使用情绪大模型,请联系客服或购买服务包。' });
messages.value.push(aiMessage); messages.value.push(aiMessage);
@ -1115,39 +1215,6 @@ async function handleSendMessage(input, onComplete) {
return; return;
} }
// 使
// const hasPermission = userStore.brainPerssion || userStore.swordPerssion ||
// userStore.pricePerssion || userStore.timePerssion ||
// userStore.aibullPerssion || userStore.aiGnbullPerssion ||
// userStore.airadarPerssion;
// if (!hasPermission) {
// const userMessage = reactive({ sender: 'user', text: input });
// messages.value.push(userMessage);
// const aiMessage = reactive({ sender: 'ai', text: '' });
// messages.value.push(aiMessage);
// //
// isRotating.value = false;
// messages.value = [...previousMessages, ...messages.value];
// //
// if (onComplete && typeof onComplete === 'function') {
// onComplete();
// //
// currentOnCompleteCallback.value = null;
// }
// return;
// }
const userMessage = reactive({ sender: 'user', text: input });
messages.value.push(userMessage);
// emotion store
emotionStore.addConversation({
sender: 'user',
text: input,
timestamp: new Date().toISOString()
});
// //
const thinkingMessageRef = await showThinkingProcess(); const thinkingMessageRef = await showThinkingProcess();
@ -1602,36 +1669,36 @@ function hasValidData(obj) {
return false; return false;
} }
//
async function renderChartsSequentially(clonedData) {
console.log('开始依次渲染图表');
// -
async function renderChartsSequentially(clonedData, stockIndex = 0) {
console.log(`开始渲染第${stockIndex}个股票的图表`);
// //
const chartConfigs = [ const chartConfigs = [
{ {
name: '股市温度计', name: '股市温度计',
ref: marketTemperatureRef,
ref: marketTemperatureRef.value[stockIndex],
visibility: chartVisibility.value.marketTemperature, visibility: chartVisibility.value.marketTemperature,
method: 'initChart', method: 'initChart',
params: [clonedData.GSWDJ, clonedData.KLine20, clonedData.WDRL] params: [clonedData.GSWDJ, clonedData.KLine20, clonedData.WDRL]
}, },
{ {
name: '情绪解码器', name: '情绪解码器',
ref: emotionDecodRef,
ref: emotionDecodRef.value[stockIndex],
visibility: chartVisibility.value.emotionDecod, visibility: chartVisibility.value.emotionDecod,
method: 'initQXNLZHEcharts', method: 'initQXNLZHEcharts',
params: [clonedData.KLine20, clonedData.QXJMQ] params: [clonedData.KLine20, clonedData.QXJMQ]
}, },
{ {
name: '情绪探底雷达', name: '情绪探底雷达',
ref: emotionalBottomRadarRef,
ref: emotionalBottomRadarRef.value[stockIndex],
visibility: chartVisibility.value.emotionalBottomRadar, visibility: chartVisibility.value.emotionalBottomRadar,
method: 'initEmotionalBottomRadar', method: 'initEmotionalBottomRadar',
params: [clonedData.KLine20, clonedData.QXTDLD] params: [clonedData.KLine20, clonedData.QXTDLD]
}, },
{ {
name: '情绪能量转化器', name: '情绪能量转化器',
ref: emoEnergyConverterRef,
ref: emoEnergyConverterRef.value[stockIndex],
visibility: chartVisibility.value.emoEnergyConverter, visibility: chartVisibility.value.emoEnergyConverter,
method: 'initQXNLZHEcharts', method: 'initQXNLZHEcharts',
params: [clonedData.KLine20, clonedData.QXNLZHQ] params: [clonedData.KLine20, clonedData.QXNLZHQ]
@ -1640,29 +1707,29 @@ async function renderChartsSequentially(clonedData) {
// //
for (const config of chartConfigs) { for (const config of chartConfigs) {
if (config.ref.value && config.visibility) {
console.log(`开始渲染${config.name}图表`);
console.log(`${config.name}Ref方法:`, typeof config.ref.value[config.method]);
if (config.ref && config.visibility) {
console.log(`开始渲染${stockIndex}个股票的${config.name}图表`);
console.log(`${config.name}Ref方法:`, typeof config.ref[config.method]);
if (typeof config.ref.value[config.method] === 'function') {
if (typeof config.ref[config.method] === 'function') {
try { try {
config.ref.value[config.method](...config.params);
console.log(`${config.name}图表渲染成功`);
config.ref[config.method](...config.params);
console.log(`${stockIndex}个股票的${config.name}图表渲染成功`);
// //
await new Promise(resolve => setTimeout(resolve, 800)); await new Promise(resolve => setTimeout(resolve, 800));
} catch (error) { } catch (error) {
console.error(`${config.name}图表渲染失败:`, error);
console.error(`${stockIndex}个股票的${config.name}图表渲染失败:`, error);
} }
} else { } else {
console.error(`${config.name}Ref.${config.method} 方法不存在`);
console.error(`${stockIndex}个股票的${config.name}Ref.${config.method} 方法不存在`);
} }
} else { } else {
console.log(`${config.name}图表未渲染,ref存在:`, !!config.ref.value, '数据存在:', config.visibility);
console.log(`${stockIndex}个股票的${config.name}图表未渲染,ref存在:`, !!config.ref, '数据存在:', config.visibility);
} }
} }
console.log('所有图表依次渲染完成');
console.log(`${stockIndex}个股票的所有图表依次渲染完成`);
} }
// //
@ -1746,8 +1813,12 @@ function renderCharts(data) {
} }
}, 1000); }, 1000);
//
renderChartsSequentially(clonedData);
// -
emotionStore.stockList.forEach((stock, index) => {
if (stock.apiData) {
renderChartsSequentially(stock.apiData, index);
}
});
console.log('图表渲染完成'); console.log('图表渲染完成');
} catch (error) { } catch (error) {
@ -1798,19 +1869,28 @@ function isDataLoaded() {
return false; return false;
} }
//
// -
const stockCount = emotionStore.stockList.length;
if (stockCount === 0) {
console.log('没有股票数据');
return false;
}
//
for (let i = 0; i < stockCount; i++) {
const requiredRefs = [ const requiredRefs = [
marketTemperatureRef.value,
emotionDecodRef.value,
emotionalBottomRadarRef.value,
emoEnergyConverterRef.value
marketTemperatureRef.value[i],
emotionDecodRef.value[i],
emotionalBottomRadarRef.value[i],
emoEnergyConverterRef.value[i]
]; ];
const allRefsLoaded = requiredRefs.every(ref => ref !== null); const allRefsLoaded = requiredRefs.every(ref => ref !== null);
if (!allRefsLoaded) { if (!allRefsLoaded) {
console.log('图表组件尚未完全加载');
console.log(`${i}个股票的图表组件尚未完全加载`);
return false; return false;
} }
}
console.log('所有数据和组件已加载完成'); console.log('所有数据和组件已加载完成');
return true; return true;
@ -1954,6 +2034,8 @@ const handleContainerScroll = () => {
onMounted(async () => { onMounted(async () => {
// //
loadConversationsFromStore(); loadConversationsFromStore();
// stockList
loadConversationsFromStockList();
// //
// try { // try {

6
src/views/components/emoEnergyConverter.vue

@ -1,5 +1,5 @@
<template> <template>
<div ref="qxnlzhqEchartsRef" id="qxnlzhqEcharts"></div>
<div ref="qxnlzhqEchartsRef" class="qxnlzhqEcharts"></div>
</template> </template>
<script setup> <script setup>
@ -932,7 +932,7 @@ onBeforeUnmount(() => {
}); });
</script> </script>
<style scoped> <style scoped>
#qxnlzhqEcharts {
.qxnlzhqEcharts {
width: 100%; width: 100%;
height: 542px; height: 542px;
margin: 0; margin: 0;
@ -942,7 +942,7 @@ onBeforeUnmount(() => {
/* 手机端适配样式 */ /* 手机端适配样式 */
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
#qxnlzhqEcharts {
.qxnlzhqEcharts {
width: 100%; width: 100%;
height: 300px; height: 300px;
/* margin: 0; */ /* margin: 0; */

9
src/views/components/emotionDecod.vue

@ -1,7 +1,7 @@
<template> <template>
<!-- <div ref="qxnlzhqEchartsRef" id="qxnlzhqEcharts"></div> --> <!-- <div ref="qxnlzhqEchartsRef" id="qxnlzhqEcharts"></div> -->
<div class="qxjmqbox"> <div class="qxjmqbox">
<div ref="KlineCanvs" id="qxjmqEcharts"></div>
<div ref="KlineCanvs" class="qxjmqEcharts"></div>
</div> </div>
</template> </template>
@ -381,6 +381,9 @@ function initQXNLZHEcharts(kline, qxnlzhqData) {
}; };
// echarts // echarts
if (KlineCanvsChart) {
KlineCanvsChart.dispose();
}
KlineCanvsChart = echarts.init(KlineCanvs.value); KlineCanvsChart = echarts.init(KlineCanvs.value);
KlineCanvsChart.setOption(KlineOption); KlineCanvsChart.setOption(KlineOption);
@ -483,7 +486,7 @@ onBeforeUnmount(() => {
margin: 0 auto; margin: 0 auto;
} }
#qxjmqEcharts {
.qxjmqEcharts {
width: 100%; width: 100%;
height: 542px; height: 542px;
margin: 0; margin: 0;
@ -494,7 +497,7 @@ onBeforeUnmount(() => {
/* 手机端适配样式 */ /* 手机端适配样式 */
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
#qxjmqEcharts {
.qxjmqEcharts {
width: 100%; width: 100%;
height: 400px; height: 400px;
margin: 0; margin: 0;

13
src/views/components/emotionalBottomRadar.vue

@ -1,5 +1,5 @@
<template> <template>
<div ref="bottomRadarRef" id="bottomRadarChart"></div>
<div ref="bottomRadarRef" class="bottomRadarChart"></div>
</template> </template>
<script setup> <script setup>
@ -16,8 +16,11 @@ function initEmotionalBottomRadar(KlineData, barAndLineData) {
bottomRadarChart = null bottomRadarChart = null
} }
let bottomRadarChartDom = document.getElementById('bottomRadarChart')
bottomRadarChart = echarts.init(bottomRadarChartDom)
if (!bottomRadarRef.value) {
console.error('bottomRadarRef not found');
return;
}
bottomRadarChart = echarts.init(bottomRadarRef.value)
// -x // -x
let dateArray = barAndLineData.map(subArray => subArray[0]) let dateArray = barAndLineData.map(subArray => subArray[0])
@ -738,7 +741,7 @@ onBeforeUnmount(() => {
</script> </script>
<style> <style>
#bottomRadarChart {
.bottomRadarChart {
width: 100%; width: 100%;
height: 542px; height: 542px;
box-sizing: border-box; box-sizing: border-box;
@ -749,7 +752,7 @@ onBeforeUnmount(() => {
/* 手机端适配样式 */ /* 手机端适配样式 */
@media only screen and (max-width: 768px) { @media only screen and (max-width: 768px) {
#bottomRadarChart {
.bottomRadarChart {
width: 90% !important; width: 90% !important;
height: 560px; height: 560px;
padding: 0; padding: 0;

Loading…
Cancel
Save