Files
vf_react/src/services/eventService.js

395 lines
14 KiB
JavaScript
Executable File

// 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<object>} 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<object>} 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<object>} The detailed calendar event object.
*/
getEventDetail: (eventId) => {
return apiRequest(`/api/v1/calendar/events/${eventId}`);
},
/**
* 切换未来事件的关注状态
* @param {number} eventId - 未来事件ID
* @returns {Promise<object>} 返回关注状态
*/
toggleFollow: async (eventId) => {
return await apiRequest(`/api/v1/calendar/events/${eventId}/follow`, {
method: 'POST'
});
},
/**
* 获取用户关注的所有未来事件
* @returns {Promise<object>} 返回关注的未来事件列表
*/
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,
};