Files
frontend/src/screens/profile/UserProfileScreen.tsx
lafay c00b915e5f
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m31s
Frontend CI / ota-android (push) Successful in 10m26s
Frontend CI / build-android-apk (push) Successful in 40m43s
chore: remove IDE config files and improve web platform compatibility
- Remove `.idea/` IntelliJ configuration files from version control
- Add web-specific touch handling for swipeable message bubbles and schedule screen
- Fix CSS touch-action rules for better web scrolling behavior
- Add nestedScrollEnabled to ScrollViews for proper gesture handling
- Improve null safety checks in profile screens
- Add horizontal ScrollView wrapper for notification filter tags
- Add hasHeader prop support for embedded profile screens
2026-04-14 02:12:53 +08:00

237 lines
7.2 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 { useAppColors } 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 { useUserProfile, ProfileMode, TABS, TAB_ICONS, createSharedProfileStyles } from './useUserProfile';
interface UserProfileScreenProps {
mode: ProfileMode;
userId?: string;
hasHeader?: boolean;
}
export const UserProfileScreen: React.FC<UserProfileScreenProps> = ({ mode, userId, hasHeader = false }) => {
const colors = useAppColors();
const sharedStyles = useMemo(() => createSharedProfileStyles(colors), [colors]);
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 });
// 渲染帖子列表
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={hasHeader ? ['bottom'] : ['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>
);
}
const safeAreaEdges = hasHeader ? ['bottom'] : (mode === 'self' ? ['top', 'bottom'] : ['bottom']);
// 桌面端使用双栏布局
if (isDesktop || isTablet) {
return (
<SafeAreaView style={sharedStyles.container} edges={safeAreaEdges as any}>
<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={safeAreaEdges as any}>
<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;