Files
frontend/src/navigation/SimpleMobileTabNavigator.tsx
2026-03-16 17:47:10 +08:00

283 lines
7.1 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.
/**
* 简单的移动端 Tab Navigator
*
* 不使用 React Navigation 的 Tab Navigator而是使用一个简单的自定义实现
* 这样可以完全避免 React Navigation 状态恢复的问题
*/
import React, { useEffect, useState, useCallback } from 'react';
import {
View,
StyleSheet,
TouchableOpacity,
Text,
Dimensions,
} from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { colors, shadows } from '../theme';
import { useTotalUnreadCount } from '../stores';
import { messageManager } from '../stores';
// ==================== 导入屏幕组件 ====================
import { HomeScreen, SearchScreen } from '../screens/home';
import { ScheduleScreen, CourseDetailScreen } from '../screens/schedule';
import {
MessageListScreen,
NotificationsScreen,
PrivateChatInfoScreen,
} from '../screens/message';
import { ProfileScreen, SettingsScreen, EditProfileScreen, NotificationSettingsScreen, BlockedUsersScreen, AccountSecurityScreen } from '../screens/profile';
// ==================== 类型定义 ====================
type TabName = 'HomeTab' | 'MessageTab' | 'ScheduleTab' | 'ProfileTab';
// ==================== 常量 ====================
const TAB_BAR_HEIGHT = 64;
const TAB_BAR_MARGIN = 14;
const TAB_BAR_FLOATING_MARGIN = 12;
// ==================== 组件 ====================
/**
* 简化的 Stack Navigator 包装
*/
function SimpleStackNavigator({
children,
screenName
}: {
children: React.ReactNode;
screenName: string;
}) {
return (
<View style={styles.stackContainer}>
{children}
</View>
);
}
/**
* 主组件SimpleMobileTabNavigator
*/
export function SimpleMobileTabNavigator() {
const insets = useSafeAreaInsets();
const messageUnreadCount = useTotalUnreadCount();
const [activeTab, setActiveTab] = useState<TabName>('HomeTab');
// 初始化 MessageManager
useEffect(() => {
messageManager.initialize();
}, []);
// 处理 Tab 切换
const handleTabPress = useCallback((tabName: TabName) => {
setActiveTab(tabName);
}, []);
// 渲染当前 Tab 的内容
const renderTabContent = () => {
switch (activeTab) {
case 'HomeTab':
return <HomeScreen />;
case 'MessageTab':
return <MessageListScreen />;
case 'ScheduleTab':
return <ScheduleScreen />;
case 'ProfileTab':
return <ProfileScreen />;
default:
return <HomeScreen />;
}
};
// 渲染 Tab Bar 图标
const renderTabIcon = (tabName: TabName, isActive: boolean) => {
const iconColor = isActive ? colors.primary.main : colors.text.secondary;
const iconSize = isActive ? 26 : 24;
switch (tabName) {
case 'HomeTab':
return (
<MaterialCommunityIcons
name={isActive ? 'home' : 'home-outline'}
size={iconSize}
color={iconColor}
/>
);
case 'MessageTab':
return (
<MaterialCommunityIcons
name={isActive ? 'message-text' : 'message-text-outline'}
size={iconSize}
color={iconColor}
/>
);
case 'ScheduleTab':
return (
<MaterialCommunityIcons
name={isActive ? 'calendar-today' : 'calendar-today'}
size={iconSize}
color={iconColor}
/>
);
case 'ProfileTab':
return (
<MaterialCommunityIcons
name={isActive ? 'account' : 'account-outline'}
size={iconSize}
color={iconColor}
/>
);
default:
return null;
}
};
// 渲染 Tab Bar 标签
const renderTabLabel = (tabName: TabName, isActive: boolean) => {
const labels: Record<TabName, string> = {
HomeTab: '首页',
MessageTab: '消息',
ScheduleTab: '课表',
ProfileTab: '我的',
};
return (
<Text
style={[
styles.tabLabel,
isActive && styles.tabLabelActive,
]}
>
{labels[tabName]}
</Text>
);
};
return (
<View style={styles.container}>
{/* 内容区域 */}
<View style={styles.content}>
{renderTabContent()}
</View>
{/* Tab Bar */}
<View
style={[
styles.tabBar,
{
bottom: TAB_BAR_FLOATING_MARGIN + insets.bottom,
},
]}
>
{(['HomeTab', 'MessageTab', 'ScheduleTab', 'ProfileTab'] as TabName[]).map(
(tabName) => {
const isActive = activeTab === tabName;
const showBadge = tabName === 'MessageTab' && messageUnreadCount > 0;
return (
<TouchableOpacity
key={tabName}
style={[
styles.tabItem,
isActive && styles.tabItemActive,
]}
onPress={() => handleTabPress(tabName)}
activeOpacity={0.7}
>
<View style={styles.tabIconContainer}>
{renderTabIcon(tabName, isActive)}
{showBadge && (
<View style={styles.badge}>
<Text style={styles.badgeText}>
{messageUnreadCount > 99 ? '99+' : messageUnreadCount}
</Text>
</View>
)}
</View>
{renderTabLabel(tabName, isActive)}
</TouchableOpacity>
);
}
)}
</View>
</View>
);
}
// ==================== 样式 ====================
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: colors.background.default,
},
stackContainer: {
flex: 1,
},
content: {
flex: 1,
marginBottom: TAB_BAR_HEIGHT + TAB_BAR_FLOATING_MARGIN * 2,
},
tabBar: {
position: 'absolute',
left: TAB_BAR_MARGIN,
right: TAB_BAR_MARGIN,
height: TAB_BAR_HEIGHT,
backgroundColor: colors.background.paper,
borderRadius: 24,
borderTopWidth: 1,
borderTopColor: `${colors.divider}88`,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingHorizontal: 8,
...shadows.lg,
},
tabItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
paddingVertical: 6,
borderRadius: 18,
},
tabItemActive: {
backgroundColor: `${colors.primary.main}15`,
},
tabIconContainer: {
position: 'relative',
width: 38,
height: 30,
alignItems: 'center',
justifyContent: 'center',
borderRadius: 15,
},
tabLabel: {
fontSize: 12,
fontWeight: '600',
marginTop: -2,
letterSpacing: 0.2,
color: colors.text.secondary,
},
tabLabelActive: {
color: colors.primary.main,
},
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',
},
});