From befa68cc517dfd93c6fdc9b93dce71a10aea9050 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Mon, 3 Nov 2025 10:06:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8E=A5=E5=85=A5=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/slices/communityDataSlice.js | 61 ++++++++++++++++-- src/views/Community/index.js | 86 ++++++-------------------- 2 files changed, 76 insertions(+), 71 deletions(-) diff --git a/src/store/slices/communityDataSlice.js b/src/store/slices/communityDataSlice.js index 3e5a87e9..b8f8524b 100644 --- a/src/store/slices/communityDataSlice.js +++ b/src/store/slices/communityDataSlice.js @@ -156,6 +156,38 @@ export const fetchHotEvents = createAsyncThunk( } ); +/** + * 获取动态新闻(无缓存,每次都发起请求) + * 用于 DynamicNewsCard 组件,需要保持实时性 + */ +export const fetchDynamicNews = createAsyncThunk( + 'communityData/fetchDynamicNews', + async (_, { rejectWithValue }) => { + try { + logger.debug('CommunityData', '开始获取动态新闻'); + const response = await eventService.getEvents({ + page: 1, + per_page: 5, + sort: 'new' + }); + + if (response.success && response.data?.events) { + logger.info('CommunityData', '动态新闻加载成功', { + count: response.data.events.length, + total: response.data.pagination?.total || 0 + }); + return response.data.events; + } + + logger.warn('CommunityData', '动态新闻返回数据为空', response); + return []; + } catch (error) { + logger.error('CommunityData', '获取动态新闻失败', error); + return rejectWithValue(error.message || '获取动态新闻失败'); + } + } +); + // ==================== Slice 定义 ==================== const communityDataSlice = createSlice({ @@ -164,29 +196,34 @@ const communityDataSlice = createSlice({ // 数据 popularKeywords: [], hotEvents: [], + dynamicNews: [], // 动态新闻(无缓存) // 加载状态 loading: { popularKeywords: false, - hotEvents: false + hotEvents: false, + dynamicNews: false }, // 错误信息 error: { popularKeywords: null, - hotEvents: null + hotEvents: null, + dynamicNews: null }, // 最后更新时间 lastUpdated: { popularKeywords: null, - hotEvents: null + hotEvents: null, + dynamicNews: null } }, reducers: { /** * 清除所有缓存(Redux + localStorage) + * 注意:dynamicNews 不使用 localStorage 缓存 */ clearCache: (state) => { // 清除 localStorage @@ -195,15 +232,17 @@ const communityDataSlice = createSlice({ // 清除 Redux 状态 state.popularKeywords = []; state.hotEvents = []; + state.dynamicNews = []; // 动态新闻也清除 state.lastUpdated.popularKeywords = null; state.lastUpdated.hotEvents = null; + state.lastUpdated.dynamicNews = null; logger.info('CommunityData', '所有缓存已清除'); }, /** * 清除指定类型的缓存 - * @param {string} payload - 缓存类型 ('popularKeywords' | 'hotEvents') + * @param {string} payload - 缓存类型 ('popularKeywords' | 'hotEvents' | 'dynamicNews') */ clearSpecificCache: (state, action) => { const type = action.payload; @@ -218,6 +257,11 @@ const communityDataSlice = createSlice({ state.hotEvents = []; state.lastUpdated.hotEvents = null; logger.info('CommunityData', '热点事件缓存已清除'); + } else if (type === 'dynamicNews') { + // dynamicNews 不使用 localStorage,只清除 Redux state + state.dynamicNews = []; + state.lastUpdated.dynamicNews = null; + logger.info('CommunityData', '动态新闻数据已清除'); } }, @@ -235,6 +279,7 @@ const communityDataSlice = createSlice({ // 使用工厂函数创建 reducers,消除重复代码 createDataReducers(builder, fetchPopularKeywords, 'popularKeywords'); createDataReducers(builder, fetchHotEvents, 'hotEvents'); + createDataReducers(builder, fetchDynamicNews, 'dynamicNews'); } }); @@ -245,6 +290,7 @@ export const { clearCache, clearSpecificCache, preloadData } = communityDataSlic // 基础选择器(Selectors) export const selectPopularKeywords = (state) => state.communityData.popularKeywords; export const selectHotEvents = (state) => state.communityData.hotEvents; +export const selectDynamicNews = (state) => state.communityData.dynamicNews; export const selectLoading = (state) => state.communityData.loading; export const selectError = (state) => state.communityData.error; export const selectLastUpdated = (state) => state.communityData.lastUpdated; @@ -264,6 +310,13 @@ export const selectHotEventsWithLoading = (state) => ({ lastUpdated: state.communityData.lastUpdated.hotEvents }); +export const selectDynamicNewsWithLoading = (state) => ({ + data: state.communityData.dynamicNews, + loading: state.communityData.loading.dynamicNews, + error: state.communityData.error.dynamicNews, + lastUpdated: state.communityData.lastUpdated.dynamicNews +}); + // 工具函数:检查数据是否需要刷新(超过指定时间) export const shouldRefresh = (lastUpdated, thresholdMinutes = 30) => { if (!lastUpdated) return true; diff --git a/src/views/Community/index.js b/src/views/Community/index.js index e88cb2b1..1f92c805 100644 --- a/src/views/Community/index.js +++ b/src/views/Community/index.js @@ -2,7 +2,12 @@ import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { useSelector, useDispatch } from 'react-redux'; -import { fetchPopularKeywords, fetchHotEvents } from '../../store/slices/communityDataSlice'; +import { + fetchPopularKeywords, + fetchHotEvents, + fetchDynamicNews, + selectDynamicNewsWithLoading +} from '../../store/slices/communityDataSlice'; import { Box, Container, @@ -42,6 +47,11 @@ const Community = () => { // Redux状态 const { popularKeywords, hotEvents } = useSelector(state => state.communityData); + const { + data: dynamicNewsEvents, + loading: dynamicNewsLoading, + error: dynamicNewsError + } = useSelector(selectDynamicNewsWithLoading); // Chakra UI hooks const bgColor = useColorModeValue('gray.50', 'gray.900'); @@ -57,10 +67,6 @@ const Community = () => { const [selectedEvent, setSelectedEvent] = useState(null); const [selectedEventForStock, setSelectedEventForStock] = useState(null); - // 动态新闻数据状态 - const [dynamicNewsEvents, setDynamicNewsEvents] = useState([]); - const [dynamicNewsLoading, setDynamicNewsLoading] = useState(true); - // 🎯 初始化Community埋点Hook const communityEvents = useCommunityEvents({ navigate }); @@ -88,75 +94,21 @@ const Community = () => { }; }, [events]); - // 加载热门关键词和热点事件(使用Redux,内部有缓存判断) + // 加载热门关键词、热点事件和动态新闻(使用Redux) useEffect(() => { dispatch(fetchPopularKeywords()); dispatch(fetchHotEvents()); + dispatch(fetchDynamicNews()); }, [dispatch]); - // 加载动态新闻数据 + // 每5分钟刷新一次动态新闻 useEffect(() => { - const fetchDynamicNews = async () => { - setDynamicNewsLoading(true); - try { - // 检查是否使用 mock 模式 - // 开发阶段默认使用 mock 数据 - const useMock = true; // TODO: 生产环境改为环境变量控制 - // const useMock = process.env.REACT_APP_USE_MOCK === 'true' || - // localStorage.getItem('use_mock_data') === 'true'; + const interval = setInterval(() => { + dispatch(fetchDynamicNews()); + }, 5 * 60 * 1000); - if (useMock) { - // 使用 mock 数据 - const { generateMockEvents } = await import('../../mocks/data/events'); - const mockData = generateMockEvents({ page: 1, per_page: 30 }); - - // 调试:检查第一个事件的 related_stocks 和 historical_events 数据 - if (mockData.events[0]) { - console.log('Mock 数据第一个事件的股票:', mockData.events[0].related_stocks); - console.log('Mock 数据第一个事件的历史事件:', mockData.events[0].historical_events); - } - - setDynamicNewsEvents(mockData.events); - logger.info('Community', '动态新闻(Mock)加载成功', { - count: mockData.events.length, - mode: 'mock', - firstEventStocks: mockData.events[0]?.related_stocks?.length || 0 - }); - } else { - // 使用真实 API - const timeRange = getCurrentTradingTimeRange(); - const response = await fetch( - `/api/events/dynamic-news?start_time=${timeRange.startTime.toISOString()}&end_time=${timeRange.endTime.toISOString()}&count=30`, - { credentials: 'include' } - ); - const data = await response.json(); - - if (data.success && data.data) { - setDynamicNewsEvents(data.data); - logger.info('Community', '动态新闻加载成功', { - count: data.data.length, - timeRange: timeRange.description, - mode: 'api' - }); - } else { - logger.warn('Community', '动态新闻加载失败', data); - setDynamicNewsEvents([]); - } - } - } catch (error) { - logger.error('Community', '动态新闻加载异常', error); - setDynamicNewsEvents([]); - } finally { - setDynamicNewsLoading(false); - } - }; - - fetchDynamicNews(); - - // 每5分钟刷新一次动态新闻 - const interval = setInterval(fetchDynamicNews, 5 * 60 * 1000); return () => clearInterval(interval); - }, []); + }, [dispatch]); // 🎯 PostHog 追踪:页面浏览 // useEffect(() => { @@ -178,7 +130,7 @@ const Community = () => { industryFilter: filters.industry_code, }); } - }, [events, loading, pagination, filters, communityEvents]); + }, [events, loading, pagination, filters]); // ⚡ 首次访问社区时,延迟显示权限引导 useEffect(() => {