update ui
This commit is contained in:
@@ -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,47 +52,63 @@ 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 loadData = async () => {
|
||||
setLoading(true);
|
||||
// 加载日线数据
|
||||
const loadDailyData = useCallback(async () => {
|
||||
const data = await fetchIndexKline(indexCode);
|
||||
|
||||
if (isMounted && data && data.data && data.data.length > 0) {
|
||||
if (data && data.data && data.data.length > 0) {
|
||||
// 取最近一个交易日的数据
|
||||
const latest = data.data[data.data.length - 1];
|
||||
const prevClose = latest.prev_close || latest.close;
|
||||
@@ -103,25 +119,97 @@ const MiniIndexChart = ({ indexCode, indexName }) => {
|
||||
isPositive: latest.close >= prevClose
|
||||
});
|
||||
|
||||
// 准备图表数据(最近60个交易日)
|
||||
setCurrentDate(latest.time);
|
||||
|
||||
// 准备K线图数据(最近60个交易日)
|
||||
const recentData = data.data.slice(-60);
|
||||
setChartData({
|
||||
dates: recentData.map(item => item.time),
|
||||
values: recentData.map(item => item.close)
|
||||
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];
|
||||
// 分钟线没有 prev_close,使用第一条数据的 open 作为开盘价
|
||||
const dayOpen = data.data[0].open;
|
||||
|
||||
setLatestData({
|
||||
close: latest.close,
|
||||
change: dayOpen ? (((latest.close - dayOpen) / dayOpen) * 100).toFixed(2) : '0.00',
|
||||
isPositive: latest.close >= dayOpen
|
||||
});
|
||||
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user