feat: 添加滚动组件
This commit is contained in:
@@ -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>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user