2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 用户状态管理
|
|
|
|
|
|
* 使用Zustand进行状态管理
|
|
|
|
|
|
* 对接真实后端API
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { create } from 'zustand';
|
|
|
|
|
|
import { User, Post, Notification, NotificationBadge } from '../types';
|
|
|
|
|
|
import { PaginatedData } from '../services/api';
|
|
|
|
|
|
import {
|
|
|
|
|
|
authService,
|
|
|
|
|
|
postService,
|
|
|
|
|
|
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[]>;
|
2026-03-24 05:18:22 +08:00
|
|
|
|
fetchPosts: (type: 'follow' | 'hot' | 'latest', page?: number) => Promise<PaginatedData<Post>>;
|
2026-03-09 21:29:03 +08:00
|
|
|
|
fetchNotifications: (type?: string, page?: number) => Promise<Notification[]>;
|
|
|
|
|
|
markNotificationAsRead: (notificationId: string) => Promise<void>;
|
|
|
|
|
|
markAllNotificationsAsRead: () => Promise<void>;
|
|
|
|
|
|
fetchNotificationBadge: () => Promise<void>;
|
|
|
|
|
|
setMessageUnreadCount: (count: number) => void;
|
|
|
|
|
|
addSearchHistory: (keyword: string) => void;
|
|
|
|
|
|
clearSearchHistory: () => void;
|
|
|
|
|
|
|
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
|
|
|
|
// 关注用户操作(authService 不管理状态,所以保留在 store)
|
2026-03-09 21:29:03 +08:00
|
|
|
|
followUser: (userId: string) => Promise<void>;
|
|
|
|
|
|
unfollowUser: (userId: string) => Promise<void>;
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新数据
|
|
|
|
|
|
refreshAll: () => Promise<void>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
export const useUserStore = create<UserState>((set, get) => {
|
|
|
|
|
|
return {
|
|
|
|
|
|
// 初始状态
|
|
|
|
|
|
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;
|
|
|
|
|
|
},
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
// 获取用户帖子
|
|
|
|
|
|
fetchUserPosts: async (userId: string, page = 1) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await postService.getUserPosts(userId, page);
|
|
|
|
|
|
const newPosts = response.list;
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
set(state => ({
|
2026-03-18 12:11:49 +08:00
|
|
|
|
posts: page === 1 ? newPosts : [...state.posts, ...newPosts]
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}));
|
2026-03-18 12:11:49 +08:00
|
|
|
|
|
|
|
|
|
|
return newPosts;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户帖子失败:', error);
|
|
|
|
|
|
return [];
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
2026-03-18 12:11:49 +08:00
|
|
|
|
},
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
// 获取帖子列表(首页)
|
2026-03-24 05:18:22 +08:00
|
|
|
|
fetchPosts: async (type: 'follow' | 'hot' | 'latest', page = 1) => {
|
2026-03-18 12:11:49 +08:00
|
|
|
|
set({ isLoadingPosts: true });
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
try {
|
|
|
|
|
|
let response;
|
|
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
|
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,
|
|
|
|
|
|
};
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
2026-03-18 12:11:49 +08:00
|
|
|
|
},
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
// 获取通知列表
|
|
|
|
|
|
fetchNotifications: async (type?: string, page = 1) => {
|
|
|
|
|
|
set({ isLoadingNotifications: true });
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const response = await notificationService.getNotifications(
|
|
|
|
|
|
page,
|
|
|
|
|
|
20,
|
|
|
|
|
|
type as any
|
2026-03-09 21:29:03 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
const newNotifications = response.list;
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
set(state => ({
|
2026-03-18 12:11:49 +08:00
|
|
|
|
notifications: page === 1 ? newNotifications : [...state.notifications, ...newNotifications],
|
|
|
|
|
|
isLoadingNotifications: false
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}));
|
2026-03-18 12:11:49 +08:00
|
|
|
|
|
|
|
|
|
|
return newNotifications;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取通知列表失败:', error);
|
|
|
|
|
|
set({ isLoadingNotifications: false });
|
|
|
|
|
|
return [];
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
2026-03-18 12:11:49 +08:00
|
|
|
|
},
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
// 标记通知为已读
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
set(state => ({
|
2026-03-18 12:11:49 +08:00
|
|
|
|
notifications: state.notifications.map(n => ({ ...n, isRead: true })),
|
|
|
|
|
|
notificationBadge: {
|
|
|
|
|
|
total: 0,
|
|
|
|
|
|
likes: 0,
|
|
|
|
|
|
comments: 0,
|
|
|
|
|
|
follows: 0,
|
|
|
|
|
|
system: 0,
|
|
|
|
|
|
}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}));
|
2026-03-18 12:11:49 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('标记所有通知为已读失败:', error);
|
2026-03-09 21:29:03 +08:00
|
|
|
|
}
|
2026-03-18 12:11:49 +08:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 获取通知角标
|
|
|
|
|
|
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 });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 添加搜索历史
|
|
|
|
|
|
addSearchHistory: (keyword: string) => {
|
|
|
|
|
|
set(state => {
|
|
|
|
|
|
const history = [keyword, ...state.searchHistory.filter(k => k !== keyword)].slice(0, 20);
|
|
|
|
|
|
return { searchHistory: history };
|
|
|
|
|
|
});
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 清除搜索历史
|
|
|
|
|
|
clearSearchHistory: () => {
|
|
|
|
|
|
set({ searchHistory: [] });
|
|
|
|
|
|
},
|
|
|
|
|
|
|
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
|
|
|
|
// 关注用户 - authService 不管理状态,所以由 store 管理
|
2026-03-18 12:11:49 +08:00
|
|
|
|
followUser: async (userId: string) => {
|
|
|
|
|
|
const originalUsers = get().users;
|
|
|
|
|
|
const originalUserCache = get().userCache;
|
2026-04-11 22:35:11 +08:00
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
set(state => ({
|
|
|
|
|
|
users: state.users.map(u =>
|
|
|
|
|
|
u.id === userId
|
2026-03-18 12:11:49 +08:00
|
|
|
|
? { ...u, isFollowing: true, followersCount: u.followers_count + 1 }
|
2026-03-09 21:29:03 +08:00
|
|
|
|
: u
|
|
|
|
|
|
),
|
|
|
|
|
|
userCache: Object.fromEntries(
|
|
|
|
|
|
Object.entries(state.userCache).map(([id, user]) => [
|
|
|
|
|
|
id,
|
|
|
|
|
|
id === userId
|
2026-03-18 12:11:49 +08:00
|
|
|
|
? { ...user, isFollowing: true, followersCount: user.followers_count + 1 }
|
2026-03-09 21:29:03 +08:00
|
|
|
|
: user
|
|
|
|
|
|
])
|
|
|
|
|
|
)
|
|
|
|
|
|
}));
|
2026-04-11 22:35:11 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await authService.followUser(userId);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('关注用户失败:', error);
|
2026-04-11 22:35:11 +08:00
|
|
|
|
set({ users: originalUsers, userCache: originalUserCache });
|
2026-03-18 12:11:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
unfollowUser: async (userId: string) => {
|
|
|
|
|
|
const originalUsers = get().users;
|
|
|
|
|
|
const originalUserCache = get().userCache;
|
2026-04-11 22:35:11 +08:00
|
|
|
|
|
2026-03-09 21:29:03 +08:00
|
|
|
|
set(state => ({
|
|
|
|
|
|
users: state.users.map(u =>
|
|
|
|
|
|
u.id === userId
|
2026-03-18 12:11:49 +08:00
|
|
|
|
? { ...u, isFollowing: false, followersCount: Math.max(0, u.followers_count - 1) }
|
2026-03-09 21:29:03 +08:00
|
|
|
|
: u
|
|
|
|
|
|
),
|
|
|
|
|
|
userCache: Object.fromEntries(
|
|
|
|
|
|
Object.entries(state.userCache).map(([id, user]) => [
|
|
|
|
|
|
id,
|
|
|
|
|
|
id === userId
|
2026-03-18 12:11:49 +08:00
|
|
|
|
? { ...user, isFollowing: false, followersCount: Math.max(0, user.followers_count - 1) }
|
2026-03-09 21:29:03 +08:00
|
|
|
|
: user
|
|
|
|
|
|
])
|
|
|
|
|
|
)
|
|
|
|
|
|
}));
|
2026-04-11 22:35:11 +08:00
|
|
|
|
|
2026-03-18 12:11:49 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await authService.unfollowUser(userId);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('取消关注用户失败:', error);
|
2026-04-11 22:35:11 +08:00
|
|
|
|
set({ users: originalUsers, userCache: originalUserCache });
|
2026-03-18 12:11:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
|
|
// 刷新所有数据
|
|
|
|
|
|
refreshAll: async () => {
|
|
|
|
|
|
await Promise.all([
|
2026-03-24 05:18:22 +08:00
|
|
|
|
get().fetchPosts('latest', 1),
|
2026-03-18 12:11:49 +08:00
|
|
|
|
get().fetchNotificationBadge(),
|
|
|
|
|
|
]);
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
2026-03-09 21:29:03 +08:00
|
|
|
|
|
|
|
|
|
|
// 导出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);
|