refactor(PostCard, PostCardGrid, HomeScreen): enhance PostCard component and remove PostCardGrid
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

- 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.
This commit is contained in:
lafay
2026-03-24 05:52:24 +08:00
parent f9f4e73747
commit b91e78c921
13 changed files with 623 additions and 547 deletions

View File

@@ -249,7 +249,10 @@ export function RootNavigator({ isAuthenticated, isInitializing }: RootNavigator
>
{isAuthenticated ? (
<>
<RootStack.Screen name="Main">
<RootStack.Screen
name="Main"
navigationKey={isMobile ? 'main-mobile' : 'main-desktop'}
>
{() =>
isMobile ? (
<SimpleMobileTabNavigator />

View File

@@ -1,18 +1,18 @@
/**
* 简单的移动端 Tab Navigator
* 简单的移动端 Tab Navigator(自定义 Tab 栏)
*
* 不使用 React Navigation 的 Tab Navigator而是使用一个简单的自定义实现
* 这样可以完全避免 React Navigation 状态恢复的问题
* 策略(在 EnsureSingleNavigator 限制下尽量省加载):
* - 首页、消息:屏幕本身不带根级 NativeStack首次进入后保留挂载仅 display 隐藏 → 来回切换快。
* - 课表、我的:各含一个 NativeStack同一时刻只挂载当前选中的一个避免重复注册 Navigator。
*
* 其它可叠加的优化(按需再做,不放在本文件里):
* - 数据TanStack Query 调 staleTime、列表 prefetch、占位骨架
* - 时机InteractionManager.runAfterInteractions 再拉非首屏接口
* - 渲染React.memo 重列表项、避免 Tab 切换时整树不必要 setState
*/
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
StyleSheet,
TouchableOpacity,
Text,
Dimensions,
} from 'react-native';
import { View, StyleSheet, TouchableOpacity, Text } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
@@ -22,13 +22,9 @@ import { useTotalUnreadCount } from '../stores';
import { messageManager } from '../stores';
// ==================== 导入屏幕组件 ====================
import { HomeScreen, SearchScreen } from '../screens/home';
import { HomeScreen } from '../screens/home';
import { ScheduleScreen, CourseDetailScreen } from '../screens/schedule';
import {
MessageListScreen,
NotificationsScreen,
PrivateChatInfoScreen,
} from '../screens/message';
import { MessageListScreen } from '../screens/message';
import { ProfileScreen, SettingsScreen, EditProfileScreen, NotificationSettingsScreen, BlockedUsersScreen, AccountSecurityScreen } from '../screens/profile';
// ==================== 类型定义 ====================
@@ -178,11 +174,10 @@ export function SimpleMobileTabNavigator() {
const insets = useSafeAreaInsets();
const messageUnreadCount = useTotalUnreadCount();
const [activeTab, setActiveTab] = useState<TabName>('HomeTab');
const [mountedTabs, setMountedTabs] = useState<Record<TabName, boolean>>({
/** 无嵌套 Stack 的 Tab保留实例避免每次切回都整页重挂载 */
const [plainTabEverShown, setPlainTabEverShown] = useState({
HomeTab: true,
MessageTab: false,
ScheduleTab: false,
ProfileTab: false,
});
// 初始化 MessageManager
@@ -190,36 +185,13 @@ export function SimpleMobileTabNavigator() {
messageManager.initialize();
}, []);
// 处理 Tab 切换
const handleTabPress = useCallback((tabName: TabName) => {
setMountedTabs((prev) => {
if (prev[tabName]) {
return prev;
}
return {
...prev,
[tabName]: true,
};
});
if (tabName === 'HomeTab' || tabName === 'MessageTab') {
setPlainTabEverShown((prev) => ({ ...prev, [tabName]: true }));
}
setActiveTab(tabName);
}, []);
// 根据 Tab 名称渲染对应内容
const renderTabView = (tabName: TabName) => {
switch (tabName) {
case 'HomeTab':
return <HomeScreen />;
case 'MessageTab':
return <MessageListScreen />;
case 'ScheduleTab':
return <ScheduleStackNavigatorComponent />;
case 'ProfileTab':
return <ProfileStackNavigatorComponent />;
default:
return <HomeScreen />;
}
};
// 渲染 Tab Bar 图标
const renderTabIcon = (tabName: TabName, isActive: boolean) => {
const iconColor = isActive ? colors.primary.main : colors.text.secondary;
@@ -284,31 +256,43 @@ export function SimpleMobileTabNavigator() {
);
};
// 计算底部安全距离
const bottomSafeArea = TAB_BAR_HEIGHT + TAB_BAR_FLOATING_MARGIN * 2 + insets.bottom;
return (
<View style={styles.container}>
{/* 内容区域 */}
<View style={styles.content}>
{(Object.keys(mountedTabs) as TabName[]).map((tabName) => {
if (!mountedTabs[tabName]) {
return null;
}
const isActive = activeTab === tabName;
return (
<View
key={tabName}
style={[
styles.tabScreen,
!isActive && styles.tabScreenHidden,
]}
pointerEvents={isActive ? 'auto' : 'none'}
>
{renderTabView(tabName)}
</View>
);
})}
{plainTabEverShown.HomeTab && (
<View
key="HomeTab"
style={[
styles.tabScreen,
activeTab !== 'HomeTab' && styles.tabScreenHidden,
]}
pointerEvents={activeTab === 'HomeTab' ? 'auto' : 'none'}
>
<HomeScreen />
</View>
)}
{plainTabEverShown.MessageTab && (
<View
key="MessageTab"
style={[
styles.tabScreen,
activeTab !== 'MessageTab' && styles.tabScreenHidden,
]}
pointerEvents={activeTab === 'MessageTab' ? 'auto' : 'none'}
>
<MessageListScreen />
</View>
)}
{activeTab === 'ScheduleTab' && (
<View key="ScheduleTab" style={styles.tabScreen}>
<ScheduleStackNavigatorComponent />
</View>
)}
{activeTab === 'ProfileTab' && (
<View key="ProfileTab" style={styles.tabScreen}>
<ProfileStackNavigatorComponent />
</View>
)}
</View>
{/* Tab Bar */}
@@ -361,9 +345,6 @@ const styles = StyleSheet.create({
flex: 1,
backgroundColor: colors.background.default,
},
stackContainer: {
flex: 1,
},
content: {
flex: 1,
position: 'relative',
@@ -374,9 +355,6 @@ const styles = StyleSheet.create({
tabScreenHidden: {
display: 'none',
},
bottomSpacer: {
backgroundColor: 'transparent',
},
tabBar: {
position: 'absolute',
left: TAB_BAR_MARGIN,
@@ -390,6 +368,9 @@ const styles = StyleSheet.create({
justifyContent: 'space-around',
paddingHorizontal: 8,
...shadows.lg,
// 叠在全屏内容之上shadows.lg 含 elevation需最后覆盖
zIndex: 100,
elevation: 100,
},
tabItem: {
flex: 1,