// src/services/eventService.js import { logger } from '../utils/logger'; const apiRequest = async (url, options = {}) => { const method = options.method || 'GET'; const requestData = options.body ? JSON.parse(options.body) : null; logger.api.request(method, url, requestData); try { const response = await fetch(url, { ...options, headers: { 'Content-Type': 'application/json', ...options.headers, }, credentials: 'include', }); if (!response.ok) { const errorText = await response.text(); const error = new Error(`HTTP error! status: ${response.status}`); logger.api.error(method, url, error, { errorText, ...requestData }); throw error; } const data = await response.json(); logger.api.response(method, url, response.status, data); return data; } catch (error) { logger.api.error(method, url, error, requestData); throw error; } }; export const eventService = { getEvents: (params = {}) => { // Filter out null, undefined, and empty strings (but keep 0 and false) const cleanParams = Object.fromEntries( Object.entries(params).filter(([_, v]) => v !== null && v !== undefined && v !== '') ); const query = new URLSearchParams(cleanParams).toString(); return apiRequest(`/api/events?${query}`); }, getHotEvents: (params = {}) => { const query = new URLSearchParams(params).toString(); return apiRequest(`/api/events/hot?${query}`); }, getPopularKeywords: (limit = 20) => { return apiRequest(`/api/events/keywords/popular?limit=${limit}`); }, calendar: { /** * Fetches event counts for a given month, for display on the calendar grid. * @param {number} year - The year to fetch counts for. * @param {number} month - The month to fetch counts for (1-12). * @returns {Promise} A list of objects with date, count, and className. */ getEventCounts: (year, month) => { // Note: The backend route is `/api/v1/calendar/event-counts`, not `/api/calendar-event-counts` return apiRequest(`/api/v1/calendar/event-counts?year=${year}&month=${month}`); }, /** * Fetches a list of all events for a specific date. * @param {string} date - The date in 'YYYY-MM-DD' format. * @param {string} [type='all'] - The type of events to fetch ('event', 'data', or 'all'). * @returns {Promise} A list of event objects for the given date. */ getEventsForDate: (date, type = 'all') => { return apiRequest(`/api/v1/calendar/events?date=${date}&type=${type}`); }, /** * Fetches the full details of a single calendar event. * @param {number} eventId - The ID of the calendar event. * @returns {Promise} The detailed calendar event object. */ getEventDetail: (eventId) => { return apiRequest(`/api/v1/calendar/events/${eventId}`); }, /** * 切换未来事件的关注状态 * @param {number} eventId - 未来事件ID * @returns {Promise} 返回关注状态 */ toggleFollow: async (eventId) => { return await apiRequest(`/api/v1/calendar/events/${eventId}/follow`, { method: 'POST' }); }, /** * 获取用户关注的所有未来事件 * @returns {Promise} 返回关注的未来事件列表 */ getFollowingEvents: async () => { return await apiRequest(`/api/account/future-events/following`); }, }, getEventDetail: async (eventId) => { return await apiRequest(`/api/events/${eventId}`); }, getRelatedStocks: async (eventId) => { return await apiRequest(`/api/events/${eventId}/stocks`); }, addRelatedStock: async (eventId, stockData) => { return await apiRequest(`/api/events/${eventId}/stocks`, { method: 'POST', body: JSON.stringify(stockData), }); }, deleteRelatedStock: async (stockId) => { return await apiRequest(`/api/stocks/${stockId}`, { method: 'DELETE', }); }, getRelatedConcepts: async (eventId) => { return await apiRequest(`/api/events/${eventId}/concepts`); }, getHistoricalEvents: async (eventId) => { return await apiRequest(`/api/events/${eventId}/historical`); }, getExpectationScore: async (eventId) => { return await apiRequest(`/api/events/${eventId}/expectation-score`); }, getTransmissionChainAnalysis: async (eventId) => { // This API is optimized: it filters isolated nodes and provides a clean data structure. return await apiRequest(`/api/events/${eventId}/transmission`); }, getChainNodeDetail: async (eventId, nodeId) => { return await apiRequest(`/api/events/${eventId}/chain-node/${nodeId}`); }, getSankeyData: async (eventId) => { return await apiRequest(`/api/events/${eventId}/sankey-data`); }, getSankeyNodeDetail: async (eventId, nodeInfo) => { const params = new URLSearchParams({ node_name: nodeInfo.name, node_type: nodeInfo.type, }); if (nodeInfo.level !== undefined && nodeInfo.level !== null) { params.append('node_level', nodeInfo.level); } const url = `/api/events/${eventId}/sankey-data?${params.toString()}`; return await apiRequest(url); }, toggleFollow: async (eventId, isFollowing) => { return await apiRequest(`/api/events/${eventId}/follow`, { method: 'POST', body: JSON.stringify({ is_following: isFollowing }), }); }, // 帖子相关API getPosts: async (eventId, sortType = 'latest', page = 1, perPage = 20) => { try { return await apiRequest(`/api/events/${eventId}/posts?sort=${sortType}&page=${page}&per_page=${perPage}`); } catch (error) { logger.error('eventService', 'getPosts', error, { eventId, sortType, page }); return { success: false, data: [], pagination: {} }; } }, createPost: async (eventId, postData) => { try { return await apiRequest(`/api/events/${eventId}/posts`, { method: 'POST', body: JSON.stringify(postData) }); } catch (error) { logger.error('eventService', 'createPost', error, { eventId }); return { success: false, message: '创建帖子失败' }; } }, deletePost: async (postId) => { try { return await apiRequest(`/api/posts/${postId}`, { method: 'DELETE' }); } catch (error) { logger.error('eventService', 'deletePost', error, { postId }); return { success: false, message: '删除帖子失败' }; } }, likePost: async (postId) => { try { return await apiRequest(`/api/posts/${postId}/like`, { method: 'POST' }); } catch (error) { logger.error('eventService', 'likePost', error, { postId }); return { success: false, message: '点赞失败' }; } }, // 评论相关API getPostComments: async (postId, sortType = 'latest') => { try { return await apiRequest(`/api/posts/${postId}/comments?sort=${sortType}`); } catch (error) { logger.error('eventService', 'getPostComments', error, { postId, sortType }); return { success: false, data: [] }; } }, addPostComment: async (postId, commentData) => { try { return await apiRequest(`/api/posts/${postId}/comments`, { method: 'POST', body: JSON.stringify(commentData) }); } catch (error) { logger.error('eventService', 'addPostComment', error, { postId }); return { success: false, message: '添加评论失败' }; } }, deleteComment: async (commentId) => { try { return await apiRequest(`/api/comments/${commentId}`, { method: 'DELETE' }); } catch (error) { logger.error('eventService', 'deleteComment', error, { commentId }); return { success: false, message: '删除评论失败' }; } }, likeComment: async (commentId) => { try { return await apiRequest(`/api/comments/${commentId}/like`, { method: 'POST' }); } catch (error) { logger.error('eventService', 'likeComment', error, { commentId }); return { success: false, message: '点赞失败' }; } }, // 兼容旧版本的评论API getComments: async (eventId, sortType = 'latest') => { logger.warn('eventService', 'getComments 已废弃,请使用 getPosts'); try { return await apiRequest(`/api/events/${eventId}/posts?sort=${sortType}&page=1&per_page=20`); } catch (error) { logger.error('eventService', 'getComments', error, { eventId, sortType }); return { success: false, data: [], pagination: {} }; } }, addComment: async (eventId, commentData) => { logger.warn('eventService', 'addComment 已废弃,请使用 createPost'); try { return await apiRequest(`/api/events/${eventId}/posts`, { method: 'POST', body: JSON.stringify(commentData) }); } catch (error) { logger.error('eventService', 'addComment', error, { eventId }); return { success: false, message: '创建帖子失败' }; } }, }; export const stockService = { getQuotes: async (codes, eventTime = null) => { try { const requestBody = { codes: codes }; if (eventTime) { requestBody.event_time = eventTime; } logger.debug('stockService', '获取股票报价', requestBody); const response = await apiRequest(`/api/stock/quotes`, { method: 'POST', body: JSON.stringify(requestBody) }); logger.debug('stockService', '股票报价响应', { success: response.success, dataKeys: response.data ? Object.keys(response.data) : [] }); if (response.success && response.data) { return response.data; } else { logger.warn('stockService', '股票报价响应格式异常', response); return {}; } } catch (error) { logger.error('stockService', 'getQuotes', error, { codes, eventTime }); throw error; } }, getForecastReport: async (stockCode) => { return await apiRequest(`/api/stock/${stockCode}/forecast-report`); }, getKlineData: async (stockCode, chartType = 'timeline', eventTime = null) => { try { const params = new URLSearchParams(); params.append('type', chartType); if (eventTime) { params.append('event_time', eventTime); } const url = `/api/stock/${stockCode}/kline?${params.toString()}`; logger.debug('stockService', '获取K线数据', { stockCode, chartType, eventTime }); const response = await apiRequest(url); if (response.error) { logger.warn('stockService', 'K线数据响应包含错误', response.error); return response; } return response; } catch (error) { logger.error('stockService', 'getKlineData', error, { stockCode, chartType }); throw error; } }, getTransmissionChainAnalysis: async (eventId) => { return await apiRequest(`/api/events/${eventId}/transmission`); }, async getSankeyNodeDetail(eventId, nodeInfo) { try { const params = new URLSearchParams({ node_name: nodeInfo.name, node_type: nodeInfo.type, node_level: nodeInfo.level }); const url = `/api/events/${eventId}/sankey-node-detail?${params}`; return await apiRequest(url); } catch (error) { logger.error('stockService', 'getSankeyNodeDetail', error, { eventId, nodeInfo }); throw error; } }, getChainNodeDetail: async (eventId, nodeId) => { return await apiRequest(`/api/events/${eventId}/transmission/node/${nodeId}`); }, getHistoricalEventStocks: async (eventId) => { return await apiRequest(`/api/historical-events/${eventId}/stocks`); }, }; export const indexService = { getKlineData: async (indexCode, chartType = 'timeline', eventTime = null) => { try { const params = new URLSearchParams(); params.append('type', chartType); if (eventTime) { params.append('event_time', eventTime); } const url = `/api/index/${indexCode}/kline?${params.toString()}`; logger.debug('indexService', '获取指数K线数据', { indexCode, chartType, eventTime }); const response = await apiRequest(url); return response; } catch (error) { logger.error('indexService', 'getKlineData', error, { indexCode, chartType }); throw error; } }, }; export default { eventService, stockService, indexService, };