From 19a8866305c36585e120947af2d3c4b0c57dbd7f Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Thu, 30 Oct 2025 13:04:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=90=E4=BA=A4=20Custom=20Hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StockDetailPanel/hooks/useEventStocks.js | 135 ++++++++++++++++++ .../StockDetailPanel/hooks/useWatchlist.js | 131 +++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 src/views/Community/components/StockDetailPanel/hooks/useEventStocks.js create mode 100644 src/views/Community/components/StockDetailPanel/hooks/useWatchlist.js diff --git a/src/views/Community/components/StockDetailPanel/hooks/useEventStocks.js b/src/views/Community/components/StockDetailPanel/hooks/useEventStocks.js new file mode 100644 index 00000000..90f596ff --- /dev/null +++ b/src/views/Community/components/StockDetailPanel/hooks/useEventStocks.js @@ -0,0 +1,135 @@ +// src/views/Community/components/StockDetailPanel/hooks/useEventStocks.js +import { useSelector, useDispatch } from 'react-redux'; +import { useEffect, useCallback, useMemo } from 'react'; +import { + fetchEventStocks, + fetchStockQuotes, + fetchEventDetail, + fetchHistoricalEvents, + fetchChainAnalysis, + fetchExpectationScore +} from '../../../../../store/slices/stockSlice'; +import { logger } from '../../../../../utils/logger'; + +/** + * 事件股票数据 Hook + * 封装事件相关的所有数据加载逻辑 + * + * @param {string} eventId - 事件ID + * @param {string} eventTime - 事件时间 + * @returns {Object} 事件数据和加载状态 + */ +export const useEventStocks = (eventId, eventTime) => { + const dispatch = useDispatch(); + + // 从 Redux 获取数据 + const stocks = useSelector(state => + eventId ? (state.stock.eventStocksCache[eventId] || []) : [] + ); + const quotes = useSelector(state => state.stock.quotes); + const eventDetail = useSelector(state => + eventId ? state.stock.eventDetailsCache[eventId] : null + ); + const historicalEvents = useSelector(state => + eventId ? (state.stock.historicalEventsCache[eventId] || []) : [] + ); + const chainAnalysis = useSelector(state => + eventId ? state.stock.chainAnalysisCache[eventId] : null + ); + const expectationScore = useSelector(state => + eventId ? state.stock.expectationScores[eventId] : null + ); + + // 加载状态 + const loading = useSelector(state => state.stock.loading); + + // 加载所有数据 + const loadAllData = useCallback(() => { + if (!eventId) { + logger.warn('useEventStocks', 'eventId 为空,跳过数据加载'); + return; + } + + logger.debug('useEventStocks', '开始加载事件数据', { eventId }); + + // 并发加载所有数据 + dispatch(fetchEventStocks({ eventId })); + dispatch(fetchEventDetail({ eventId })); + dispatch(fetchHistoricalEvents({ eventId })); + dispatch(fetchChainAnalysis({ eventId })); + dispatch(fetchExpectationScore({ eventId })); + }, [dispatch, eventId]); + + // 强制刷新所有数据 + const refreshAllData = useCallback(() => { + if (!eventId) return; + + logger.debug('useEventStocks', '强制刷新事件数据', { eventId }); + + dispatch(fetchEventStocks({ eventId, forceRefresh: true })); + dispatch(fetchEventDetail({ eventId, forceRefresh: true })); + dispatch(fetchHistoricalEvents({ eventId, forceRefresh: true })); + dispatch(fetchChainAnalysis({ eventId, forceRefresh: true })); + dispatch(fetchExpectationScore({ eventId })); + }, [dispatch, eventId]); + + // 只刷新行情数据 + const refreshQuotes = useCallback(() => { + if (stocks.length === 0) return; + + const codes = stocks.map(s => s.stock_code); + logger.debug('useEventStocks', '刷新行情数据', { + stockCount: codes.length, + eventTime + }); + + dispatch(fetchStockQuotes({ codes, eventTime })); + }, [dispatch, stocks, eventTime]); + + // 自动加载事件数据 + useEffect(() => { + if (eventId) { + loadAllData(); + } + }, [loadAllData]); + + // 自动加载行情数据 + useEffect(() => { + if (stocks.length > 0) { + refreshQuotes(); + } + }, [stocks.length, eventId]); // 注意:这里不依赖 refreshQuotes,避免重复请求 + + // 计算股票行情合并数据 + const stocksWithQuotes = useMemo(() => { + return stocks.map(stock => ({ + ...stock, + quote: quotes[stock.stock_code] || null + })); + }, [stocks, quotes]); + + return { + // 数据 + stocks, + stocksWithQuotes, + quotes, + eventDetail, + historicalEvents, + chainAnalysis, + expectationScore, + + // 加载状态 + loading: { + stocks: loading.stocks, + quotes: loading.quotes, + eventDetail: loading.eventDetail, + historicalEvents: loading.historicalEvents, + chainAnalysis: loading.chainAnalysis + }, + + // 方法 + loadAllData, + refreshAllData, + refreshQuotes + }; +}; diff --git a/src/views/Community/components/StockDetailPanel/hooks/useWatchlist.js b/src/views/Community/components/StockDetailPanel/hooks/useWatchlist.js new file mode 100644 index 00000000..c1b8f031 --- /dev/null +++ b/src/views/Community/components/StockDetailPanel/hooks/useWatchlist.js @@ -0,0 +1,131 @@ +// src/views/Community/components/StockDetailPanel/hooks/useWatchlist.js +import { useSelector, useDispatch } from 'react-redux'; +import { useEffect, useCallback, useMemo } from 'react'; +import { loadWatchlist, toggleWatchlist as toggleWatchlistAction } from '../../../../../store/slices/stockSlice'; +import { message } from 'antd'; +import { logger } from '../../../../../utils/logger'; + +/** + * 自选股管理 Hook + * 封装自选股的加载、添加、移除逻辑 + * + * @returns {Object} 自选股数据和操作方法 + */ +export const useWatchlist = () => { + const dispatch = useDispatch(); + + // 从 Redux 获取自选股列表 + const watchlistArray = useSelector(state => state.stock.watchlist); + const loading = useSelector(state => state.stock.loading.watchlist); + + // 转换为 Set 方便快速查询 + const watchlistSet = useMemo(() => { + return new Set(watchlistArray); + }, [watchlistArray]); + + // 初始化时加载自选股列表 + useEffect(() => { + dispatch(loadWatchlist()); + }, [dispatch]); + + /** + * 检查股票是否在自选股中 + * @param {string} stockCode - 股票代码 + * @returns {boolean} + */ + const isInWatchlist = useCallback((stockCode) => { + return watchlistSet.has(stockCode); + }, [watchlistSet]); + + /** + * 切换自选股状态 + * @param {string} stockCode - 股票代码 + * @param {string} stockName - 股票名称 + * @returns {Promise} 操作是否成功 + */ + const toggleWatchlist = useCallback(async (stockCode, stockName) => { + const wasInWatchlist = watchlistSet.has(stockCode); + + logger.debug('useWatchlist', '切换自选股状态', { + stockCode, + stockName, + wasInWatchlist + }); + + try { + await dispatch(toggleWatchlistAction({ + stockCode, + stockName, + isInWatchlist: wasInWatchlist + })).unwrap(); + + message.success(wasInWatchlist ? '已从自选股移除' : '已加入自选股'); + return true; + } catch (error) { + logger.error('useWatchlist', '切换自选股失败', error, { + stockCode, + stockName + }); + message.error(error.message || '操作失败,请稍后重试'); + return false; + } + }, [dispatch, watchlistSet]); + + /** + * 批量添加到自选股 + * @param {Array<{code: string, name: string}>} stocks - 股票列表 + * @returns {Promise} 成功添加的数量 + */ + const batchAddToWatchlist = useCallback(async (stocks) => { + logger.debug('useWatchlist', '批量添加自选股', { + count: stocks.length + }); + + let successCount = 0; + const promises = stocks.map(async ({ code, name }) => { + if (!watchlistSet.has(code)) { + try { + await dispatch(toggleWatchlistAction({ + stockCode: code, + stockName: name, + isInWatchlist: false + })).unwrap(); + successCount++; + } catch (error) { + logger.error('useWatchlist', '添加失败', error, { code, name }); + } + } + }); + + await Promise.all(promises); + + if (successCount > 0) { + message.success(`成功添加 ${successCount} 只股票到自选股`); + } + + return successCount; + }, [dispatch, watchlistSet]); + + /** + * 刷新自选股列表 + */ + const refresh = useCallback(() => { + logger.debug('useWatchlist', '刷新自选股列表'); + dispatch(loadWatchlist()); + }, [dispatch]); + + return { + // 数据 + watchlist: watchlistArray, + watchlistSet, + loading, + + // 查询方法 + isInWatchlist, + + // 操作方法 + toggleWatchlist, + batchAddToWatchlist, + refresh + }; +};