/** * 主导航组件 * 配置底部Tab导航和Stack导航 * 根据登录状态自动切换AuthStack和MainStack * 支持响应式布局:移动端底部导航,平板/桌面端侧边导航 */ import React, { useEffect, useState, useCallback } from 'react'; import { StyleSheet, View, ActivityIndicator, TouchableOpacity, Animated, ScrollView, Text as RNText, Platform, } from 'react-native'; import { NavigationContainer, LinkingOptions, NavigatorScreenParams, RouteProp, useNavigation, useRoute, } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useTheme } from 'react-native-paper'; import { MaterialCommunityIcons } from '@expo/vector-icons'; import { useSafeAreaInsets, SafeAreaView } from 'react-native-safe-area-context'; import { colors, shadows } from '../theme'; import { useAuthStore, useTotalUnreadCount } from '../stores'; import { messageManager } from '../stores'; import { useResponsive } from '../hooks'; import AsyncStorage from '@react-native-async-storage/async-storage'; import type { RootStackParamList, MainTabParamList, HomeStackParamList, MessageStackParamList, ScheduleStackParamList, ProfileStackParamList, AuthStackParamList, } from './types'; import { SimpleMobileTabNavigator } from './SimpleMobileTabNavigator'; // ==================== 导入屏幕组件 ==================== import { HomeScreen, PostDetailScreen, SearchScreen } from '../screens/home'; import { ScheduleScreen, CourseDetailScreen } from '../screens/schedule'; import { MessageListScreen, ChatScreen, NotificationsScreen, CreateGroupScreen, JoinGroupScreen, GroupRequestDetailScreen, GroupInviteDetailScreen, GroupInfoScreen, GroupMembersScreen, PrivateChatInfoScreen } from '../screens/message'; import { ProfileScreen, SettingsScreen, EditProfileScreen, NotificationSettingsScreen, BlockedUsersScreen, AccountSecurityScreen } from '../screens/profile'; import { CreatePostScreen } from '../screens/create'; import { UserScreen } from '../screens/profile'; import FollowListScreen from '../screens/profile/FollowListScreen'; import { LoginScreen, RegisterScreen, ForgotPasswordScreen } from '../screens/auth'; // ==================== Stack Navigators ==================== const RootStack = createNativeStackNavigator(); const AuthStack = createNativeStackNavigator(); const HomeStack = createNativeStackNavigator(); const MessageStack = createNativeStackNavigator(); const ScheduleStack = createNativeStackNavigator(); const ProfileStack = createNativeStackNavigator(); const Tab = createBottomTabNavigator(); // ==================== 导航栏宽度常量 ==================== const SIDEBAR_WIDTH_DESKTOP = 240; const SIDEBAR_WIDTH_TABLET = 200; const SIDEBAR_COLLAPSED_WIDTH = 72; const MOBILE_TAB_FLOATING_MARGIN = 12; // ==================== 持久化存储键 ==================== const LAST_ACTIVE_TAB_KEY = 'last_active_tab'; // ==================== 导航项类型 ==================== type TabName = 'HomeTab' | 'MessageTab' | 'ScheduleTab' | 'ProfileTab'; type IconName = React.ComponentProps['name']; interface NavItem { name: TabName; label: string; icon: IconName; iconOutline: IconName; badge?: number; } // ==================== AuthStack导航 ==================== function AuthStackNavigator() { return ( ); } // ==================== 首页Stack导航 ==================== function HomeStackNavigator() { const paperTheme = useTheme(); return ( ); } // ==================== 消息Stack导航 ==================== function MessageStackNavigator() { return ( ); } // ==================== 个人中心Stack导航 ==================== function ProfileStackNavigator() { return ( ); } // ==================== 侧边导航栏组件 ==================== interface SidebarProps { activeTab: TabName; onTabChange: (tab: TabName) => void; unreadCount: number; isCollapsed: boolean; onToggleCollapse: () => void; width: number; } function Sidebar({ activeTab, onTabChange, unreadCount, isCollapsed, onToggleCollapse, width, }: SidebarProps) { const insets = useSafeAreaInsets(); // 调试日志 useEffect(() => { console.log('[Debug Sidebar]', { width, isCollapsed, activeTab, unreadCount, insetsTop: insets.top, insetsBottom: insets.bottom, }); }, [width, isCollapsed, activeTab, unreadCount, insets]); const navItems: NavItem[] = [ { name: 'HomeTab', label: '首页', icon: 'home', iconOutline: 'home-outline' }, { name: 'MessageTab', label: '消息', icon: 'message-text', iconOutline: 'message-text-outline', badge: unreadCount }, { name: 'ScheduleTab', label: '课表', icon: 'calendar-today', iconOutline: 'calendar-today' }, { name: 'ProfileTab', label: '我的', icon: 'account', iconOutline: 'account-outline' }, ]; return ( {/* Logo/品牌区域 */} {!isCollapsed && ( 胡萝卜BBS )} {isCollapsed && ( )} {/* 导航项 */} {navItems.map((item) => { const isActive = activeTab === item.name; const showBadge = !!(item.badge && item.badge > 0); return ( onTabChange(item.name)} activeOpacity={0.7} > {showBadge && ( {item.badge! > 99 ? '99+' : item.badge} )} {!isCollapsed && ( {item.label} )} ); })} {/* 底部折叠按钮 */} ); } // ==================== Text 组件 ==================== function Text({ style, children, numberOfLines }: { style?: any; children: React.ReactNode; numberOfLines?: number }) { return ( {children} ); } // ==================== 课程表Stack导航 ==================== function ScheduleStackNavigator() { return ( ); } // ==================== 响应式 Tab Navigator ==================== interface ResponsiveTabNavigatorProps { children?: React.ReactNode; } function ResponsiveTabNavigator({ children }: ResponsiveTabNavigatorProps) { const navigation = useNavigation>(); const route = useRoute>(); const { isMobile, isTablet, isDesktop, width, height } = useResponsive(); const insets = useSafeAreaInsets(); const [activeTab, setActiveTab] = useState('HomeTab'); const [isCollapsed, setIsCollapsed] = useState(false); const messageUnreadCount = useTotalUnreadCount(); // 【新架构】MessageManager 自动处理 WebSocket 消息和未读数更新 useEffect(() => { messageManager.initialize(); }, []); // 同步 URL 中的 Main 子路由到桌面侧边栏激活态 useEffect(() => { const targetTab = route.params?.screen; if (!targetTab) return; if ((targetTab === 'HomeTab' || targetTab === 'MessageTab' || targetTab === 'ScheduleTab' || targetTab === 'ProfileTab') && targetTab !== activeTab) { setActiveTab(targetTab); } }, [route.params?.screen, activeTab]); // 计算侧边栏宽度 const sidebarWidth = useCallback(() => { if (isMobile) return 0; // 移动端不显示侧边栏 if (isCollapsed) return SIDEBAR_COLLAPSED_WIDTH; if (isDesktop) return SIDEBAR_WIDTH_DESKTOP; if (isTablet) return SIDEBAR_WIDTH_TABLET; // 默认返回桌面宽度,避免返回 0 导致导航栏消失 return SIDEBAR_WIDTH_DESKTOP; }, [isCollapsed, isDesktop, isTablet, isMobile]); // 切换 Tab const handleTabChange = useCallback((tab: TabName) => { setActiveTab(tab); // 在大屏幕模式下,通过导航切换 Tab if (!isMobile) { let tabTarget: NavigatorScreenParams; if (tab === 'HomeTab') { tabTarget = { screen: 'HomeTab', params: { screen: 'Home' } }; } else if (tab === 'MessageTab') { tabTarget = { screen: 'MessageTab', params: { screen: 'MessageList' } }; } else if (tab === 'ScheduleTab') { tabTarget = { screen: 'ScheduleTab', params: { screen: 'Schedule' } }; } else { tabTarget = { screen: 'ProfileTab', params: { screen: 'Profile' } }; } navigation.navigate('Main', tabTarget); } }, [navigation, isMobile]); // 切换折叠状态 const handleToggleCollapse = useCallback(() => { setIsCollapsed(prev => !prev); }, []); // 移动端:使用底部 Tab 导航 if (isMobile) { // 使用简化的移动端导航,避免 React Navigation 状态恢复问题 return ; } // 渲染当前 Tab 的内容(仅在大屏模式下使用) // 注意:直接渲染屏幕组件,不使用 Stack Navigators // 这样可以避免 React Navigation 状态恢复问题 const renderTabContent = () => { switch (activeTab) { case 'HomeTab': return ; case 'MessageTab': return ; case 'ScheduleTab': return ; case 'ProfileTab': return ; default: return ; } }; // 平板/桌面端:使用侧边导航 const currentSidebarWidth = sidebarWidth(); return ( {/* 侧边导航栏 */} {/* 主内容区域 */} {renderTabContent()} ); } // ==================== 底部Tab导航(移动端)==================== function MainTabNavigator() { const insets = useSafeAreaInsets(); const messageUnreadCount = useTotalUnreadCount(); // 【新架构】MessageManager 自动处理 WebSocket 消息和未读数更新 useEffect(() => { messageManager.initialize(); }, []); return ( ( ), }} /> 0 ? (messageUnreadCount > 99 ? '99+' : messageUnreadCount) : undefined, tabBarIcon: ({ color, size, focused }) => ( ), }} /> ( ), }} /> ( ), }} /> ); } // ==================== 根导航 ==================== export default function MainNavigator() { const { isAuthenticated, fetchCurrentUser } = useAuthStore(); const [initializing, setInitializing] = useState(true); const linking: LinkingOptions = { prefixes: [], config: { screens: { Auth: { screens: { Login: 'login', Register: 'register', ForgotPassword: 'forgot-password', }, }, Main: { screens: { HomeTab: { screens: { Home: 'home', Search: 'search', }, }, MessageTab: { screens: { MessageList: 'messages', Notifications: 'notifications', }, }, ProfileTab: { screens: { Profile: 'me', Settings: 'settings', EditProfile: 'me/edit', AccountSecurity: 'me/security', NotificationSettings: 'me/notifications', BlockedUsers: 'me/blocked', }, }, }, }, PostDetail: 'posts/:postId', UserProfile: 'users/:userId', CreatePost: 'posts/create', Chat: 'chat/:conversationId', FollowList: 'users/:userId/:type', }, }, }; // 初始化时检查登录状态 useEffect(() => { const initAuth = async () => { await fetchCurrentUser(); setInitializing(false); }; initAuth(); }, [fetchCurrentUser]); // 加载中显示 if (initializing) { return ( ); } return ( {/* 根据登录状态显示不同Stack */} {isAuthenticated ? ( <> ) : ( <> {/* 未登录时也可以查看帖子详情 */} )} ); } // ==================== 样式 ==================== const styles = StyleSheet.create({ // 响应式容器 responsiveContainer: { flex: 1, flexDirection: 'row', backgroundColor: colors.background.default, }, // 侧边栏样式 sidebar: { backgroundColor: colors.background.paper, borderRightWidth: 1, borderRightColor: colors.divider, flexDirection: 'column', ...shadows.md, }, sidebarHeader: { paddingHorizontal: 16, paddingVertical: 20, borderBottomWidth: 1, borderBottomColor: colors.divider, alignItems: 'center', justifyContent: 'center', }, logoContainer: { flexDirection: 'row', alignItems: 'center', }, logoText: { fontSize: 20, fontWeight: '700', color: colors.primary.main, marginLeft: 12, }, sidebarContent: { flex: 1, paddingTop: 8, }, sidebarItem: { flexDirection: 'row', alignItems: 'center', paddingHorizontal: 16, paddingVertical: 12, marginHorizontal: 8, marginVertical: 4, borderRadius: 12, }, sidebarItemActive: { backgroundColor: `${colors.primary.main}15`, }, sidebarItemCollapsed: { justifyContent: 'center', paddingHorizontal: 0, }, sidebarIconContainer: { position: 'relative', width: 40, height: 40, alignItems: 'center', justifyContent: 'center', borderRadius: 12, }, sidebarLabel: { fontSize: 15, fontWeight: '500', color: colors.text.secondary, marginLeft: 12, flex: 1, }, sidebarLabelActive: { color: colors.primary.main, fontWeight: '600', }, collapseButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'flex-end', paddingHorizontal: 16, paddingVertical: 12, borderTopWidth: 1, borderTopColor: colors.divider, }, collapseButtonCollapsed: { justifyContent: 'center', paddingHorizontal: 0, }, // 徽章样式 badge: { position: 'absolute', top: 2, right: 2, backgroundColor: colors.error.main, borderRadius: 10, minWidth: 18, height: 18, alignItems: 'center', justifyContent: 'center', paddingHorizontal: 4, }, badgeText: { color: colors.primary.contrast, fontSize: 10, fontWeight: '600', }, // 主内容区域 mainContent: { flex: 1, backgroundColor: colors.background.default, }, // 底部 Tab 样式 tabIconContainer: { alignItems: 'center', justifyContent: 'center', width: 38, height: 30, borderRadius: 15, }, tabIconActive: { backgroundColor: `${colors.primary.main}20`, transform: [{ translateY: -1 }], }, loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: colors.background.default, }, });