feat: 接入真实数据

This commit is contained in:
zdl
2025-11-03 10:06:48 +08:00
parent 7ae4bc418f
commit befa68cc51
2 changed files with 76 additions and 71 deletions

View File

@@ -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;

View File

@@ -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]);
// 加载动态新闻数据
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';
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);
useEffect(() => {
const interval = setInterval(() => {
dispatch(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(() => {