feat: 接入真实数据
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
Reference in New Issue
Block a user