Initial frontend repository commit.
Include app source and update .gitignore to exclude local release artifacts and signing files. Made-with: Cursor
This commit is contained in:
603
src/stores/userStore.ts
Normal file
603
src/stores/userStore.ts
Normal file
@@ -0,0 +1,603 @@
|
||||
/**
|
||||
* 用户状态管理
|
||||
* 使用Zustand进行状态管理
|
||||
* 对接真实后端API
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { User, Post, Notification, NotificationBadge } from '../types';
|
||||
import { PaginatedData } from '../services/api';
|
||||
import {
|
||||
authService,
|
||||
postService,
|
||||
commentService,
|
||||
notificationService,
|
||||
} from '../services';
|
||||
import { userManager } from './userManager';
|
||||
import { messageManager } from './messageManager';
|
||||
|
||||
interface UserState {
|
||||
// 状态
|
||||
users: User[];
|
||||
userCache: Record<string, User>; // 用户缓存
|
||||
posts: Post[];
|
||||
notifications: Notification[];
|
||||
notificationBadge: NotificationBadge;
|
||||
messageUnreadCount: number;
|
||||
|
||||
// 加载状态
|
||||
isLoadingPosts: boolean;
|
||||
isLoadingNotifications: boolean;
|
||||
|
||||
// 搜索历史
|
||||
searchHistory: string[];
|
||||
|
||||
// 操作
|
||||
fetchUser: (userId: string) => Promise<User | undefined>;
|
||||
fetchUserPosts: (userId: string, page?: number) => Promise<Post[]>;
|
||||
fetchPosts: (type: 'recommend' | 'follow' | 'hot' | 'latest', page?: number) => Promise<PaginatedData<Post>>;
|
||||
fetchNotifications: (type?: string, page?: number) => Promise<Notification[]>;
|
||||
markNotificationAsRead: (notificationId: string) => Promise<void>;
|
||||
markAllNotificationsAsRead: () => Promise<void>;
|
||||
fetchNotificationBadge: () => Promise<void>;
|
||||
setMessageUnreadCount: (count: number) => void;
|
||||
fetchMessageUnreadCount: () => Promise<void>;
|
||||
addSearchHistory: (keyword: string) => void;
|
||||
clearSearchHistory: () => void;
|
||||
|
||||
// 互动操作
|
||||
likePost: (postId: string) => Promise<void>;
|
||||
unlikePost: (postId: string) => Promise<void>;
|
||||
favoritePost: (postId: string) => Promise<void>;
|
||||
unfavoritePost: (postId: string) => Promise<void>;
|
||||
likeComment: (commentId: string) => Promise<void>;
|
||||
unlikeComment: (commentId: string) => Promise<void>;
|
||||
followUser: (userId: string) => Promise<void>;
|
||||
unfollowUser: (userId: string) => Promise<void>;
|
||||
|
||||
// 刷新数据
|
||||
refreshAll: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserState>((set, get) => ({
|
||||
// 初始状态
|
||||
users: [],
|
||||
userCache: {},
|
||||
posts: [],
|
||||
notifications: [],
|
||||
notificationBadge: {
|
||||
total: 0,
|
||||
likes: 0,
|
||||
comments: 0,
|
||||
follows: 0,
|
||||
system: 0,
|
||||
},
|
||||
messageUnreadCount: 0,
|
||||
searchHistory: [],
|
||||
isLoadingPosts: false,
|
||||
isLoadingNotifications: false,
|
||||
|
||||
// 获取用户信息
|
||||
fetchUser: async (userId: string) => {
|
||||
// 先检查缓存
|
||||
const cached = get().userCache[userId];
|
||||
if (cached) return cached;
|
||||
|
||||
try {
|
||||
const user = await userManager.getUserById(userId);
|
||||
if (user) {
|
||||
set(state => ({
|
||||
userCache: { ...state.userCache, [userId]: user }
|
||||
}));
|
||||
return user;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
// 获取用户帖子
|
||||
fetchUserPosts: async (userId: string, page = 1) => {
|
||||
try {
|
||||
const response = await postService.getUserPosts(userId, page);
|
||||
const newPosts = response.list;
|
||||
|
||||
set(state => ({
|
||||
posts: page === 1 ? newPosts : [...state.posts, ...newPosts]
|
||||
}));
|
||||
|
||||
return newPosts;
|
||||
} catch (error) {
|
||||
console.error('获取用户帖子失败:', error);
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
// 获取帖子列表(首页)
|
||||
fetchPosts: async (type: 'recommend' | 'follow' | 'hot' | 'latest', page = 1) => {
|
||||
set({ isLoadingPosts: true });
|
||||
|
||||
try {
|
||||
let response;
|
||||
|
||||
switch (type) {
|
||||
case 'recommend':
|
||||
response = await postService.getRecommendedPosts(page);
|
||||
break;
|
||||
case 'follow':
|
||||
response = await postService.getFollowingPosts(page);
|
||||
break;
|
||||
case 'hot':
|
||||
response = await postService.getHotPosts(page);
|
||||
break;
|
||||
case 'latest':
|
||||
default:
|
||||
response = await postService.getLatestPosts(page);
|
||||
break;
|
||||
}
|
||||
|
||||
const newPosts = response.list;
|
||||
|
||||
set(state => ({
|
||||
posts: page === 1 ? newPosts : [...state.posts, ...newPosts],
|
||||
isLoadingPosts: false
|
||||
}));
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('获取帖子列表失败:', error);
|
||||
set({ isLoadingPosts: false });
|
||||
return {
|
||||
list: [],
|
||||
total: 0,
|
||||
page,
|
||||
page_size: 20,
|
||||
total_pages: 0,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
// 获取通知列表
|
||||
fetchNotifications: async (type?: string, page = 1) => {
|
||||
set({ isLoadingNotifications: true });
|
||||
|
||||
try {
|
||||
const response = await notificationService.getNotifications(
|
||||
page,
|
||||
20,
|
||||
type as any
|
||||
);
|
||||
|
||||
const newNotifications = response.list;
|
||||
|
||||
set(state => ({
|
||||
notifications: page === 1 ? newNotifications : [...state.notifications, ...newNotifications],
|
||||
isLoadingNotifications: false
|
||||
}));
|
||||
|
||||
return newNotifications;
|
||||
} catch (error) {
|
||||
console.error('获取通知列表失败:', error);
|
||||
set({ isLoadingNotifications: false });
|
||||
return [];
|
||||
}
|
||||
},
|
||||
|
||||
// 标记通知为已读
|
||||
markNotificationAsRead: async (notificationId: string) => {
|
||||
try {
|
||||
await notificationService.markAsRead(notificationId);
|
||||
|
||||
set(state => {
|
||||
const notifications = state.notifications.map(n =>
|
||||
n.id === notificationId ? { ...n, isRead: true } : n
|
||||
);
|
||||
|
||||
return {
|
||||
notifications,
|
||||
notificationBadge: {
|
||||
total: notifications.filter(n => !n.isRead).length,
|
||||
likes: notifications.filter(n => n.type === 'like_post' && !n.isRead).length,
|
||||
comments: notifications.filter(n => n.type === 'comment' && !n.isRead).length,
|
||||
follows: notifications.filter(n => n.type === 'follow' && !n.isRead).length,
|
||||
system: notifications.filter(n => n.type === 'system' && !n.isRead).length,
|
||||
}
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('标记通知为已读失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 标记所有通知为已读
|
||||
markAllNotificationsAsRead: async () => {
|
||||
try {
|
||||
await notificationService.markAllAsRead();
|
||||
|
||||
set(state => ({
|
||||
notifications: state.notifications.map(n => ({ ...n, isRead: true })),
|
||||
notificationBadge: {
|
||||
total: 0,
|
||||
likes: 0,
|
||||
comments: 0,
|
||||
follows: 0,
|
||||
system: 0,
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('标记所有通知为已读失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 获取通知角标
|
||||
fetchNotificationBadge: async () => {
|
||||
try {
|
||||
const badge = await notificationService.getNotificationBadge();
|
||||
set({ notificationBadge: badge });
|
||||
} catch (error) {
|
||||
console.error('获取通知角标失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 设置消息未读数
|
||||
// 注意:未读数现在由 MessageManager 统一管理
|
||||
// 此方法保留用于 TabBar 角标显示,从 MessageManager 同步
|
||||
setMessageUnreadCount: (count: number) => {
|
||||
set({ messageUnreadCount: count });
|
||||
},
|
||||
|
||||
// 从后端拉取最新消息未读数
|
||||
// @deprecated 请使用 messageManager.fetchUnreadCount() 代替
|
||||
// 此方法保留用于向后兼容
|
||||
fetchMessageUnreadCount: async () => {
|
||||
try {
|
||||
await messageManager.fetchUnreadCount();
|
||||
const unread = messageManager.getUnreadCount();
|
||||
set({ messageUnreadCount: unread.total + unread.system });
|
||||
} catch (error) {
|
||||
console.error('获取消息未读数失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
// 添加搜索历史
|
||||
addSearchHistory: (keyword: string) => {
|
||||
set(state => {
|
||||
const history = [keyword, ...state.searchHistory.filter(k => k !== keyword)].slice(0, 20);
|
||||
return { searchHistory: history };
|
||||
});
|
||||
},
|
||||
|
||||
// 清除搜索历史
|
||||
clearSearchHistory: () => {
|
||||
set({ searchHistory: [] });
|
||||
},
|
||||
|
||||
// 点赞帖子 - 乐观更新
|
||||
likePost: async (postId: string) => {
|
||||
console.log('[userStore] likePost called:', postId);
|
||||
|
||||
// 先乐观更新本地状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_liked: true, likes_count: p.likes_count + 1 } : p
|
||||
)
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
const updatedPost = await postService.likePost(postId);
|
||||
if (updatedPost) {
|
||||
console.log('[userStore] likePost success, updated post:', updatedPost);
|
||||
// 使用后端返回的更新后数据更新状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? updatedPost : p
|
||||
)
|
||||
}));
|
||||
} else {
|
||||
// API 返回失败,回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_liked: false, likes_count: Math.max(0, p.likes_count - 1) } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('点赞帖子失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_liked: false, likes_count: Math.max(0, p.likes_count - 1) } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 取消点赞 - 乐观更新
|
||||
unlikePost: async (postId: string) => {
|
||||
console.log('[userStore] unlikePost called:', postId);
|
||||
|
||||
// 先乐观更新本地状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_liked: false, likes_count: Math.max(0, p.likes_count - 1) } : p
|
||||
)
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
const updatedPost = await postService.unlikePost(postId);
|
||||
if (updatedPost) {
|
||||
console.log('[userStore] unlikePost success, updated post:', updatedPost);
|
||||
// 使用后端返回的更新后数据更新状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? updatedPost : p
|
||||
)
|
||||
}));
|
||||
} else {
|
||||
// API 返回失败,回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_liked: true, likes_count: p.likes_count + 1 } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取消点赞帖子失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_liked: true, likes_count: p.likes_count + 1 } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 收藏帖子 - 乐观更新
|
||||
favoritePost: async (postId: string) => {
|
||||
console.log('[userStore] favoritePost called:', postId);
|
||||
|
||||
// 先乐观更新本地状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_favorited: true, favorites_count: p.favorites_count + 1 } : p
|
||||
)
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
const updatedPost = await postService.favoritePost(postId);
|
||||
if (updatedPost) {
|
||||
console.log('[userStore] favoritePost success, updated post:', updatedPost);
|
||||
// 使用后端返回的更新后数据更新状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? updatedPost : p
|
||||
)
|
||||
}));
|
||||
} else {
|
||||
// API 返回失败,回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_favorited: false, favorites_count: Math.max(0, p.favorites_count - 1) } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('收藏帖子失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_favorited: false, favorites_count: Math.max(0, p.favorites_count - 1) } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 取消收藏 - 乐观更新
|
||||
unfavoritePost: async (postId: string) => {
|
||||
console.log('[userStore] unfavoritePost called:', postId);
|
||||
|
||||
// 先乐观更新本地状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_favorited: false, favorites_count: Math.max(0, p.favorites_count - 1) } : p
|
||||
)
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
const updatedPost = await postService.unfavoritePost(postId);
|
||||
if (updatedPost) {
|
||||
console.log('[userStore] unfavoritePost success, updated post:', updatedPost);
|
||||
// 使用后端返回的更新后数据更新状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? updatedPost : p
|
||||
)
|
||||
}));
|
||||
} else {
|
||||
// API 返回失败,回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_favorited: true, favorites_count: p.favorites_count + 1 } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('取消收藏帖子失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p =>
|
||||
p.id === postId ? { ...p, is_favorited: true, favorites_count: p.favorites_count + 1 } : p
|
||||
)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 点赞评论
|
||||
likeComment: async (commentId: string) => {
|
||||
console.log('[userStore] likeComment called:', commentId);
|
||||
// 先更新本地状态(更新posts中的帖子的评论点赞状态)
|
||||
set(state => ({
|
||||
posts: state.posts.map(p => ({
|
||||
...p,
|
||||
top_comment: p.top_comment && p.top_comment.id === commentId
|
||||
? { ...p.top_comment, is_liked: true, likes_count: p.top_comment.likes_count + 1 }
|
||||
: p.top_comment
|
||||
}))
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
await commentService.likeComment(commentId);
|
||||
console.log('[userStore] likeComment success:', commentId);
|
||||
} catch (error) {
|
||||
console.error('点赞评论失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p => ({
|
||||
...p,
|
||||
top_comment: p.top_comment && p.top_comment.id === commentId
|
||||
? { ...p.top_comment, is_liked: false, likes_count: Math.max(0, p.top_comment.likes_count - 1) }
|
||||
: p.top_comment
|
||||
}))
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 取消点赞评论
|
||||
unlikeComment: async (commentId: string) => {
|
||||
console.log('[userStore] unlikeComment called:', commentId);
|
||||
// 先更新本地状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p => ({
|
||||
...p,
|
||||
top_comment: p.top_comment && p.top_comment.id === commentId
|
||||
? { ...p.top_comment, is_liked: false, likes_count: Math.max(0, p.top_comment.likes_count - 1) }
|
||||
: p.top_comment
|
||||
}))
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
await commentService.unlikeComment(commentId);
|
||||
console.log('[userStore] unlikeComment success:', commentId);
|
||||
} catch (error) {
|
||||
console.error('取消点赞评论失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
posts: state.posts.map(p => ({
|
||||
...p,
|
||||
top_comment: p.top_comment && p.top_comment.id === commentId
|
||||
? { ...p.top_comment, is_liked: true, likes_count: p.top_comment.likes_count + 1 }
|
||||
: p.top_comment
|
||||
}))
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 关注用户
|
||||
followUser: async (userId: string) => {
|
||||
// 先更新本地状态
|
||||
set(state => ({
|
||||
users: state.users.map(u =>
|
||||
u.id === userId
|
||||
? { ...u, isFollowing: true, followersCount: u.followers_count + 1 }
|
||||
: u
|
||||
),
|
||||
userCache: Object.fromEntries(
|
||||
Object.entries(state.userCache).map(([id, user]) => [
|
||||
id,
|
||||
id === userId
|
||||
? { ...user, isFollowing: true, followersCount: user.followers_count + 1 }
|
||||
: user
|
||||
])
|
||||
)
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
await authService.followUser(userId);
|
||||
} catch (error) {
|
||||
console.error('关注用户失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
users: state.users.map(u =>
|
||||
u.id === userId
|
||||
? { ...u, isFollowing: false, followersCount: Math.max(0, u.followers_count - 1) }
|
||||
: u
|
||||
),
|
||||
userCache: Object.fromEntries(
|
||||
Object.entries(state.userCache).map(([id, user]) => [
|
||||
id,
|
||||
id === userId
|
||||
? { ...user, isFollowing: false, followersCount: Math.max(0, user.followers_count - 1) }
|
||||
: user
|
||||
])
|
||||
)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 取消关注
|
||||
unfollowUser: async (userId: string) => {
|
||||
// 先更新本地状态
|
||||
set(state => ({
|
||||
users: state.users.map(u =>
|
||||
u.id === userId
|
||||
? { ...u, isFollowing: false, followersCount: Math.max(0, u.followers_count - 1) }
|
||||
: u
|
||||
),
|
||||
userCache: Object.fromEntries(
|
||||
Object.entries(state.userCache).map(([id, user]) => [
|
||||
id,
|
||||
id === userId
|
||||
? { ...user, isFollowing: false, followersCount: Math.max(0, user.followers_count - 1) }
|
||||
: user
|
||||
])
|
||||
)
|
||||
}));
|
||||
|
||||
// 调用API
|
||||
try {
|
||||
await authService.unfollowUser(userId);
|
||||
} catch (error) {
|
||||
console.error('取消关注用户失败:', error);
|
||||
// 失败回滚状态
|
||||
set(state => ({
|
||||
users: state.users.map(u =>
|
||||
u.id === userId
|
||||
? { ...u, isFollowing: true, followersCount: u.followers_count + 1 }
|
||||
: u
|
||||
),
|
||||
userCache: Object.fromEntries(
|
||||
Object.entries(state.userCache).map(([id, user]) => [
|
||||
id,
|
||||
id === userId
|
||||
? { ...user, isFollowing: true, followersCount: user.followers_count + 1 }
|
||||
: user
|
||||
])
|
||||
)
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
// 刷新所有数据
|
||||
refreshAll: async () => {
|
||||
await Promise.all([
|
||||
get().fetchPosts('recommend', 1),
|
||||
get().fetchNotificationBadge(),
|
||||
]);
|
||||
},
|
||||
}));
|
||||
|
||||
// 导出selector hooks以优化性能
|
||||
export const usePosts = () => useUserStore((state) => state.posts);
|
||||
export const useNotifications = () => useUserStore((state) => state.notifications);
|
||||
export const useNotificationBadge = () => useUserStore((state) => state.notificationBadge);
|
||||
export const useMessageUnreadCount = () => useUserStore((state) => state.messageUnreadCount);
|
||||
export const useSearchHistory = () => useUserStore((state) => state.searchHistory);
|
||||
export const useIsLoadingPosts = () => useUserStore((state) => state.isLoadingPosts);
|
||||
Reference in New Issue
Block a user