2026-03-09 21:29:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 用户主页 UserScreen(响应式适配)
|
|
|
|
|
|
* 胡萝卜BBS - 查看其他用户资料
|
|
|
|
|
|
* 支持桌面端双栏布局
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
|
|
|
|
import {
|
|
|
|
|
|
View,
|
|
|
|
|
|
FlatList,
|
|
|
|
|
|
StyleSheet,
|
|
|
|
|
|
RefreshControl,
|
|
|
|
|
|
ScrollView,
|
|
|
|
|
|
Alert,
|
|
|
|
|
|
} from 'react-native';
|
|
|
|
|
|
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
|
|
|
import { useNavigation, useRoute, RouteProp } from '@react-navigation/native';
|
|
|
|
|
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
|
|
|
|
import { colors, spacing } from '../../theme';
|
|
|
|
|
|
import { Post, User } from '../../types';
|
|
|
|
|
|
import { useUserStore } from '../../stores';
|
|
|
|
|
|
import { useCurrentUser } from '../../stores/authStore';
|
|
|
|
|
|
import { authService, postService, messageService } from '../../services';
|
|
|
|
|
|
import { userManager } from '../../stores/userManager';
|
|
|
|
|
|
import { UserProfileHeader, PostCard, TabBar } from '../../components/business';
|
|
|
|
|
|
import { Loading, EmptyState, ResponsiveContainer } from '../../components/common';
|
|
|
|
|
|
import { useResponsive } from '../../hooks';
|
|
|
|
|
|
import { HomeStackParamList, RootStackParamList } from '../../navigation/types';
|
|
|
|
|
|
|
|
|
|
|
|
type NavigationProp = NativeStackNavigationProp<HomeStackParamList, 'UserProfile'>;
|
|
|
|
|
|
type UserRouteProp = RouteProp<HomeStackParamList, 'UserProfile'>;
|
|
|
|
|
|
|
|
|
|
|
|
const TABS = ['帖子', '收藏'];
|
|
|
|
|
|
const TAB_ICONS = ['file-document-outline', 'bookmark-outline'];
|
|
|
|
|
|
|
|
|
|
|
|
export const UserScreen: React.FC = () => {
|
|
|
|
|
|
const navigation = useNavigation<NavigationProp>();
|
|
|
|
|
|
const route = useRoute<UserRouteProp>();
|
|
|
|
|
|
const userId = route.params?.userId || '';
|
|
|
|
|
|
|
|
|
|
|
|
// 使用 any 类型来访问根导航
|
|
|
|
|
|
const rootNavigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
|
|
|
|
|
|
|
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
|
|
|
|
const { followUser, unfollowUser, posts: storePosts } = useUserStore();
|
2026-03-09 21:29:03 +08:00
|
|
|
|
const currentUser = useCurrentUser();
|
|
|
|
|
|
|
|
|
|
|
|
// 响应式布局
|
|
|
|
|
|
const { isDesktop, isTablet } = useResponsive();
|
|
|
|
|
|
|
|
|
|
|
|
const [user, setUser] = useState<User | null>(null);
|
|
|
|
|
|
const [posts, setPosts] = useState<Post[]>([]);
|
|
|
|
|
|
const [favorites, setFavorites] = useState<Post[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
const [refreshing, setRefreshing] = useState(false);
|
|
|
|
|
|
const [activeTab, setActiveTab] = useState(0);
|
|
|
|
|
|
const [isBlocked, setIsBlocked] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载用户信息
|
|
|
|
|
|
const loadUserData = useCallback(async (forceRefresh = false) => {
|
|
|
|
|
|
if (!userId) {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 强制从服务器获取最新数据,确保关注状态是最新的
|
|
|
|
|
|
const userData = await userManager.getUserById(userId, forceRefresh);
|
|
|
|
|
|
setUser(userData || null);
|
|
|
|
|
|
const blockStatus = await authService.getBlockStatus(userId);
|
|
|
|
|
|
setIsBlocked(blockStatus);
|
|
|
|
|
|
|
|
|
|
|
|
const response = await postService.getUserPosts(userId);
|
|
|
|
|
|
setPosts(response.list);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('加载用户数据失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}, [userId]);
|
|
|
|
|
|
|
|
|
|
|
|
// 加载用户收藏
|
|
|
|
|
|
const loadUserFavorites = useCallback(async () => {
|
|
|
|
|
|
if (!userId) return;
|
|
|
|
|
|
try {
|
|
|
|
|
|
const response = await postService.getUserFavorites(userId);
|
|
|
|
|
|
setFavorites(response.list);
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取用户收藏失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [userId]);
|
|
|
|
|
|
|
|
|
|
|
|
// 监听 tab 切换
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (activeTab === 1) {
|
|
|
|
|
|
loadUserFavorites();
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [activeTab, loadUserFavorites]);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
// 首次加载时强制刷新,确保关注状态是最新的
|
|
|
|
|
|
loadUserData(true);
|
|
|
|
|
|
}, [loadUserData]);
|
|
|
|
|
|
|
|
|
|
|
|
// 同步 store 中的帖子状态到本地(用于点赞、收藏等状态更新)
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
// 同步帖子列表状态
|
|
|
|
|
|
if (posts.length > 0) {
|
|
|
|
|
|
let hasChanges = false;
|
|
|
|
|
|
const updatedPosts = posts.map(localPost => {
|
|
|
|
|
|
const storePost = storePosts.find(sp => sp.id === localPost.id);
|
|
|
|
|
|
if (storePost && (
|
|
|
|
|
|
storePost.is_liked !== localPost.is_liked ||
|
|
|
|
|
|
storePost.is_favorited !== localPost.is_favorited ||
|
|
|
|
|
|
storePost.likes_count !== localPost.likes_count ||
|
|
|
|
|
|
storePost.favorites_count !== localPost.favorites_count
|
|
|
|
|
|
)) {
|
|
|
|
|
|
hasChanges = true;
|
|
|
|
|
|
return {
|
|
|
|
|
|
...localPost,
|
|
|
|
|
|
is_liked: storePost.is_liked,
|
|
|
|
|
|
is_favorited: storePost.is_favorited,
|
|
|
|
|
|
likes_count: storePost.likes_count,
|
|
|
|
|
|
favorites_count: storePost.favorites_count,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return localPost;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (hasChanges) {
|
|
|
|
|
|
setPosts(updatedPosts);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 同步收藏列表状态
|
|
|
|
|
|
if (favorites.length > 0) {
|
|
|
|
|
|
let hasChanges = false;
|
|
|
|
|
|
const updatedFavorites = favorites.map(localPost => {
|
|
|
|
|
|
const storePost = storePosts.find(sp => sp.id === localPost.id);
|
|
|
|
|
|
if (storePost && (
|
|
|
|
|
|
storePost.is_liked !== localPost.is_liked ||
|
|
|
|
|
|
storePost.is_favorited !== localPost.is_favorited ||
|
|
|
|
|
|
storePost.likes_count !== localPost.likes_count ||
|
|
|
|
|
|
storePost.favorites_count !== localPost.favorites_count
|
|
|
|
|
|
)) {
|
|
|
|
|
|
hasChanges = true;
|
|
|
|
|
|
return {
|
|
|
|
|
|
...localPost,
|
|
|
|
|
|
is_liked: storePost.is_liked,
|
|
|
|
|
|
is_favorited: storePost.is_favorited,
|
|
|
|
|
|
likes_count: storePost.likes_count,
|
|
|
|
|
|
favorites_count: storePost.favorites_count,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
return localPost;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (hasChanges) {
|
|
|
|
|
|
setFavorites(updatedFavorites);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [storePosts]);
|
|
|
|
|
|
|
|
|
|
|
|
// 下拉刷新
|
|
|
|
|
|
const onRefresh = useCallback(() => {
|
|
|
|
|
|
setRefreshing(true);
|
|
|
|
|
|
loadUserData(true);
|
|
|
|
|
|
setRefreshing(false);
|
|
|
|
|
|
}, [loadUserData]);
|
|
|
|
|
|
|
|
|
|
|
|
// 关注/取消关注
|
|
|
|
|
|
const handleFollow = () => {
|
|
|
|
|
|
if (!user) return;
|
|
|
|
|
|
if (user.is_following) {
|
|
|
|
|
|
unfollowUser(user.id);
|
|
|
|
|
|
setUser({ ...user, is_following: false, followers_count: user.followers_count - 1 });
|
|
|
|
|
|
} else {
|
|
|
|
|
|
followUser(user.id);
|
|
|
|
|
|
setUser({ ...user, is_following: true, followers_count: user.followers_count + 1 });
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到关注列表
|
|
|
|
|
|
const handleFollowingPress = () => {
|
|
|
|
|
|
(rootNavigation as any).navigate('FollowList', { userId, type: 'following' });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到粉丝列表
|
|
|
|
|
|
const handleFollowersPress = () => {
|
|
|
|
|
|
(rootNavigation as any).navigate('FollowList', { userId, type: 'followers' });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到帖子详情
|
|
|
|
|
|
const handlePostPress = (postId: string, scrollToComments: boolean = false) => {
|
|
|
|
|
|
navigation.navigate('PostDetail', { postId, scrollToComments });
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到用户主页(这里不做处理)
|
|
|
|
|
|
const handleUserPress = (postUserId: string) => {
|
|
|
|
|
|
if (postUserId !== userId) {
|
|
|
|
|
|
navigation.push('UserProfile', { userId: postUserId });
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 删除帖子
|
|
|
|
|
|
const handleDeletePost = async (postId: string) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const success = await postService.deletePost(postId);
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
// 从帖子列表中移除
|
|
|
|
|
|
setPosts(prev => prev.filter(p => p.id !== postId));
|
|
|
|
|
|
// 也从收藏列表中移除(如果存在)
|
|
|
|
|
|
setFavorites(prev => prev.filter(p => p.id !== postId));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('删除帖子失败');
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('删除帖子失败:', error);
|
|
|
|
|
|
throw error;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 跳转到聊天界面
|
|
|
|
|
|
const handleMessage = async () => {
|
|
|
|
|
|
if (!user) return;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 前端只提供对方的用户ID,会话ID由后端生成
|
|
|
|
|
|
const conversation = await messageService.createConversation(user.id);
|
|
|
|
|
|
if (conversation) {
|
|
|
|
|
|
// 跳转到聊天界面 - 使用 rootNavigation 确保在正确的导航栈中
|
|
|
|
|
|
(rootNavigation as any).navigate('Chat', {
|
|
|
|
|
|
conversationId: conversation.id.toString(),
|
|
|
|
|
|
userId: user.id
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('创建会话失败:', error);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 处理更多按钮点击
|
|
|
|
|
|
const handleMore = () => {
|
|
|
|
|
|
if (!user) return;
|
|
|
|
|
|
|
|
|
|
|
|
Alert.alert(
|
|
|
|
|
|
'更多操作',
|
|
|
|
|
|
undefined,
|
|
|
|
|
|
[
|
|
|
|
|
|
{ text: '取消', style: 'cancel' },
|
|
|
|
|
|
{
|
|
|
|
|
|
text: isBlocked ? '取消拉黑' : '拉黑用户',
|
|
|
|
|
|
style: 'destructive',
|
|
|
|
|
|
onPress: () => {
|
|
|
|
|
|
Alert.alert(
|
|
|
|
|
|
isBlocked ? '确认取消拉黑' : '确认拉黑',
|
|
|
|
|
|
isBlocked
|
|
|
|
|
|
? '取消拉黑后,对方可以重新与你建立关系。'
|
|
|
|
|
|
: '拉黑后,对方将无法给你发送私聊消息,且会互相移除关注关系。',
|
|
|
|
|
|
[
|
|
|
|
|
|
{ text: '取消', style: 'cancel' },
|
|
|
|
|
|
{
|
|
|
|
|
|
text: '确定',
|
|
|
|
|
|
style: 'destructive',
|
|
|
|
|
|
onPress: async () => {
|
|
|
|
|
|
const ok = isBlocked
|
|
|
|
|
|
? await authService.unblockUser(user.id)
|
|
|
|
|
|
: await authService.blockUser(user.id);
|
|
|
|
|
|
if (!ok) {
|
|
|
|
|
|
Alert.alert('失败', isBlocked ? '取消拉黑失败,请稍后重试' : '拉黑失败,请稍后重试');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
setUser(prev =>
|
|
|
|
|
|
prev
|
|
|
|
|
|
? {
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
is_following: false,
|
|
|
|
|
|
is_following_me: false,
|
|
|
|
|
|
}
|
|
|
|
|
|
: prev
|
|
|
|
|
|
);
|
|
|
|
|
|
setIsBlocked(!isBlocked);
|
|
|
|
|
|
Alert.alert('成功', isBlocked ? '已取消拉黑' : '已拉黑该用户');
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
]
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染内容
|
|
|
|
|
|
const renderContent = () => {
|
|
|
|
|
|
if (loading) return <Loading />;
|
|
|
|
|
|
|
|
|
|
|
|
if (activeTab === 0) {
|
|
|
|
|
|
// 帖子
|
|
|
|
|
|
if (posts.length === 0) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<EmptyState
|
|
|
|
|
|
title="还没有帖子"
|
|
|
|
|
|
description="这个用户还没有发布任何帖子"
|
|
|
|
|
|
icon="file-document-edit-outline"
|
|
|
|
|
|
variant="modern"
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View style={styles.postsContainer}>
|
|
|
|
|
|
{posts.map((post, index) => {
|
|
|
|
|
|
const isPostAuthor = currentUser?.id === post.author?.id;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View key={post.id} style={[
|
|
|
|
|
|
styles.postWrapper,
|
|
|
|
|
|
index === posts.length - 1 && styles.lastPost,
|
|
|
|
|
|
]}>
|
|
|
|
|
|
<PostCard
|
|
|
|
|
|
post={post}
|
|
|
|
|
|
onPress={() => handlePostPress(post.id)}
|
|
|
|
|
|
onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}}
|
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
|
|
|
|
onLike={() => post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onComment={() => handlePostPress(post.id, true)}
|
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
|
|
|
|
onBookmark={() => post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onShare={() => {}}
|
|
|
|
|
|
onDelete={() => handleDeletePost(post.id)}
|
|
|
|
|
|
isPostAuthor={isPostAuthor}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (activeTab === 1) {
|
|
|
|
|
|
// 收藏
|
|
|
|
|
|
if (favorites.length === 0) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<EmptyState
|
|
|
|
|
|
title="还没有收藏"
|
|
|
|
|
|
description="这个用户还没有收藏任何帖子"
|
|
|
|
|
|
icon="bookmark-heart-outline"
|
|
|
|
|
|
variant="modern"
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View style={styles.postsContainer}>
|
|
|
|
|
|
{favorites.map((post, index) => {
|
|
|
|
|
|
const isPostAuthor = currentUser?.id === post.author?.id;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<View key={post.id} style={[
|
|
|
|
|
|
styles.postWrapper,
|
|
|
|
|
|
index === favorites.length - 1 && styles.lastPost,
|
|
|
|
|
|
]}>
|
|
|
|
|
|
<PostCard
|
|
|
|
|
|
post={post}
|
|
|
|
|
|
onPress={() => handlePostPress(post.id)}
|
|
|
|
|
|
onUserPress={() => post.author ? handleUserPress(post.author.id) : () => {}}
|
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
|
|
|
|
onLike={() => post.is_liked ? postService.unlikePost(post.id) : postService.likePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onComment={() => handlePostPress(post.id, true)}
|
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
|
|
|
|
onBookmark={() => post.is_favorited ? postService.unfavoritePost(post.id) : postService.favoritePost(post.id)}
|
2026-03-09 21:29:03 +08:00
|
|
|
|
onShare={() => {}}
|
|
|
|
|
|
onDelete={() => handleDeletePost(post.id)}
|
|
|
|
|
|
isPostAuthor={isPostAuthor}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染用户信息头部
|
|
|
|
|
|
const renderUserHeader = () => {
|
|
|
|
|
|
if (!user) return null;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<UserProfileHeader
|
|
|
|
|
|
user={user}
|
|
|
|
|
|
isCurrentUser={false}
|
|
|
|
|
|
onFollow={handleFollow}
|
|
|
|
|
|
onMessage={handleMessage}
|
|
|
|
|
|
onMore={handleMore}
|
|
|
|
|
|
onFollowingPress={handleFollowingPress}
|
|
|
|
|
|
onFollowersPress={handleFollowersPress}
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染 TabBar 和内容
|
|
|
|
|
|
const renderTabBarAndContent = () => (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<View style={styles.tabBarContainer}>
|
|
|
|
|
|
<TabBar
|
|
|
|
|
|
tabs={TABS}
|
|
|
|
|
|
activeIndex={activeTab}
|
|
|
|
|
|
onTabChange={setActiveTab}
|
|
|
|
|
|
variant="modern"
|
|
|
|
|
|
icons={TAB_ICONS}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<View style={styles.contentContainer}>
|
|
|
|
|
|
{renderContent()}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return <Loading fullScreen />;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!user) {
|
|
|
|
|
|
return (
|
2026-03-18 10:58:41 +08:00
|
|
|
|
<SafeAreaView style={styles.container} edges={['bottom']}>
|
2026-03-09 21:29:03 +08:00
|
|
|
|
<EmptyState
|
|
|
|
|
|
title="用户不存在"
|
|
|
|
|
|
description="该用户可能已被删除"
|
|
|
|
|
|
icon="account-off-outline"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 桌面端使用双栏布局
|
|
|
|
|
|
if (isDesktop || isTablet) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
|
|
|
|
<ResponsiveContainer maxWidth={1400}>
|
|
|
|
|
|
<View style={styles.desktopContainer}>
|
|
|
|
|
|
{/* 左侧:用户信息 */}
|
|
|
|
|
|
<View style={styles.desktopSidebar}>
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
|
refreshControl={
|
|
|
|
|
|
<RefreshControl
|
|
|
|
|
|
refreshing={refreshing}
|
|
|
|
|
|
onRefresh={onRefresh}
|
|
|
|
|
|
colors={[colors.primary.main]}
|
|
|
|
|
|
tintColor={colors.primary.main}
|
|
|
|
|
|
/>
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
{renderUserHeader()}
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 右侧:帖子列表 */}
|
|
|
|
|
|
<View style={styles.desktopContent}>
|
|
|
|
|
|
<ScrollView
|
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
|
contentContainerStyle={styles.desktopScrollContent}
|
|
|
|
|
|
>
|
|
|
|
|
|
{renderTabBarAndContent()}
|
|
|
|
|
|
</ScrollView>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</ResponsiveContainer>
|
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 移动端使用单栏布局
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SafeAreaView style={styles.container} edges={['bottom']}>
|
|
|
|
|
|
<FlatList
|
|
|
|
|
|
data={[{ key: 'header' }]}
|
|
|
|
|
|
renderItem={({ item }) => (
|
|
|
|
|
|
<View>
|
|
|
|
|
|
{renderUserHeader()}
|
|
|
|
|
|
<View style={styles.tabBarContainer}>
|
|
|
|
|
|
<TabBar
|
|
|
|
|
|
tabs={TABS}
|
|
|
|
|
|
activeIndex={activeTab}
|
|
|
|
|
|
onTabChange={setActiveTab}
|
|
|
|
|
|
variant="modern"
|
|
|
|
|
|
icons={TAB_ICONS}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
<View style={styles.contentContainer}>
|
|
|
|
|
|
{renderContent()}
|
|
|
|
|
|
</View>
|
|
|
|
|
|
</View>
|
|
|
|
|
|
)}
|
|
|
|
|
|
keyExtractor={item => item.key}
|
|
|
|
|
|
showsVerticalScrollIndicator={false}
|
|
|
|
|
|
refreshControl={
|
|
|
|
|
|
<RefreshControl
|
|
|
|
|
|
refreshing={refreshing}
|
|
|
|
|
|
onRefresh={onRefresh}
|
|
|
|
|
|
colors={[colors.primary.main]}
|
|
|
|
|
|
tintColor={colors.primary.main}
|
|
|
|
|
|
/>
|
|
|
|
|
|
}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</SafeAreaView>
|
|
|
|
|
|
);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const styles = StyleSheet.create({
|
|
|
|
|
|
container: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
backgroundColor: colors.background.default,
|
|
|
|
|
|
},
|
|
|
|
|
|
// 桌面端双栏布局
|
|
|
|
|
|
desktopContainer: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
flexDirection: 'row',
|
|
|
|
|
|
gap: spacing.lg,
|
|
|
|
|
|
padding: spacing.lg,
|
|
|
|
|
|
},
|
|
|
|
|
|
desktopSidebar: {
|
|
|
|
|
|
width: 380,
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
desktopContent: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
minWidth: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
desktopScrollContent: {
|
|
|
|
|
|
flexGrow: 1,
|
|
|
|
|
|
},
|
|
|
|
|
|
tabBarContainer: {
|
|
|
|
|
|
marginTop: spacing.sm,
|
|
|
|
|
|
marginBottom: spacing.xs,
|
|
|
|
|
|
},
|
|
|
|
|
|
contentContainer: {
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
minHeight: 350,
|
|
|
|
|
|
paddingTop: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
postsContainer: {
|
|
|
|
|
|
paddingHorizontal: spacing.md,
|
|
|
|
|
|
paddingTop: spacing.sm,
|
|
|
|
|
|
},
|
|
|
|
|
|
postWrapper: {
|
|
|
|
|
|
marginBottom: spacing.md,
|
|
|
|
|
|
backgroundColor: colors.background.paper,
|
|
|
|
|
|
borderRadius: 16,
|
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
|
shadowColor: '#000',
|
|
|
|
|
|
shadowOffset: { width: 0, height: 2 },
|
|
|
|
|
|
shadowOpacity: 0.06,
|
|
|
|
|
|
shadowRadius: 8,
|
|
|
|
|
|
elevation: 2,
|
|
|
|
|
|
},
|
|
|
|
|
|
lastPost: {
|
|
|
|
|
|
marginBottom: spacing['2xl'],
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
export default UserScreen;
|