Files
frontend/src/screens/profile/UserProfileScreen.tsx
lafay b91e78c921
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m44s
Frontend CI / ota-android (push) Successful in 11m56s
Frontend CI / build-android-apk (push) Successful in 49m3s
refactor(PostCard, PostCardGrid, HomeScreen): enhance PostCard component and remove PostCardGrid
- Introduced a new PostCardRelativeTime component for dynamic time display, improving user experience with real-time updates.
- Refactored PostCard to utilize memoization for performance optimization and prevent unnecessary re-renders.
- Removed the PostCardGrid component to streamline the codebase, consolidating functionality within the PostCard component.
- Updated HomeScreen to leverage the new PostCard features, ensuring consistent post rendering and interaction handling.
2026-03-24 05:52:24 +08:00

236 lines
7.3 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.
/**
* 统一的用户主页组件
* 胡萝卜BBS - 支持两种模式self当前用户和 other其他用户
* 采用现代卡片式设计,优化视觉层次和交互体验
* 支持桌面端双栏布局
*/
import React, { useCallback, useMemo } from 'react';
import {
View,
RefreshControl,
ScrollView,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { NavigationProp } from '@react-navigation/native';
import { colors } from '../../theme';
import { Post } from '../../types';
import { PostCard, TabBar, UserProfileHeader } from '../../components/business';
import { Loading, EmptyState, ResponsiveContainer } from '../../components/common';
import { useResponsive } from '../../hooks';
import { ProfileStackParamList } from '../../navigation/types';
import { useUserProfile, ProfileMode, TABS, TAB_ICONS, sharedStyles } from './useUserProfile';
interface UserProfileScreenProps {
mode: ProfileMode;
userId?: string; // 仅 other 模式需要
/** 使用 NavigationProp 避免与具体 screen 的 NativeStackNavigationProp 在 setParams 上不兼容 */
profileNavigation?: NavigationProp<ProfileStackParamList>; // 仅 self 模式需要
}
export const UserProfileScreen: React.FC<UserProfileScreenProps> = ({ mode, userId, profileNavigation }) => {
const { isDesktop, isTablet } = useResponsive();
const {
user,
posts,
favorites,
loading,
refreshing,
activeTab,
setActiveTab,
scrollBottomInset,
onRefresh,
handleFollow,
handlePostAction,
handleFollowingPress,
handleFollowersPress,
handleMessage,
handleMore,
handleSettings,
handleEditProfile,
isCurrentUser,
currentUser,
} = useUserProfile({ mode, userId, isDesktop, isTablet, profileNavigation });
// 渲染帖子列表
const renderPostList = useCallback((postList: Post[], emptyTitle: string, emptyDesc: string) => {
if (postList.length === 0) {
return (
<EmptyState
title={emptyTitle}
description={emptyDesc}
icon={activeTab === 0 ? 'file-document-edit-outline' : 'bookmark-heart-outline'}
variant="modern"
/>
);
}
return (
<View style={sharedStyles.postsContainer}>
{postList.map((post, index) => {
const isPostAuthor = currentUser?.id === post.author?.id;
return (
<View key={post.id} style={[
sharedStyles.postWrapper,
index === postList.length - 1 && sharedStyles.lastPost,
]}>
<PostCard
post={post}
onAction={(action) => handlePostAction(post, action)}
isPostAuthor={isPostAuthor}
/>
</View>
);
})}
</View>
);
}, [currentUser?.id, handlePostAction, activeTab]);
// 渲染内容
const renderContent = useCallback(() => {
if (loading) return <Loading />;
if (activeTab === 0) {
return renderPostList(
posts,
mode === 'self' ? '还没有帖子' : '这个用户还没有发布任何帖子',
mode === 'self' ? '分享你的想法,发布第一条帖子吧' : ''
);
}
if (activeTab === 1) {
return renderPostList(
favorites,
mode === 'self' ? '还没有收藏' : '这个用户还没有收藏任何帖子',
mode === 'self' ? '发现喜欢的内容,点击收藏按钮保存' : ''
);
}
return null;
}, [loading, activeTab, posts, favorites, mode, renderPostList]);
// 渲染用户信息头部
const renderUserHeader = useMemo(() => {
if (!user) return null;
return (
<UserProfileHeader
user={user}
isCurrentUser={isCurrentUser}
onFollow={handleFollow}
onSettings={handleSettings}
onEditProfile={handleEditProfile}
onMessage={handleMessage}
onMore={handleMore}
onFollowingPress={handleFollowingPress}
onFollowersPress={handleFollowersPress}
/>
);
}, [user, isCurrentUser, handleFollow, handleSettings, handleEditProfile, handleMessage, handleMore, handleFollowingPress, handleFollowersPress]);
// 渲染 TabBar 和内容
const renderTabBarAndContent = useMemo(() => (
<>
<View style={sharedStyles.tabBarContainer}>
<TabBar
tabs={TABS}
activeIndex={activeTab}
onTabChange={setActiveTab}
variant="modern"
icons={TAB_ICONS}
/>
</View>
<View style={sharedStyles.contentContainer}>
{renderContent()}
</View>
</>
), [activeTab, renderContent]);
// 未登录/用户不存在状态
if (mode === 'self' && !currentUser) {
return (
<SafeAreaView style={sharedStyles.container} edges={['top', 'bottom']}>
<EmptyState
title="未登录"
description="请先登录"
icon="account-off-outline"
/>
</SafeAreaView>
);
}
if (mode === 'other' && !user && !loading) {
return (
<SafeAreaView style={sharedStyles.container} edges={['bottom']}>
<EmptyState
title="用户不存在"
description="该用户可能已被删除"
icon="account-off-outline"
/>
</SafeAreaView>
);
}
// 桌面端使用双栏布局
if (isDesktop || isTablet) {
return (
<SafeAreaView style={sharedStyles.container} edges={mode === 'self' ? ['top', 'bottom'] : ['bottom']}>
<ResponsiveContainer maxWidth={1400}>
<View style={sharedStyles.desktopContainer}>
{/* 左侧:用户信息 */}
<View style={sharedStyles.desktopSidebar}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{ paddingBottom: scrollBottomInset }}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={[colors.primary.main]}
tintColor={colors.primary.main}
/>
}
>
{renderUserHeader}
</ScrollView>
</View>
{/* 右侧:帖子列表 */}
<View style={sharedStyles.desktopContent}>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={[sharedStyles.desktopScrollContent, { paddingBottom: scrollBottomInset }]}
>
{renderTabBarAndContent}
</ScrollView>
</View>
</View>
</ResponsiveContainer>
</SafeAreaView>
);
}
// 移动端使用单栏布局
return (
<SafeAreaView style={sharedStyles.container} edges={mode === 'self' ? ['top', 'bottom'] : ['bottom']}>
<ScrollView
showsVerticalScrollIndicator={false}
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
colors={[colors.primary.main]}
tintColor={colors.primary.main}
/>
}
contentContainerStyle={[sharedStyles.scrollContent, { paddingBottom: scrollBottomInset }]}
>
{renderUserHeader}
{renderTabBarAndContent}
</ScrollView>
</SafeAreaView>
);
};
export default UserProfileScreen;