Files
frontend/src/stores/userStore.ts
lafay 4b5ce1ba21
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 4m47s
Frontend CI / ota-android (push) Successful in 10m48s
Frontend CI / build-android-apk (push) Successful in 1h36m37s
refactor(platform): extract blurActiveElement to infrastructure module
- 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
2026-04-11 22:35:11 +08:00

327 lines
9.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 用户状态管理
* 使用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);