// src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js // 相关股票列表区组件(纯内容,不含标题) import React, { useState, useEffect, useMemo } from 'react'; import { VStack } from '@chakra-ui/react'; import dayjs from 'dayjs'; import StockListItem from './StockListItem'; import { fetchBatchKlineData, klineDataCache, getCacheKey } from '../StockDetailPanel/utils/klineDataCache'; import { logger } from '../../../../utils/logger'; /** * 相关股票列表区组件(纯内容部分) * 只负责渲染详细的股票列表,精简模式由外层 CollapsibleSection 的 simpleContent 提供 * @param {Object} props * @param {Array} props.stocks - 股票数组 * @param {Object} props.quotes - 股票行情字典 { [stockCode]: { change: number } } * @param {string} props.eventTime - 事件时间 * @param {Function} props.isInWatchlist - 检查股票是否在自选股中的函数 * @param {Function} props.onWatchlistToggle - 切换自选股回调 */ const RelatedStocksSection = ({ stocks, quotes = {}, eventTime = null, isInWatchlist = () => false, onWatchlistToggle }) => { // 分时图数据状态:{ [stockCode]: data[] } const [timelineDataMap, setTimelineDataMap] = useState({}); const [timelineLoading, setTimelineLoading] = useState(false); // 日K线数据状态:{ [stockCode]: data[] } const [dailyDataMap, setDailyDataMap] = useState({}); const [dailyLoading, setDailyLoading] = useState(false); // 稳定的事件时间 const stableEventTime = useMemo(() => { return eventTime ? dayjs(eventTime).format('YYYY-MM-DD HH:mm') : ''; }, [eventTime]); // 稳定的股票列表 key const stocksKey = useMemo(() => { if (!stocks || stocks.length === 0) return ''; return stocks.map(s => s.stock_code).sort().join(','); }, [stocks]); // 计算分时图是否应该显示 loading const shouldShowTimelineLoading = useMemo(() => { if (!stocks || stocks.length === 0) return false; const currentDataKeys = Object.keys(timelineDataMap).sort().join(','); if (stocksKey !== currentDataKeys) { return true; } return timelineLoading; }, [stocks, stocksKey, timelineDataMap, timelineLoading]); // 计算日K线是否应该显示 loading const shouldShowDailyLoading = useMemo(() => { if (!stocks || stocks.length === 0) return false; const currentDataKeys = Object.keys(dailyDataMap).sort().join(','); if (stocksKey !== currentDataKeys) { return true; } return dailyLoading; }, [stocks, stocksKey, dailyDataMap, dailyLoading]); // 批量加载分时图数据 useEffect(() => { if (!stocks || stocks.length === 0) { setTimelineDataMap({}); setTimelineLoading(false); return; } setTimelineLoading(true); const stockCodes = stocks.map(s => s.stock_code); // 检查缓存 const cachedData = {}; stockCodes.forEach(code => { const cacheKey = getCacheKey(code, stableEventTime, 'timeline'); const cached = klineDataCache.get(cacheKey); if (cached !== undefined) { cachedData[code] = cached; } }); if (Object.keys(cachedData).length === stockCodes.length) { setTimelineDataMap(cachedData); setTimelineLoading(false); logger.debug('RelatedStocksSection', '分时图数据全部来自缓存', { stockCount: stockCodes.length }); return; } logger.debug('RelatedStocksSection', '批量加载分时图数据', { totalCount: stockCodes.length, eventTime: stableEventTime }); fetchBatchKlineData(stockCodes, stableEventTime, 'timeline') .then((batchData) => { setTimelineDataMap({ ...cachedData, ...batchData }); setTimelineLoading(false); }) .catch((error) => { logger.error('RelatedStocksSection', '批量加载分时图数据失败', error); setTimelineDataMap(cachedData); setTimelineLoading(false); }); }, [stocksKey, stableEventTime]); // 批量加载日K线数据 useEffect(() => { if (!stocks || stocks.length === 0) { setDailyDataMap({}); setDailyLoading(false); return; } setDailyLoading(true); const stockCodes = stocks.map(s => s.stock_code); // 检查缓存 const cachedData = {}; stockCodes.forEach(code => { const cacheKey = getCacheKey(code, stableEventTime, 'daily'); const cached = klineDataCache.get(cacheKey); if (cached !== undefined) { cachedData[code] = cached; } }); if (Object.keys(cachedData).length === stockCodes.length) { setDailyDataMap(cachedData); setDailyLoading(false); logger.debug('RelatedStocksSection', '日K线数据全部来自缓存', { stockCount: stockCodes.length }); return; } logger.debug('RelatedStocksSection', '批量加载日K线数据', { totalCount: stockCodes.length, eventTime: stableEventTime }); fetchBatchKlineData(stockCodes, stableEventTime, 'daily') .then((batchData) => { setDailyDataMap({ ...cachedData, ...batchData }); setDailyLoading(false); }) .catch((error) => { logger.error('RelatedStocksSection', '批量加载日K线数据失败', error); setDailyDataMap(cachedData); setDailyLoading(false); }); }, [stocksKey, stableEventTime]); // 如果没有股票数据,不渲染 if (!stocks || stocks.length === 0) { return null; } return ( {stocks.map((stock, index) => ( ))} ); }; export default RelatedStocksSection;