diff --git a/src/views/Company/hooks/useCompanyData.ts b/src/views/Company/hooks/useCompanyData.ts index b507a7d6..23db1953 100644 --- a/src/views/Company/hooks/useCompanyData.ts +++ b/src/views/Company/hooks/useCompanyData.ts @@ -3,20 +3,39 @@ * - 使用 axios 请求 * - 懒加载策略 * - 自动取消请求 + * - 自选股状态与 Redux 全局状态同步 */ -import { useState, useEffect, useCallback, useRef } from 'react'; +import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useToast } from '@chakra-ui/react'; +import { useSelector, useDispatch } from 'react-redux'; import axios from '@utils/axiosConfig'; import { logger } from '@utils/logger'; import { useAuth } from '@contexts/AuthContext'; +import { toggleWatchlist as reduxToggleWatchlist, loadWatchlist } from '@store/slices/stockSlice'; import type { StockInfo, - WatchlistItem, UseCompanyDataReturn, ApiResponse, } from '../types'; +// Store 类型(因为 store 是 JS 文件,这里内联定义) +interface WatchlistItem { + stock_code: string; + stock_name: string; +} + +interface StockState { + watchlist: WatchlistItem[]; + loading: { + watchlist: boolean; + }; +} + +interface RootState { + stock: StockState; +} + interface UseCompanyDataOptions { stockCode: string; /** 是否自动加载股票信息 */ @@ -27,17 +46,28 @@ interface UseCompanyDataOptions { /** * Company 页面数据管理 Hook + * + * 自选股状态现在从 Redux 全局状态读取,确保与导航栏等其他组件同步 */ export const useCompanyData = ({ stockCode, autoLoadStockInfo = true, autoLoadWatchlist = true, }: UseCompanyDataOptions): UseCompanyDataReturn => { - // 状态 + // 本地状态(仅股票信息) const [stockInfo, setStockInfo] = useState(null); const [stockInfoLoading, setStockInfoLoading] = useState(false); - const [isInWatchlist, setIsInWatchlist] = useState(false); - const [watchlistLoading, setWatchlistLoading] = useState(false); + + // Redux 状态(自选股) + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const dispatch = useDispatch(); + const watchlist = useSelector((state: RootState) => state.stock.watchlist); + const watchlistLoading = useSelector((state: RootState) => state.stock.loading.watchlist); + + // 从 Redux watchlist 中派生当前股票的自选状态 + const isInWatchlist = useMemo(() => { + return watchlist.some((item) => item.stock_code === stockCode); + }, [watchlist, stockCode]); // Hooks const toast = useToast(); @@ -76,47 +106,7 @@ export const useCompanyData = ({ }, [stockCode]); /** - * 加载自选股状态(优化:只检查单个股票,避免加载整个列表) - */ - const loadWatchlistStatus = useCallback(async () => { - if (!isAuthenticated || !stockCode) { - setIsInWatchlist(false); - return; - } - - try { - const { data } = await axios.get>( - `/api/account/watchlist/check/${stockCode}` - ); - - if (data.success && data.data) { - setIsInWatchlist(data.data.is_in_watchlist); - } else { - setIsInWatchlist(false); - } - } catch (error: any) { - // 接口不存在时降级到原方案 - if (error.response?.status === 404) { - try { - const { data: listData } = await axios.get>( - '/api/account/watchlist' - ); - if (listData.success && Array.isArray(listData.data)) { - const codes = new Set(listData.data.map((item) => item.stock_code)); - setIsInWatchlist(codes.has(stockCode)); - } - } catch { - setIsInWatchlist(false); - } - } else { - logger.error('useCompanyData', 'loadWatchlistStatus', error); - setIsInWatchlist(false); - } - } - }, [stockCode, isAuthenticated]); - - /** - * 切换自选股状态 + * 切换自选股状态(使用 Redux action,自动同步全局状态) */ const toggleWatchlist = useCallback(async () => { if (!stockCode) { @@ -129,27 +119,31 @@ export const useCompanyData = ({ return; } - setWatchlistLoading(true); - try { - if (isInWatchlist) { - // 移除自选 - await axios.delete(`/api/account/watchlist/${stockCode}`); - setIsInWatchlist(false); - toast({ title: '已从自选移除', status: 'info', duration: 1500 }); - } else { - // 添加自选 - await axios.post('/api/account/watchlist', { stock_code: stockCode }); - setIsInWatchlist(true); - toast({ title: '已加入自选', status: 'success', duration: 1500 }); + // 使用 Redux action,状态会自动同步到全局 + // @ts-expect-error stockSlice 是 JS 文件,TypeScript 无法推断 thunk 参数类型 + const result = await dispatch(reduxToggleWatchlist({ + stockCode, + stockName: stockInfo?.stock_name || '', + isInWatchlist, + })); + + // 检查是否成功(rejected action 会有 error 属性) + if (result.error) { + throw new Error(result.error.message || '操作失败'); } + + // 显示提示 + toast({ + title: isInWatchlist ? '已从自选移除' : '已加入自选', + status: isInWatchlist ? 'info' : 'success', + duration: 1500, + }); } catch (error: any) { logger.error('useCompanyData', 'toggleWatchlist', error, { stockCode }); toast({ title: '操作失败,请稍后重试', status: 'error', duration: 2000 }); - } finally { - setWatchlistLoading(false); } - }, [stockCode, isAuthenticated, isInWatchlist, toast]); + }, [stockCode, stockInfo?.stock_name, isAuthenticated, isInWatchlist, toast, dispatch]); /** * 刷新股票信息 @@ -169,12 +163,13 @@ export const useCompanyData = ({ }; }, [autoLoadStockInfo, loadStockInfo]); - // 自动加载自选股状态 + // 自动加载自选股列表(从 Redux) useEffect(() => { - if (autoLoadWatchlist) { - loadWatchlistStatus(); + if (autoLoadWatchlist && isAuthenticated && watchlist.length === 0) { + // 只有当 Redux 中没有数据时才加载 + dispatch(loadWatchlist()); } - }, [autoLoadWatchlist, loadWatchlistStatus]); + }, [autoLoadWatchlist, isAuthenticated, watchlist.length, dispatch]); return { stockInfo,