Browse Source

主页按钮优化;情绪大模型新增保存用户查询记录功能

ds_hxl
宋杰 2 weeks ago
parent
commit
4fd25981da
  1. 2
      src/main.js
  2. 103
      src/store/emotion.ts
  3. 154
      src/views/AiEmotion.vue
  4. 223
      src/views/components/StockTabs.vue
  5. 6
      src/views/components/emotionalBottomRadar.vue
  6. 43
      src/views/components/marketTemperature.vue

2
src/main.js

@ -6,12 +6,14 @@ import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
// import 'reset-css';
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
import { useAppBridge } from './assets/js/useAppBridge.js'
const { packageFun, fullClose } = useAppBridge()
const app = createApp(App)
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}

103
src/store/emotion.ts

@ -4,7 +4,25 @@ import { defineStore } from 'pinia';
export const useEmotionStore = defineStore('emotion', {
state: () => ({
history: [] as HistoryItem[], // 历史记录数组
stockList: [] as StockData[], // 当前显示的股票列表
activeStockIndex: 0, // 当前激活的股票索引
maxStocks: 10, // 最大股票数量限制
}),
persist: {
key: 'emotion-store',
storage: localStorage,
paths: ['history', 'stockList', 'activeStockIndex']
},
getters: {
// 获取当前激活的股票
activeStock: (state) => {
return state.stockList[state.activeStockIndex] || null;
},
// 获取股票数量
stockCount: (state) => state.stockList.length,
// 是否达到最大股票数量
isMaxStocks: (state) => state.stockList.length >= state.maxStocks,
},
actions: {
// 添加历史记录
addHistory(item: HistoryItem) {
@ -14,6 +32,79 @@ export const useEmotionStore = defineStore('emotion', {
clearHistory() {
this.history = [];
},
// 添加新股票
addStock(stockData: StockData) {
// 检查是否已存在相同的股票
const existingIndex = this.stockList.findIndex(
stock => stock.stockInfo.code === stockData.stockInfo.code &&
stock.stockInfo.market === stockData.stockInfo.market
);
if (existingIndex !== -1) {
// 如果股票已存在,更新数据并切换到该股票
this.stockList[existingIndex] = stockData;
this.activeStockIndex = existingIndex;
} else {
// 如果达到最大数量,移除最旧的股票
if (this.stockList.length >= this.maxStocks) {
this.stockList.shift();
if (this.activeStockIndex > 0) {
this.activeStockIndex--;
}
}
// 添加新股票并设为当前激活
this.stockList.push(stockData);
this.activeStockIndex = this.stockList.length - 1;
}
// 同时添加到历史记录
this.addHistory({
queryText: stockData.queryText,
stockInfo: stockData.stockInfo,
apiData: stockData.apiData,
timestamp: stockData.timestamp
});
},
// 切换股票
switchStock(index: number) {
if (index >= 0 && index < this.stockList.length) {
this.activeStockIndex = index;
}
},
// 移除股票
removeStock(index: number) {
if (index >= 0 && index < this.stockList.length) {
this.stockList.splice(index, 1);
// 调整激活索引
if (this.activeStockIndex >= this.stockList.length) {
this.activeStockIndex = Math.max(0, this.stockList.length - 1);
} else if (this.activeStockIndex > index) {
this.activeStockIndex--;
}
}
},
// 更新股票数据
updateStockData(index: number, apiData: any) {
if (index >= 0 && index < this.stockList.length) {
this.stockList[index].apiData = apiData;
this.stockList[index].timestamp = new Date().toISOString();
}
},
// 清空所有股票
clearAllStocks() {
this.stockList = [];
this.activeStockIndex = 0;
},
// 从历史记录恢复股票
restoreFromHistory(historyItem: HistoryItem) {
const stockData: StockData = {
queryText: historyItem.queryText,
stockInfo: historyItem.stockInfo,
apiData: historyItem.apiData,
timestamp: historyItem.timestamp
};
this.addStock(stockData);
},
},
});
@ -28,3 +119,15 @@ interface HistoryItem {
apiData: any; // 接口返回的原始数据(包含图表数据)
timestamp: string; // 记录时间
}
// 定义股票数据的类型
interface StockData {
queryText: string; // 用户输入的查询文本
stockInfo: {
name: string; // 股票名称
code: string; // 股票代码
market: string; // 市场
};
apiData: any; // API返回的完整数据
timestamp: string; // 数据获取时间
}

154
src/views/AiEmotion.vue

@ -2,25 +2,14 @@
<div class="ai-emotion-container" ref="userInputDisplayRef">
<!-- 金轮 -->
<div class="golden-wheel">
<img
src="@/assets/img/AiEmotion/金轮.png"
class="golden-wheel-img"
alt="金轮图标"
:class="{ 'rotating-image': isRotating }"
/>
<img src="@/assets/img/AiEmotion/金轮.png" class="golden-wheel-img" alt="金轮图标"
:class="{ 'rotating-image': isRotating }" />
</div>
<!-- 消息显示区域 -->
<div class="user-input-display">
<div
v-for="(message, index) in messages"
:key="index"
class="message-container"
>
<div v-for="(message, index) in messages" :key="index" class="message-container">
<!-- 用户输入内容 -->
<div
v-if="message.sender === 'user'"
class="message-bubble user-message"
>
<div v-if="message.sender === 'user'" class="message-bubble user-message">
{{ message.text }}
</div>
<!-- AI返回结果 -->
@ -29,17 +18,11 @@
</div>
</div>
</div>
<!-- 输入框和发送按钮 -->
<!-- <footer class="input-container fixed-bottom">
<input type="text" v-model="userInput" placeholder="请输入内容..." class="input-box" />
<button @click="handleSendMessage(userInput)" class="send-button">发送</button>
</footer> -->
<!-- <div class="input-container fixed-bottom">
<input type="text" v-model="userInput" placeholder="请输入内容..." class="input-box" />
<button @click="sendMessage" class="send-button">发送</button>
</div> -->
</div>
<!-- 股票标签页 -->
<StockTabs />
<!-- 渲染整个页面 -->
<div v-if="isPageLoaded" class="class01">
<div class="class00">
@ -144,7 +127,7 @@
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ref, reactive, computed, watch, nextTick, onMounted } from 'vue';
import { getReplyAPI } from '@/api/AiEmotionApi.js'; //
import axios from 'axios';
import item from '@/assets/img/AiEmotion/bk01.png'; //
@ -152,22 +135,57 @@ import emotionDecod from '@/views/components/emotionDecod.vue'; // 导入情绪
import emotionalBottomRadar from '@/views/components/emotionalBottomRadar.vue'; //
import emoEnergyConverter from '@/views/components/emoEnergyConverter.vue'; //
import marketTemperature from '@/views/components/marketTemperature.vue';
import StockTabs from '@/views/components/StockTabs.vue'; //
import blueBorderImg from '@/assets/img/AiEmotion/blueBorder.png' //
import { ElMessage } from 'element-plus';
import { useEmotionStore } from '@/store/emotion'; // Pinia store
// 使Pinia store
const emotionStore = useEmotionStore();
const stockName = ref(""); //
//
const marketTemperatureRef = ref(null); //
const emoEnergyConverterRef = ref(null)
const emotionDecodRef = ref(null)
const emotionalBottomRadarRef = ref(null)
const userInputDisplayRef = ref(null);//
//
const messages = ref([]);
const displayDate = ref(""); //
const isPageLoaded = ref(false); //
const isRotating = ref(false);//
const userInputDisplayRef = ref(null);//
const data1 = ref(null); //
const data2 = ref(); //
const version1 = ref(2); //
// - store
const currentStock = computed(() => emotionStore.activeStock);
const stockName = computed(() => currentStock.value?.stockInfo.name || "");
const displayDate = computed(() => {
if (!currentStock.value?.apiData) return "";
const lastData = currentStock.value.apiData.GSWDJ?.at(-1);
return lastData ? lastData[0] : "";
});
const data1 = computed(() => {
if (!currentStock.value?.apiData) return null;
const lastData = currentStock.value.apiData.GSWDJ?.at(-1);
return lastData ? Math.round(lastData[1]) : null;
});
const data2 = computed(() => {
if (!currentStock.value?.apiData) return null;
const lastData = currentStock.value.apiData.GSWDJ?.at(-1);
return lastData ? Math.round(lastData[2]) : null;
});
//
watch(currentStock, (newStock) => {
if (newStock && newStock.apiData) {
isPageLoaded.value = true;
nextTick(() => {
renderCharts(newStock.apiData);
});
} else {
isPageLoaded.value = false;
}
}, { immediate: true });
//使
defineExpose({ handleSendMessage })
//
@ -221,10 +239,8 @@ async function handleSendMessage(input) {
if (parsedData && parsedData.market && parsedData.code) {
console.log("工作流接口返回股票信息:", parsedData);
//
stockName.value = parsedData.name || "未知股票";
//
fetchData(parsedData.code, parsedData.market);
fetchData(parsedData.code, parsedData.market, parsedData.name || "未知股票", input.trim());
// span01
// updateSpan01();
@ -243,7 +259,7 @@ async function handleSendMessage(input) {
}
//
async function fetchData(code, market) {
async function fetchData(code, market, stockName, queryText) {
try {
const stockDataParams = {
// token: '+XgqsgdW0RLIbIG2pxnnbZi0+fEeMx8pywnIlrmTxtkSaPZ9xjSOWrxq+s0rL3RrfNhXPvGtz9srFfjwu8A',
@ -270,22 +286,23 @@ async function fetchData(code, market) {
if (stockDataResponse.code === 200 && stockDataResponse.data) {
console.log(stockDataResponse.code)
// xx
const lastTwoNumbers = stockDataResponse.data.GSWDJ.map(([date, num1, num2]) => [
date,
Math.round(num1),
Math.round(num2)
]).at(-1);
//
displayDate.value = lastTwoNumbers[0];//
data1.value = lastTwoNumbers[1] //
data2.value = lastTwoNumbers[2] //
console.log('1111111111111111111111')
//
// renderCharts(stockDataResponse.data);
//
isPageLoaded.value = true;
renderCharts(stockDataResponse.data);
//
const stockData = {
queryText: queryText,
stockInfo: {
name: stockName,
code: code,
market: market
},
apiData: stockDataResponse.data,
timestamp: new Date().toISOString()
};
// store
emotionStore.addStock(stockData);
console.log('股票数据已添加到store');
} else {
ElMessage.error('获取接口数据失败');
@ -298,6 +315,9 @@ async function fetchData(code, market) {
//
function renderCharts(data) {
nextTick(() => {
// DOM
setTimeout(() => {
try {
//
if (marketTemperatureRef.value && data.GSWDJ) {
console.log("开始渲染股市温度计图表");
@ -330,6 +350,11 @@ function renderCharts(data) {
emoEnergyConverterRef.value.initQXNLZHEcharts(data.KLine20, data.QXNLZHQ);
console.log("情绪能量转化器图表已渲染");
}
} catch (error) {
console.error('图表渲染过程中发生错误:', error);
ElMessage.error('图表渲染失败,请重试');
}
}, 100); // 100msDOM
});
}
@ -398,6 +423,7 @@ onMounted(() => {
max-width: 500px;
height: auto;
}
/* 定义旋转动画 */
@keyframes rotate {
from {
@ -875,22 +901,27 @@ onMounted(() => {
.class0301 img {
width: 100%;
margin: 10px 10px;
}
.class0403 img {
width: 100%;
margin: 10px 10px;
}
.class0501 img {
width: 100%;
margin: 10px 10px;
}
.class0702 img {
width: 100%;
margin: 10px 10px;
}
.class0700 img {
width: 100%;
margin: 10px 10px;
}
.scaled-img img {
@ -900,18 +931,19 @@ onMounted(() => {
.class09 img {
width: 100%;
margin: 10px 10px;
}
.img01 {
height: auto;
margin-left: 0rem;
width: 35%;
width: 25%;
margin-top: 10px;
}
.title1 {
font-size: 10px;
margin-left: 30px;
margin-left: 13px;
}
.class02 .span02 {
@ -966,9 +998,9 @@ onMounted(() => {
}
.img02 {
width: 35%;
width: 25%;
height: auto;
margin-left: 5rem;
margin-left: 6rem;
}
.class0401 {
@ -977,9 +1009,9 @@ onMounted(() => {
.img03,
.img04 {
width: 35%;
width: 25%;
height: auto;
margin-left: 5rem;
margin-left: 6rem;
}
.text-container p {
@ -1030,7 +1062,7 @@ onMounted(() => {
background-repeat: no-repeat;
width: 127%;
height: auto;
min-height: 35rem;
min-height: 32rem;
margin: 0 auto;
margin-left: -41px;
@ -1049,9 +1081,9 @@ onMounted(() => {
.div00 {
display: flex;
flex-direction: column;
margin-left: 3rem;
margin-left: 5rem;
gap: 0;
margin-top: -8rem;
margin-top: -6rem;
width: 100%;
height: auto;
}
@ -1106,7 +1138,11 @@ onMounted(() => {
text-align: center;
transform: translate(-50%, -50%);
margin-left: -5rem;
}
.class0502,
.class0601 {
padding-top: 3rem;
}
}
</style>

223
src/views/components/StockTabs.vue

@ -0,0 +1,223 @@
<template>
<div class="stock-tabs" v-if="stockList.length > 0">
<div class="tabs-container">
<div
v-for="(stock, index) in stockList"
:key="`${stock.stockInfo.code}-${stock.stockInfo.market}`"
:class="['tab-item', { active: index === activeStockIndex }]"
@click="switchStock(index)"
>
<div class="stock-info">
<span class="stock-name">{{ stock.stockInfo.name }}</span>
<span class="stock-code">{{ stock.stockInfo.code }}</span>
</div>
<button
class="close-btn"
@click.stop="removeStock(index)"
v-if="stockList.length > 1"
>
×
</button>
</div>
</div>
<div class="tabs-actions">
<span class="stock-count">{{ stockCount }}/{{ maxStocks }}</span>
<button
class="clear-all-btn"
@click="clearAllStocks"
v-if="stockList.length > 1"
>
清空全部
</button>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useEmotionStore } from '@/store/emotion'
import { ElMessageBox } from 'element-plus'
const emotionStore = useEmotionStore()
//
const stockList = computed(() => emotionStore.stockList)
const activeStockIndex = computed(() => emotionStore.activeStockIndex)
const stockCount = computed(() => emotionStore.stockCount)
const maxStocks = computed(() => emotionStore.maxStocks)
//
const switchStock = (index) => {
emotionStore.switchStock(index)
}
//
const removeStock = async (index) => {
try {
await ElMessageBox.confirm(
'确定要移除这只股票吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
emotionStore.removeStock(index)
} catch {
//
}
}
//
const clearAllStocks = async () => {
try {
await ElMessageBox.confirm(
'确定要清空所有股票吗?',
'提示',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
)
emotionStore.clearAllStocks()
} catch {
//
}
}
</script>
<style scoped>
.stock-tabs {
background: #f8f9fa;
border-radius: 8px;
padding: 12px;
margin-bottom: 16px;
border: 1px solid #e9ecef;
}
.tabs-container {
display: flex;
gap: 8px;
flex-wrap: wrap;
margin-bottom: 8px;
}
.tab-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #ffffff;
border: 1px solid #dee2e6;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
min-width: 120px;
}
.tab-item:hover {
border-color: #007bff;
box-shadow: 0 2px 4px rgba(0, 123, 255, 0.1);
}
.tab-item.active {
background: #007bff;
border-color: #007bff;
color: white;
}
.stock-info {
display: flex;
flex-direction: column;
flex: 1;
}
.stock-name {
font-size: 14px;
font-weight: 500;
line-height: 1.2;
}
.stock-code {
font-size: 12px;
opacity: 0.8;
line-height: 1.2;
}
.close-btn {
width: 20px;
height: 20px;
border: none;
background: rgba(0, 0, 0, 0.1);
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 1;
transition: background-color 0.2s ease;
}
.close-btn:hover {
background: rgba(255, 0, 0, 0.2);
}
.tab-item.active .close-btn {
background: rgba(255, 255, 255, 0.2);
}
.tab-item.active .close-btn:hover {
background: rgba(255, 255, 255, 0.3);
}
.tabs-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 8px;
border-top: 1px solid #e9ecef;
}
.stock-count {
font-size: 12px;
color: #6c757d;
}
.clear-all-btn {
padding: 4px 8px;
font-size: 12px;
background: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.clear-all-btn:hover {
background: #c82333;
}
/* 响应式设计 */
@media (max-width: 768px) {
.tabs-container {
gap: 6px;
}
.tab-item {
min-width: 100px;
padding: 6px 8px;
}
.stock-name {
font-size: 13px;
}
.stock-code {
font-size: 11px;
}
}
</style>

6
src/views/components/emotionalBottomRadar.vue

@ -8,6 +8,12 @@ import * as echarts from 'echarts'
const bottomRadarRef = ref(null)
let bottomRadarChart = null
function initEmotionalBottomRadar(KlineData, barAndLineData) {
//
if (bottomRadarChart) {
bottomRadarChart.dispose()
bottomRadarChart = null
}
let bottomRadarChartDom = document.getElementById('bottomRadarChart')
bottomRadarChart = echarts.init(bottomRadarChartDom)

43
src/views/components/marketTemperature.vue

@ -55,7 +55,7 @@
</template>
<script setup>
import { ref, computed, onMounted, defineExpose, defineProps, onUnmounted } from 'vue'
import { ref, computed, onMounted, defineExpose, defineProps, onUnmounted, onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'
const props = defineProps({
@ -72,6 +72,7 @@ const props = defineProps({
const KlineCanvs = ref()
const WDRL = ref([])
const klineDataRaw = ref([]) // K 线
let chartInstance = null //
const indexCodes = ['NDX', 'DJIA', 'SPX', 'STI', 'KLSE', 'TSX', 'N225', 'KS11', 'JKSE', '1A0001', 'HSI', 'I63', 'VNINDE']
const isIndexCode = computed(() => indexCodes.includes(props.code))
@ -162,6 +163,13 @@ function initChart(raw, klineDataRawValue, WDRLValue) {
console.error('initChart: raw, klineDataRawValue or WDRLValue is undefined')
return
}
//
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
// K 线
const klineData = klineDataRawValue.map(item => {
const open = item[1]
@ -179,8 +187,9 @@ function initChart(raw, klineDataRawValue, WDRLValue) {
const marketData = raw.map(item => Math.round(item[1]))
const stockData = raw.map(item => Math.round(item[2]))
const chart = echarts.init(KlineCanvs.value)
chart.setOption({
//
chartInstance = echarts.init(KlineCanvs.value)
chartInstance.setOption({
tooltip: {},
legend: { data: ['K线', '市场温度', '股票温度'], textStyle: { color: 'white' } },
xAxis: {
@ -232,14 +241,16 @@ function initChart(raw, klineDataRawValue, WDRLValue) {
})
//
const resizeHandler = () => {
chart.resize()
if (chartInstance) {
chartInstance.resize()
}
}
window.addEventListener('resize', resizeHandler)
//
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
})
// resize便
if (!window.marketTempResizeHandler) {
window.marketTempResizeHandler = resizeHandler
}
//
adjustCellFontSize()
}
@ -263,6 +274,20 @@ function adjustCellFontSize() {
})
}
}
//
onBeforeUnmount(() => {
//
if (chartInstance) {
chartInstance.dispose()
chartInstance = null
}
// resize
if (window.marketTempResizeHandler) {
window.removeEventListener('resize', window.marketTempResizeHandler)
window.marketTempResizeHandler = null
}
})
defineExpose({ initChart })
</script>
@ -353,7 +378,7 @@ defineExpose({ initChart })
}
.border3 {
margin-top: 40px;
margin-top: 25px;
border-radius: 8px;
padding: 20px;
margin-left: -13px;

Loading…
Cancel
Save