400 lines
14 KiB
JavaScript
Executable File
400 lines
14 KiB
JavaScript
Executable File
// src/services/eventService.js
|
||
|
||
const apiRequest = async (url, options = {}) => {
|
||
try {
|
||
console.log(`Making API request to: ${url}`);
|
||
|
||
const response = await fetch(url, {
|
||
...options,
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...options.headers,
|
||
},
|
||
credentials: 'include', // 包含 cookies,以便后端识别登录状态
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
console.error(`API request failed: ${response.status} - ${errorText}`);
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
console.log(`API response from ${url}:`, data);
|
||
|
||
return data;
|
||
} catch (error) {
|
||
console.error(`API request failed for ${url}:`, error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const eventService = {
|
||
getEvents: (params = {}) => {
|
||
// Filter out empty params
|
||
const cleanParams = Object.fromEntries(Object.entries(params).filter(([_, v]) => v != null && 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) {
|
||
console.error('获取帖子失败:', error);
|
||
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) {
|
||
console.error('创建帖子失败:', error);
|
||
return { success: false, message: '创建帖子失败' };
|
||
}
|
||
},
|
||
|
||
deletePost: async (postId) => {
|
||
try {
|
||
return await apiRequest(`/api/posts/${postId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
} catch (error) {
|
||
console.error('删除帖子失败:', error);
|
||
return { success: false, message: '删除帖子失败' };
|
||
}
|
||
},
|
||
|
||
likePost: async (postId) => {
|
||
try {
|
||
return await apiRequest(`/api/posts/${postId}/like`, {
|
||
method: 'POST'
|
||
});
|
||
} catch (error) {
|
||
console.error('点赞失败:', error);
|
||
return { success: false, message: '点赞失败' };
|
||
}
|
||
},
|
||
|
||
// 评论相关API
|
||
getPostComments: async (postId, sortType = 'latest') => {
|
||
try {
|
||
return await apiRequest(`/api/posts/${postId}/comments?sort=${sortType}`);
|
||
} catch (error) {
|
||
console.error('获取评论失败:', error);
|
||
return { success: false, data: [] };
|
||
}
|
||
},
|
||
|
||
addPostComment: async (postId, commentData) => {
|
||
try {
|
||
return await apiRequest(`/api/posts/${postId}/comments`, {
|
||
method: 'POST',
|
||
body: JSON.stringify(commentData)
|
||
});
|
||
} catch (error) {
|
||
console.error('添加评论失败:', error);
|
||
return { success: false, message: '添加评论失败' };
|
||
}
|
||
},
|
||
|
||
deleteComment: async (commentId) => {
|
||
try {
|
||
return await apiRequest(`/api/comments/${commentId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
} catch (error) {
|
||
console.error('删除评论失败:', error);
|
||
return { success: false, message: '删除评论失败' };
|
||
}
|
||
},
|
||
|
||
likeComment: async (commentId) => {
|
||
try {
|
||
return await apiRequest(`/api/comments/${commentId}/like`, {
|
||
method: 'POST'
|
||
});
|
||
} catch (error) {
|
||
console.error('点赞失败:', error);
|
||
return { success: false, message: '点赞失败' };
|
||
}
|
||
},
|
||
|
||
// 兼容旧版本的评论API
|
||
getComments: async (eventId, sortType = 'latest') => {
|
||
console.warn('getComments 已废弃,请使用 getPosts');
|
||
// 直接调用 getPosts 的实现,避免循环引用
|
||
try {
|
||
return await apiRequest(`/api/events/${eventId}/posts?sort=${sortType}&page=1&per_page=20`);
|
||
} catch (error) {
|
||
console.error('获取帖子失败:', error);
|
||
return { success: false, data: [], pagination: {} };
|
||
}
|
||
},
|
||
|
||
addComment: async (eventId, commentData) => {
|
||
console.warn('addComment 已废弃,请使用 createPost');
|
||
// 直接调用 createPost 的实现,避免循环引用
|
||
try {
|
||
return await apiRequest(`/api/events/${eventId}/posts`, {
|
||
method: 'POST',
|
||
body: JSON.stringify(commentData)
|
||
});
|
||
} catch (error) {
|
||
console.error('创建帖子失败:', error);
|
||
return { success: false, message: '创建帖子失败' };
|
||
}
|
||
},
|
||
};
|
||
|
||
export const stockService = {
|
||
getQuotes: async (codes, eventTime = null) => {
|
||
try {
|
||
const requestBody = {
|
||
codes: codes
|
||
};
|
||
|
||
if (eventTime) {
|
||
requestBody.event_time = eventTime;
|
||
}
|
||
|
||
console.log(`获取股票报价,请求体:`, requestBody);
|
||
|
||
const response = await apiRequest(`/api/stock/quotes`, {
|
||
method: 'POST',
|
||
body: JSON.stringify(requestBody)
|
||
});
|
||
|
||
console.log('股票报价原始响应:', response);
|
||
console.log('response.success:', response.success);
|
||
console.log('response.data:', response.data);
|
||
|
||
if (response.success && response.data) {
|
||
console.log('返回股票报价数据:', response.data);
|
||
return response.data;
|
||
} else {
|
||
console.warn('股票报价响应格式异常:', response);
|
||
return {};
|
||
}
|
||
} catch (error) {
|
||
console.error('获取股票报价失败:', error);
|
||
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()}`;
|
||
console.log(`获取K线数据: ${url}`);
|
||
|
||
const response = await apiRequest(url);
|
||
|
||
if (response.error) {
|
||
console.warn('K线数据响应包含错误:', response.error);
|
||
return response;
|
||
}
|
||
|
||
return response;
|
||
} catch (error) {
|
||
console.error('获取K线数据失败:', error);
|
||
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 response = await fetch(`/api/events/${eventId}/sankey-node-detail?${params}`, {
|
||
method: 'GET',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(`HTTP error! status: ${response.status}`);
|
||
}
|
||
|
||
const data = await response.json();
|
||
return data;
|
||
} catch (error) {
|
||
console.error('Error fetching sankey node detail:', error);
|
||
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()}`;
|
||
console.log(`获取指数K线数据: ${url}`);
|
||
const response = await apiRequest(url);
|
||
return response;
|
||
} catch (error) {
|
||
console.error('获取指数K线数据失败:', error);
|
||
throw error;
|
||
}
|
||
},
|
||
};
|
||
|
||
export default {
|
||
eventService,
|
||
stockService,
|
||
indexService,
|
||
};
|