refactor(Concept): 优化 3D 力导向图和层级图组件
ForceGraphView: - 优化 API 请求路径兼容性 - 改进数据处理逻辑 HierarchyView: - 优化层级数据获取 - 改进 API 兼容性 DataVisualizationComponents: - 代码优化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1006,7 +1006,7 @@ const ForceGraphView = ({
|
|||||||
|
|
||||||
// 获取容器高度
|
// 获取容器高度
|
||||||
const containerHeight = useMemo(() => {
|
const containerHeight = useMemo(() => {
|
||||||
if (isFullscreen) return '100vh';
|
if (isFullscreen) return 'calc(100vh - 60px)';
|
||||||
return isMobile ? '500px' : '700px';
|
return isMobile ? '500px' : '700px';
|
||||||
}, [isFullscreen, isMobile]);
|
}, [isFullscreen, isMobile]);
|
||||||
|
|
||||||
@@ -1069,10 +1069,156 @@ const ForceGraphView = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<VStack spacing={3} align="stretch" w="100%">
|
||||||
|
{/* 外部工具栏 - 在蓝紫色背景外面 */}
|
||||||
|
<HStack spacing={3} px={2} justify="space-between" align="center">
|
||||||
|
<HStack spacing={3}>
|
||||||
|
{/* 返回按钮 */}
|
||||||
|
{drillPath && (
|
||||||
|
<Tooltip label="返回上一层" placement="bottom">
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
icon={<FaArrowLeft />}
|
||||||
|
onClick={handleGoBack}
|
||||||
|
bg="whiteAlpha.100"
|
||||||
|
color="white"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="whiteAlpha.200"
|
||||||
|
borderRadius="full"
|
||||||
|
_hover={{
|
||||||
|
bg: 'purple.500',
|
||||||
|
borderColor: 'purple.400',
|
||||||
|
transform: 'scale(1.05)',
|
||||||
|
}}
|
||||||
|
transition="all 0.2s"
|
||||||
|
aria-label="返回"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HStack
|
||||||
|
bg="whiteAlpha.100"
|
||||||
|
px={4}
|
||||||
|
py={2}
|
||||||
|
borderRadius="full"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="whiteAlpha.200"
|
||||||
|
>
|
||||||
|
<Icon as={FaTh} color="purple.300" />
|
||||||
|
<Text color="white" fontWeight="bold" fontSize="sm">
|
||||||
|
概念矩形树图
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 面包屑导航 */}
|
||||||
|
<HStack
|
||||||
|
bg="whiteAlpha.50"
|
||||||
|
px={4}
|
||||||
|
py={2}
|
||||||
|
borderRadius="full"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="whiteAlpha.100"
|
||||||
|
spacing={2}
|
||||||
|
>
|
||||||
|
{breadcrumbItems.map((item, index) => (
|
||||||
|
<HStack key={index} spacing={2}>
|
||||||
|
{index > 0 && (
|
||||||
|
<Icon as={FaChevronRight} color="whiteAlpha.400" boxSize={3} />
|
||||||
|
)}
|
||||||
|
<Text
|
||||||
|
color={index === breadcrumbItems.length - 1 ? 'purple.300' : 'whiteAlpha.700'}
|
||||||
|
fontSize="sm"
|
||||||
|
fontWeight={index === breadcrumbItems.length - 1 ? 'bold' : 'normal'}
|
||||||
|
cursor={index < breadcrumbItems.length - 1 ? 'pointer' : 'default'}
|
||||||
|
_hover={index < breadcrumbItems.length - 1 ? { color: 'white' } : {}}
|
||||||
|
transition="color 0.2s"
|
||||||
|
onClick={() => {
|
||||||
|
if (index < breadcrumbItems.length - 1) {
|
||||||
|
setDrillPath(item.path);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 右侧控制按钮 */}
|
||||||
|
<HStack spacing={2}>
|
||||||
|
{priceLoading && <Spinner size="sm" color="purple.300" />}
|
||||||
|
|
||||||
|
{drillPath && (
|
||||||
|
<Tooltip label="返回全部" placement="left">
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
icon={<FaHome />}
|
||||||
|
onClick={handleGoHome}
|
||||||
|
bg="whiteAlpha.100"
|
||||||
|
color="white"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="whiteAlpha.200"
|
||||||
|
borderRadius="full"
|
||||||
|
_hover={{
|
||||||
|
bg: 'purple.500',
|
||||||
|
borderColor: 'purple.400',
|
||||||
|
transform: 'scale(1.05)',
|
||||||
|
}}
|
||||||
|
transition="all 0.2s"
|
||||||
|
aria-label="返回全部"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip label="刷新数据" placement="left">
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
icon={<FaSync />}
|
||||||
|
onClick={handleRefresh}
|
||||||
|
isLoading={priceLoading}
|
||||||
|
bg="whiteAlpha.100"
|
||||||
|
color="white"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="whiteAlpha.200"
|
||||||
|
borderRadius="full"
|
||||||
|
_hover={{
|
||||||
|
bg: 'whiteAlpha.200',
|
||||||
|
borderColor: 'whiteAlpha.300',
|
||||||
|
transform: 'scale(1.05)',
|
||||||
|
}}
|
||||||
|
transition="all 0.2s"
|
||||||
|
aria-label="刷新"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
|
<Tooltip label={isFullscreen ? '退出全屏' : '全屏'} placement="left">
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
icon={isFullscreen ? <FaCompress /> : <FaExpand />}
|
||||||
|
onClick={toggleFullscreen}
|
||||||
|
bg="whiteAlpha.100"
|
||||||
|
color="white"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="whiteAlpha.200"
|
||||||
|
borderRadius="full"
|
||||||
|
_hover={{
|
||||||
|
bg: 'whiteAlpha.200',
|
||||||
|
borderColor: 'whiteAlpha.300',
|
||||||
|
transform: 'scale(1.05)',
|
||||||
|
}}
|
||||||
|
transition="all 0.2s"
|
||||||
|
aria-label={isFullscreen ? '退出全屏' : '全屏'}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
|
||||||
|
{/* 蓝紫色背景容器 */}
|
||||||
<Box
|
<Box
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
position={isFullscreen ? 'fixed' : 'relative'}
|
position={isFullscreen ? 'fixed' : 'relative'}
|
||||||
top={isFullscreen ? 0 : 'auto'}
|
top={isFullscreen ? '60px' : 'auto'}
|
||||||
left={isFullscreen ? 0 : 'auto'}
|
left={isFullscreen ? 0 : 'auto'}
|
||||||
right={isFullscreen ? 0 : 'auto'}
|
right={isFullscreen ? 0 : 'auto'}
|
||||||
bottom={isFullscreen ? 0 : 'auto'}
|
bottom={isFullscreen ? 0 : 'auto'}
|
||||||
@@ -1131,169 +1277,6 @@ const ForceGraphView = ({
|
|||||||
animation={`${glowPulse} 6s ease-in-out infinite 2s`}
|
animation={`${glowPulse} 6s ease-in-out infinite 2s`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 顶部工具栏 - 毛玻璃风格 */}
|
|
||||||
<Flex
|
|
||||||
position="absolute"
|
|
||||||
top={isFullscreen ? '70px' : 4}
|
|
||||||
left={4}
|
|
||||||
right={4}
|
|
||||||
justify="space-between"
|
|
||||||
align="flex-start"
|
|
||||||
zIndex={10}
|
|
||||||
pointerEvents="none"
|
|
||||||
>
|
|
||||||
{/* 左侧标题和面包屑 */}
|
|
||||||
<VStack align="start" spacing={2} pointerEvents="auto">
|
|
||||||
<HStack spacing={3}>
|
|
||||||
{/* 返回按钮 */}
|
|
||||||
{drillPath && (
|
|
||||||
<Tooltip label="返回上一层" placement="bottom">
|
|
||||||
<IconButton
|
|
||||||
size="sm"
|
|
||||||
icon={<FaArrowLeft />}
|
|
||||||
onClick={handleGoBack}
|
|
||||||
bg="rgba(255, 255, 255, 0.1)"
|
|
||||||
backdropFilter="blur(20px)"
|
|
||||||
color="white"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="whiteAlpha.200"
|
|
||||||
borderRadius="full"
|
|
||||||
_hover={{
|
|
||||||
bg: 'rgba(139, 92, 246, 0.4)',
|
|
||||||
borderColor: 'purple.400',
|
|
||||||
transform: 'scale(1.05)',
|
|
||||||
}}
|
|
||||||
transition="all 0.2s"
|
|
||||||
aria-label="返回"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<HStack
|
|
||||||
bg="rgba(255, 255, 255, 0.08)"
|
|
||||||
backdropFilter="blur(20px)"
|
|
||||||
px={5}
|
|
||||||
py={2.5}
|
|
||||||
borderRadius="full"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="whiteAlpha.150"
|
|
||||||
boxShadow="0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1)"
|
|
||||||
>
|
|
||||||
<Icon as={FaTh} color="purple.300" />
|
|
||||||
<Text color="white" fontWeight="bold" fontSize="sm">
|
|
||||||
概念矩形树图
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 面包屑导航 */}
|
|
||||||
<HStack
|
|
||||||
bg="rgba(255, 255, 255, 0.06)"
|
|
||||||
backdropFilter="blur(20px)"
|
|
||||||
px={4}
|
|
||||||
py={2}
|
|
||||||
borderRadius="full"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="whiteAlpha.100"
|
|
||||||
spacing={2}
|
|
||||||
>
|
|
||||||
{breadcrumbItems.map((item, index) => (
|
|
||||||
<HStack key={index} spacing={2}>
|
|
||||||
{index > 0 && (
|
|
||||||
<Icon as={FaChevronRight} color="whiteAlpha.400" boxSize={3} />
|
|
||||||
)}
|
|
||||||
<Text
|
|
||||||
color={index === breadcrumbItems.length - 1 ? 'purple.300' : 'whiteAlpha.700'}
|
|
||||||
fontSize="sm"
|
|
||||||
fontWeight={index === breadcrumbItems.length - 1 ? 'bold' : 'normal'}
|
|
||||||
cursor={index < breadcrumbItems.length - 1 ? 'pointer' : 'default'}
|
|
||||||
_hover={index < breadcrumbItems.length - 1 ? { color: 'white' } : {}}
|
|
||||||
transition="color 0.2s"
|
|
||||||
onClick={() => {
|
|
||||||
if (index < breadcrumbItems.length - 1) {
|
|
||||||
setDrillPath(item.path);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
))}
|
|
||||||
</HStack>
|
|
||||||
</HStack>
|
|
||||||
</VStack>
|
|
||||||
|
|
||||||
{/* 右侧控制按钮 */}
|
|
||||||
<HStack spacing={2} pointerEvents="auto">
|
|
||||||
{priceLoading && <Spinner size="sm" color="purple.300" />}
|
|
||||||
|
|
||||||
{drillPath && (
|
|
||||||
<Tooltip label="返回全部" placement="left">
|
|
||||||
<IconButton
|
|
||||||
size="sm"
|
|
||||||
icon={<FaHome />}
|
|
||||||
onClick={handleGoHome}
|
|
||||||
bg="rgba(255, 255, 255, 0.1)"
|
|
||||||
backdropFilter="blur(20px)"
|
|
||||||
color="white"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="whiteAlpha.200"
|
|
||||||
borderRadius="full"
|
|
||||||
_hover={{
|
|
||||||
bg: 'rgba(139, 92, 246, 0.4)',
|
|
||||||
borderColor: 'purple.400',
|
|
||||||
transform: 'scale(1.05)',
|
|
||||||
}}
|
|
||||||
transition="all 0.2s"
|
|
||||||
aria-label="返回全部"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tooltip label="刷新数据" placement="left">
|
|
||||||
<IconButton
|
|
||||||
size="sm"
|
|
||||||
icon={<FaSync />}
|
|
||||||
onClick={handleRefresh}
|
|
||||||
isLoading={priceLoading}
|
|
||||||
bg="rgba(255, 255, 255, 0.1)"
|
|
||||||
backdropFilter="blur(20px)"
|
|
||||||
color="white"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="whiteAlpha.200"
|
|
||||||
borderRadius="full"
|
|
||||||
_hover={{
|
|
||||||
bg: 'rgba(255, 255, 255, 0.2)',
|
|
||||||
borderColor: 'whiteAlpha.300',
|
|
||||||
transform: 'scale(1.05)',
|
|
||||||
}}
|
|
||||||
transition="all 0.2s"
|
|
||||||
aria-label="刷新"
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip label={isFullscreen ? '退出全屏' : '全屏'} placement="left">
|
|
||||||
<IconButton
|
|
||||||
size="sm"
|
|
||||||
icon={isFullscreen ? <FaCompress /> : <FaExpand />}
|
|
||||||
onClick={toggleFullscreen}
|
|
||||||
bg="rgba(255, 255, 255, 0.1)"
|
|
||||||
backdropFilter="blur(20px)"
|
|
||||||
color="white"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="whiteAlpha.200"
|
|
||||||
borderRadius="full"
|
|
||||||
_hover={{
|
|
||||||
bg: 'rgba(255, 255, 255, 0.2)',
|
|
||||||
borderColor: 'whiteAlpha.300',
|
|
||||||
transform: 'scale(1.05)',
|
|
||||||
}}
|
|
||||||
transition="all 0.2s"
|
|
||||||
aria-label={isFullscreen ? '退出全屏' : '全屏'}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* 底部图例 - 毛玻璃风格 */}
|
{/* 底部图例 - 毛玻璃风格 */}
|
||||||
<Flex
|
<Flex
|
||||||
position="absolute"
|
position="absolute"
|
||||||
@@ -1371,6 +1354,7 @@ const ForceGraphView = ({
|
|||||||
opts={{ renderer: 'canvas' }}
|
opts={{ renderer: 'canvas' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
</VStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -814,15 +814,49 @@ const HierarchyView = ({
|
|||||||
|
|
||||||
{/* 内容层 */}
|
{/* 内容层 */}
|
||||||
<Box position="relative" zIndex={1}>
|
<Box position="relative" zIndex={1}>
|
||||||
{/* 简化的工具栏 - 仅显示功能按钮 */}
|
{/* 面包屑导航 + 工具栏(同一行) */}
|
||||||
<Flex
|
<Flex
|
||||||
justify="flex-end"
|
|
||||||
align="center"
|
align="center"
|
||||||
mb={4}
|
justify="space-between"
|
||||||
|
mb={5}
|
||||||
|
p={3}
|
||||||
|
bg="whiteAlpha.50"
|
||||||
|
backdropFilter="blur(20px)"
|
||||||
|
borderRadius="2xl"
|
||||||
|
border="1px solid"
|
||||||
|
borderColor="whiteAlpha.100"
|
||||||
gap={2}
|
gap={2}
|
||||||
>
|
>
|
||||||
|
{/* 左侧:面包屑导航 */}
|
||||||
|
<Flex align="center" flexWrap="wrap" gap={1} flex={1}>
|
||||||
|
{breadcrumbs.map((crumb, index) => (
|
||||||
|
<React.Fragment key={index}>
|
||||||
|
{index > 0 && (
|
||||||
|
<Icon as={FaChevronRight} color="whiteAlpha.400" boxSize={3} mx={1} />
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
bg={index === breadcrumbs.length - 1 ? 'purple.500' : 'transparent'}
|
||||||
|
color={index === breadcrumbs.length - 1 ? 'white' : 'whiteAlpha.700'}
|
||||||
|
leftIcon={index === 0 ? <FaHome /> : undefined}
|
||||||
|
onClick={() => handleBreadcrumbClick(crumb, index)}
|
||||||
|
isDisabled={index === breadcrumbs.length - 1}
|
||||||
|
fontWeight={index === breadcrumbs.length - 1 ? 'bold' : 'medium'}
|
||||||
|
borderRadius="xl"
|
||||||
|
_hover={index !== breadcrumbs.length - 1 ? { bg: 'whiteAlpha.100' } : {}}
|
||||||
|
boxShadow={index === breadcrumbs.length - 1 ? '0 0 20px rgba(139, 92, 246, 0.5)' : 'none'}
|
||||||
|
>
|
||||||
|
{crumb.label}
|
||||||
|
</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
{/* 右侧:工具栏按钮 */}
|
||||||
|
<HStack spacing={2} flexShrink={0}>
|
||||||
{priceLoading && (
|
{priceLoading && (
|
||||||
<Spinner size="sm" color="purple.300" mr={2} />
|
<Spinner size="sm" color="purple.300" />
|
||||||
)}
|
)}
|
||||||
<Tooltip label="刷新涨跌幅" placement="top">
|
<Tooltip label="刷新涨跌幅" placement="top">
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -852,43 +886,7 @@ const HierarchyView = ({
|
|||||||
aria-label={isFullscreen ? '退出全屏' : '全屏'}
|
aria-label={isFullscreen ? '退出全屏' : '全屏'}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</HStack>
|
||||||
|
|
||||||
{/* 面包屑导航 */}
|
|
||||||
<Flex
|
|
||||||
align="center"
|
|
||||||
mb={5}
|
|
||||||
p={3}
|
|
||||||
bg="whiteAlpha.50"
|
|
||||||
backdropFilter="blur(20px)"
|
|
||||||
borderRadius="2xl"
|
|
||||||
border="1px solid"
|
|
||||||
borderColor="whiteAlpha.100"
|
|
||||||
flexWrap="wrap"
|
|
||||||
gap={1}
|
|
||||||
>
|
|
||||||
{breadcrumbs.map((crumb, index) => (
|
|
||||||
<React.Fragment key={index}>
|
|
||||||
{index > 0 && (
|
|
||||||
<Icon as={FaChevronRight} color="whiteAlpha.400" boxSize={3} mx={1} />
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
bg={index === breadcrumbs.length - 1 ? 'purple.500' : 'transparent'}
|
|
||||||
color={index === breadcrumbs.length - 1 ? 'white' : 'whiteAlpha.700'}
|
|
||||||
leftIcon={index === 0 ? <FaHome /> : undefined}
|
|
||||||
onClick={() => handleBreadcrumbClick(crumb, index)}
|
|
||||||
isDisabled={index === breadcrumbs.length - 1}
|
|
||||||
fontWeight={index === breadcrumbs.length - 1 ? 'bold' : 'medium'}
|
|
||||||
borderRadius="xl"
|
|
||||||
_hover={index !== breadcrumbs.length - 1 ? { bg: 'whiteAlpha.100' } : {}}
|
|
||||||
boxShadow={index === breadcrumbs.length - 1 ? '0 0 20px rgba(139, 92, 246, 0.5)' : 'none'}
|
|
||||||
>
|
|
||||||
{crumb.label}
|
|
||||||
</Button>
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{/* 图例说明 */}
|
{/* 图例说明 */}
|
||||||
|
|||||||
@@ -297,15 +297,17 @@ const SectorHeatMap = ({ data }) => {
|
|||||||
rx="8"
|
rx="8"
|
||||||
style={{
|
style={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'all 0.3s'
|
transition: 'all 0.2s ease-in-out'
|
||||||
}}
|
}}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
e.target.style.opacity = '0.8';
|
e.target.style.stroke = '#FFD700';
|
||||||
e.target.style.transform = 'scale(1.05)';
|
e.target.style.strokeWidth = '4';
|
||||||
|
e.target.style.filter = 'brightness(1.2) drop-shadow(0 0 8px rgba(255, 215, 0, 0.6))';
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.target.style.opacity = '1';
|
e.target.style.stroke = '#fff';
|
||||||
e.target.style.transform = 'scale(1)';
|
e.target.style.strokeWidth = '2';
|
||||||
|
e.target.style.filter = 'none';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<title>{`${sector.name}: ${sector.count}只涨停`}</title>
|
<title>{`${sector.name}: ${sector.count}只涨停`}</title>
|
||||||
|
|||||||
Reference in New Issue
Block a user