feat: 传导练UI调整
This commit is contained in:
@@ -28,9 +28,11 @@ import {
|
||||
ModalCloseButton,
|
||||
Icon,
|
||||
useColorModeValue,
|
||||
Tooltip
|
||||
Tooltip,
|
||||
Center
|
||||
} from '@chakra-ui/react';
|
||||
import { InfoIcon, ViewIcon } from '@chakra-ui/icons';
|
||||
import { Share2, GitBranch, Inbox } from 'lucide-react';
|
||||
import ReactECharts from 'echarts-for-react';
|
||||
import { eventService } from '../../../services/eventService';
|
||||
import CitedContent from '../../../components/Citation/CitedContent';
|
||||
@@ -637,7 +639,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box p={6}>
|
||||
<Box>
|
||||
{/* 统计信息条 */}
|
||||
<Box
|
||||
mb={4}
|
||||
@@ -647,56 +649,57 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
||||
borderColor={PROFESSIONAL_COLORS.border.default}
|
||||
bg={PROFESSIONAL_COLORS.background.secondary}
|
||||
>
|
||||
<HStack spacing={6} wrap="wrap">
|
||||
<Stat>
|
||||
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>总节点数</StatLabel>
|
||||
<StatNumber color={PROFESSIONAL_COLORS.text.primary}>{stats.totalNodes}</StatNumber>
|
||||
<Flex wrap="wrap" gap={{ base: 3, md: 6 }}>
|
||||
<Stat minW="fit-content">
|
||||
<StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">总节点数</StatLabel>
|
||||
<StatNumber fontSize={{ base: "xl", md: "2xl" }} color={PROFESSIONAL_COLORS.text.primary}>{stats.totalNodes}</StatNumber>
|
||||
</Stat>
|
||||
<Stat>
|
||||
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>涉及行业</StatLabel>
|
||||
<StatNumber color={PROFESSIONAL_COLORS.text.primary}>{stats.involvedIndustries}</StatNumber>
|
||||
<Stat minW="fit-content">
|
||||
<StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">涉及行业</StatLabel>
|
||||
<StatNumber fontSize={{ base: "xl", md: "2xl" }} color={PROFESSIONAL_COLORS.text.primary}>{stats.involvedIndustries}</StatNumber>
|
||||
</Stat>
|
||||
<Stat>
|
||||
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>相关公司</StatLabel>
|
||||
<StatNumber color={PROFESSIONAL_COLORS.text.primary}>{stats.relatedCompanies}</StatNumber>
|
||||
<Stat minW="fit-content">
|
||||
<StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">相关公司</StatLabel>
|
||||
<StatNumber fontSize={{ base: "xl", md: "2xl" }} color={PROFESSIONAL_COLORS.text.primary}>{stats.relatedCompanies}</StatNumber>
|
||||
</Stat>
|
||||
<Stat>
|
||||
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>正向影响</StatLabel>
|
||||
<StatNumber color="#10B981">{stats.positiveImpact}</StatNumber>
|
||||
<Stat minW="fit-content">
|
||||
<StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">正向影响</StatLabel>
|
||||
<StatNumber fontSize={{ base: "xl", md: "2xl" }} color="#10B981">{stats.positiveImpact}</StatNumber>
|
||||
</Stat>
|
||||
<Stat>
|
||||
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>负向影响</StatLabel>
|
||||
<StatNumber color="#EF4444">{stats.negativeImpact}</StatNumber>
|
||||
<Stat minW="fit-content">
|
||||
<StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">负向影响</StatLabel>
|
||||
<StatNumber fontSize={{ base: "xl", md: "2xl" }} color="#EF4444">{stats.negativeImpact}</StatNumber>
|
||||
</Stat>
|
||||
<Stat>
|
||||
<StatLabel color={PROFESSIONAL_COLORS.text.secondary}>循环效应</StatLabel>
|
||||
<StatNumber color="#A855F7">{stats.circularEffect}</StatNumber>
|
||||
<Stat minW="fit-content">
|
||||
<StatLabel fontSize={{ base: "xs", md: "sm" }} color={PROFESSIONAL_COLORS.text.secondary} whiteSpace="nowrap">循环效应</StatLabel>
|
||||
<StatNumber fontSize={{ base: "xl", md: "2xl" }} color="#A855F7">{stats.circularEffect}</StatNumber>
|
||||
</Stat>
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
{/* 自定义图例 */}
|
||||
<Box mb={4}>
|
||||
<HStack spacing={4} wrap="wrap">
|
||||
{Object.entries(NODE_STYLES).map(([type, style]) => (
|
||||
<Tag
|
||||
key={type}
|
||||
size="md"
|
||||
bg={PROFESSIONAL_COLORS.background.secondary}
|
||||
color={PROFESSIONAL_COLORS.text.primary}
|
||||
borderWidth="1px"
|
||||
borderColor={PROFESSIONAL_COLORS.border.default}
|
||||
>
|
||||
<Box w={3} h={3} bg={style.color} borderRadius="sm" mr={2} />
|
||||
{NODE_TYPE_LABELS[type] || type}
|
||||
</Tag>
|
||||
))}
|
||||
</HStack>
|
||||
</Box>
|
||||
<Flex mb={4} wrap="wrap" gap={2}>
|
||||
{Object.entries(NODE_STYLES).map(([type, style]) => (
|
||||
<Tag
|
||||
key={type}
|
||||
size="sm"
|
||||
px={2}
|
||||
py={1}
|
||||
bg={PROFESSIONAL_COLORS.background.secondary}
|
||||
color={PROFESSIONAL_COLORS.text.primary}
|
||||
borderWidth="1px"
|
||||
borderColor={PROFESSIONAL_COLORS.border.default}
|
||||
>
|
||||
<Box w={2.5} h={2.5} bg={style.color} borderRadius="sm" mr={1.5} />
|
||||
{NODE_TYPE_LABELS[type] || type}
|
||||
</Tag>
|
||||
))}
|
||||
</Flex>
|
||||
|
||||
{/* 视图切换按钮 */}
|
||||
<Flex mb={4} gap={2}>
|
||||
<Button
|
||||
leftIcon={<Icon as={Share2} boxSize={4} />}
|
||||
bg={viewMode === 'graph' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.background.secondary}
|
||||
color={viewMode === 'graph' ? 'black' : PROFESSIONAL_COLORS.text.primary}
|
||||
_hover={{
|
||||
@@ -710,6 +713,7 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
||||
力导向图
|
||||
</Button>
|
||||
<Button
|
||||
leftIcon={<Icon as={GitBranch} boxSize={4} />}
|
||||
bg={viewMode === 'sankey' ? PROFESSIONAL_COLORS.gold[500] : PROFESSIONAL_COLORS.background.secondary}
|
||||
color={viewMode === 'sankey' ? 'black' : PROFESSIONAL_COLORS.text.primary}
|
||||
_hover={{
|
||||
@@ -722,7 +726,6 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
||||
>
|
||||
桑基图
|
||||
</Button>
|
||||
|
||||
</Flex>
|
||||
|
||||
{loading && (
|
||||
@@ -748,75 +751,97 @@ const TransmissionChainAnalysis = ({ eventId }) => {
|
||||
|
||||
{!loading && !error && (
|
||||
<Box>
|
||||
{/* 提示信息 */}
|
||||
<Alert
|
||||
status="info"
|
||||
mb={4}
|
||||
borderRadius="md"
|
||||
bg="rgba(59, 130, 246, 0.1)"
|
||||
color="#3B82F6"
|
||||
borderWidth="1px"
|
||||
borderColor="#3B82F6"
|
||||
>
|
||||
<AlertIcon />
|
||||
<Text fontSize="sm" color={PROFESSIONAL_COLORS.text.secondary}>
|
||||
<Icon as={ViewIcon} mr={2} />
|
||||
点击图表中的节点可以查看详细信息
|
||||
</Text>
|
||||
</Alert>
|
||||
|
||||
{/* 图表容器 */}
|
||||
{/* 图表容器 - 宽高比 2:1,H5 自适应 */}
|
||||
<Box
|
||||
h={viewMode === 'sankey' ? "600px" : "700px"}
|
||||
position="relative"
|
||||
w="100%"
|
||||
pb={{ base: "75%", md: "50%" }}
|
||||
border="1px solid"
|
||||
borderColor={PROFESSIONAL_COLORS.border.default}
|
||||
borderRadius="lg"
|
||||
boxShadow="0 4px 12px rgba(0, 0, 0, 0.3)"
|
||||
bg={PROFESSIONAL_COLORS.background.card}
|
||||
p={4}
|
||||
ref={containerRef}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
top={0}
|
||||
left={0}
|
||||
right={0}
|
||||
bottom={0}
|
||||
p={4}
|
||||
>
|
||||
{/* 提示信息 - 固定在左上角 */}
|
||||
<Text
|
||||
position="absolute"
|
||||
top={2}
|
||||
left={3}
|
||||
fontSize="xs"
|
||||
color={PROFESSIONAL_COLORS.text.muted}
|
||||
zIndex={1}
|
||||
bg="rgba(0, 0, 0, 0.5)"
|
||||
px={2}
|
||||
py={1}
|
||||
borderRadius="md"
|
||||
>
|
||||
<Icon as={ViewIcon} mr={1} boxSize={3} />
|
||||
点击节点查看详情
|
||||
</Text>
|
||||
{chartReady && (
|
||||
<>
|
||||
{viewMode === 'graph' ? (
|
||||
<ReactECharts
|
||||
option={graphData ? getGraphOption(graphData) : {}}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
onEvents={{
|
||||
click: handleGraphNodeClick
|
||||
}}
|
||||
opts={{
|
||||
renderer: 'canvas',
|
||||
devicePixelRatio: window.devicePixelRatio || 1
|
||||
}}
|
||||
lazyUpdate={true}
|
||||
notMerge={false}
|
||||
shouldSetOption={(prevProps, props) => {
|
||||
// 减少不必要的重新渲染
|
||||
return JSON.stringify(prevProps.option) !== JSON.stringify(props.option);
|
||||
}}
|
||||
/>
|
||||
{/* 空状态提示 */}
|
||||
{(viewMode === 'graph' && (!graphData || !graphData.nodes || graphData.nodes.length === 0)) ||
|
||||
(viewMode === 'sankey' && (!sankeyData || !sankeyData.nodes || sankeyData.nodes.length === 0)) ? (
|
||||
<Center h="100%" flexDirection="column">
|
||||
<Icon as={Inbox} boxSize={12} color={PROFESSIONAL_COLORS.text.muted} />
|
||||
<Text mt={4} color={PROFESSIONAL_COLORS.text.muted} fontSize="sm">
|
||||
暂无传导链数据
|
||||
</Text>
|
||||
</Center>
|
||||
) : (
|
||||
<ReactECharts
|
||||
option={sankeyData ? getSankeyOption(sankeyData) : {}}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
onEvents={{
|
||||
click: handleSankeyNodeClick
|
||||
}}
|
||||
opts={{
|
||||
renderer: 'canvas',
|
||||
devicePixelRatio: window.devicePixelRatio || 1
|
||||
}}
|
||||
lazyUpdate={true}
|
||||
notMerge={false}
|
||||
shouldSetOption={(prevProps, props) => {
|
||||
// 减少不必要的重新渲染
|
||||
return JSON.stringify(prevProps.option) !== JSON.stringify(props.option);
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
{viewMode === 'graph' ? (
|
||||
<ReactECharts
|
||||
option={graphData ? getGraphOption(graphData) : {}}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
onEvents={{
|
||||
click: handleGraphNodeClick
|
||||
}}
|
||||
opts={{
|
||||
renderer: 'canvas',
|
||||
devicePixelRatio: window.devicePixelRatio || 1
|
||||
}}
|
||||
lazyUpdate={true}
|
||||
notMerge={false}
|
||||
shouldSetOption={(prevProps, props) => {
|
||||
// 减少不必要的重新渲染
|
||||
return JSON.stringify(prevProps.option) !== JSON.stringify(props.option);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ReactECharts
|
||||
option={sankeyData ? getSankeyOption(sankeyData) : {}}
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
onEvents={{
|
||||
click: handleSankeyNodeClick
|
||||
}}
|
||||
opts={{
|
||||
renderer: 'canvas',
|
||||
devicePixelRatio: window.devicePixelRatio || 1
|
||||
}}
|
||||
lazyUpdate={true}
|
||||
notMerge={false}
|
||||
shouldSetOption={(prevProps, props) => {
|
||||
// 减少不必要的重新渲染
|
||||
return JSON.stringify(prevProps.option) !== JSON.stringify(props.option);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user