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 定义 ====================
|
// ==================== Slice 定义 ====================
|
||||||
|
|
||||||
const communityDataSlice = createSlice({
|
const communityDataSlice = createSlice({
|
||||||
@@ -164,29 +196,34 @@ const communityDataSlice = createSlice({
|
|||||||
// 数据
|
// 数据
|
||||||
popularKeywords: [],
|
popularKeywords: [],
|
||||||
hotEvents: [],
|
hotEvents: [],
|
||||||
|
dynamicNews: [], // 动态新闻(无缓存)
|
||||||
|
|
||||||
// 加载状态
|
// 加载状态
|
||||||
loading: {
|
loading: {
|
||||||
popularKeywords: false,
|
popularKeywords: false,
|
||||||
hotEvents: false
|
hotEvents: false,
|
||||||
|
dynamicNews: false
|
||||||
},
|
},
|
||||||
|
|
||||||
// 错误信息
|
// 错误信息
|
||||||
error: {
|
error: {
|
||||||
popularKeywords: null,
|
popularKeywords: null,
|
||||||
hotEvents: null
|
hotEvents: null,
|
||||||
|
dynamicNews: null
|
||||||
},
|
},
|
||||||
|
|
||||||
// 最后更新时间
|
// 最后更新时间
|
||||||
lastUpdated: {
|
lastUpdated: {
|
||||||
popularKeywords: null,
|
popularKeywords: null,
|
||||||
hotEvents: null
|
hotEvents: null,
|
||||||
|
dynamicNews: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
reducers: {
|
reducers: {
|
||||||
/**
|
/**
|
||||||
* 清除所有缓存(Redux + localStorage)
|
* 清除所有缓存(Redux + localStorage)
|
||||||
|
* 注意:dynamicNews 不使用 localStorage 缓存
|
||||||
*/
|
*/
|
||||||
clearCache: (state) => {
|
clearCache: (state) => {
|
||||||
// 清除 localStorage
|
// 清除 localStorage
|
||||||
@@ -195,15 +232,17 @@ const communityDataSlice = createSlice({
|
|||||||
// 清除 Redux 状态
|
// 清除 Redux 状态
|
||||||
state.popularKeywords = [];
|
state.popularKeywords = [];
|
||||||
state.hotEvents = [];
|
state.hotEvents = [];
|
||||||
|
state.dynamicNews = []; // 动态新闻也清除
|
||||||
state.lastUpdated.popularKeywords = null;
|
state.lastUpdated.popularKeywords = null;
|
||||||
state.lastUpdated.hotEvents = null;
|
state.lastUpdated.hotEvents = null;
|
||||||
|
state.lastUpdated.dynamicNews = null;
|
||||||
|
|
||||||
logger.info('CommunityData', '所有缓存已清除');
|
logger.info('CommunityData', '所有缓存已清除');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 清除指定类型的缓存
|
* 清除指定类型的缓存
|
||||||
* @param {string} payload - 缓存类型 ('popularKeywords' | 'hotEvents')
|
* @param {string} payload - 缓存类型 ('popularKeywords' | 'hotEvents' | 'dynamicNews')
|
||||||
*/
|
*/
|
||||||
clearSpecificCache: (state, action) => {
|
clearSpecificCache: (state, action) => {
|
||||||
const type = action.payload;
|
const type = action.payload;
|
||||||
@@ -218,6 +257,11 @@ const communityDataSlice = createSlice({
|
|||||||
state.hotEvents = [];
|
state.hotEvents = [];
|
||||||
state.lastUpdated.hotEvents = null;
|
state.lastUpdated.hotEvents = null;
|
||||||
logger.info('CommunityData', '热点事件缓存已清除');
|
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,消除重复代码
|
// 使用工厂函数创建 reducers,消除重复代码
|
||||||
createDataReducers(builder, fetchPopularKeywords, 'popularKeywords');
|
createDataReducers(builder, fetchPopularKeywords, 'popularKeywords');
|
||||||
createDataReducers(builder, fetchHotEvents, 'hotEvents');
|
createDataReducers(builder, fetchHotEvents, 'hotEvents');
|
||||||
|
createDataReducers(builder, fetchDynamicNews, 'dynamicNews');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -245,6 +290,7 @@ export const { clearCache, clearSpecificCache, preloadData } = communityDataSlic
|
|||||||
// 基础选择器(Selectors)
|
// 基础选择器(Selectors)
|
||||||
export const selectPopularKeywords = (state) => state.communityData.popularKeywords;
|
export const selectPopularKeywords = (state) => state.communityData.popularKeywords;
|
||||||
export const selectHotEvents = (state) => state.communityData.hotEvents;
|
export const selectHotEvents = (state) => state.communityData.hotEvents;
|
||||||
|
export const selectDynamicNews = (state) => state.communityData.dynamicNews;
|
||||||
export const selectLoading = (state) => state.communityData.loading;
|
export const selectLoading = (state) => state.communityData.loading;
|
||||||
export const selectError = (state) => state.communityData.error;
|
export const selectError = (state) => state.communityData.error;
|
||||||
export const selectLastUpdated = (state) => state.communityData.lastUpdated;
|
export const selectLastUpdated = (state) => state.communityData.lastUpdated;
|
||||||
@@ -264,6 +310,13 @@ export const selectHotEventsWithLoading = (state) => ({
|
|||||||
lastUpdated: state.communityData.lastUpdated.hotEvents
|
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) => {
|
export const shouldRefresh = (lastUpdated, thresholdMinutes = 30) => {
|
||||||
if (!lastUpdated) return true;
|
if (!lastUpdated) return true;
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useSelector, useDispatch } from 'react-redux';
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
import { fetchPopularKeywords, fetchHotEvents } from '../../store/slices/communityDataSlice';
|
import {
|
||||||
|
fetchPopularKeywords,
|
||||||
|
fetchHotEvents,
|
||||||
|
fetchDynamicNews,
|
||||||
|
selectDynamicNewsWithLoading
|
||||||
|
} from '../../store/slices/communityDataSlice';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Container,
|
Container,
|
||||||
@@ -42,6 +47,11 @@ const Community = () => {
|
|||||||
|
|
||||||
// Redux状态
|
// Redux状态
|
||||||
const { popularKeywords, hotEvents } = useSelector(state => state.communityData);
|
const { popularKeywords, hotEvents } = useSelector(state => state.communityData);
|
||||||
|
const {
|
||||||
|
data: dynamicNewsEvents,
|
||||||
|
loading: dynamicNewsLoading,
|
||||||
|
error: dynamicNewsError
|
||||||
|
} = useSelector(selectDynamicNewsWithLoading);
|
||||||
|
|
||||||
// Chakra UI hooks
|
// Chakra UI hooks
|
||||||
const bgColor = useColorModeValue('gray.50', 'gray.900');
|
const bgColor = useColorModeValue('gray.50', 'gray.900');
|
||||||
@@ -57,10 +67,6 @@ const Community = () => {
|
|||||||
const [selectedEvent, setSelectedEvent] = useState(null);
|
const [selectedEvent, setSelectedEvent] = useState(null);
|
||||||
const [selectedEventForStock, setSelectedEventForStock] = useState(null);
|
const [selectedEventForStock, setSelectedEventForStock] = useState(null);
|
||||||
|
|
||||||
// 动态新闻数据状态
|
|
||||||
const [dynamicNewsEvents, setDynamicNewsEvents] = useState([]);
|
|
||||||
const [dynamicNewsLoading, setDynamicNewsLoading] = useState(true);
|
|
||||||
|
|
||||||
// 🎯 初始化Community埋点Hook
|
// 🎯 初始化Community埋点Hook
|
||||||
const communityEvents = useCommunityEvents({ navigate });
|
const communityEvents = useCommunityEvents({ navigate });
|
||||||
|
|
||||||
@@ -88,75 +94,21 @@ const Community = () => {
|
|||||||
};
|
};
|
||||||
}, [events]);
|
}, [events]);
|
||||||
|
|
||||||
// 加载热门关键词和热点事件(使用Redux,内部有缓存判断)
|
// 加载热门关键词、热点事件和动态新闻(使用Redux)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchPopularKeywords());
|
dispatch(fetchPopularKeywords());
|
||||||
dispatch(fetchHotEvents());
|
dispatch(fetchHotEvents());
|
||||||
|
dispatch(fetchDynamicNews());
|
||||||
}, [dispatch]);
|
}, [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分钟刷新一次动态新闻
|
// 每5分钟刷新一次动态新闻
|
||||||
const interval = setInterval(fetchDynamicNews, 5 * 60 * 1000);
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
dispatch(fetchDynamicNews());
|
||||||
|
}, 5 * 60 * 1000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, [dispatch]);
|
||||||
|
|
||||||
// 🎯 PostHog 追踪:页面浏览
|
// 🎯 PostHog 追踪:页面浏览
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
@@ -178,7 +130,7 @@ const Community = () => {
|
|||||||
industryFilter: filters.industry_code,
|
industryFilter: filters.industry_code,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [events, loading, pagination, filters, communityEvents]);
|
}, [events, loading, pagination, filters]);
|
||||||
|
|
||||||
// ⚡ 首次访问社区时,延迟显示权限引导
|
// ⚡ 首次访问社区时,延迟显示权限引导
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user