feat: 添加滚动组件
This commit is contained in:
@@ -8,7 +8,6 @@ import {
|
||||
Badge,
|
||||
Button,
|
||||
Collapse,
|
||||
useDisclosure,
|
||||
Skeleton,
|
||||
Alert,
|
||||
AlertIcon,
|
||||
@@ -19,13 +18,6 @@ import {
|
||||
Icon,
|
||||
useColorModeValue,
|
||||
Tooltip,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalCloseButton,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
Spinner,
|
||||
Table,
|
||||
Thead,
|
||||
@@ -43,7 +35,9 @@ import {
|
||||
FaChartLine,
|
||||
FaEye,
|
||||
FaTimes,
|
||||
FaInfoCircle
|
||||
FaInfoCircle,
|
||||
FaChevronDown,
|
||||
FaChevronUp
|
||||
} from 'react-icons/fa';
|
||||
import { stockService } from '../../../services/eventService';
|
||||
import { logger } from '../../../utils/logger';
|
||||
@@ -57,11 +51,9 @@ const HistoricalEvents = ({
|
||||
// 所有 useState/useEffect/useContext/useRef/useCallback/useMemo 必须在组件顶层、顺序一致
|
||||
// 不要在 if/循环/回调中调用 Hook
|
||||
const [expandedEvents, setExpandedEvents] = useState(new Set());
|
||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||
const [expandedStocks, setExpandedStocks] = useState(new Set()); // 追踪哪些事件的股票列表被展开
|
||||
const [eventStocks, setEventStocks] = useState({});
|
||||
const [loadingStocks, setLoadingStocks] = useState(false);
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [loadingStocks, setLoadingStocks] = useState({});
|
||||
|
||||
// 颜色主题
|
||||
const timelineBg = useColorModeValue('#D4AF37', '#B8860B');
|
||||
@@ -80,36 +72,48 @@ const HistoricalEvents = ({
|
||||
setExpandedEvents(newExpanded);
|
||||
};
|
||||
|
||||
// 显示事件相关股票
|
||||
const showEventStocks = async (event) => {
|
||||
setSelectedEvent(event);
|
||||
setLoadingStocks(true);
|
||||
onOpen();
|
||||
// 切换股票列表展开状态
|
||||
const toggleStocksExpansion = async (event) => {
|
||||
const eventId = event.id;
|
||||
const newExpanded = new Set(expandedStocks);
|
||||
|
||||
// 如果正在收起,直接更新状态
|
||||
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 {
|
||||
// 如果已经加载过该事件的股票数据,直接使用缓存
|
||||
if (eventStocks[event.id]) {
|
||||
setLoadingStocks(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用API获取历史事件相关股票
|
||||
const response = await stockService.getHistoricalEventStocks(event.id);
|
||||
const response = await stockService.getHistoricalEventStocks(eventId);
|
||||
setEventStocks(prev => ({
|
||||
...prev,
|
||||
[event.id]: response.data || []
|
||||
[eventId]: response.data || []
|
||||
}));
|
||||
} catch (err) {
|
||||
logger.error('HistoricalEvents', 'showEventStocks', err, {
|
||||
eventId: event.id,
|
||||
logger.error('HistoricalEvents', 'toggleStocksExpansion', err, {
|
||||
eventId: eventId,
|
||||
eventTitle: event.title
|
||||
});
|
||||
setEventStocks(prev => ({
|
||||
...prev,
|
||||
[event.id]: []
|
||||
[eventId]: []
|
||||
}));
|
||||
} finally {
|
||||
setLoadingStocks(false);
|
||||
setLoadingStocks(prev => ({ ...prev, [eventId]: false }));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -377,7 +381,8 @@ const HistoricalEvents = ({
|
||||
<Button
|
||||
size="sm"
|
||||
leftIcon={<Icon as={FaChartLine} />}
|
||||
onClick={() => showEventStocks(event)}
|
||||
rightIcon={<Icon as={expandedStocks.has(event.id) ? FaChevronUp : FaChevronDown} />}
|
||||
onClick={() => toggleStocksExpansion(event)}
|
||||
colorScheme="blue"
|
||||
variant="outline"
|
||||
>
|
||||
@@ -416,6 +421,31 @@ const HistoricalEvents = ({
|
||||
</VStack>
|
||||
</Box>
|
||||
</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>
|
||||
</CardBody>
|
||||
</Card>
|
||||
@@ -426,40 +456,6 @@ const HistoricalEvents = ({
|
||||
</VStack>
|
||||
</Box>
|
||||
</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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user