update pay function
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { logger } from '../../utils/logger';
|
||||
import { useConceptTimelineEvents } from './hooks/useConceptTimelineEvents';
|
||||
import RiskDisclaimer from '../../components/RiskDisclaimer';
|
||||
import FullCalendar from '@fullcalendar/react';
|
||||
import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import dayjs from 'dayjs';
|
||||
import 'dayjs/locale/zh-cn';
|
||||
import {
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
@@ -24,21 +29,28 @@ import {
|
||||
Divider,
|
||||
useToast,
|
||||
useDisclosure,
|
||||
SimpleGrid,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import {
|
||||
ChevronDownIcon,
|
||||
ChevronRightIcon,
|
||||
ExternalLinkIcon,
|
||||
ViewIcon
|
||||
ViewIcon,
|
||||
CalendarIcon,
|
||||
} from '@chakra-ui/icons';
|
||||
import {
|
||||
FaChartLine,
|
||||
FaArrowUp,
|
||||
FaArrowDown,
|
||||
FaHistory
|
||||
FaHistory,
|
||||
FaNewspaper,
|
||||
FaFileAlt,
|
||||
} from 'react-icons/fa';
|
||||
import { keyframes } from '@emotion/react';
|
||||
|
||||
dayjs.locale('zh-cn');
|
||||
|
||||
// 动画定义
|
||||
const pulseAnimation = keyframes`
|
||||
0% { transform: translate(-50%, -50%) scale(1); opacity: 0.5; }
|
||||
@@ -79,7 +91,11 @@ const ConceptTimelineModal = ({
|
||||
|
||||
const [timelineData, setTimelineData] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [expandedDates, setExpandedDates] = useState({});
|
||||
const [selectedDate, setSelectedDate] = useState(null);
|
||||
const [selectedDateData, setSelectedDateData] = useState(null);
|
||||
|
||||
// 日期详情Modal
|
||||
const { isOpen: isDateDetailOpen, onOpen: onDateDetailOpen, onClose: onDateDetailClose } = useDisclosure();
|
||||
|
||||
// 研报全文Modal相关状态
|
||||
const [selectedReport, setSelectedReport] = useState(null);
|
||||
@@ -89,6 +105,71 @@ const ConceptTimelineModal = ({
|
||||
const [selectedNews, setSelectedNews] = useState(null);
|
||||
const [isNewsModalOpen, setIsNewsModalOpen] = useState(false);
|
||||
|
||||
// 转换时间轴数据为日历事件格式
|
||||
const calendarEvents = useMemo(() => {
|
||||
return timelineData.map(item => {
|
||||
const priceInfo = getPriceInfo(item.price);
|
||||
const hasEvents = item.events && item.events.length > 0;
|
||||
const newsCount = item.events.filter(e => e.type === 'news').length;
|
||||
const reportCount = item.events.filter(e => e.type === 'report').length;
|
||||
|
||||
// 根据涨跌幅和事件确定颜色
|
||||
let backgroundColor = '#e2e8f0'; // 默认灰色(无数据)
|
||||
if (hasEvents) {
|
||||
backgroundColor = '#9F7AEA'; // 紫色(有事件)
|
||||
} else if (item.price) {
|
||||
if (priceInfo.color === 'red') {
|
||||
backgroundColor = '#FC8181'; // 红色(上涨)
|
||||
} else if (priceInfo.color === 'green') {
|
||||
backgroundColor = '#68D391'; // 绿色(下跌)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: item.date,
|
||||
title: hasEvents
|
||||
? `📰${newsCount} 📊${reportCount}`
|
||||
: (item.price ? priceInfo.text : ''),
|
||||
date: item.date,
|
||||
backgroundColor,
|
||||
borderColor: backgroundColor,
|
||||
extendedProps: {
|
||||
...item,
|
||||
newsCount,
|
||||
reportCount,
|
||||
priceInfo,
|
||||
}
|
||||
};
|
||||
});
|
||||
}, [timelineData]);
|
||||
|
||||
// 处理日期点击
|
||||
const handleDateClick = (info) => {
|
||||
const clickedDate = info.dateStr;
|
||||
const dateData = timelineData.find(item => item.date === clickedDate);
|
||||
|
||||
if (dateData) {
|
||||
setSelectedDate(clickedDate);
|
||||
setSelectedDateData(dateData);
|
||||
onDateDetailOpen();
|
||||
|
||||
// 追踪日期点击
|
||||
trackDateToggled(clickedDate, true);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理事件点击
|
||||
const handleEventClick = (info) => {
|
||||
const clickedDate = info.event.id;
|
||||
const dateData = timelineData.find(item => item.date === clickedDate);
|
||||
|
||||
if (dateData) {
|
||||
setSelectedDate(clickedDate);
|
||||
setSelectedDateData(dateData);
|
||||
onDateDetailOpen();
|
||||
}
|
||||
};
|
||||
|
||||
// 获取时间轴数据
|
||||
const fetchTimelineData = async () => {
|
||||
setLoading(true);
|
||||
@@ -535,321 +616,121 @@ const ConceptTimelineModal = ({
|
||||
</VStack>
|
||||
</Center>
|
||||
) : timelineData.length > 0 ? (
|
||||
<Box position="relative" maxW="1000px" mx="auto" px={4}>
|
||||
<VStack spacing={6} align="stretch" position="relative">
|
||||
{timelineData.map((item, index) => {
|
||||
const priceInfo = getPriceInfo(item.price);
|
||||
const hasEvents = item.events.length > 0;
|
||||
const isExpanded = expandedDates[item.date];
|
||||
const isLeft = index % 2 === 0; // 偶数项在左,奇数项在右
|
||||
<Box position="relative" maxW="1200px" mx="auto" px={4}>
|
||||
{/* 图例说明 */}
|
||||
<Flex justify="center" mb={4} flexWrap="wrap" gap={3}>
|
||||
<HStack spacing={2}>
|
||||
<Box w={4} h={4} bg="#9F7AEA" borderRadius="sm" />
|
||||
<Text fontSize="sm">有新闻/研报</Text>
|
||||
</HStack>
|
||||
<HStack spacing={2}>
|
||||
<Box w={4} h={4} bg="#FC8181" borderRadius="sm" />
|
||||
<Text fontSize="sm">上涨</Text>
|
||||
</HStack>
|
||||
<HStack spacing={2}>
|
||||
<Box w={4} h={4} bg="#68D391" borderRadius="sm" />
|
||||
<Text fontSize="sm">下跌</Text>
|
||||
</HStack>
|
||||
<HStack spacing={2}>
|
||||
<Box w={4} h={4} bg="#e2e8f0" borderRadius="sm" />
|
||||
<Text fontSize="sm">无数据</Text>
|
||||
</HStack>
|
||||
</Flex>
|
||||
|
||||
return (
|
||||
<Box key={`${item.date}-${index}`} position="relative">
|
||||
<Flex
|
||||
direction={isLeft ? 'row' : 'row-reverse'}
|
||||
align="flex-start"
|
||||
position="relative"
|
||||
gap={4}
|
||||
>
|
||||
{/* 内容区域 */}
|
||||
{/* FullCalendar 日历组件 */}
|
||||
<Box
|
||||
flex={1}
|
||||
maxW="450px"
|
||||
height={{ base: '600px', md: '700px' }}
|
||||
bg="white"
|
||||
p={5}
|
||||
borderRadius="xl"
|
||||
boxShadow="lg"
|
||||
border="2px solid"
|
||||
borderColor={hasEvents ? 'purple.200' : 'gray.200'}
|
||||
position="relative"
|
||||
_hover={{
|
||||
transform: 'translateY(-4px)',
|
||||
boxShadow: '2xl',
|
||||
borderColor: hasEvents ? 'purple.400' : 'gray.300'
|
||||
}}
|
||||
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
|
||||
_before={{
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
top: '20px',
|
||||
[isLeft ? 'right' : 'left']: '-12px',
|
||||
width: 0,
|
||||
height: 0,
|
||||
borderStyle: 'solid',
|
||||
borderWidth: isLeft ? '12px 12px 12px 0' : '12px 0 12px 12px',
|
||||
borderColor: isLeft
|
||||
? `transparent ${hasEvents ? 'var(--chakra-colors-purple-200)' : 'var(--chakra-colors-gray-200)'} transparent transparent`
|
||||
: `transparent transparent transparent ${hasEvents ? 'var(--chakra-colors-purple-200)' : 'var(--chakra-colors-gray-200)'}`
|
||||
p={4}
|
||||
sx={{
|
||||
// FullCalendar 样式定制
|
||||
'.fc': {
|
||||
height: '100%',
|
||||
},
|
||||
'.fc-header-toolbar': {
|
||||
marginBottom: '1.5rem',
|
||||
},
|
||||
'.fc-toolbar-title': {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'purple.600',
|
||||
},
|
||||
'.fc-button': {
|
||||
backgroundColor: '#9F7AEA',
|
||||
borderColor: '#9F7AEA',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: '#805AD5',
|
||||
borderColor: '#805AD5',
|
||||
},
|
||||
'&:active, &:focus': {
|
||||
backgroundColor: '#6B46C1',
|
||||
borderColor: '#6B46C1',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
},
|
||||
'.fc-button-active': {
|
||||
backgroundColor: '#6B46C1',
|
||||
borderColor: '#6B46C1',
|
||||
},
|
||||
'.fc-daygrid-day': {
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': {
|
||||
backgroundColor: 'purple.50',
|
||||
},
|
||||
},
|
||||
'.fc-daygrid-day-number': {
|
||||
padding: '4px',
|
||||
fontSize: '0.875rem',
|
||||
},
|
||||
'.fc-event': {
|
||||
cursor: 'pointer',
|
||||
border: 'none',
|
||||
padding: '2px 4px',
|
||||
fontSize: '0.75rem',
|
||||
fontWeight: 'bold',
|
||||
borderRadius: '4px',
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': {
|
||||
transform: 'scale(1.05)',
|
||||
boxShadow: '0 2px 8px rgba(0,0,0,0.2)',
|
||||
},
|
||||
},
|
||||
'.fc-daygrid-event-harness': {
|
||||
marginBottom: '2px',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<VStack align="stretch" spacing={3}>
|
||||
{/* 日期标签 */}
|
||||
<HStack justify="space-between" flexWrap="wrap">
|
||||
<Badge
|
||||
colorScheme="purple"
|
||||
variant="subtle"
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
>
|
||||
{formatDateDisplay(item.date)}
|
||||
</Badge>
|
||||
|
||||
{item.price && (
|
||||
<Badge
|
||||
colorScheme={priceInfo.color}
|
||||
variant="solid"
|
||||
px={3}
|
||||
py={1}
|
||||
borderRadius="full"
|
||||
fontSize="sm"
|
||||
>
|
||||
<HStack spacing={1}>
|
||||
{priceInfo.icon && (
|
||||
<Icon as={priceInfo.icon} boxSize={3} />
|
||||
)}
|
||||
<Text>{priceInfo.text}</Text>
|
||||
</HStack>
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
{/* 股票数量 */}
|
||||
{item.price && item.price.stock_count && (
|
||||
<Text fontSize="xs" color="gray.600">
|
||||
📊 统计股票: {item.price.stock_count} 只
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* 事件信息 */}
|
||||
{hasEvents ? (
|
||||
<Box>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
colorScheme="purple"
|
||||
width="full"
|
||||
justifyContent="space-between"
|
||||
rightIcon={
|
||||
<Icon
|
||||
as={isExpanded ? ChevronDownIcon : ChevronRightIcon}
|
||||
transition="transform 0.2s"
|
||||
<FullCalendar
|
||||
plugins={[dayGridPlugin, interactionPlugin]}
|
||||
initialView="dayGridMonth"
|
||||
locale="zh-cn"
|
||||
headerToolbar={{
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: '',
|
||||
}}
|
||||
events={calendarEvents}
|
||||
dateClick={handleDateClick}
|
||||
eventClick={handleEventClick}
|
||||
height="100%"
|
||||
dayMaxEvents={3}
|
||||
moreLinkText="更多"
|
||||
buttonText={{
|
||||
today: '今天',
|
||||
month: '月',
|
||||
week: '周',
|
||||
}}
|
||||
eventDisplay="block"
|
||||
displayEventTime={false}
|
||||
/>
|
||||
}
|
||||
onClick={() => toggleDateExpand(item.date)}
|
||||
>
|
||||
<HStack spacing={2} fontSize="sm">
|
||||
{item.events.filter(e => e.type === 'news').length > 0 && (
|
||||
<Badge colorScheme="blue" variant="solid">
|
||||
📰 {item.events.filter(e => e.type === 'news').length}
|
||||
</Badge>
|
||||
)}
|
||||
{item.events.filter(e => e.type === 'report').length > 0 && (
|
||||
<Badge colorScheme="green" variant="solid">
|
||||
📊 {item.events.filter(e => e.type === 'report').length}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
</Button>
|
||||
|
||||
<Collapse in={isExpanded} animateOpacity>
|
||||
<VStack
|
||||
align="stretch"
|
||||
spacing={3}
|
||||
mt={3}
|
||||
maxH="300px"
|
||||
overflowY="auto"
|
||||
css={{
|
||||
'&::-webkit-scrollbar': { width: '6px' },
|
||||
'&::-webkit-scrollbar-track': { background: '#f1f1f1', borderRadius: '10px' },
|
||||
'&::-webkit-scrollbar-thumb': { background: '#c1c1c1', borderRadius: '10px' },
|
||||
'&::-webkit-scrollbar-thumb:hover': { background: '#a8a8a8' }
|
||||
}}
|
||||
>
|
||||
{item.events.map((event, eventIdx) => (
|
||||
<Box
|
||||
key={eventIdx}
|
||||
p={3}
|
||||
bg={event.type === 'news' ? 'blue.50' : 'green.50'}
|
||||
borderRadius="md"
|
||||
borderLeft="3px solid"
|
||||
borderLeftColor={event.type === 'news' ? 'blue.400' : 'green.400'}
|
||||
_hover={{ transform: 'translateX(4px)' }}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<VStack align="start" spacing={2}>
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Badge
|
||||
colorScheme={event.type === 'news' ? 'blue' : 'green'}
|
||||
variant="solid"
|
||||
>
|
||||
{event.type === 'news' ? '新闻' : '研报'}
|
||||
</Badge>
|
||||
{event.source && (
|
||||
<Badge variant="subtle">{event.source}</Badge>
|
||||
)}
|
||||
{event.publisher && (
|
||||
<Badge colorScheme="purple" variant="subtle">
|
||||
{event.publisher}
|
||||
</Badge>
|
||||
)}
|
||||
{event.rating && (
|
||||
<Badge colorScheme="orange" variant="solid">
|
||||
{event.rating}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
<Text fontWeight="bold" fontSize="sm" color="gray.800">
|
||||
{event.title}
|
||||
</Text>
|
||||
<Text fontSize="xs" color="gray.600" noOfLines={3}>
|
||||
{event.content || '暂无内容'}
|
||||
</Text>
|
||||
<Button
|
||||
size="xs"
|
||||
variant="link"
|
||||
colorScheme="blue"
|
||||
leftIcon={<ViewIcon />}
|
||||
onClick={() => {
|
||||
if (event.type === 'news') {
|
||||
trackNewsClicked(event, item.date);
|
||||
trackNewsDetailOpened(event);
|
||||
setSelectedNews({
|
||||
title: event.title,
|
||||
content: event.content,
|
||||
source: event.source,
|
||||
time: event.time,
|
||||
url: event.url
|
||||
});
|
||||
setIsNewsModalOpen(true);
|
||||
} else if (event.type === 'report') {
|
||||
trackReportClicked(event, item.date);
|
||||
trackReportDetailOpened(event);
|
||||
setSelectedReport({
|
||||
title: event.title,
|
||||
content: event.content,
|
||||
publisher: event.publisher,
|
||||
author: event.author,
|
||||
time: event.time,
|
||||
rating: event.rating,
|
||||
security_name: event.security_name,
|
||||
content_url: event.content_url
|
||||
});
|
||||
setIsReportModalOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
查看详情
|
||||
</Button>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</VStack>
|
||||
</Collapse>
|
||||
</Box>
|
||||
) : (
|
||||
<Text fontSize="sm" color="gray.400" fontStyle="italic" textAlign="center" py={2}>
|
||||
📭 当日无相关资讯
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Box>
|
||||
|
||||
{/* 时间轴节点 */}
|
||||
<Box
|
||||
position="relative"
|
||||
zIndex={2}
|
||||
flexShrink={0}
|
||||
>
|
||||
<Box
|
||||
w={hasEvents ? 16 : 12}
|
||||
h={hasEvents ? 16 : 12}
|
||||
borderRadius="full"
|
||||
bgGradient={
|
||||
hasEvents
|
||||
? 'linear(135deg, purple.400, pink.400)'
|
||||
: item.price
|
||||
? (priceInfo.color === 'red'
|
||||
? 'linear(135deg, red.400, red.500)'
|
||||
: priceInfo.color === 'green'
|
||||
? 'linear(135deg, green.400, green.500)'
|
||||
: 'linear(135deg, gray.300, gray.400)')
|
||||
: 'linear(135deg, gray.200, gray.300)'
|
||||
}
|
||||
border="4px solid white"
|
||||
boxShadow="0 4px 12px rgba(0,0,0,0.15)"
|
||||
position="relative"
|
||||
cursor={hasEvents ? 'pointer' : 'default'}
|
||||
onClick={() => hasEvents && toggleDateExpand(item.date)}
|
||||
_hover={hasEvents ? {
|
||||
transform: 'scale(1.15)',
|
||||
boxShadow: '0 6px 20px rgba(139, 92, 246, 0.4)'
|
||||
} : {}}
|
||||
transition="all 0.3s"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
{hasEvents && (
|
||||
<>
|
||||
<Badge
|
||||
position="absolute"
|
||||
top="-1"
|
||||
right="-1"
|
||||
colorScheme="red"
|
||||
borderRadius="full"
|
||||
minW="22px"
|
||||
h="22px"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
fontSize="xs"
|
||||
fontWeight="bold"
|
||||
boxShadow="md"
|
||||
border="2px solid white"
|
||||
>
|
||||
{item.events.length}
|
||||
</Badge>
|
||||
<Box
|
||||
position="absolute"
|
||||
top="50%"
|
||||
left="50%"
|
||||
transform="translate(-50%, -50%)"
|
||||
w="100%"
|
||||
h="100%"
|
||||
borderRadius="full"
|
||||
border="2px solid"
|
||||
borderColor="purple.300"
|
||||
opacity={0.5}
|
||||
animation={`${pulseAnimation} 2s infinite`}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* 连接线到下一个节点 */}
|
||||
{index < timelineData.length - 1 && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="100%"
|
||||
left="50%"
|
||||
transform="translateX(-50%)"
|
||||
width="2px"
|
||||
height="40px"
|
||||
bgGradient="linear(to-b, purple.300, pink.300)"
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</VStack>
|
||||
|
||||
{/* 时间轴结束标记 */}
|
||||
<Box textAlign="center" py={8}>
|
||||
{/* 底部说明 */}
|
||||
<Box textAlign="center" mt={6}>
|
||||
<Badge
|
||||
colorScheme="purple"
|
||||
variant="subtle"
|
||||
@@ -888,6 +769,172 @@ const ConceptTimelineModal = ({
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* 日期详情 Modal */}
|
||||
{isDateDetailOpen && selectedDateData && (
|
||||
<Modal
|
||||
isOpen={isDateDetailOpen}
|
||||
onClose={onDateDetailClose}
|
||||
size="4xl"
|
||||
scrollBehavior="inside"
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader bgGradient="linear(to-r, purple.500, pink.500)" color="white">
|
||||
<VStack align="start" spacing={2}>
|
||||
<HStack>
|
||||
<Icon as={CalendarIcon} />
|
||||
<Text>{formatDateDisplay(selectedDate)}</Text>
|
||||
</HStack>
|
||||
{selectedDateData.price && (
|
||||
<HStack spacing={3} fontSize="sm">
|
||||
<Badge colorScheme={getPriceInfo(selectedDateData.price).color} variant="solid" px={3} py={1}>
|
||||
<HStack spacing={1}>
|
||||
<Icon
|
||||
as={getPriceInfo(selectedDateData.price).icon}
|
||||
boxSize={3}
|
||||
/>
|
||||
<Text>{getPriceInfo(selectedDateData.price).text}</Text>
|
||||
</HStack>
|
||||
</Badge>
|
||||
{selectedDateData.price.stock_count && (
|
||||
<Text opacity={0.9}>
|
||||
📊 统计股票: {selectedDateData.price.stock_count} 只
|
||||
</Text>
|
||||
)}
|
||||
</HStack>
|
||||
)}
|
||||
</VStack>
|
||||
</ModalHeader>
|
||||
<ModalCloseButton color="white" />
|
||||
|
||||
<ModalBody py={6}>
|
||||
{selectedDateData.events && selectedDateData.events.length > 0 ? (
|
||||
<VStack align="stretch" spacing={4}>
|
||||
{selectedDateData.events.map((event, eventIdx) => (
|
||||
<Box
|
||||
key={eventIdx}
|
||||
p={4}
|
||||
bg={event.type === 'news' ? 'blue.50' : 'green.50'}
|
||||
borderRadius="lg"
|
||||
borderLeft="4px solid"
|
||||
borderLeftColor={event.type === 'news' ? 'blue.400' : 'green.400'}
|
||||
_hover={{
|
||||
transform: 'translateX(4px)',
|
||||
boxShadow: 'md',
|
||||
}}
|
||||
transition="all 0.2s"
|
||||
>
|
||||
<VStack align="start" spacing={3}>
|
||||
<HStack spacing={2} flexWrap="wrap">
|
||||
<Badge
|
||||
colorScheme={event.type === 'news' ? 'blue' : 'green'}
|
||||
variant="solid"
|
||||
fontSize="sm"
|
||||
>
|
||||
{event.type === 'news' ? '📰 新闻' : '📊 研报'}
|
||||
</Badge>
|
||||
{event.source && (
|
||||
<Badge variant="subtle" fontSize="xs">
|
||||
{event.source}
|
||||
</Badge>
|
||||
)}
|
||||
{event.publisher && (
|
||||
<Badge colorScheme="purple" variant="subtle" fontSize="xs">
|
||||
{event.publisher}
|
||||
</Badge>
|
||||
)}
|
||||
{event.rating && (
|
||||
<Badge colorScheme="orange" variant="solid" fontSize="xs">
|
||||
{event.rating}
|
||||
</Badge>
|
||||
)}
|
||||
{event.security_name && (
|
||||
<Badge colorScheme="cyan" variant="subtle" fontSize="xs">
|
||||
{event.security_name}
|
||||
</Badge>
|
||||
)}
|
||||
</HStack>
|
||||
|
||||
<Text fontWeight="bold" fontSize="md" color="gray.800">
|
||||
{event.title}
|
||||
</Text>
|
||||
|
||||
<Text fontSize="sm" color="gray.600" noOfLines={4}>
|
||||
{event.content || '暂无内容'}
|
||||
</Text>
|
||||
|
||||
{event.time && (
|
||||
<Text fontSize="xs" color="gray.500">
|
||||
🕐 {formatDateTime(event.time)}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<Button
|
||||
size="sm"
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
leftIcon={<ViewIcon />}
|
||||
onClick={() => {
|
||||
if (event.type === 'news') {
|
||||
trackNewsClicked(event, selectedDate);
|
||||
trackNewsDetailOpened(event);
|
||||
setSelectedNews({
|
||||
title: event.title,
|
||||
content: event.content,
|
||||
source: event.source,
|
||||
time: event.time,
|
||||
url: event.url,
|
||||
});
|
||||
setIsNewsModalOpen(true);
|
||||
} else if (event.type === 'report') {
|
||||
trackReportClicked(event, selectedDate);
|
||||
trackReportDetailOpened(event);
|
||||
setSelectedReport({
|
||||
title: event.title,
|
||||
content: event.content,
|
||||
publisher: event.publisher,
|
||||
author: event.author,
|
||||
time: event.time,
|
||||
rating: event.rating,
|
||||
security_name: event.security_name,
|
||||
content_url: event.content_url,
|
||||
});
|
||||
setIsReportModalOpen(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
查看全文
|
||||
</Button>
|
||||
</VStack>
|
||||
</Box>
|
||||
))}
|
||||
</VStack>
|
||||
) : (
|
||||
<Center py={12}>
|
||||
<VStack spacing={4}>
|
||||
<Icon as={FaHistory} boxSize={16} color="gray.300" />
|
||||
<Text fontSize="lg" color="gray.500">
|
||||
当日无新闻或研报
|
||||
</Text>
|
||||
{selectedDateData.price && (
|
||||
<Text fontSize="sm" color="gray.400">
|
||||
仅有涨跌幅数据
|
||||
</Text>
|
||||
)}
|
||||
</VStack>
|
||||
</Center>
|
||||
)}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter borderTop="1px solid" borderColor="gray.200">
|
||||
<Button colorScheme="purple" onClick={onDateDetailClose}>
|
||||
关闭
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* 研报全文Modal */}
|
||||
{isReportModalOpen && (
|
||||
<Modal
|
||||
|
||||
Reference in New Issue
Block a user