事件中心的涨停原因里面和事件相关
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* RelatedEventCard - 关联事件卡片组件
|
||||
* 在板块卡片内显示涨停归因的关联事件
|
||||
*/
|
||||
import React, { memo } from 'react';
|
||||
import React, { memo } from "react";
|
||||
import {
|
||||
Box,
|
||||
HStack,
|
||||
@@ -12,10 +12,10 @@ import {
|
||||
Icon,
|
||||
Progress,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { Newspaper, TrendingUp, ExternalLink } from 'lucide-react';
|
||||
import type { RelatedEvent } from '@/types/limitAnalyse';
|
||||
import { textColors, bgColors } from '@/constants/limitAnalyseTheme';
|
||||
} from "@chakra-ui/react";
|
||||
import { Newspaper, TrendingUp, ExternalLink } from "lucide-react";
|
||||
import type { RelatedEvent } from "@/types/limitAnalyse";
|
||||
import { textColors, bgColors } from "@/constants/limitAnalyseTheme";
|
||||
|
||||
interface RelatedEventCardProps {
|
||||
/** 事件数据 */
|
||||
@@ -30,210 +30,218 @@ interface RelatedEventCardProps {
|
||||
* 根据相关度分数获取颜色
|
||||
*/
|
||||
const getRelevanceColor = (score: number): string => {
|
||||
if (score >= 80) return '#10B981'; // 高相关 - 绿色
|
||||
if (score >= 60) return '#F59E0B'; // 中相关 - 橙色
|
||||
return '#6B7280'; // 低相关 - 灰色
|
||||
if (score >= 80) return "#10B981"; // 高相关 - 绿色
|
||||
if (score >= 60) return "#F59E0B"; // 中相关 - 橙色
|
||||
return "#6B7280"; // 低相关 - 灰色
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据相关度分数获取标签
|
||||
*/
|
||||
const getRelevanceLabel = (score: number): string => {
|
||||
if (score >= 80) return '高度相关';
|
||||
if (score >= 60) return '中度相关';
|
||||
return '参考';
|
||||
if (score >= 80) return "高度相关";
|
||||
if (score >= 60) return "中度相关";
|
||||
return "参考";
|
||||
};
|
||||
|
||||
/**
|
||||
* RelatedEventCard 组件
|
||||
*/
|
||||
const RelatedEventCard: React.FC<RelatedEventCardProps> = memo(({
|
||||
event,
|
||||
compact = false,
|
||||
onClick
|
||||
}) => {
|
||||
const relevanceColor = getRelevanceColor(event.relevance_score || 0);
|
||||
const relevanceLabel = getRelevanceLabel(event.relevance_score || 0);
|
||||
const RelatedEventCard: React.FC<RelatedEventCardProps> = memo(
|
||||
({ event, compact = false, onClick }) => {
|
||||
const relevanceColor = getRelevanceColor(event.relevance_score || 0);
|
||||
const relevanceLabel = getRelevanceLabel(event.relevance_score || 0);
|
||||
|
||||
const handleClick = () => {
|
||||
onClick?.(event);
|
||||
};
|
||||
const handleClick = () => {
|
||||
onClick?.(event);
|
||||
};
|
||||
|
||||
// 紧凑模式(板块卡片内摘要)
|
||||
if (compact) {
|
||||
// 紧凑模式(板块卡片内摘要)
|
||||
if (compact) {
|
||||
return (
|
||||
<Box
|
||||
p={2}
|
||||
bg="rgba(96, 165, 250, 0.08)"
|
||||
borderRadius="md"
|
||||
cursor={onClick ? "pointer" : "default"}
|
||||
onClick={handleClick}
|
||||
_hover={onClick ? { bg: "rgba(96, 165, 250, 0.15)" } : undefined}
|
||||
transition="background 0.2s"
|
||||
>
|
||||
<HStack spacing={2} align="start">
|
||||
<Icon
|
||||
as={Newspaper}
|
||||
boxSize={3.5}
|
||||
color="#60A5FA"
|
||||
mt={0.5}
|
||||
flexShrink={0}
|
||||
/>
|
||||
<VStack align="start" spacing={0.5} flex={1} minW={0}>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={textColors.secondary}
|
||||
noOfLines={1}
|
||||
fontWeight="500"
|
||||
>
|
||||
{event.title}
|
||||
</Text>
|
||||
<HStack spacing={2}>
|
||||
<Badge
|
||||
size="xs"
|
||||
bg={`${relevanceColor}20`}
|
||||
color={relevanceColor}
|
||||
fontSize="10px"
|
||||
px={1.5}
|
||||
borderRadius="sm"
|
||||
>
|
||||
{relevanceLabel}
|
||||
</Badge>
|
||||
{event.match_score && event.match_score > 1 && (
|
||||
<Text fontSize="10px" color={textColors.muted}>
|
||||
匹配{event.match_score}个概念
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
</HStack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// 完整模式(弹窗列表)
|
||||
return (
|
||||
<Box
|
||||
p={2}
|
||||
bg="rgba(96, 165, 250, 0.08)"
|
||||
borderRadius="md"
|
||||
cursor={onClick ? 'pointer' : 'default'}
|
||||
p={4}
|
||||
bg={bgColors.item}
|
||||
borderRadius="12px"
|
||||
border="1px solid rgba(255, 255, 255, 0.06)"
|
||||
cursor={onClick ? "pointer" : "default"}
|
||||
onClick={handleClick}
|
||||
_hover={onClick ? { bg: 'rgba(96, 165, 250, 0.15)' } : undefined}
|
||||
transition="background 0.2s"
|
||||
_hover={
|
||||
onClick
|
||||
? {
|
||||
bg: bgColors.hover,
|
||||
borderColor: "rgba(96, 165, 250, 0.3)",
|
||||
transform: "translateY(-1px)",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<HStack spacing={2} align="start">
|
||||
<Icon as={Newspaper} boxSize={3.5} color="#60A5FA" mt={0.5} flexShrink={0} />
|
||||
<VStack align="start" spacing={0.5} flex={1} minW={0}>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={textColors.secondary}
|
||||
noOfLines={1}
|
||||
fontWeight="500"
|
||||
>
|
||||
{event.title}
|
||||
</Text>
|
||||
<HStack spacing={2}>
|
||||
<Badge
|
||||
size="xs"
|
||||
bg={`${relevanceColor}20`}
|
||||
color={relevanceColor}
|
||||
fontSize="10px"
|
||||
px={1.5}
|
||||
borderRadius="sm"
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{/* 标题和跳转图标 */}
|
||||
<HStack justify="space-between" align="start">
|
||||
<HStack spacing={2} flex={1}>
|
||||
<Icon as={Newspaper} boxSize={4} color="#60A5FA" />
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColors.primary}
|
||||
noOfLines={2}
|
||||
>
|
||||
{relevanceLabel}
|
||||
</Badge>
|
||||
{event.match_score && event.match_score > 1 && (
|
||||
<Text fontSize="10px" color={textColors.muted}>
|
||||
匹配{event.match_score}个概念
|
||||
</Text>
|
||||
)}
|
||||
{event.title}
|
||||
</Text>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</HStack>
|
||||
{onClick && (
|
||||
<Icon
|
||||
as={ExternalLink}
|
||||
boxSize={4}
|
||||
color={textColors.muted}
|
||||
_hover={{ color: "#60A5FA" }}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 相关度分数 */}
|
||||
<HStack spacing={3}>
|
||||
<HStack spacing={1.5}>
|
||||
<Icon as={TrendingUp} boxSize={3.5} color={relevanceColor} />
|
||||
<Text fontSize="xs" color={textColors.muted}>
|
||||
相关度
|
||||
</Text>
|
||||
</HStack>
|
||||
<Box flex={1}>
|
||||
<Progress
|
||||
value={event.relevance_score || 0}
|
||||
size="xs"
|
||||
borderRadius="full"
|
||||
bg="rgba(255, 255, 255, 0.1)"
|
||||
sx={{
|
||||
"& > div": {
|
||||
background: `linear-gradient(90deg, ${relevanceColor}80, ${relevanceColor})`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Badge
|
||||
bg={`${relevanceColor}20`}
|
||||
color={relevanceColor}
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
borderRadius="md"
|
||||
>
|
||||
{event.relevance_score || 0}分
|
||||
</Badge>
|
||||
</HStack>
|
||||
|
||||
{/* 匹配的概念 */}
|
||||
{event.matched_concepts && event.matched_concepts.length > 0 && (
|
||||
<Box>
|
||||
<Text fontSize="xs" color={textColors.muted} mb={1.5}>
|
||||
匹配概念:
|
||||
</Text>
|
||||
<HStack spacing={1.5} flexWrap="wrap">
|
||||
{event.matched_concepts.slice(0, 5).map((concept, idx) => (
|
||||
<Badge
|
||||
key={idx}
|
||||
size="sm"
|
||||
bg="rgba(139, 92, 246, 0.15)"
|
||||
color="#A78BFA"
|
||||
fontSize="10px"
|
||||
px={2}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
>
|
||||
{concept}
|
||||
</Badge>
|
||||
))}
|
||||
{event.matched_concepts.length > 5 && (
|
||||
<Badge
|
||||
size="sm"
|
||||
bg="rgba(255, 255, 255, 0.1)"
|
||||
color={textColors.muted}
|
||||
fontSize="10px"
|
||||
px={2}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
>
|
||||
+{event.matched_concepts.length - 5}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 相关原因 */}
|
||||
{event.relevance_reason && (
|
||||
<Tooltip label={event.relevance_reason} placement="top" hasArrow>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={textColors.muted}
|
||||
noOfLines={2}
|
||||
lineHeight="1.5"
|
||||
cursor="help"
|
||||
>
|
||||
{event.relevance_reason}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// 完整模式(弹窗列表)
|
||||
return (
|
||||
<Box
|
||||
p={4}
|
||||
bg={bgColors.item}
|
||||
borderRadius="12px"
|
||||
border="1px solid rgba(255, 255, 255, 0.06)"
|
||||
cursor={onClick ? 'pointer' : 'default'}
|
||||
onClick={handleClick}
|
||||
_hover={onClick ? {
|
||||
bg: bgColors.hover,
|
||||
borderColor: 'rgba(96, 165, 250, 0.3)',
|
||||
transform: 'translateY(-1px)',
|
||||
} : undefined}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{/* 标题和跳转图标 */}
|
||||
<HStack justify="space-between" align="start">
|
||||
<HStack spacing={2} flex={1}>
|
||||
<Icon as={Newspaper} boxSize={4} color="#60A5FA" />
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColors.primary}
|
||||
noOfLines={2}
|
||||
>
|
||||
{event.title}
|
||||
</Text>
|
||||
</HStack>
|
||||
{onClick && (
|
||||
<Icon
|
||||
as={ExternalLink}
|
||||
boxSize={4}
|
||||
color={textColors.muted}
|
||||
_hover={{ color: '#60A5FA' }}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 相关度分数 */}
|
||||
<HStack spacing={3}>
|
||||
<HStack spacing={1.5}>
|
||||
<Icon as={TrendingUp} boxSize={3.5} color={relevanceColor} />
|
||||
<Text fontSize="xs" color={textColors.muted}>
|
||||
相关度
|
||||
</Text>
|
||||
</HStack>
|
||||
<Box flex={1}>
|
||||
<Progress
|
||||
value={event.relevance_score || 0}
|
||||
size="xs"
|
||||
borderRadius="full"
|
||||
bg="rgba(255, 255, 255, 0.1)"
|
||||
sx={{
|
||||
'& > div': {
|
||||
background: `linear-gradient(90deg, ${relevanceColor}80, ${relevanceColor})`,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Badge
|
||||
bg={`${relevanceColor}20`}
|
||||
color={relevanceColor}
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
borderRadius="md"
|
||||
>
|
||||
{event.relevance_score || 0}分
|
||||
</Badge>
|
||||
</HStack>
|
||||
|
||||
{/* 匹配的概念 */}
|
||||
{event.matched_concepts && event.matched_concepts.length > 0 && (
|
||||
<Box>
|
||||
<Text fontSize="xs" color={textColors.muted} mb={1.5}>
|
||||
匹配概念:
|
||||
</Text>
|
||||
<HStack spacing={1.5} flexWrap="wrap">
|
||||
{event.matched_concepts.slice(0, 5).map((concept, idx) => (
|
||||
<Badge
|
||||
key={idx}
|
||||
size="sm"
|
||||
bg="rgba(139, 92, 246, 0.15)"
|
||||
color="#A78BFA"
|
||||
fontSize="10px"
|
||||
px={2}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
>
|
||||
{concept}
|
||||
</Badge>
|
||||
))}
|
||||
{event.matched_concepts.length > 5 && (
|
||||
<Badge
|
||||
size="sm"
|
||||
bg="rgba(255, 255, 255, 0.1)"
|
||||
color={textColors.muted}
|
||||
fontSize="10px"
|
||||
px={2}
|
||||
py={0.5}
|
||||
borderRadius="md"
|
||||
>
|
||||
+{event.matched_concepts.length - 5}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* 相关原因 */}
|
||||
{event.relevance_reason && (
|
||||
<Tooltip label={event.relevance_reason} placement="top" hasArrow>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color={textColors.muted}
|
||||
noOfLines={2}
|
||||
lineHeight="1.5"
|
||||
cursor="help"
|
||||
>
|
||||
{event.relevance_reason}
|
||||
</Text>
|
||||
</Tooltip>
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
RelatedEventCard.displayName = 'RelatedEventCard';
|
||||
RelatedEventCard.displayName = "RelatedEventCard";
|
||||
|
||||
export default RelatedEventCard;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* RelatedEventsModal - 关联事件弹窗
|
||||
* 显示板块完整的关联事件列表
|
||||
*/
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import React, { memo, useCallback } from "react";
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
@@ -17,13 +17,18 @@ import {
|
||||
Icon,
|
||||
Box,
|
||||
Divider,
|
||||
} from '@chakra-ui/react';
|
||||
import { Newspaper, TrendingUp, Layers } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import type { RelatedEvent } from '@/types/limitAnalyse';
|
||||
import { textColors, bgColors, borderColors, goldColors } from '@/constants/limitAnalyseTheme';
|
||||
import { scrollbarStyles } from '@/styles/limitAnalyseStyles';
|
||||
import RelatedEventCard from './RelatedEventCard';
|
||||
} from "@chakra-ui/react";
|
||||
import { Newspaper, TrendingUp, Layers } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import type { RelatedEvent } from "@/types/limitAnalyse";
|
||||
import {
|
||||
textColors,
|
||||
bgColors,
|
||||
borderColors,
|
||||
goldColors,
|
||||
} from "@/constants/limitAnalyseTheme";
|
||||
import { scrollbarStyles } from "@/styles/limitAnalyseStyles";
|
||||
import RelatedEventCard from "./RelatedEventCard";
|
||||
|
||||
interface RelatedEventsModalProps {
|
||||
/** 是否打开 */
|
||||
@@ -41,110 +46,128 @@ interface RelatedEventsModalProps {
|
||||
/**
|
||||
* RelatedEventsModal 组件
|
||||
*/
|
||||
const RelatedEventsModal: React.FC<RelatedEventsModalProps> = memo(({
|
||||
isOpen,
|
||||
onClose,
|
||||
sectorName,
|
||||
events,
|
||||
limitUpCount = 0,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const RelatedEventsModal: React.FC<RelatedEventsModalProps> = memo(
|
||||
({ isOpen, onClose, sectorName, events, limitUpCount = 0 }) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 点击事件跳转到事件详情
|
||||
const handleEventClick = useCallback((event: RelatedEvent) => {
|
||||
onClose();
|
||||
navigate(`/community?event_id=${event.event_id}`);
|
||||
}, [navigate, onClose]);
|
||||
// 点击事件跳转到事件详情
|
||||
const handleEventClick = useCallback(
|
||||
(event: RelatedEvent) => {
|
||||
onClose();
|
||||
navigate(`/community?event_id=${event.event_id}`);
|
||||
},
|
||||
[navigate, onClose]
|
||||
);
|
||||
|
||||
// 按相关度排序
|
||||
const sortedEvents = [...events].sort(
|
||||
(a, b) => (b.relevance_score || 0) - (a.relevance_score || 0)
|
||||
);
|
||||
// 按相关度排序
|
||||
const sortedEvents = [...events].sort(
|
||||
(a, b) => (b.relevance_score || 0) - (a.relevance_score || 0)
|
||||
);
|
||||
|
||||
// 高相关事件数量
|
||||
const highRelevanceCount = events.filter(e => (e.relevance_score || 0) >= 80).length;
|
||||
// 高相关事件数量
|
||||
const highRelevanceCount = events.filter(
|
||||
(e) => (e.relevance_score || 0) >= 80
|
||||
).length;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
size="xl"
|
||||
isCentered
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalOverlay bg="blackAlpha.700" backdropFilter="blur(10px)" />
|
||||
<ModalContent
|
||||
bg={bgColors.card}
|
||||
border={`1px solid ${borderColors.gold}`}
|
||||
borderRadius="20px"
|
||||
maxH="80vh"
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
size="xl"
|
||||
isCentered
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
{/* 头部 */}
|
||||
<ModalHeader pb={2}>
|
||||
<VStack align="start" spacing={2}>
|
||||
<HStack spacing={3}>
|
||||
<Icon as={Layers} boxSize={5} color={goldColors.primary} />
|
||||
<Text fontSize="lg" fontWeight="bold" color={textColors.primary}>
|
||||
{sectorName} - 涨停归因分析
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack spacing={4}>
|
||||
<HStack spacing={1.5}>
|
||||
<Icon as={TrendingUp} boxSize={4} color="#EF4444" />
|
||||
<Text fontSize="sm" color={textColors.secondary}>
|
||||
涨停 <Text as="span" color="#EF4444" fontWeight="bold">{limitUpCount}</Text> 只
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack spacing={1.5}>
|
||||
<Icon as={Newspaper} boxSize={4} color="#60A5FA" />
|
||||
<Text fontSize="sm" color={textColors.secondary}>
|
||||
关联事件 <Text as="span" color="#60A5FA" fontWeight="bold">{events.length}</Text> 条
|
||||
</Text>
|
||||
</HStack>
|
||||
{highRelevanceCount > 0 && (
|
||||
<Badge
|
||||
bg="rgba(16, 185, 129, 0.15)"
|
||||
color="#10B981"
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
borderRadius="md"
|
||||
<ModalOverlay bg="blackAlpha.700" backdropFilter="blur(10px)" />
|
||||
<ModalContent
|
||||
bg={bgColors.card}
|
||||
border={`1px solid ${borderColors.gold}`}
|
||||
borderRadius="20px"
|
||||
maxH="80vh"
|
||||
>
|
||||
{/* 头部 */}
|
||||
<ModalHeader pb={2}>
|
||||
<VStack align="start" spacing={2}>
|
||||
<HStack spacing={3}>
|
||||
<Icon as={Layers} boxSize={5} color={goldColors.primary} />
|
||||
<Text
|
||||
fontSize="lg"
|
||||
fontWeight="bold"
|
||||
color={textColors.primary}
|
||||
>
|
||||
高度相关 {highRelevanceCount}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
</ModalHeader>
|
||||
|
||||
<ModalCloseButton color={textColors.muted} />
|
||||
|
||||
<Divider borderColor="rgba(255, 255, 255, 0.1)" />
|
||||
|
||||
{/* 事件列表 */}
|
||||
<ModalBody py={4} sx={scrollbarStyles}>
|
||||
{sortedEvents.length === 0 ? (
|
||||
<Box textAlign="center" py={10}>
|
||||
<Icon as={Newspaper} boxSize={10} color={textColors.muted} mb={3} />
|
||||
<Text color={textColors.muted}>暂无关联事件</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<VStack spacing={3} align="stretch">
|
||||
{sortedEvents.map((event, index) => (
|
||||
<RelatedEventCard
|
||||
key={event.event_id || index}
|
||||
event={event}
|
||||
compact={false}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
{sectorName} - 涨停归因分析
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack spacing={4}>
|
||||
<HStack spacing={1.5}>
|
||||
<Icon as={TrendingUp} boxSize={4} color="#EF4444" />
|
||||
<Text fontSize="sm" color={textColors.secondary}>
|
||||
涨停{" "}
|
||||
<Text as="span" color="#EF4444" fontWeight="bold">
|
||||
{limitUpCount}
|
||||
</Text>{" "}
|
||||
只
|
||||
</Text>
|
||||
</HStack>
|
||||
<HStack spacing={1.5}>
|
||||
<Icon as={Newspaper} boxSize={4} color="#60A5FA" />
|
||||
<Text fontSize="sm" color={textColors.secondary}>
|
||||
关联事件{" "}
|
||||
<Text as="span" color="#60A5FA" fontWeight="bold">
|
||||
{events.length}
|
||||
</Text>{" "}
|
||||
条
|
||||
</Text>
|
||||
</HStack>
|
||||
{highRelevanceCount > 0 && (
|
||||
<Badge
|
||||
bg="rgba(16, 185, 129, 0.15)"
|
||||
color="#10B981"
|
||||
fontSize="xs"
|
||||
px={2}
|
||||
borderRadius="md"
|
||||
>
|
||||
高度相关 {highRelevanceCount}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</VStack>
|
||||
)}
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
</ModalHeader>
|
||||
|
||||
RelatedEventsModal.displayName = 'RelatedEventsModal';
|
||||
<ModalCloseButton color={textColors.muted} />
|
||||
|
||||
<Divider borderColor="rgba(255, 255, 255, 0.1)" />
|
||||
|
||||
{/* 事件列表 */}
|
||||
<ModalBody py={4} sx={scrollbarStyles}>
|
||||
{sortedEvents.length === 0 ? (
|
||||
<Box textAlign="center" py={10}>
|
||||
<Icon
|
||||
as={Newspaper}
|
||||
boxSize={10}
|
||||
color={textColors.muted}
|
||||
mb={3}
|
||||
/>
|
||||
<Text color={textColors.muted}>暂无关联事件</Text>
|
||||
</Box>
|
||||
) : (
|
||||
<VStack spacing={3} align="stretch">
|
||||
{sortedEvents.map((event, index) => (
|
||||
<RelatedEventCard
|
||||
key={event.event_id || index}
|
||||
event={event}
|
||||
compact={false}
|
||||
onClick={handleEventClick}
|
||||
/>
|
||||
))}
|
||||
</VStack>
|
||||
)}
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
RelatedEventsModal.displayName = "RelatedEventsModal";
|
||||
|
||||
export default RelatedEventsModal;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* RelatedEvents 组件导出
|
||||
*/
|
||||
export { default as RelatedEventCard } from './RelatedEventCard';
|
||||
export { default as RelatedEventsModal } from './RelatedEventsModal';
|
||||
export { default as RelatedEventCard } from "./RelatedEventCard";
|
||||
export { default as RelatedEventsModal } from "./RelatedEventsModal";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* 单个板块项组件
|
||||
* 支持 accordion 和 card 两种显示模式
|
||||
*/
|
||||
import React, { memo, useCallback, useMemo, useState } from "react";
|
||||
import React, { memo, useCallback, useMemo } from "react";
|
||||
import {
|
||||
Box,
|
||||
VStack,
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
WrapItem,
|
||||
Tag,
|
||||
TagLabel,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
AccordionButton,
|
||||
AccordionPanel,
|
||||
@@ -26,15 +25,22 @@ import {
|
||||
useDisclosure,
|
||||
Divider,
|
||||
} from "@chakra-ui/react";
|
||||
import { Star, Zap, ChevronDown, ChevronUp, Sparkles, Newspaper, ChevronRight } from "lucide-react";
|
||||
import type { SectorData, LimitUpStock } from "@/types/limitAnalyse";
|
||||
import { formatLimitUpTime, getSectorColor } from "@/utils/limitAnalyseUtils";
|
||||
import {
|
||||
Star,
|
||||
Zap,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Sparkles,
|
||||
Newspaper,
|
||||
ChevronRight,
|
||||
} from "lucide-react";
|
||||
import type { SectorData } from "@/types/limitAnalyse";
|
||||
import { formatLimitUpTime } from "@/utils/limitAnalyseUtils";
|
||||
import {
|
||||
goldColors,
|
||||
textColors,
|
||||
bgColors,
|
||||
marketColors,
|
||||
SECTOR_COLORS,
|
||||
} from "@/constants/limitAnalyseTheme";
|
||||
import StockListItem from "./StockListItem";
|
||||
import { RelatedEventCard, RelatedEventsModal } from "./RelatedEvents";
|
||||
@@ -112,11 +118,13 @@ const SectorItem = memo<SectorItemProps>(
|
||||
const sectorColor = getSectorSingleColor(sector);
|
||||
|
||||
// 关联事件弹窗控制
|
||||
const { isOpen: isEventsModalOpen, onOpen: onOpenEventsModal, onClose: onCloseEventsModal } = useDisclosure();
|
||||
const {
|
||||
isOpen: isEventsModalOpen,
|
||||
onOpen: onOpenEventsModal,
|
||||
onClose: onCloseEventsModal,
|
||||
} = useDisclosure();
|
||||
|
||||
const firstLimitTime = leadingStock
|
||||
? formatLimitUpTime(leadingStock)
|
||||
: "-";
|
||||
const firstLimitTime = leadingStock ? formatLimitUpTime(leadingStock) : "-";
|
||||
|
||||
// 按时间排序的股票
|
||||
const sortedStocks = useMemo(() => {
|
||||
@@ -133,10 +141,13 @@ const SectorItem = memo<SectorItemProps>(
|
||||
}, [relatedEvents]);
|
||||
|
||||
// 点击查看更多事件
|
||||
const handleViewMoreEvents = useCallback((e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onOpenEventsModal();
|
||||
}, [onOpenEventsModal]);
|
||||
const handleViewMoreEvents = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onOpenEventsModal();
|
||||
},
|
||||
[onOpenEventsModal]
|
||||
);
|
||||
|
||||
// Card 模式
|
||||
if (displayMode === "card") {
|
||||
@@ -185,11 +196,19 @@ const SectorItem = memo<SectorItemProps>(
|
||||
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<HStack spacing={2}>
|
||||
<Text fontWeight="bold" fontSize="md" color={textColors.primary}>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize="md"
|
||||
color={textColors.primary}
|
||||
>
|
||||
{sector}
|
||||
</Text>
|
||||
{sector === "公告" && (
|
||||
<Icon as={Sparkles} boxSize={4} color={goldColors.primary} />
|
||||
<Icon
|
||||
as={Sparkles}
|
||||
boxSize={4}
|
||||
color={goldColors.primary}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
<HStack spacing={3} fontSize="xs" color={textColors.muted}>
|
||||
@@ -214,7 +233,11 @@ const SectorItem = memo<SectorItemProps>(
|
||||
<HStack spacing={4}>
|
||||
{leadingStock && (
|
||||
<HStack spacing={2}>
|
||||
<Text color={goldColors.light} fontWeight="500" fontSize="sm">
|
||||
<Text
|
||||
color={goldColors.light}
|
||||
fontWeight="500"
|
||||
fontSize="sm"
|
||||
>
|
||||
{leadingStock.sname}
|
||||
</Text>
|
||||
<Badge
|
||||
@@ -270,14 +293,25 @@ const SectorItem = memo<SectorItemProps>(
|
||||
|
||||
{/* Body - 展开的个股明细 */}
|
||||
<Collapse in={isExpanded} animateOpacity>
|
||||
<Box borderTop="1px solid rgba(255, 255, 255, 0.06)" bg="rgba(0, 0, 0, 0.2)">
|
||||
<Box
|
||||
borderTop="1px solid rgba(255, 255, 255, 0.06)"
|
||||
bg="rgba(0, 0, 0, 0.2)"
|
||||
>
|
||||
{/* 关联事件区域 */}
|
||||
{relatedEvents.length > 0 && (
|
||||
<Box px={4} py={3} borderBottom="1px solid rgba(255, 255, 255, 0.06)">
|
||||
<Box
|
||||
px={4}
|
||||
py={3}
|
||||
borderBottom="1px solid rgba(255, 255, 255, 0.06)"
|
||||
>
|
||||
<HStack justify="space-between" mb={2}>
|
||||
<HStack spacing={2}>
|
||||
<Icon as={Newspaper} boxSize={4} color="#60A5FA" />
|
||||
<Text fontSize="sm" fontWeight="600" color={textColors.secondary}>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColors.secondary}
|
||||
>
|
||||
涨停归因
|
||||
</Text>
|
||||
<Badge
|
||||
@@ -371,7 +405,11 @@ const SectorItem = memo<SectorItemProps>(
|
||||
|
||||
<VStack align="start" spacing={0} flex={1}>
|
||||
<HStack spacing={2}>
|
||||
<Text fontWeight="bold" fontSize="md" color={textColors.primary}>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
fontSize="md"
|
||||
color={textColors.primary}
|
||||
>
|
||||
{sector}
|
||||
</Text>
|
||||
{sector === "公告" && (
|
||||
@@ -399,7 +437,11 @@ const SectorItem = memo<SectorItemProps>(
|
||||
<HStack justify="space-between" mb={2}>
|
||||
<HStack spacing={2}>
|
||||
<Icon as={Newspaper} boxSize={4} color="#60A5FA" />
|
||||
<Text fontSize="sm" fontWeight="600" color={textColors.secondary}>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColors.secondary}
|
||||
>
|
||||
涨停归因
|
||||
</Text>
|
||||
<Badge
|
||||
|
||||
Reference in New Issue
Block a user