Files
vf_react/src/contexts/GlobalSidebarContext.js
zdl 06475f82a4 refactor(events): 关注事件数据源统一到 Redux
- useFollowingEvents: 改用 Redux selector 获取关注事件
- GlobalSidebarContext: 移除本地 followingEvents 状态,使用 Redux
- 侧边栏和导航栏共享同一数据源,保持状态同步

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-23 20:17:57 +08:00

217 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* GlobalSidebarContext - 全局右侧工具栏状态管理
*
* 管理侧边栏的展开/收起状态和数据加载
* 自选股和关注事件数据都从 Redux 获取,与导航栏共用数据源
*/
import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useAuth } from './AuthContext';
import { logger } from '@/utils/logger';
import {
loadWatchlist,
loadWatchlistQuotes,
toggleWatchlist,
loadFollowingEvents,
loadEventComments,
toggleFollowEvent
} from '@/store/slices/stockSlice';
const GlobalSidebarContext = createContext(null);
/**
* GlobalSidebarProvider - 全局侧边栏 Provider
*/
export const GlobalSidebarProvider = ({ children }) => {
const { user } = useAuth();
const userId = user?.id;
const dispatch = useDispatch();
// 侧边栏展开/收起状态(默认折叠)
const [isOpen, setIsOpen] = useState(false);
// 从 Redux 获取自选股数据(与导航栏共用)
const watchlist = useSelector(state => state.stock.watchlist || []);
const watchlistQuotes = useSelector(state => state.stock.watchlistQuotes || []);
const watchlistLoading = useSelector(state => state.stock.loading?.watchlist);
const quotesLoading = useSelector(state => state.stock.loading?.watchlistQuotes);
// 将 watchlistQuotes 数组转换为 { stock_code: quote } 格式(兼容现有组件)
const realtimeQuotes = React.useMemo(() => {
const quotesMap = {};
watchlistQuotes.forEach(item => {
quotesMap[item.stock_code] = item;
});
return quotesMap;
}, [watchlistQuotes]);
// 从 Redux 获取关注事件数据(与导航栏共用)
const followingEvents = useSelector(state => state.stock.followingEvents || []);
const eventComments = useSelector(state => state.stock.eventComments || []);
const eventsLoading = useSelector(state => state.stock.loading?.followingEvents || false);
// 防止重复加载
const hasLoadedRef = useRef(false);
/**
* 切换侧边栏展开/收起
*/
const toggle = useCallback(() => {
setIsOpen(prev => !prev);
}, []);
/**
* 加载实时行情(通过 Redux
*/
const loadRealtimeQuotes = useCallback(() => {
if (!userId) return;
dispatch(loadWatchlistQuotes());
}, [userId, dispatch]);
/**
* 加载所有数据(自选股和关注事件都从 Redux 获取)
*/
const loadData = useCallback(() => {
if (!userId) return;
// 自选股通过 Redux 加载
dispatch(loadWatchlist());
dispatch(loadWatchlistQuotes());
// 关注事件和评论通过 Redux 加载
dispatch(loadFollowingEvents());
dispatch(loadEventComments());
}, [userId, dispatch]);
/**
* 刷新数据
*/
const refresh = useCallback(async () => {
await loadData();
}, [loadData]);
/**
* 取消关注股票(通过 Redux
*/
const unwatchStock = useCallback(async (stockCode) => {
if (!userId) return;
try {
// 找到股票名称
const stockItem = watchlist.find(s => s.stock_code === stockCode);
const stockName = stockItem?.stock_name || '';
// 通过 Redux action 移除(乐观更新)
await dispatch(toggleWatchlist({
stockCode,
stockName,
isInWatchlist: true // 表示当前在自选股中,需要移除
})).unwrap();
logger.debug('GlobalSidebar', 'unwatchStock 成功', { stockCode });
} catch (error) {
logger.error('GlobalSidebar', 'unwatchStock', error, { stockCode, userId });
}
}, [userId, dispatch, watchlist]);
/**
* 取消关注事件(通过 Redux
*/
const unfollowEvent = useCallback(async (eventId) => {
if (!userId) return;
try {
// 通过 Redux action 取消关注(乐观更新)
await dispatch(toggleFollowEvent({
eventId,
isFollowing: true // 表示当前已关注,需要取消
})).unwrap();
logger.debug('GlobalSidebar', 'unfollowEvent 成功', { eventId });
} catch (error) {
logger.error('GlobalSidebar', 'unfollowEvent', error, { eventId, userId });
// 失败时重新加载列表
dispatch(loadFollowingEvents());
}
}, [userId, dispatch]);
// 用户登录后加载数据
useEffect(() => {
if (user && !hasLoadedRef.current) {
console.log('[GlobalSidebar] 用户登录,加载数据');
hasLoadedRef.current = true;
loadData();
}
// 用户登出时重置(所有状态由 Redux 管理)
if (!user) {
hasLoadedRef.current = false;
}
}, [user, loadData]);
// 页面可见性变化时刷新数据
useEffect(() => {
const onVisibilityChange = () => {
if (document.visibilityState === 'visible' && user) {
console.log('[GlobalSidebar] 页面可见,刷新数据');
loadData();
}
};
document.addEventListener('visibilitychange', onVisibilityChange);
return () => document.removeEventListener('visibilitychange', onVisibilityChange);
}, [user, loadData]);
// 定时刷新实时行情(每分钟一次,两个面板共用)
useEffect(() => {
if (watchlist.length > 0 && userId) {
const interval = setInterval(() => {
console.log('[GlobalSidebar] 定时刷新行情');
dispatch(loadWatchlistQuotes());
}, 60000);
return () => clearInterval(interval);
}
}, [watchlist.length, userId, dispatch]);
const value = {
// 状态
isOpen,
toggle,
// 数据watchlist 和 realtimeQuotes 从 Redux 获取)
watchlist,
realtimeQuotes,
followingEvents,
eventComments,
// 加载状态
loading: watchlistLoading || eventsLoading,
quotesLoading,
// 方法
refresh,
loadRealtimeQuotes,
unwatchStock,
unfollowEvent,
};
return (
<GlobalSidebarContext.Provider value={value}>
{children}
</GlobalSidebarContext.Provider>
);
};
/**
* useGlobalSidebar - 获取全局侧边栏 Context
*/
export const useGlobalSidebar = () => {
const context = useContext(GlobalSidebarContext);
if (!context) {
throw new Error('useGlobalSidebar must be used within a GlobalSidebarProvider');
}
return context;
};
export default GlobalSidebarContext;