- Centralize duplicated Platform.OS === 'web' blur logic across 16 components - Add blurActiveElement utility to src/infrastructure/platform module - Optimize MessageBubble and ImageSegment with React.memo custom comparators - Add useMemo for computed values in MessageSegmentsRenderer - Update DTO types with is_system_notice and notice_content fields - Fix keyboard dismissal order in useChatScreen handleDismiss - Simplify userStore follow/unfollow state updates - Remove unnecessary TypeScript type assertions throughout codebase
327 lines
9.7 KiB
TypeScript
327 lines
9.7 KiB
TypeScript
/**
|
||
* 用户状态管理
|
||
* 使用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[]>;
|
||
fetchPosts: (type: '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;
|
||
addSearchHistory: (keyword: string) => void;
|
||
clearSearchHistory: () => void;
|
||
|
||
// 关注用户操作(authService 不管理状态,所以保留在 store)
|
||
followUser: (userId: string) => Promise<void>;
|
||
unfollowUser: (userId: string) => Promise<void>;
|
||
|
||
// 刷新数据
|
||
refreshAll: () => Promise<void>;
|
||
}
|
||
|
||
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;
|
||
},
|
||
|
||
// 获取用户帖子
|
||
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: 'follow' | 'hot' | 'latest', page = 1) => {
|
||
set({ isLoadingPosts: true });
|
||
|
||
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,
|
||
};
|
||
}
|
||
},
|
||
|
||
// 获取通知列表
|
||
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 });
|
||
},
|
||
|
||
// 添加搜索历史
|
||
addSearchHistory: (keyword: string) => {
|
||
set(state => {
|
||
const history = [keyword, ...state.searchHistory.filter(k => k !== keyword)].slice(0, 20);
|
||
return { searchHistory: history };
|
||
});
|
||
},
|
||
|
||
// 清除搜索历史
|
||
clearSearchHistory: () => {
|
||
set({ searchHistory: [] });
|
||
},
|
||
|
||
// 关注用户 - authService 不管理状态,所以由 store 管理
|
||
followUser: async (userId: string) => {
|
||
const originalUsers = get().users;
|
||
const originalUserCache = get().userCache;
|
||
|
||
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
|
||
])
|
||
)
|
||
}));
|
||
|
||
try {
|
||
await authService.followUser(userId);
|
||
} catch (error) {
|
||
console.error('关注用户失败:', error);
|
||
set({ users: originalUsers, userCache: originalUserCache });
|
||
}
|
||
},
|
||
|
||
unfollowUser: async (userId: string) => {
|
||
const originalUsers = get().users;
|
||
const originalUserCache = get().userCache;
|
||
|
||
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
|
||
])
|
||
)
|
||
}));
|
||
|
||
try {
|
||
await authService.unfollowUser(userId);
|
||
} catch (error) {
|
||
console.error('取消关注用户失败:', error);
|
||
set({ users: originalUsers, userCache: originalUserCache });
|
||
}
|
||
},
|
||
|
||
// 刷新所有数据
|
||
refreshAll: async () => {
|
||
await Promise.all([
|
||
get().fetchPosts('latest', 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);
|