feat: 添加滚动组件

This commit is contained in:
zdl
2025-11-02 16:41:21 +08:00
parent e7e2b3bb11
commit 0110dc2fdc

View File

@@ -8,7 +8,6 @@ import {
Badge, Badge,
Button, Button,
Collapse, Collapse,
useDisclosure,
Skeleton, Skeleton,
Alert, Alert,
AlertIcon, AlertIcon,
@@ -19,13 +18,6 @@ import {
Icon, Icon,
useColorModeValue, useColorModeValue,
Tooltip, Tooltip,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalCloseButton,
ModalBody,
ModalFooter,
Spinner, Spinner,
Table, Table,
Thead, Thead,
@@ -43,7 +35,9 @@ import {
FaChartLine, FaChartLine,
FaEye, FaEye,
FaTimes, FaTimes,
FaInfoCircle FaInfoCircle,
FaChevronDown,
FaChevronUp
} from 'react-icons/fa'; } from 'react-icons/fa';
import { stockService } from '../../../services/eventService'; import { stockService } from '../../../services/eventService';
import { logger } from '../../../utils/logger'; import { logger } from '../../../utils/logger';
@@ -57,11 +51,9 @@ const HistoricalEvents = ({
// 所有 useState/useEffect/useContext/useRef/useCallback/useMemo 必须在组件顶层、顺序一致 // 所有 useState/useEffect/useContext/useRef/useCallback/useMemo 必须在组件顶层、顺序一致
// 不要在 if/循环/回调中调用 Hook // 不要在 if/循环/回调中调用 Hook
const [expandedEvents, setExpandedEvents] = useState(new Set()); const [expandedEvents, setExpandedEvents] = useState(new Set());
const [selectedEvent, setSelectedEvent] = useState(null); const [expandedStocks, setExpandedStocks] = useState(new Set()); // 追踪哪些事件的股票列表被展开
const [eventStocks, setEventStocks] = useState({}); const [eventStocks, setEventStocks] = useState({});
const [loadingStocks, setLoadingStocks] = useState(false); const [loadingStocks, setLoadingStocks] = useState({});
const { isOpen, onOpen, onClose } = useDisclosure();
// 颜色主题 // 颜色主题
const timelineBg = useColorModeValue('#D4AF37', '#B8860B'); const timelineBg = useColorModeValue('#D4AF37', '#B8860B');
@@ -80,36 +72,48 @@ const HistoricalEvents = ({
setExpandedEvents(newExpanded); setExpandedEvents(newExpanded);
}; };
// 显示事件相关股票 // 切换股票列表展开状态
const showEventStocks = async (event) => { const toggleStocksExpansion = async (event) => {
setSelectedEvent(event); const eventId = event.id;
setLoadingStocks(true); const newExpanded = new Set(expandedStocks);
onOpen();
// 如果正在收起,直接更新状态
if (newExpanded.has(eventId)) {
newExpanded.delete(eventId);
setExpandedStocks(newExpanded);
return;
}
// 如果正在展开,先展开再加载数据
newExpanded.add(eventId);
setExpandedStocks(newExpanded);
// 如果已经加载过该事件的股票数据,不再重复加载
if (eventStocks[eventId]) {
return;
}
// 标记为加载中
setLoadingStocks(prev => ({ ...prev, [eventId]: true }));
try { try {
// 如果已经加载过该事件的股票数据,直接使用缓存
if (eventStocks[event.id]) {
setLoadingStocks(false);
return;
}
// 调用API获取历史事件相关股票 // 调用API获取历史事件相关股票
const response = await stockService.getHistoricalEventStocks(event.id); const response = await stockService.getHistoricalEventStocks(eventId);
setEventStocks(prev => ({ setEventStocks(prev => ({
...prev, ...prev,
[event.id]: response.data || [] [eventId]: response.data || []
})); }));
} catch (err) { } catch (err) {
logger.error('HistoricalEvents', 'showEventStocks', err, { logger.error('HistoricalEvents', 'toggleStocksExpansion', err, {
eventId: event.id, eventId: eventId,
eventTitle: event.title eventTitle: event.title
}); });
setEventStocks(prev => ({ setEventStocks(prev => ({
...prev, ...prev,
[event.id]: [] [eventId]: []
})); }));
} finally { } finally {
setLoadingStocks(false); setLoadingStocks(prev => ({ ...prev, [eventId]: false }));
} }
}; };
@@ -377,7 +381,8 @@ const HistoricalEvents = ({
<Button <Button
size="sm" size="sm"
leftIcon={<Icon as={FaChartLine} />} leftIcon={<Icon as={FaChartLine} />}
onClick={() => showEventStocks(event)} rightIcon={<Icon as={expandedStocks.has(event.id) ? FaChevronUp : FaChevronDown} />}
onClick={() => toggleStocksExpansion(event)}
colorScheme="blue" colorScheme="blue"
variant="outline" variant="outline"
> >
@@ -416,6 +421,31 @@ const HistoricalEvents = ({
</VStack> </VStack>
</Box> </Box>
</Collapse> </Collapse>
{/* 相关股票列表 Collapse */}
<Collapse in={expandedStocks.has(event.id)} animateOpacity>
<Box
mt={3}
pt={3}
borderTop="1px solid"
borderTopColor={borderColor}
bg={useColorModeValue('gray.50', 'gray.750')}
p={3}
borderRadius="md"
>
{loadingStocks[event.id] ? (
<VStack spacing={4} py={8}>
<Spinner size="lg" color="blue.500" />
<Text color={textSecondary}>加载相关股票数据...</Text>
</VStack>
) : (
<StocksList
stocks={eventStocks[event.id] || []}
eventTradingDate={event.event_date}
/>
)}
</Box>
</Collapse>
</VStack> </VStack>
</CardBody> </CardBody>
</Card> </Card>
@@ -426,40 +456,6 @@ const HistoricalEvents = ({
</VStack> </VStack>
</Box> </Box>
</VStack> </VStack>
{/* 事件相关股票模态框 */}
<Modal isOpen={isOpen} onClose={onClose} size="4xl">
<ModalOverlay />
<ModalContent maxW="80vw" maxH="85vh">
<ModalHeader>
<VStack align="flex-start" spacing={1}>
<Text>{selectedEvent?.title || '历史事件'}</Text>
<Text fontSize="sm" color={textSecondary} fontWeight="normal">
相关股票信息
</Text>
</VStack>
</ModalHeader>
<ModalCloseButton />
<ModalBody overflowY="auto" maxH="calc(85vh - 180px)">
{loadingStocks ? (
<VStack spacing={4} py={8}>
<Spinner size="lg" color="blue.500" />
<Text color={textSecondary}>加载相关股票数据...</Text>
</VStack>
) : (
<StocksList
stocks={selectedEvent ? eventStocks[selectedEvent.id] || [] : []}
eventTradingDate={selectedEvent ? selectedEvent.event_date : null}
/>
)}
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>关闭</Button>
</ModalFooter>
</ModalContent>
</Modal>
</> </>
); );
}; };