update ui

This commit is contained in:
2025-11-13 16:34:34 +08:00
parent 5ddf8d3c09
commit 9d6c0ac55c

View File

@@ -1,7 +1,7 @@
// src/views/Community/components/HeroPanel.js
// 顶部说明面板组件:产品功能介绍 + 沪深指数折线图 + 热门概念词云图
import React, { useEffect, useState, useMemo } from 'react';
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import {
Box,
Card,
@@ -52,76 +52,164 @@ const fetchPopularConcepts = async () => {
},
body: JSON.stringify({
query: '',
size: 50, // 获取前50个概念用于词云
size: 18, // 获取前18个概念
page: 1,
sort_by: 'change_pct'
})
});
const data = await response.json();
if (data.results) {
logger.debug('HeroPanel', 'fetchPopularConcepts response', {
total: data.total,
resultsCount: data.results?.length
});
if (data.results && data.results.length > 0) {
return data.results.map(item => ({
name: item.concept,
value: Math.abs(item.price_info?.avg_change_pct || 1), // 使用涨跌幅绝对值作为权重
value: Math.abs(item.price_info?.avg_change_pct || 1) + 5, // 使用涨跌幅绝对值 + 基础权重
change_pct: item.price_info?.avg_change_pct || 0,
}));
}
return [];
} catch (error) {
logger.error('HeroPanel', 'fetchPopularConcepts', error);
logger.error('HeroPanel', 'fetchPopularConcepts error', error);
return [];
}
};
/**
* 迷你折线图组件
* 判断当前是否在交易时间内
*/
const isInTradingTime = () => {
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
const timeInMinutes = hours * 60 + minutes;
// 9:30 - 15:00 (570分钟 - 900分钟)
return timeInMinutes >= 570 && timeInMinutes <= 900;
};
/**
* 迷你K线图组件支持实时更新
*/
const MiniIndexChart = ({ indexCode, indexName }) => {
const [chartData, setChartData] = useState(null);
const [loading, setLoading] = useState(true);
const [latestData, setLatestData] = useState(null);
const [currentDate, setCurrentDate] = useState('');
const chartBg = useColorModeValue('transparent', 'transparent');
const lineColor = useColorModeValue('#FFD700', '#FFD700'); // 金色
const areaColor = useColorModeValue('rgba(255, 215, 0, 0.15)', 'rgba(255, 215, 0, 0.1)');
const upColor = '#00da3c';
const downColor = '#ec0000';
useEffect(() => {
let isMounted = true;
// 加载日线数据
const loadDailyData = useCallback(async () => {
const data = await fetchIndexKline(indexCode);
const loadData = async () => {
setLoading(true);
const data = await fetchIndexKline(indexCode);
if (data && data.data && data.data.length > 0) {
// 取最近一个交易日的数据
const latest = data.data[data.data.length - 1];
const prevClose = latest.prev_close || latest.close;
if (isMounted && data && data.data && data.data.length > 0) {
// 取最近一个交易日的数据
setLatestData({
close: latest.close,
change: prevClose ? (((latest.close - prevClose) / prevClose) * 100).toFixed(2) : '0.00',
isPositive: latest.close >= prevClose
});
setCurrentDate(latest.time);
// 准备K线图数据最近60个交易日
const recentData = data.data.slice(-60);
setChartData({
dates: recentData.map(item => item.time),
klineData: recentData.map(item => [
item.open,
item.close,
item.low,
item.high
])
});
}
setLoading(false);
}, [indexCode]);
// 加载分钟线数据(仅在交易时间)
const loadMinuteData = useCallback(async () => {
try {
const response = await fetch(`/api/index/${indexCode}/kline?type=minute`);
if (!response.ok) return;
const data = await response.json();
if (data && data.data && data.data.length > 0) {
// 取最新分钟数据
const latest = data.data[data.data.length - 1];
const prevClose = latest.prev_close || latest.close;
// 分钟线没有 prev_close,使用第一条数据的 open 作为开盘价
const dayOpen = data.data[0].open;
setLatestData({
close: latest.close,
change: prevClose ? (((latest.close - prevClose) / prevClose) * 100).toFixed(2) : '0.00',
isPositive: latest.close >= prevClose
change: dayOpen ? (((latest.close - dayOpen) / dayOpen) * 100).toFixed(2) : '0.00',
isPositive: latest.close >= dayOpen
});
// 准备图表数据最近60个交易日
const recentData = data.data.slice(-60);
setChartData({
dates: recentData.map(item => item.time),
values: recentData.map(item => item.close)
logger.debug('HeroPanel', 'Minute data updated', {
indexCode,
close: latest.close,
time: latest.time,
change: (((latest.close - dayOpen) / dayOpen) * 100).toFixed(2)
});
}
} catch (error) {
logger.error('HeroPanel', 'loadMinuteData error', error);
}
}, [indexCode]);
// 初始加载和定时更新
useEffect(() => {
let isMounted = true;
let intervalId = null;
const init = async () => {
setLoading(true);
await loadDailyData();
if (isMounted) {
// 如果在交易时间,立即加载一次分钟数据
if (isInTradingTime()) {
await loadMinuteData();
}
setLoading(false);
}
};
loadData();
init();
// 设置定时器:交易时间内每分钟更新
if (isInTradingTime()) {
intervalId = setInterval(() => {
if (isInTradingTime()) {
loadMinuteData();
} else {
// 如果超出交易时间,清除定时器
if (intervalId) {
clearInterval(intervalId);
}
}
}, 60000); // 每60秒更新一次
}
return () => {
isMounted = false;
if (intervalId) {
clearInterval(intervalId);
}
};
}, [indexCode]);
}, [indexCode, loadDailyData, loadMinuteData]);
const chartOption = useMemo(() => {
if (!chartData) return {};
@@ -129,8 +217,8 @@ const MiniIndexChart = ({ indexCode, indexName }) => {
return {
backgroundColor: chartBg,
grid: {
left: 5,
right: 5,
left: 10,
right: 10,
top: 5,
bottom: 20,
containLabel: false
@@ -146,36 +234,18 @@ const MiniIndexChart = ({ indexCode, indexName }) => {
scale: true
},
series: [{
type: 'line',
data: chartData.values,
smooth: true,
symbol: 'none',
lineStyle: {
color: lineColor,
width: 2,
shadowColor: lineColor,
shadowBlur: 8,
shadowOffsetY: 2
type: 'candlestick',
data: chartData.klineData,
itemStyle: {
color: upColor,
color0: downColor,
borderColor: upColor,
borderColor0: downColor
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0,
color: areaColor
}, {
offset: 1,
color: 'rgba(255, 215, 0, 0)'
}]
}
}
barWidth: '60%'
}]
};
}, [chartData, chartBg, lineColor, areaColor]);
}, [chartData, chartBg, upColor, downColor]);
if (loading) {
return (
@@ -186,15 +256,18 @@ const MiniIndexChart = ({ indexCode, indexName }) => {
}
return (
<VStack spacing={2} align="stretch" h="120px">
<VStack spacing={2} align="stretch" h="140px">
<HStack justify="space-between">
<VStack align="start" spacing={0}>
<Text fontSize="xs" color="whiteAlpha.700">{indexName}</Text>
<Text fontSize="lg" fontWeight="bold" color="white">
{latestData?.close.toFixed(2)}
</Text>
<Text fontSize="xs" color="whiteAlpha.500">
{currentDate}
</Text>
</VStack>
<HStack spacing={1}>
<VStack align="end" spacing={0}>
<Text
fontSize="sm"
fontWeight="bold"
@@ -202,12 +275,17 @@ const MiniIndexChart = ({ indexCode, indexName }) => {
>
{latestData?.isPositive ? '↑' : '↓'} {latestData?.isPositive ? '+' : ''}{latestData?.change}%
</Text>
</HStack>
{isInTradingTime() && (
<Text fontSize="xs" color="green.400">
实时更新
</Text>
)}
</VStack>
</HStack>
<Box flex="1">
<ReactECharts
option={chartOption}
style={{ height: '80px', width: '100%' }}
style={{ height: '90px', width: '100%' }}
opts={{ renderer: 'canvas' }}
/>
</Box>