diff --git a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js index a96594bb..b17cc424 100644 --- a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js +++ b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js @@ -81,7 +81,7 @@ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { // 使用 Hook 获取实时数据 // - autoLoad: false - 禁用自动加载所有数据,改为手动触发 - // - autoLoadQuotes: false - 禁用自动加载行情,延迟到展开时加载(减少请求) + // - autoLoadQuotes: true - 股票数据加载后自动加载行情(相关股票默认展开) const { stocks, quotes, @@ -93,7 +93,7 @@ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { loadHistoricalData, loadChainAnalysis, refreshQuotes - } = useEventStocks(event?.id, event?.created_at, { autoLoad: false, autoLoadQuotes: false }); + } = useEventStocks(event?.id, event?.created_at, { autoLoad: false, autoLoadQuotes: true }); // 🎯 加载事件详情(增加浏览量)- 与 EventDetailModal 保持一致 const loadEventDetail = useCallback(async () => { @@ -229,12 +229,14 @@ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { setHasLoadedHistorical(false); setHasLoadedTransmission(false); - // 相关股票默认展开,预加载股票列表 + // 相关股票默认展开,预加载股票列表和行情数据 setIsStocksOpen(true); if (canAccessStocks) { - console.log('%c📊 [相关股票] 事件切换,预加载股票列表(获取数量)', 'color: #10B981; font-weight: bold;', { eventId: event?.id }); + console.log('%c📊 [相关股票] 事件切换,预加载股票列表和行情数据', 'color: #10B981; font-weight: bold;', { eventId: event?.id }); loadStocksData(); setHasLoadedStocks(true); + // 由于默认展开,直接加载行情数据 + setHasLoadedQuotes(true); } // 历史事件默认折叠,但预加载数据(显示数量吸引点击) diff --git a/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js b/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js index 2be81942..ebcacaa2 100644 --- a/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js +++ b/src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js @@ -1,9 +1,12 @@ // src/views/Community/components/DynamicNewsDetail/RelatedStocksSection.js // 相关股票列表区组件(纯内容,不含标题) -import React from 'react'; +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'; /** * 相关股票列表区组件(纯内容部分) @@ -22,6 +25,85 @@ const RelatedStocksSection = ({ watchlistSet = new Set(), onWatchlistToggle }) => { + // K线数据状态:{ [stockCode]: data[] } + const [klineDataMap, setKlineDataMap] = useState({}); + const [klineLoading, setKlineLoading] = 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 shouldShowLoading = useMemo(() => { + if (!stocks || stocks.length === 0) return false; + const currentDataKeys = Object.keys(klineDataMap).sort().join(','); + if (stocksKey !== currentDataKeys) { + return true; + } + return klineLoading; + }, [stocks, stocksKey, klineDataMap, klineLoading]); + + // 批量加载K线数据 + useEffect(() => { + if (!stocks || stocks.length === 0) { + setKlineDataMap({}); + setKlineLoading(false); + return; + } + + // 立即设置 loading 状态 + setKlineLoading(true); + + const stockCodes = stocks.map(s => s.stock_code); + + // 先检查缓存 + const cachedData = {}; + const uncachedCodes = []; + stockCodes.forEach(code => { + const cacheKey = getCacheKey(code, stableEventTime, 'timeline'); + const cached = klineDataCache.get(cacheKey); + if (cached !== undefined) { + cachedData[code] = cached; + } else { + uncachedCodes.push(code); + } + }); + + // 如果全部缓存命中,直接使用 + if (uncachedCodes.length === 0) { + setKlineDataMap(cachedData); + setKlineLoading(false); + logger.debug('RelatedStocksSection', 'K线数据全部来自缓存', { stockCount: stockCodes.length }); + return; + } + + logger.debug('RelatedStocksSection', '批量加载K线数据', { + totalCount: stockCodes.length, + cachedCount: Object.keys(cachedData).length, + uncachedCount: uncachedCodes.length, + eventTime: stableEventTime + }); + + // 批量请求 + fetchBatchKlineData(stockCodes, stableEventTime, 'timeline') + .then((batchData) => { + setKlineDataMap({ ...cachedData, ...batchData }); + setKlineLoading(false); + }) + .catch((error) => { + logger.error('RelatedStocksSection', '批量加载K线数据失败', error); + setKlineDataMap(cachedData); + setKlineLoading(false); + }); + }, [stocksKey, stableEventTime]); + // 如果没有股票数据,不渲染 if (!stocks || stocks.length === 0) { return null; @@ -37,6 +119,8 @@ const RelatedStocksSection = ({ eventTime={eventTime} isInWatchlist={watchlistSet.has(stock.stock_code)} onWatchlistToggle={onWatchlistToggle} + klineData={klineDataMap[stock.stock_code]} + klineLoading={shouldShowLoading && !klineDataMap[stock.stock_code]} /> ))} diff --git a/src/views/Community/components/DynamicNewsDetail/StockListItem.js b/src/views/Community/components/DynamicNewsDetail/StockListItem.js index 0188a3f5..f20e54fd 100644 --- a/src/views/Community/components/DynamicNewsDetail/StockListItem.js +++ b/src/views/Community/components/DynamicNewsDetail/StockListItem.js @@ -39,13 +39,17 @@ import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; * @param {string} props.eventTime - 事件时间(可选) * @param {boolean} props.isInWatchlist - 是否在自选股中 * @param {Function} props.onWatchlistToggle - 切换自选股回调 + * @param {Array} props.klineData - 预加载的K线数据(可选,由父组件批量加载后传入) + * @param {boolean} props.klineLoading - K线数据加载状态 */ const StockListItem = ({ stock, quote = null, eventTime = null, isInWatchlist = false, - onWatchlistToggle + onWatchlistToggle, + klineData, + klineLoading = false }) => { const isMobile = useSelector(selectIsMobile); const cardBg = PROFESSIONAL_COLORS.background.card; @@ -237,6 +241,8 @@ const StockListItem = ({