feat: 提交 Custom Hooks
This commit is contained in:
@@ -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
|
||||
};
|
||||
};
|
||||
@@ -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<boolean>} 操作是否成功
|
||||
*/
|
||||
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<number>} 成功添加的数量
|
||||
*/
|
||||
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
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user