update ui
This commit is contained in:
@@ -41,8 +41,6 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { TrendingUp, Activity, Globe, Zap } from 'lucide-react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import * as echarts from 'echarts';
|
||||
import 'echarts-wordcloud'; // 导入词云插件
|
||||
import { logger } from '../../../utils/logger';
|
||||
|
||||
/**
|
||||
@@ -389,13 +387,13 @@ const MiniIndexChart = ({ indexCode, indexName }) => {
|
||||
};
|
||||
|
||||
/**
|
||||
* 概念词云图组件
|
||||
* 概念流动动画组件(替代词云)
|
||||
*/
|
||||
const ConceptWordCloud = () => {
|
||||
const ConceptFlowAnimation = () => {
|
||||
const [concepts, setConcepts] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const chartBg = useColorModeValue('transparent', 'transparent');
|
||||
const [isPaused, setIsPaused] = useState(false);
|
||||
const [hoveredConcept, setHoveredConcept] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
@@ -418,94 +416,26 @@ const ConceptWordCloud = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const chartOption = useMemo(() => {
|
||||
if (concepts.length === 0) {
|
||||
logger.warn('ConceptWordCloud', 'No concepts data available');
|
||||
return {};
|
||||
}
|
||||
// 根据涨跌幅获取颜色
|
||||
const getColor = (changePct) => {
|
||||
if (changePct > 7) return '#ff0000'; // 超大涨:纯红
|
||||
if (changePct > 5) return '#ff1744'; // 大涨:亮红色
|
||||
if (changePct > 3) return '#ff4d4f'; // 中涨:红色
|
||||
if (changePct > 1) return '#ff7875'; // 小涨:浅红
|
||||
if (changePct > 0) return '#ffa940'; // 微涨:橙色
|
||||
if (changePct === 0) return '#FFD700'; // 平盘:金色
|
||||
if (changePct > -1) return '#73d13d'; // 微跌:浅绿
|
||||
if (changePct > -3) return '#52c41a'; // 小跌:绿色
|
||||
if (changePct > -5) return '#00c853'; // 中跌:深绿
|
||||
if (changePct > -7) return '#00a152'; // 大跌:更深绿
|
||||
return '#00796b'; // 超大跌:墨绿
|
||||
};
|
||||
|
||||
logger.debug('ConceptWordCloud', 'Rendering word cloud with concepts', {
|
||||
count: concepts.length,
|
||||
sample: concepts.slice(0, 3)
|
||||
});
|
||||
|
||||
return {
|
||||
backgroundColor: chartBg,
|
||||
tooltip: {
|
||||
show: true,
|
||||
backgroundColor: 'rgba(20, 20, 20, 0.95)',
|
||||
borderColor: '#FFD700',
|
||||
borderWidth: 1,
|
||||
textStyle: {
|
||||
color: '#fff',
|
||||
fontSize: 12
|
||||
},
|
||||
formatter: (params) => {
|
||||
const changePct = params.data.change_pct;
|
||||
const sign = changePct > 0 ? '+' : '';
|
||||
const color = changePct > 0 ? '#ec0000' : '#00da3c';
|
||||
const icon = changePct > 0 ? '📈' : '📉';
|
||||
return `
|
||||
<div style="font-weight: bold; font-size: 14px; margin-bottom: 6px; border-bottom: 1px solid #FFD700; padding-bottom: 4px;">
|
||||
${icon} ${params.name}
|
||||
</div>
|
||||
<div style="color: ${color}; font-weight: bold; font-size: 16px;">
|
||||
涨跌幅: ${sign}${changePct.toFixed(2)}%
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
type: 'wordCloud',
|
||||
shape: 'circle',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
right: null,
|
||||
bottom: null,
|
||||
sizeRange: [14, 42],
|
||||
rotationRange: [-30, 30],
|
||||
rotationStep: 15,
|
||||
gridSize: 10,
|
||||
drawOutOfBound: false,
|
||||
layoutAnimation: true,
|
||||
textStyle: {
|
||||
fontFamily: 'sans-serif',
|
||||
fontWeight: 'bold',
|
||||
color: function (params) {
|
||||
// 根据涨跌幅设置颜色(中国市场惯例:涨红跌绿)
|
||||
const changePct = params.data.change_pct;
|
||||
if (changePct > 7) return '#ff0000'; // 超大涨:纯红
|
||||
if (changePct > 5) return '#ff1744'; // 大涨:亮红色
|
||||
if (changePct > 3) return '#ff4d4f'; // 中涨:红色
|
||||
if (changePct > 1) return '#ff7875'; // 小涨:浅红
|
||||
if (changePct > 0) return '#ffa940'; // 微涨:橙色
|
||||
if (changePct === 0) return '#FFD700'; // 平盘:金色
|
||||
if (changePct > -1) return '#73d13d'; // 微跌:浅绿
|
||||
if (changePct > -3) return '#52c41a'; // 小跌:绿色
|
||||
if (changePct > -5) return '#00c853'; // 中跌:深绿
|
||||
if (changePct > -7) return '#00a152'; // 大跌:更深绿
|
||||
return '#00796b'; // 超大跌:墨绿
|
||||
},
|
||||
shadowBlur: 3,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
shadowOffsetX: 1,
|
||||
shadowOffsetY: 1
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'self',
|
||||
textStyle: {
|
||||
shadowBlur: 20,
|
||||
shadowColor: '#FFD700',
|
||||
fontSize: 28,
|
||||
fontWeight: 'bolder'
|
||||
}
|
||||
},
|
||||
data: concepts
|
||||
}]
|
||||
};
|
||||
}, [concepts, chartBg]);
|
||||
// 处理概念点击
|
||||
const handleConceptClick = (conceptName) => {
|
||||
const url = `https://valuefrontier.cn/htmls/${conceptName}.html`;
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
@@ -525,15 +455,135 @@ const ConceptWordCloud = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// 将概念分成3行
|
||||
const rows = [
|
||||
concepts.slice(0, 6),
|
||||
concepts.slice(6, 12),
|
||||
concepts.slice(12, 18)
|
||||
];
|
||||
|
||||
return (
|
||||
<ReactECharts
|
||||
echarts={echarts}
|
||||
option={chartOption}
|
||||
style={{ height: '200px', width: '100%' }}
|
||||
opts={{ renderer: 'canvas' }}
|
||||
notMerge={true}
|
||||
lazyUpdate={true}
|
||||
/>
|
||||
<VStack spacing={2} h="200px" justify="center" overflow="hidden" position="relative" w="100%">
|
||||
{rows.map((row, rowIndex) => {
|
||||
// 每行不同的速度:第一行20秒,第二行25秒,第三行30秒
|
||||
const duration = 25 + rowIndex * 5;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={rowIndex}
|
||||
position="relative"
|
||||
width="100%"
|
||||
height="50px"
|
||||
overflow="hidden"
|
||||
>
|
||||
<HStack
|
||||
spacing={3}
|
||||
position="absolute"
|
||||
left={0}
|
||||
h="100%"
|
||||
align="center"
|
||||
animation={!isPaused ? `flowRight${rowIndex} ${duration}s linear infinite` : 'none'}
|
||||
_hover={{ animationPlayState: 'paused' }}
|
||||
sx={{
|
||||
[`@keyframes flowRight${rowIndex}`]: {
|
||||
'0%': { transform: 'translateX(0)' },
|
||||
'100%': { transform: 'translateX(-50%)' }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* 渲染两次以实现无缝循环 */}
|
||||
{[...row, ...row].map((concept, idx) => {
|
||||
const changePct = concept.change_pct;
|
||||
const color = getColor(changePct);
|
||||
const sign = changePct > 0 ? '+' : '';
|
||||
const icon = changePct > 0 ? '📈' : '📉';
|
||||
const isHovered = hoveredConcept === `${rowIndex}-${idx}`;
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={`${rowIndex}-${idx}`}
|
||||
px={4}
|
||||
py={2}
|
||||
borderRadius="full"
|
||||
bg="whiteAlpha.200"
|
||||
borderWidth="2px"
|
||||
borderColor={color}
|
||||
color="white"
|
||||
fontWeight="bold"
|
||||
fontSize="sm"
|
||||
whiteSpace="nowrap"
|
||||
cursor="pointer"
|
||||
position="relative"
|
||||
transition="all 0.3s ease"
|
||||
boxShadow={`0 0 10px ${color}40`}
|
||||
onMouseEnter={() => {
|
||||
setIsPaused(true);
|
||||
setHoveredConcept(`${rowIndex}-${idx}`);
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
setIsPaused(false);
|
||||
setHoveredConcept(null);
|
||||
}}
|
||||
onClick={() => handleConceptClick(concept.name)}
|
||||
_hover={{
|
||||
transform: 'scale(1.15)',
|
||||
bg: 'whiteAlpha.300',
|
||||
boxShadow: `0 0 20px ${color}80`,
|
||||
zIndex: 100
|
||||
}}
|
||||
>
|
||||
{/* 概念名称 */}
|
||||
<Text as="span" color={color}>
|
||||
{concept.name}
|
||||
</Text>
|
||||
|
||||
{/* 悬停时显示涨跌幅 */}
|
||||
{isHovered && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="-45px"
|
||||
left="50%"
|
||||
transform="translateX(-50%)"
|
||||
bg="rgba(20, 20, 20, 0.95)"
|
||||
borderWidth="1px"
|
||||
borderColor="#FFD700"
|
||||
borderRadius="md"
|
||||
px={3}
|
||||
py={2}
|
||||
whiteSpace="nowrap"
|
||||
boxShadow="0 4px 12px rgba(0,0,0,0.3)"
|
||||
zIndex={200}
|
||||
_before={{
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
bottom: '-6px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderLeft: '6px solid transparent',
|
||||
borderRight: '6px solid transparent',
|
||||
borderTop: '6px solid #FFD700'
|
||||
}}
|
||||
>
|
||||
<VStack spacing={1}>
|
||||
<Text fontSize="xs" color="white" fontWeight="bold">
|
||||
{icon} {concept.name}
|
||||
</Text>
|
||||
<Text fontSize="sm" color={color} fontWeight="bold">
|
||||
{sign}{changePct.toFixed(2)}%
|
||||
</Text>
|
||||
</VStack>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</HStack>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -763,12 +813,14 @@ const HeroPanel = () => {
|
||||
boxShadow: "0 6px 24px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1)"
|
||||
}}
|
||||
>
|
||||
<ConceptWordCloud />
|
||||
<ConceptFlowAnimation />
|
||||
</Box>
|
||||
<HStack spacing={2} fontSize="xs" color="whiteAlpha.600" w="100%" justify="center">
|
||||
<Text>💡 字体大小表示热度</Text>
|
||||
<Text>🌊 概念流动展示</Text>
|
||||
<Text>•</Text>
|
||||
<Text>🎨 颜色表示涨跌幅</Text>
|
||||
<Text>•</Text>
|
||||
<Text>👆 点击查看详情</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user