Browse Source

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

ds_hxl
宋杰 2 weeks ago
parent
commit
4fd25981da
  1. 2
      src/main.js
  2. 103
      src/store/emotion.ts
  3. 1210
      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);
},
},
});
@ -27,4 +118,16 @@ interface HistoryItem {
};
apiData: any; // 接口返回的原始数据(包含图表数据)
timestamp: string; // 记录时间
}
// 定义股票数据的类型
interface StockData {
queryText: string; // 用户输入的查询文本
stockInfo: {
name: string; // 股票名称
code: string; // 股票代码
market: string; // 市场
};
apiData: any; // API返回的完整数据
timestamp: string; // 数据获取时间
}

1210
src/views/AiEmotion.vue
File diff suppressed because it is too large
View File

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