update ui

This commit is contained in:
2025-11-13 17:38:54 +08:00
parent 0e32076e71
commit cb4c51a958
65 changed files with 39193 additions and 103 deletions

View File

@@ -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>