diff --git a/src/store/slices/stockSlice.js b/src/store/slices/stockSlice.js index b21e10ce..53ab942a 100644 --- a/src/store/slices/stockSlice.js +++ b/src/store/slices/stockSlice.js @@ -4,6 +4,56 @@ import { eventService, stockService } from '../../services/eventService'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; +// ==================== Watchlist 缓存配置 ==================== +const WATCHLIST_CACHE_KEY = 'watchlist_cache'; +const WATCHLIST_CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 7天 + +/** + * 从 localStorage 读取自选股缓存 + */ +const loadWatchlistFromCache = () => { + try { + const cached = localStorage.getItem(WATCHLIST_CACHE_KEY); + if (!cached) return null; + + const { data, timestamp } = JSON.parse(cached); + const now = Date.now(); + + // 检查缓存是否过期(7天) + if (now - timestamp > WATCHLIST_CACHE_DURATION) { + localStorage.removeItem(WATCHLIST_CACHE_KEY); + logger.debug('stockSlice', '自选股缓存已过期'); + return null; + } + + logger.debug('stockSlice', '自选股 localStorage 缓存命中', { + count: data?.length || 0, + age: Math.round((now - timestamp) / 1000 / 60) + '分钟前' + }); + return data; + } catch (error) { + logger.error('stockSlice', 'loadWatchlistFromCache', error); + return null; + } +}; + +/** + * 保存自选股到 localStorage + */ +const saveWatchlistToCache = (data) => { + try { + localStorage.setItem(WATCHLIST_CACHE_KEY, JSON.stringify({ + data, + timestamp: Date.now() + })); + logger.debug('stockSlice', '自选股已缓存到 localStorage', { + count: data?.length || 0 + }); + } catch (error) { + logger.error('stockSlice', 'saveWatchlistToCache', error); + } +}; + // ==================== Async Thunks ==================== /** @@ -153,13 +203,28 @@ export const fetchExpectationScore = createAsyncThunk( /** * 加载用户自选股列表(包含完整信息) + * 缓存策略:Redux 内存缓存 → localStorage 持久缓存(7天) → API 请求 */ export const loadWatchlist = createAsyncThunk( 'stock/loadWatchlist', - async () => { + async (_, { getState }) => { logger.debug('stockSlice', 'loadWatchlist'); try { + // 1. 先检查 Redux 内存缓存 + const reduxCached = getState().stock.watchlist; + if (reduxCached && reduxCached.length > 0) { + logger.debug('stockSlice', 'Redux watchlist 缓存命中', { count: reduxCached.length }); + return reduxCached; + } + + // 2. 再检查 localStorage 持久缓存(7天有效期) + const localCached = loadWatchlistFromCache(); + if (localCached && localCached.length > 0) { + return localCached; + } + + // 3. 缓存无效,调用 API const apiBase = getApiBase(); const response = await fetch(`${apiBase}/api/account/watchlist`, { credentials: 'include' @@ -172,6 +237,10 @@ export const loadWatchlist = createAsyncThunk( stock_code: item.stock_code, stock_name: item.stock_name, })); + + // 保存到 localStorage 缓存 + saveWatchlistToCache(watchlistData); + logger.debug('stockSlice', '自选股列表加载成功', { count: watchlistData.length }); @@ -490,9 +559,10 @@ const stockSlice = createSlice({ state.watchlist = state.watchlist.filter(item => item.stock_code !== stockCode); } }) - // fulfilled: 乐观更新模式下状态已在 pending 更新,这里无需操作 - .addCase(toggleWatchlist.fulfilled, () => { - // 状态已在 pending 时更新 + // fulfilled: 同步更新 localStorage 缓存 + .addCase(toggleWatchlist.fulfilled, (state) => { + // 状态已在 pending 时更新,这里同步到 localStorage + saveWatchlistToCache(state.watchlist); }); } });