diff --git a/src/store/api/eventsApi.js b/src/store/api/eventsApi.js new file mode 100644 index 00000000..14fc016b --- /dev/null +++ b/src/store/api/eventsApi.js @@ -0,0 +1,144 @@ +// src/store/api/eventsApi.js +// RTK Query API for Events - 事件数据获取 API + +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; +import { logger } from '../../utils/logger'; + +/** + * Events API Slice - 使用 RTK Query 管理事件数据 + * + * 特性: + * - ✅ 自动缓存管理(按 queryKey 缓存) + * - ✅ 自动去重请求 + * - ✅ 返回第一页刷新数据(invalidateTags) + * - ✅ 预加载支持(prefetch) + * - ✅ 统一在 Redux DevTools 中调试 + * - ✅ 无需额外 Provider + */ +export const eventsApi = createApi({ + reducerPath: 'eventsApi', + + // 基础查询配置 + baseQuery: fetchBaseQuery({ + baseUrl: '/api', + prepareHeaders: (headers) => { + // 可以在这里添加认证 token + // const token = localStorage.getItem('token'); + // if (token) { + // headers.set('Authorization', `Bearer ${token}`); + // } + return headers; + }, + }), + + // 标签类型定义(用于缓存失效) + tagTypes: ['Events'], + + // API 端点定义 + endpoints: (builder) => ({ + /** + * 获取分页事件列表 + * + * @param {Object} params + * @param {number} params.page - 页码 + * @param {number} params.per_page - 每页数量 + * @param {string} params.mode - 显示模式(vertical / four-row) + * @param {string} params.sort - 排序方式 + * @param {string} params.importance - 重要性筛选 + * @param {string} params.q - 搜索关键词 + * @param {string} params.date_range - 日期范围 + * @param {string} params.industry_code - 行业代码 + * + * @returns {Object} { events: Array, pagination: Object } + */ + getEvents: builder.query({ + query: ({ page = 1, per_page = 5, mode = 'vertical', ...filters }) => { + logger.debug('eventsApi', 'getEvents 请求', { + page, + per_page, + mode, + filters, + }); + + return { + url: '/events', + params: { + page, + per_page, + ...filters, + }, + }; + }, + + // 🔥 缓存标签:用于缓存失效 + providesTags: (result, error, { page, mode }) => { + if (error) return []; + + return [ + { type: 'Events', id: `${mode}-${page}` }, // 具体页面的标签 + { type: 'Events', id: `${mode}-LIST` }, // 模式的总标签 + ]; + }, + + // 转换响应数据 + transformResponse: (response) => { + logger.debug('eventsApi', 'getEvents 响应', { + eventsCount: response.data?.events?.length, + total: response.data?.pagination?.total, + }); + + if (!response.success || !response.data?.events) { + throw new Error('数据格式错误'); + } + + return { + events: response.data.events, + pagination: response.data.pagination || {}, + }; + }, + + // 错误处理 + transformErrorResponse: (response) => { + logger.error('eventsApi', 'getEvents 失败', new Error(response.status)); + return { + status: response.status, + message: response.data?.message || '获取事件数据失败', + }; + }, + + // 🔥 keepUnusedDataFor: 缓存保留时间(秒) + keepUnusedDataFor: 600, // 10分钟 + + // 🔥 合并查询结果(用于无限滚动) + // serializeQueryArgs: ({ endpointName, queryArgs }) => { + // const { mode, ...filters } = queryArgs; + // return `${endpointName}(${mode})`; + // }, + // merge: (currentCache, newItems) => { + // currentCache.events.push(...newItems.events); + // }, + // forceRefetch: ({ currentArg, previousArg }) => { + // return currentArg.page !== previousArg?.page; + // }, + }), + + /** + * 预加载下一页(性能优化) + * + * 用法: + * dispatch(eventsApi.util.prefetch('getEvents', { page: 2, ... })) + */ + }), +}); + +// 导出自动生成的 Hooks +export const { + useGetEventsQuery, + useLazyGetEventsQuery, // 手动触发的版本 + usePrefetch, // 预加载 Hook +} = eventsApi; + +// 导出工具方法 +export const { + util: { invalidateTags, prefetch }, +} = eventsApi; diff --git a/src/store/index.js b/src/store/index.js index 1b08c189..07f99bd3 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -7,6 +7,7 @@ import stockReducer from './slices/stockSlice'; import authModalReducer from './slices/authModalSlice'; import subscriptionReducer from './slices/subscriptionSlice'; import posthogMiddleware from './middleware/posthogMiddleware'; +import { eventsApi } from './api/eventsApi'; // ✅ RTK Query API export const store = configureStore({ reducer: { @@ -16,6 +17,7 @@ export const store = configureStore({ stock: stockReducer, // ✅ 股票和事件数据管理 authModal: authModalReducer, // ✅ 认证弹窗状态管理 subscription: subscriptionReducer, // ✅ 订阅信息状态管理 + [eventsApi.reducerPath]: eventsApi.reducer, // ✅ RTK Query 事件 API }, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ @@ -29,7 +31,9 @@ export const store = configureStore({ 'stock/fetchStockQuotes/fulfilled', ], }, - }).concat(posthogMiddleware), // ✅ PostHog 自动追踪中间件 + }) + .concat(posthogMiddleware) // ✅ PostHog 自动追踪中间件 + .concat(eventsApi.middleware), // ✅ RTK Query 中间件(自动缓存、去重、重试) }); export default store;