2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 帖子服务
|
|
|
|
|
|
* 处理帖子的增删改查、点赞、收藏等功能
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
*
|
|
|
|
|
|
* @deprecated 此服务已弃用,请使用 ProcessPostUseCase 代替
|
|
|
|
|
|
* 互动操作(点赞、收藏)已委托给 processPostUseCase
|
2026-03-09 21:29:03 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { api, PaginatedData } from './api';
|
|
|
|
|
|
import { Post, CreatePostInput } from '../types';
|
2026-03-20 23:00:27 +08:00
|
|
|
|
import { CursorPaginationRequest, CursorPaginationResponse } from '../types/dto';
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
import { processPostUseCase } from '../core/usecases/ProcessPostUseCase';
|
|
|
|
|
|
import type { Post as PostEntity } from '../core/entities/Post';
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
// 帖子列表响应
|
|
|
|
|
|
interface PostListResponse {
|
|
|
|
|
|
list: Post[];
|
|
|
|
|
|
total: number;
|
|
|
|
|
|
page: number;
|
|
|
|
|
|
page_size: number;
|
|
|
|
|
|
total_pages: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 帖子响应 - API直接返回Post对象
|
|
|
|
|
|
type PostResponse = Post;
|
|
|
|
|
|
|
|
|
|
|
|
// 创建帖子请求
|
|
|
|
|
|
interface CreatePostRequest {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
images?: string[];
|
2026-03-24 22:27:33 +08:00
|
|
|
|
channel_id?: string;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新帖子请求
|
|
|
|
|
|
interface UpdatePostRequest {
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
content?: string;
|
|
|
|
|
|
images?: string[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 帖子服务类
|
|
|
|
|
|
class PostService {
|
|
|
|
|
|
// 获取帖子列表
|
|
|
|
|
|
async getPosts(
|
|
|
|
|
|
page = 1,
|
|
|
|
|
|
pageSize = 20,
|
|
|
|
|
|
tab?: string,
|
2026-03-24 22:27:33 +08:00
|
|
|
|
channelId?: string
|
2026-03-09 21:29:03 +08:00
|
|
|
|
): Promise<PaginatedData<Post>> {
|
|
|
|
|
|
const params: Record<string, any> = {
|
|
|
|
|
|
page,
|
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (tab) {
|
|
|
|
|
|
params.tab = tab;
|
|
|
|
|
|
}
|
2026-03-24 22:27:33 +08:00
|
|
|
|
if (channelId) {
|
|
|
|
|
|
params.channel_id = channelId;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const response = await api.get<PostListResponse>('/posts', params);
|
|
|
|
|
|
return response.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取关注帖子
|
|
|
|
|
|
async getFollowingPosts(page = 1, pageSize = 20): Promise<PaginatedData<Post>> {
|
|
|
|
|
|
return this.getPosts(page, pageSize, 'follow');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取热门帖子
|
|
|
|
|
|
async getHotPosts(page = 1, pageSize = 20): Promise<PaginatedData<Post>> {
|
|
|
|
|
|
return this.getPosts(page, pageSize, 'hot');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取最新帖子
|
|
|
|
|
|
async getLatestPosts(page = 1, pageSize = 20): Promise<PaginatedData<Post>> {
|
|
|
|
|
|
return this.getPosts(page, pageSize, 'latest');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取帖子详情(不增加浏览量)
|
|
|
|
|
|
async getPost(postId: string): Promise<Post | null> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.get<PostResponse>(`/posts/${postId}`);
|
|
|
|
|
|
return response.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取帖子详情失败:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 记录帖子浏览(增加浏览量)
|
|
|
|
|
|
async recordView(postId: string): Promise<boolean> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.post(`/posts/${postId}/view`);
|
|
|
|
|
|
return response.code === 0;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('记录浏览失败:', error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建帖子
|
|
|
|
|
|
async createPost(data: CreatePostRequest): Promise<Post | null> {
|
|
|
|
|
|
const response = await api.post<PostResponse>('/posts', data);
|
|
|
|
|
|
return response.data;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新帖子
|
|
|
|
|
|
async updatePost(postId: string, data: UpdatePostRequest): Promise<Post | null> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.put<PostResponse>(`/posts/${postId}`, data);
|
|
|
|
|
|
return response.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('更新帖子失败:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 删除帖子
|
|
|
|
|
|
async deletePost(postId: string): Promise<boolean> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.delete(`/posts/${postId}`);
|
|
|
|
|
|
return response.code === 0;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除帖子失败:', error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 点赞帖子
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
// @deprecated 请使用 processPostUseCase.likePost() 代替
|
2026-03-09 21:29:03 +08:00
|
|
|
|
async likePost(postId: string): Promise<Post | null> {
|
|
|
|
|
|
try {
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
const post = await processPostUseCase.likePost(postId);
|
|
|
|
|
|
// 转换为旧版 Post 类型以保持向后兼容
|
|
|
|
|
|
return post as unknown as Post;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[postService] likePost error:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取消点赞帖子
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
// @deprecated 请使用 processPostUseCase.unlikePost() 代替
|
2026-03-09 21:29:03 +08:00
|
|
|
|
async unlikePost(postId: string): Promise<Post | null> {
|
|
|
|
|
|
try {
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
const post = await processPostUseCase.unlikePost(postId);
|
|
|
|
|
|
return post as unknown as Post;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[postService] unlikePost error:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 收藏帖子
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
// @deprecated 请使用 processPostUseCase.favoritePost() 代替
|
2026-03-09 21:29:03 +08:00
|
|
|
|
async favoritePost(postId: string): Promise<Post | null> {
|
|
|
|
|
|
try {
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
const post = await processPostUseCase.favoritePost(postId);
|
|
|
|
|
|
return post as unknown as Post;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[postService] favoritePost error:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 取消收藏帖子
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
// @deprecated 请使用 processPostUseCase.unfavoritePost() 代替
|
2026-03-09 21:29:03 +08:00
|
|
|
|
async unfavoritePost(postId: string): Promise<Post | null> {
|
|
|
|
|
|
try {
|
refactor: migrate post operations to use case architecture with differential updates
Migrate post-related operations from direct service/store calls to a new use case architecture featuring differential updates. Key changes include: (1) introduce ProcessPostUseCase to handle like, unlike, favorite, unfavorite, delete, and fetch operations with proper state management, (2) add useDifferentialPosts hook for efficient list rendering with minimal re-renders, (3) add PostDiffCalculator and PostUpdateBatcher in infrastructure layer for incremental updates, (4) deprecate NotificationItem component in favor of SystemMessageItem, (5) remove deprecated interactive methods from userStore, (6) add posts_cache table to local datasource, (7) clean up legacy MessageDTO and ConversationDTO types, (8) remove deprecated hook useBreakpointCheck, (9) refactor MessageBubble to use measureInWindow directly for better React Native compatibility
2026-03-21 20:55:36 +08:00
|
|
|
|
const post = await processPostUseCase.unfavoritePost(postId);
|
|
|
|
|
|
return post as unknown as Post;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[postService] unfavoritePost error:', error);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户帖子列表
|
|
|
|
|
|
async getUserPosts(
|
|
|
|
|
|
userId: string,
|
|
|
|
|
|
page = 1,
|
|
|
|
|
|
pageSize = 20
|
|
|
|
|
|
): Promise<PaginatedData<Post>> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.get<PostListResponse>(`/users/${userId}/posts`, {
|
|
|
|
|
|
page,
|
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
|
});
|
|
|
|
|
|
return response.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户帖子列表失败:', error);
|
|
|
|
|
|
return {
|
|
|
|
|
|
list: [],
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
page: 1,
|
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
|
total_pages: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取用户收藏列表
|
|
|
|
|
|
async getUserFavorites(
|
|
|
|
|
|
userId: string,
|
|
|
|
|
|
page = 1,
|
|
|
|
|
|
pageSize = 20
|
|
|
|
|
|
): Promise<PaginatedData<Post>> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.get<PostListResponse>(`/users/${userId}/favorites`, {
|
|
|
|
|
|
page,
|
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
|
});
|
|
|
|
|
|
return response.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户收藏列表失败:', error);
|
|
|
|
|
|
return {
|
|
|
|
|
|
list: [],
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
page: 1,
|
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
|
total_pages: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 搜索帖子
|
|
|
|
|
|
async searchPosts(
|
|
|
|
|
|
keyword: string,
|
|
|
|
|
|
page = 1,
|
|
|
|
|
|
pageSize = 20
|
|
|
|
|
|
): Promise<PaginatedData<Post>> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.get<PostListResponse>('/posts/search', {
|
|
|
|
|
|
keyword,
|
|
|
|
|
|
page,
|
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
|
});
|
|
|
|
|
|
return response.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('搜索帖子失败:', error);
|
|
|
|
|
|
return {
|
|
|
|
|
|
list: [],
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
page: 1,
|
|
|
|
|
|
page_size: pageSize,
|
|
|
|
|
|
total_pages: 0,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-24 05:21:46 +08:00
|
|
|
|
/** 上报分享成功;返回服务端最新 shares_count 供界面同步 */
|
|
|
|
|
|
async sharePost(
|
|
|
|
|
|
postId: string,
|
|
|
|
|
|
body?: { channel?: string }
|
|
|
|
|
|
): Promise<{ ok: boolean; shares_count?: number }> {
|
2026-03-09 21:29:03 +08:00
|
|
|
|
try {
|
2026-03-24 05:21:46 +08:00
|
|
|
|
const response = await api.post<{ success: boolean; shares_count: number }>(
|
|
|
|
|
|
`/posts/${postId}/share`,
|
|
|
|
|
|
body && body.channel ? { channel: body.channel } : undefined
|
|
|
|
|
|
);
|
|
|
|
|
|
if (response.code !== 0) {
|
|
|
|
|
|
return { ok: false };
|
|
|
|
|
|
}
|
|
|
|
|
|
const n = response.data?.shares_count;
|
|
|
|
|
|
return { ok: true, shares_count: typeof n === 'number' ? n : undefined };
|
2026-03-09 21:29:03 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('分享帖子失败:', error);
|
2026-03-24 05:21:46 +08:00
|
|
|
|
return { ok: false };
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 举报帖子
|
|
|
|
|
|
async reportPost(postId: string, reason: string): Promise<boolean> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await api.post(`/posts/${postId}/report`, { reason });
|
|
|
|
|
|
return response.code === 0;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('举报帖子失败:', error);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-20 23:00:27 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== 游标分页方法 ====================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取帖子列表(游标分页)
|
2026-03-21 01:36:40 +08:00
|
|
|
|
* GET /api/v1/posts
|
2026-03-24 05:18:22 +08:00
|
|
|
|
* @param params 游标分页请求参数(包含 post_type 可选:follow, hot, latest)
|
2026-03-20 23:00:27 +08:00
|
|
|
|
*/
|
2026-03-21 01:36:40 +08:00
|
|
|
|
async getPostsCursor(
|
|
|
|
|
|
params: CursorPaginationRequest = {}
|
|
|
|
|
|
): Promise<CursorPaginationResponse<Post>> {
|
|
|
|
|
|
try {
|
2026-03-21 03:22:28 +08:00
|
|
|
|
// 构建请求参数
|
|
|
|
|
|
// 后端使用 `tab` 参数区分帖子类型,使用 `cursor` 参数启用游标分页
|
|
|
|
|
|
// 始终传递 cursor 参数(空字符串表示首次请求),确保使用游标分页
|
|
|
|
|
|
const requestParams: Record<string, any> = {
|
|
|
|
|
|
cursor: params.cursor || '', // 空字符串表示首次请求
|
|
|
|
|
|
page_size: params.page_size,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 将 post_type 映射为后端的 tab 参数
|
|
|
|
|
|
if (params.post_type) {
|
|
|
|
|
|
requestParams.tab = params.post_type;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const response = await api.get<any>('/posts', requestParams);
|
|
|
|
|
|
|
|
|
|
|
|
const data = response.data;
|
2026-03-23 03:58:26 +08:00
|
|
|
|
|
|
|
|
|
|
if (data && typeof data === 'object' && Array.isArray(data.list)) {
|
|
|
|
|
|
const isPageShape =
|
|
|
|
|
|
typeof data.page === 'number' &&
|
|
|
|
|
|
typeof data.total_pages === 'number' &&
|
|
|
|
|
|
data.next_cursor === undefined &&
|
|
|
|
|
|
data.prev_cursor === undefined;
|
|
|
|
|
|
if (isPageShape) {
|
|
|
|
|
|
const currentPage = data.page || 1;
|
|
|
|
|
|
const totalPages = data.total_pages || 1;
|
|
|
|
|
|
return {
|
|
|
|
|
|
list: data.list,
|
|
|
|
|
|
next_cursor: currentPage < totalPages ? String(currentPage + 1) : null,
|
|
|
|
|
|
prev_cursor: currentPage > 1 ? String(currentPage - 1) : null,
|
|
|
|
|
|
has_more: currentPage < totalPages,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return {
|
|
|
|
|
|
list: data.list,
|
|
|
|
|
|
next_cursor: data.next_cursor ?? null,
|
|
|
|
|
|
prev_cursor: data.prev_cursor ?? null,
|
|
|
|
|
|
has_more: data.has_more ?? false,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-21 03:22:28 +08:00
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: [],
|
2026-03-21 03:22:28 +08:00
|
|
|
|
next_cursor: null,
|
|
|
|
|
|
prev_cursor: null,
|
|
|
|
|
|
has_more: false,
|
|
|
|
|
|
};
|
2026-03-20 23:00:27 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取帖子列表失败:', error);
|
|
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: [],
|
2026-03-20 23:00:27 +08:00
|
|
|
|
next_cursor: null,
|
|
|
|
|
|
prev_cursor: null,
|
|
|
|
|
|
has_more: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 搜索帖子(游标分页)
|
2026-03-21 01:36:40 +08:00
|
|
|
|
* GET /api/v1/posts/search
|
2026-03-20 23:00:27 +08:00
|
|
|
|
* @param query 搜索关键词
|
|
|
|
|
|
* @param params 游标分页请求参数
|
|
|
|
|
|
*/
|
|
|
|
|
|
async searchPostsCursor(
|
|
|
|
|
|
query: string,
|
|
|
|
|
|
params: CursorPaginationRequest = {}
|
|
|
|
|
|
): Promise<CursorPaginationResponse<Post>> {
|
|
|
|
|
|
try {
|
2026-03-21 03:22:28 +08:00
|
|
|
|
const response = await api.get<any>('/posts/search', {
|
2026-03-21 01:36:40 +08:00
|
|
|
|
...params,
|
|
|
|
|
|
keyword: query,
|
2026-03-20 23:00:27 +08:00
|
|
|
|
});
|
2026-03-21 03:22:28 +08:00
|
|
|
|
|
|
|
|
|
|
const data = response.data;
|
2026-03-23 03:58:26 +08:00
|
|
|
|
|
|
|
|
|
|
if (data && typeof data === 'object' && Array.isArray(data.list)) {
|
|
|
|
|
|
const isPageShape =
|
|
|
|
|
|
typeof data.page === 'number' &&
|
|
|
|
|
|
typeof data.total_pages === 'number' &&
|
|
|
|
|
|
data.next_cursor === undefined &&
|
|
|
|
|
|
data.prev_cursor === undefined;
|
|
|
|
|
|
if (isPageShape) {
|
2026-03-21 03:22:28 +08:00
|
|
|
|
const currentPage = data.page || 1;
|
|
|
|
|
|
const totalPages = data.total_pages || 1;
|
|
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: data.list,
|
2026-03-21 03:22:28 +08:00
|
|
|
|
next_cursor: currentPage < totalPages ? String(currentPage + 1) : null,
|
|
|
|
|
|
prev_cursor: currentPage > 1 ? String(currentPage - 1) : null,
|
|
|
|
|
|
has_more: currentPage < totalPages,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-03-23 03:58:26 +08:00
|
|
|
|
return {
|
|
|
|
|
|
list: data.list,
|
|
|
|
|
|
next_cursor: data.next_cursor ?? null,
|
|
|
|
|
|
prev_cursor: data.prev_cursor ?? null,
|
|
|
|
|
|
has_more: data.has_more ?? false,
|
|
|
|
|
|
};
|
2026-03-21 03:22:28 +08:00
|
|
|
|
}
|
2026-03-23 03:58:26 +08:00
|
|
|
|
|
2026-03-21 03:22:28 +08:00
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: [],
|
2026-03-21 03:22:28 +08:00
|
|
|
|
next_cursor: null,
|
|
|
|
|
|
prev_cursor: null,
|
|
|
|
|
|
has_more: false,
|
|
|
|
|
|
};
|
2026-03-20 23:00:27 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('搜索帖子失败:', error);
|
|
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: [],
|
2026-03-20 23:00:27 +08:00
|
|
|
|
next_cursor: null,
|
|
|
|
|
|
prev_cursor: null,
|
|
|
|
|
|
has_more: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取用户帖子列表(游标分页)
|
2026-03-21 01:36:40 +08:00
|
|
|
|
* GET /api/v1/users/:id/posts
|
2026-03-20 23:00:27 +08:00
|
|
|
|
* @param userId 用户ID
|
|
|
|
|
|
* @param params 游标分页请求参数
|
|
|
|
|
|
*/
|
|
|
|
|
|
async getUserPostsCursor(
|
|
|
|
|
|
userId: string,
|
|
|
|
|
|
params: CursorPaginationRequest = {}
|
|
|
|
|
|
): Promise<CursorPaginationResponse<Post>> {
|
|
|
|
|
|
try {
|
2026-03-21 03:22:28 +08:00
|
|
|
|
const response = await api.get<any>(
|
2026-03-21 01:36:40 +08:00
|
|
|
|
`/users/${userId}/posts`,
|
|
|
|
|
|
params
|
2026-03-20 23:00:27 +08:00
|
|
|
|
);
|
2026-03-21 03:22:28 +08:00
|
|
|
|
|
|
|
|
|
|
const data = response.data;
|
2026-03-23 03:58:26 +08:00
|
|
|
|
|
|
|
|
|
|
if (data && typeof data === 'object' && Array.isArray(data.list)) {
|
|
|
|
|
|
const isPageShape =
|
|
|
|
|
|
typeof data.page === 'number' &&
|
|
|
|
|
|
typeof data.total_pages === 'number' &&
|
|
|
|
|
|
data.next_cursor === undefined &&
|
|
|
|
|
|
data.prev_cursor === undefined;
|
|
|
|
|
|
if (isPageShape) {
|
2026-03-21 03:22:28 +08:00
|
|
|
|
const currentPage = data.page || 1;
|
|
|
|
|
|
const totalPages = data.total_pages || 1;
|
|
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: data.list,
|
2026-03-21 03:22:28 +08:00
|
|
|
|
next_cursor: currentPage < totalPages ? String(currentPage + 1) : null,
|
|
|
|
|
|
prev_cursor: currentPage > 1 ? String(currentPage - 1) : null,
|
|
|
|
|
|
has_more: currentPage < totalPages,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2026-03-23 03:58:26 +08:00
|
|
|
|
return {
|
|
|
|
|
|
list: data.list,
|
|
|
|
|
|
next_cursor: data.next_cursor ?? null,
|
|
|
|
|
|
prev_cursor: data.prev_cursor ?? null,
|
|
|
|
|
|
has_more: data.has_more ?? false,
|
|
|
|
|
|
};
|
2026-03-21 03:22:28 +08:00
|
|
|
|
}
|
2026-03-23 03:58:26 +08:00
|
|
|
|
|
2026-03-21 03:22:28 +08:00
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: [],
|
2026-03-21 03:22:28 +08:00
|
|
|
|
next_cursor: null,
|
|
|
|
|
|
prev_cursor: null,
|
|
|
|
|
|
has_more: false,
|
|
|
|
|
|
};
|
2026-03-20 23:00:27 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户帖子列表失败:', error);
|
|
|
|
|
|
return {
|
2026-03-23 03:58:26 +08:00
|
|
|
|
list: [],
|
2026-03-20 23:00:27 +08:00
|
|
|
|
next_cursor: null,
|
|
|
|
|
|
prev_cursor: null,
|
|
|
|
|
|
has_more: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 导出帖子服务实例
|
|
|
|
|
|
export const postService = new PostService();
|