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