feat: 优化架构
This commit is contained in:
29
src/services/post/channelService.ts
Normal file
29
src/services/post/channelService.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { api } from '../core/api';
|
||||
|
||||
export interface ChannelItem {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
sort_order: number;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
interface ChannelListResponse {
|
||||
list: ChannelItem[];
|
||||
}
|
||||
|
||||
class ChannelService {
|
||||
async list(): Promise<ChannelItem[]> {
|
||||
try {
|
||||
const res = await api.get<ChannelListResponse>('/channels');
|
||||
return res.data?.list || [];
|
||||
} catch (error) {
|
||||
console.error('[channelService] 获取频道失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const channelService = new ChannelService();
|
||||
|
||||
309
src/services/post/commentService.ts
Normal file
309
src/services/post/commentService.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
/**
|
||||
* 评论服务
|
||||
* 处理评论的增删改查、点赞等功能
|
||||
*/
|
||||
|
||||
import { api, PaginatedData } from '../core/api';
|
||||
import { Comment, CreateCommentInput } from '@/types';
|
||||
import { CursorPaginationRequest, CursorPaginationResponse, CommentDTO } from '@/types/dto';
|
||||
|
||||
// 评论列表响应
|
||||
interface CommentListResponse {
|
||||
list: Comment[];
|
||||
total: number;
|
||||
page: number;
|
||||
page_size: number;
|
||||
total_pages: number;
|
||||
}
|
||||
|
||||
// 评论响应
|
||||
type CommentResponse = Comment;
|
||||
|
||||
// 创建评论请求
|
||||
interface CreateCommentRequest {
|
||||
postId: string;
|
||||
content: string;
|
||||
parentId?: string;
|
||||
images?: string[]; // 图片URL列表
|
||||
replyToUserId?: string;
|
||||
}
|
||||
|
||||
// 更新评论请求
|
||||
interface UpdateCommentRequest {
|
||||
content: string;
|
||||
}
|
||||
|
||||
// 评论服务类
|
||||
class CommentService {
|
||||
// 获取帖子的评论列表
|
||||
async getPostComments(
|
||||
postId: string,
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
sort: 'latest' | 'hot' = 'latest'
|
||||
): Promise<PaginatedData<Comment>> {
|
||||
try {
|
||||
const response = await api.get<CommentListResponse>(`/comments/post/${postId}`, {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
sort,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取评论列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: pageSize,
|
||||
total_pages: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取评论的回复列表
|
||||
async getCommentReplies(
|
||||
commentId: string,
|
||||
page = 1,
|
||||
pageSize = 20
|
||||
): Promise<PaginatedData<Comment>> {
|
||||
try {
|
||||
const response = await api.get<CommentListResponse>(`/comments/${commentId}/replies`, {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取评论回复列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
page_size: pageSize,
|
||||
total_pages: 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 根据根评论ID分页获取回复(扁平化)
|
||||
async getFlatReplies(
|
||||
rootId: string,
|
||||
page = 1,
|
||||
pageSize = 10
|
||||
): Promise<PaginatedData<Comment>> {
|
||||
try {
|
||||
const response = await api.get<CommentListResponse>(`/comments/${rootId}/replies/flat`, {
|
||||
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 getComment(commentId: string): Promise<Comment | null> {
|
||||
try {
|
||||
const response = await api.get<CommentResponse>(`/comments/${commentId}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取评论详情失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建评论
|
||||
async createComment(data: CreateCommentRequest): Promise<Comment | null> {
|
||||
try {
|
||||
// 转换字段名为后端期望的snake_case
|
||||
// 只有当 parentId 有值时才包含 parent_id 字段
|
||||
const requestBody: Record<string, any> = {
|
||||
post_id: data.postId,
|
||||
content: data.content,
|
||||
};
|
||||
|
||||
// 如果有父评论ID,才添加 parent_id 字段
|
||||
if (data.parentId) {
|
||||
requestBody.parent_id = data.parentId;
|
||||
}
|
||||
|
||||
// 如果有图片,添加 images 字段
|
||||
if (data.images && data.images.length > 0) {
|
||||
requestBody.images = data.images;
|
||||
}
|
||||
|
||||
const response = await api.post<CommentResponse>('/comments', requestBody);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('创建评论失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 回复评论
|
||||
async replyComment(
|
||||
postId: string,
|
||||
parentId: string,
|
||||
content: string,
|
||||
replyToUserId?: string
|
||||
): Promise<Comment | null> {
|
||||
return this.createComment({
|
||||
postId,
|
||||
content,
|
||||
parentId,
|
||||
replyToUserId,
|
||||
});
|
||||
}
|
||||
|
||||
// 更新评论
|
||||
async updateComment(commentId: string, content: string): Promise<Comment | null> {
|
||||
try {
|
||||
const response = await api.put<CommentResponse>(`/comments/${commentId}`, { content });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('更新评论失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 删除评论
|
||||
async deleteComment(commentId: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.delete(`/comments/${commentId}`);
|
||||
return response.code === 0;
|
||||
} catch (error) {
|
||||
console.error('删除评论失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 点赞评论
|
||||
async likeComment(commentId: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.post(`/comments/${commentId}/like`);
|
||||
return response.code === 0;
|
||||
} catch (error) {
|
||||
console.error('点赞评论失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 取消点赞评论
|
||||
async unlikeComment(commentId: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.delete(`/comments/${commentId}/like`);
|
||||
return response.code === 0;
|
||||
} catch (error) {
|
||||
console.error('取消点赞评论失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户评论列表
|
||||
async getUserComments(
|
||||
userId: string,
|
||||
page = 1,
|
||||
pageSize = 20
|
||||
): Promise<PaginatedData<Comment>> {
|
||||
try {
|
||||
const response = await api.get<CommentListResponse>(`/users/${userId}/comments`, {
|
||||
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 reportComment(commentId: string, reason: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.post(`/comments/${commentId}/report`, { reason });
|
||||
return response.code === 0;
|
||||
} catch (error) {
|
||||
console.error('举报评论失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 游标分页方法 ====================
|
||||
|
||||
/**
|
||||
* 获取帖子评论列表(游标分页)
|
||||
* GET /api/v1/comments/post/:id/cursor
|
||||
* @param postId 帖子ID
|
||||
* @param params 游标分页请求参数
|
||||
*/
|
||||
async getPostCommentsCursor(
|
||||
postId: string,
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<Comment>> {
|
||||
try {
|
||||
const response = await api.get<any>(`/comments/post/${postId}/cursor`, params);
|
||||
const raw = response.data;
|
||||
return {
|
||||
list: Array.isArray(raw?.list) ? raw.list : [],
|
||||
next_cursor: raw?.next_cursor ?? null,
|
||||
prev_cursor: raw?.prev_cursor ?? null,
|
||||
has_more: raw?.has_more ?? false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取帖子评论列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取评论回复列表(游标分页)
|
||||
* GET /api/v1/comments/:id/replies/cursor
|
||||
* @param commentId 评论ID
|
||||
* @param params 游标分页请求参数
|
||||
*/
|
||||
async getCommentRepliesCursor(
|
||||
commentId: string,
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<Comment>> {
|
||||
try {
|
||||
const response = await api.get<any>(`/comments/${commentId}/replies/cursor`, params);
|
||||
const raw = response.data;
|
||||
return {
|
||||
list: Array.isArray(raw?.list) ? raw.list : [],
|
||||
next_cursor: raw?.next_cursor ?? null,
|
||||
prev_cursor: raw?.prev_cursor ?? null,
|
||||
has_more: raw?.has_more ?? false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取评论回复列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出评论服务实例
|
||||
export const commentService = new CommentService();
|
||||
15
src/services/post/index.ts
Normal file
15
src/services/post/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export { postService } from './postService';
|
||||
|
||||
export { commentService } from './commentService';
|
||||
|
||||
export { voteService } from './voteService';
|
||||
|
||||
export { channelService } from './channelService';
|
||||
export type { ChannelItem } from './channelService';
|
||||
|
||||
export { reportService, REPORT_REASONS } from './reportService';
|
||||
export type { ReportReason, ReportTargetType, ReportResponse } from './reportService';
|
||||
|
||||
export { postSyncService } from './PostSyncService';
|
||||
export type { PostDetailState, PostsState } from './PostSyncService';
|
||||
export { mergePostListWithFastPath, mergeRefreshWindow } from './postMerge';
|
||||
421
src/services/post/postService.ts
Normal file
421
src/services/post/postService.ts
Normal file
@@ -0,0 +1,421 @@
|
||||
/**
|
||||
* 帖子服务 - API 层薄封装
|
||||
* 互动操作(点赞、收藏)请使用 postSyncService
|
||||
*/
|
||||
|
||||
import { api, PaginatedData } from '../core/api';
|
||||
import { Post, CreatePostInput } from '@/types';
|
||||
import { CursorPaginationRequest, CursorPaginationResponse } from '@/types/dto';
|
||||
|
||||
// 帖子列表响应
|
||||
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[];
|
||||
channel_id?: string;
|
||||
}
|
||||
|
||||
// 更新帖子请求
|
||||
interface UpdatePostRequest {
|
||||
title?: string;
|
||||
content?: string;
|
||||
images?: string[];
|
||||
}
|
||||
|
||||
// 帖子服务类
|
||||
class PostService {
|
||||
// 获取帖子列表
|
||||
async getPosts(
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
tab?: string,
|
||||
channelId?: string
|
||||
): Promise<PaginatedData<Post>> {
|
||||
const params: Record<string, any> = {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
};
|
||||
|
||||
if (tab) {
|
||||
params.tab = tab;
|
||||
}
|
||||
if (channelId) {
|
||||
params.channel_id = channelId;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户帖子列表
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** 上报分享成功;返回服务端最新 shares_count 供界面同步 */
|
||||
async sharePost(
|
||||
postId: string,
|
||||
body?: { channel?: string }
|
||||
): Promise<{ ok: boolean; shares_count?: number }> {
|
||||
try {
|
||||
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 };
|
||||
} catch (error) {
|
||||
console.error('分享帖子失败:', error);
|
||||
return { ok: false };
|
||||
}
|
||||
}
|
||||
|
||||
// 举报帖子
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 游标分页方法 ====================
|
||||
|
||||
/**
|
||||
* 获取帖子列表(游标分页)
|
||||
* GET /api/v1/posts
|
||||
* @param params 游标分页请求参数(包含 post_type 可选:follow, hot, latest)
|
||||
*/
|
||||
async getPostsCursor(
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<Post>> {
|
||||
try {
|
||||
// 构建请求参数
|
||||
// 后端使用 `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;
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取帖子列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索帖子(游标分页)
|
||||
* GET /api/v1/posts/search
|
||||
* @param query 搜索关键词
|
||||
* @param params 游标分页请求参数
|
||||
*/
|
||||
async searchPostsCursor(
|
||||
query: string,
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<Post>> {
|
||||
try {
|
||||
const response = await api.get<any>('/posts/search', {
|
||||
...params,
|
||||
keyword: query,
|
||||
});
|
||||
|
||||
const data = response.data;
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('搜索帖子失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户帖子列表(游标分页)
|
||||
* GET /api/v1/users/:id/posts
|
||||
* @param userId 用户ID
|
||||
* @param params 游标分页请求参数
|
||||
*/
|
||||
async getUserPostsCursor(
|
||||
userId: string,
|
||||
params: CursorPaginationRequest = {}
|
||||
): Promise<CursorPaginationResponse<Post>> {
|
||||
try {
|
||||
const response = await api.get<any>(
|
||||
`/users/${userId}/posts`,
|
||||
params
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('获取用户帖子列表失败:', error);
|
||||
return {
|
||||
list: [],
|
||||
next_cursor: null,
|
||||
prev_cursor: null,
|
||||
has_more: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导出帖子服务实例
|
||||
export const postService = new PostService();
|
||||
83
src/services/post/reportService.ts
Normal file
83
src/services/post/reportService.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 举报服务
|
||||
* 处理帖子、评论、消息的举报功能
|
||||
*/
|
||||
|
||||
import { api } from '../core/api';
|
||||
|
||||
// 举报原因类型
|
||||
export type ReportReason = 'spam' | 'inappropriate' | 'harassment' | 'misinformation' | 'other';
|
||||
|
||||
// 举报目标类型
|
||||
export type ReportTargetType = 'post' | 'comment' | 'message';
|
||||
|
||||
// 创建举报请求
|
||||
interface CreateReportRequest {
|
||||
target_type: ReportTargetType;
|
||||
target_id: string;
|
||||
reason: ReportReason;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// 举报响应
|
||||
export interface ReportResponse {
|
||||
id: string;
|
||||
reporter_id: string;
|
||||
target_type: ReportTargetType;
|
||||
target_id: string;
|
||||
reason: ReportReason;
|
||||
description?: string;
|
||||
status: 'pending' | 'processing' | 'resolved' | 'rejected';
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
// 举报原因选项
|
||||
export const REPORT_REASONS: { value: ReportReason; label: string }[] = [
|
||||
{ value: 'spam', label: '垃圾广告' },
|
||||
{ value: 'inappropriate', label: '违规内容' },
|
||||
{ value: 'harassment', label: '辱骂/攻击' },
|
||||
{ value: 'misinformation', label: '虚假信息' },
|
||||
{ value: 'other', label: '其他' },
|
||||
];
|
||||
|
||||
// 举报服务类
|
||||
class ReportService {
|
||||
/**
|
||||
* 创建举报
|
||||
* @param targetType 举报目标类型
|
||||
* @param targetId 目标ID
|
||||
* @param reason 举报原因
|
||||
* @param description 详细描述(可选)
|
||||
*/
|
||||
async createReport(
|
||||
targetType: ReportTargetType,
|
||||
targetId: string,
|
||||
reason: ReportReason,
|
||||
description?: string
|
||||
): Promise<ReportResponse | null> {
|
||||
try {
|
||||
const request: CreateReportRequest = {
|
||||
target_type: targetType,
|
||||
target_id: targetId,
|
||||
reason,
|
||||
description,
|
||||
};
|
||||
const response = await api.post<ReportResponse>('/reports', request);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('创建举报失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取举报原因标签
|
||||
*/
|
||||
getReasonLabel(reason: ReportReason): string {
|
||||
const item = REPORT_REASONS.find(r => r.value === reason);
|
||||
return item?.label ?? reason;
|
||||
}
|
||||
}
|
||||
|
||||
export const reportService = new ReportService();
|
||||
export default reportService;
|
||||
114
src/services/post/voteService.ts
Normal file
114
src/services/post/voteService.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 投票服务
|
||||
* 处理投票相关的API调用
|
||||
*/
|
||||
|
||||
import { api } from '../core/api';
|
||||
import { Post, VoteResultDTO, CreateVotePostRequest } from '@/types';
|
||||
|
||||
// 投票响应
|
||||
interface VoteResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
// 更新投票选项响应
|
||||
interface UpdateVoteOptionResponse {
|
||||
success: boolean;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 投票服务类
|
||||
*/
|
||||
class VoteService {
|
||||
/**
|
||||
* 创建投票帖子
|
||||
* @param data 创建投票帖子请求数据
|
||||
* @returns 创建的帖子或null
|
||||
*/
|
||||
async createVotePost(data: CreateVotePostRequest): Promise<Post | null> {
|
||||
// 验证投票选项数量
|
||||
if (!data.vote_options || data.vote_options.length < 2) {
|
||||
console.error('[VoteService] 投票选项至少需要2个');
|
||||
return null;
|
||||
}
|
||||
if (data.vote_options.length > 10) {
|
||||
console.error('[VoteService] 投票选项最多10个');
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await api.post<Post>('/posts/vote', data);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取投票结果
|
||||
* @param postId 帖子ID
|
||||
* @returns 投票结果或null
|
||||
*/
|
||||
async getVoteResult(postId: string): Promise<VoteResultDTO | null> {
|
||||
try {
|
||||
const response = await api.get<VoteResultDTO>(`/posts/${postId}/vote`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('[VoteService] 获取投票结果失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 投票
|
||||
* @param postId 帖子ID
|
||||
* @param optionId 选项ID
|
||||
* @returns 是否成功
|
||||
*/
|
||||
async vote(postId: string, optionId: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.post<VoteResponse>(`/posts/${postId}/vote`, {
|
||||
option_id: optionId,
|
||||
});
|
||||
return response.data.success;
|
||||
} catch (error) {
|
||||
console.error('[VoteService] 投票失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消投票
|
||||
* @param postId 帖子ID
|
||||
* @returns 是否成功
|
||||
*/
|
||||
async unvote(postId: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.delete<VoteResponse>(`/posts/${postId}/vote`);
|
||||
return response.data.success;
|
||||
} catch (error) {
|
||||
console.error('[VoteService] 取消投票失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新投票选项(作者权限)
|
||||
* @param optionId 选项ID
|
||||
* @param content 新内容
|
||||
* @param postId 帖子ID
|
||||
* @returns 是否成功
|
||||
*/
|
||||
async updateVoteOption(optionId: string, content: string, postId: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await api.put<UpdateVoteOptionResponse>(
|
||||
`/posts/${postId}/vote/options/${optionId}`,
|
||||
{ content }
|
||||
);
|
||||
return response.data.success;
|
||||
} catch (error) {
|
||||
console.error('[VoteService] 更新投票选项失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const voteService = new VoteService();
|
||||
Reference in New Issue
Block a user