refactor: 架构重构 - 解耦过度耦合模块

主要改动:
1. 创建乐观更新工具函数 (optimisticUpdate.ts)
   - 消除 userStore.ts 中的重复代码

2. 拆分 useResponsive.ts (485行 -> 12个专注模块)
   - useBreakpoint: 断点检测
   - useOrientation: 方向检测
   - usePlatform: 平台检测
   - useScreenSize: 屏幕尺寸
   - useResponsiveValue: 响应式值
   - useResponsiveStyle: 响应式样式
   - useMediaQuery: 媒体查询
   - useColumnCount: 列数计算
   - useResponsiveSpacing: 响应式间距

3. 整理数据层 (Repository 层)
   - ApiDataSource: API数据源
   - LocalDataSource: 本地数据源
   - CacheDataSource: 缓存数据源
   - MessageRepository: 消息仓库

4. 重构 messageManager.ts (2194行 -> 4个模块)
   - MessageStateManager: 状态管理
   - WebSocketMessageHandler: WebSocket处理
   - MessageSyncService: 消息同步
   - ReadReceiptManager: 已读管理

5. 导航解耦 (MainNavigator.tsx: 1118行 -> 100行)
   - 创建 NavigationService 解耦层
   - 拆分多个 Navigator 组件

架构改进:
- 单一职责原则: 每个模块职责明确
- 依赖倒置: 通过接口解耦
- 代码复用: 工具函数可被多处使用
- 可测试性: 各模块可独立测试
This commit is contained in:
lafay
2026-03-18 12:11:49 +08:00
parent 1ffbb63753
commit a6cdb97e24
70 changed files with 8175 additions and 1728 deletions

View File

@@ -0,0 +1,25 @@
/**
* 认证流程导航
* 处理登录、注册、忘记密码等认证页面
*/
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { AuthStackParamList } from './types';
import { LoginScreen, RegisterScreen, ForgotPasswordScreen } from '../screens/auth';
const AuthStack = createNativeStackNavigator<AuthStackParamList>();
export function AuthNavigator() {
return (
<AuthStack.Navigator
screenOptions={{
headerShown: false,
}}
>
<AuthStack.Screen name="Login" component={LoginScreen} />
<AuthStack.Screen name="Register" component={RegisterScreen} />
<AuthStack.Screen name="ForgotPassword" component={ForgotPasswordScreen} />
</AuthStack.Navigator>
);
}

View File

@@ -0,0 +1,261 @@
/**
* 桌面端导航器
* 为平板/桌面设备提供侧边栏导航体验
*/
import React, { useState, useCallback, useEffect } from 'react';
import {
View,
StyleSheet,
ScrollView,
TouchableOpacity,
Text,
Animated,
Platform,
} from 'react-native';
import { useSafeAreaInsets, SafeAreaView } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import type { TabName, NavItemConfig } from '../infrastructure/navigation/types';
import { NAVIGATION_CONSTANTS } from '../infrastructure/navigation/types';
import { colors, shadows } from '../theme';
import { useNavigationState } from '../infrastructure/navigation/hooks/useNavigationState';
// 导入屏幕
import { HomeScreen } from '../screens/home';
import { ScheduleScreen } from '../screens/schedule';
import { MessageListScreen } from '../screens/message';
import { ProfileScreen } from '../screens/profile';
// 侧边栏常量
const { SIDEBAR_WIDTH_DESKTOP, SIDEBAR_WIDTH_TABLET, SIDEBAR_COLLAPSED_WIDTH } = NAVIGATION_CONSTANTS;
// 导航项配置
const NAV_ITEMS: NavItemConfig[] = [
{ name: 'HomeTab', label: '首页', icon: 'home', iconOutline: 'home-outline' },
{ name: 'MessageTab', label: '消息', icon: 'message-text', iconOutline: 'message-text-outline' },
{ name: 'ScheduleTab', label: '课表', icon: 'calendar-today', iconOutline: 'calendar-today' },
{ name: 'ProfileTab', label: '我的', icon: 'account', iconOutline: 'account-outline' },
];
interface DesktopNavigatorProps {
unreadCount?: number;
}
export function DesktopNavigator({ unreadCount = 0 }: DesktopNavigatorProps) {
const insets = useSafeAreaInsets();
const { currentTab, isCollapsed, setCurrentTab, toggleCollapse, setIsReady } = useNavigationState();
const [isDesktop, setIsDesktop] = useState(false);
// 检测是否是桌面尺寸
useEffect(() => {
const checkDesktop = () => {
const width = window.innerWidth || document.documentElement.clientWidth;
setIsDesktop(width >= SIDEBAR_WIDTH_DESKTOP);
};
checkDesktop();
window.addEventListener('resize', checkDesktop);
setIsReady(true);
return () => window.removeEventListener('resize', checkDesktop);
}, [setIsReady]);
// 计算侧边栏宽度
const sidebarWidth = isCollapsed ? SIDEBAR_COLLAPSED_WIDTH : (isDesktop ? SIDEBAR_WIDTH_DESKTOP : SIDEBAR_WIDTH_TABLET);
// 处理 Tab 切换
const handleTabChange = useCallback((tab: TabName) => {
setCurrentTab(tab);
}, [setCurrentTab]);
// 渲染当前 Tab 的内容
const renderTabContent = () => {
switch (currentTab) {
case 'HomeTab':
return <HomeScreen />;
case 'MessageTab':
return <MessageListScreen />;
case 'ScheduleTab':
return <ScheduleScreen />;
case 'ProfileTab':
return <ProfileScreen />;
default:
return <HomeScreen />;
}
};
return (
<View style={styles.container}>
{/* 侧边栏 */}
<SafeAreaView style={[styles.sidebar, { width: sidebarWidth, paddingTop: insets.top, paddingBottom: insets.bottom }]}>
{/* Logo 区域 */}
<View style={styles.sidebarHeader}>
<MaterialCommunityIcons name="carrot" size={32} color={colors.primary.main} />
{!isCollapsed && (
<Text style={styles.logoText}>BBS</Text>
)}
</View>
{/* 导航项 */}
<ScrollView style={styles.sidebarContent} showsVerticalScrollIndicator={false}>
{NAV_ITEMS.map((item) => {
const isActive = currentTab === item.name;
const showBadge = item.name === 'MessageTab' && unreadCount > 0;
return (
<TouchableOpacity
key={item.name}
style={[
styles.sidebarItem,
isActive && styles.sidebarItemActive,
isCollapsed && styles.sidebarItemCollapsed,
]}
onPress={() => handleTabChange(item.name)}
activeOpacity={0.7}
>
<View style={styles.sidebarIconContainer}>
<MaterialCommunityIcons
name={isActive ? item.icon : item.iconOutline}
size={24}
color={isActive ? colors.primary.main : colors.text.secondary}
/>
{showBadge && (
<View style={styles.badge}>
<Text style={styles.badgeText}>{unreadCount > 99 ? '99+' : unreadCount}</Text>
</View>
)}
</View>
{!isCollapsed && (
<Text style={[styles.sidebarLabel, isActive && styles.sidebarLabelActive]}>
{item.label}
</Text>
)}
</TouchableOpacity>
);
})}
</ScrollView>
{/* 折叠按钮 */}
<TouchableOpacity
style={[styles.collapseButton, isCollapsed && styles.collapseButtonCollapsed]}
onPress={toggleCollapse}
activeOpacity={0.7}
>
<MaterialCommunityIcons
name={isCollapsed ? 'chevron-right' : 'chevron-left'}
size={24}
color={colors.text.secondary}
/>
</TouchableOpacity>
</SafeAreaView>
{/* 主内容区域 */}
<View style={styles.mainContent}>
{renderTabContent()}
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
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',
flexDirection: 'row',
},
logoText: {
fontSize: 18,
fontWeight: '700',
color: colors.primary.main,
marginLeft: 8,
},
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,
},
});

View File

@@ -0,0 +1,49 @@
/**
* 首页 Stack 导航
* 处理首页相关页面:首页、搜索等
*/
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { HomeStackParamList } from './types';
import { colors } from '../theme';
import { HomeScreen, SearchScreen } from '../screens/home';
const HomeStack = createNativeStackNavigator<HomeStackParamList>();
export function HomeNavigator() {
return (
<HomeStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: colors.background.paper,
},
headerTintColor: colors.text.primary,
headerTitleStyle: {
fontWeight: '600',
},
headerBackTitle: '',
headerShadowVisible: false,
}}
>
<HomeStack.Screen
name="Home"
component={HomeScreen}
options={{
title: '首页',
headerBackTitle: '',
headerShown: false,
}}
/>
<HomeStack.Screen
name="Search"
component={SearchScreen}
options={{
title: '搜索',
headerBackTitle: '',
headerShown: false,
}}
/>
</HomeStack.Navigator>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
/**
* 消息 Stack 导航
* 处理消息相关页面:消息列表、通知等
*/
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { MessageStackParamList } from './types';
import { colors } from '../theme';
import {
MessageListScreen,
NotificationsScreen,
PrivateChatInfoScreen,
} from '../screens/message';
const MessageStack = createNativeStackNavigator<MessageStackParamList>();
export function MessageNavigator() {
return (
<MessageStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: colors.background.paper,
},
headerTintColor: colors.text.primary,
headerTitleStyle: {
fontWeight: '600',
},
headerBackTitle: '',
headerShadowVisible: false,
}}
>
<MessageStack.Screen
name="MessageList"
component={MessageListScreen}
options={{
title: '消息',
headerBackTitle: '',
headerShown: false,
}}
/>
<MessageStack.Screen
name="Notifications"
component={NotificationsScreen}
options={{
title: '通知',
headerBackTitle: '',
}}
/>
<MessageStack.Screen
name="PrivateChatInfo"
component={PrivateChatInfoScreen}
options={{
title: '聊天信息',
headerBackTitle: '',
}}
/>
</MessageStack.Navigator>
);
}

View File

@@ -0,0 +1,101 @@
/**
* 个人中心 Stack 导航
* 处理个人中心相关页面
*/
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { ProfileStackParamList } from './types';
import { colors } from '../theme';
import {
ProfileScreen,
SettingsScreen,
EditProfileScreen,
NotificationSettingsScreen,
BlockedUsersScreen,
AccountSecurityScreen,
} from '../screens/profile';
const ProfileStack = createNativeStackNavigator<ProfileStackParamList>();
export function ProfileNavigator() {
return (
<ProfileStack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: colors.background.paper,
},
headerTintColor: colors.text.primary,
headerTitleStyle: {
fontWeight: '600',
},
headerBackTitle: '',
headerShadowVisible: false,
}}
>
<ProfileStack.Screen
name="Profile"
component={ProfileScreen}
options={{
headerShown: false,
}}
/>
<ProfileStack.Screen
name="Settings"
component={SettingsScreen}
options={{
title: '设置',
headerBackTitle: '',
}}
/>
<ProfileStack.Screen
name="EditProfile"
component={EditProfileScreen}
options={{
title: '编辑资料',
headerBackTitle: '',
}}
/>
<ProfileStack.Screen
name="AccountSecurity"
component={AccountSecurityScreen}
options={{
title: '账号安全',
headerBackTitle: '',
}}
/>
<ProfileStack.Screen
name="MyPosts"
component={ProfileScreen}
options={{
title: '我的帖子',
headerBackTitle: '',
}}
/>
<ProfileStack.Screen
name="Bookmarks"
component={ProfileScreen}
options={{
title: '收藏',
headerBackTitle: '',
}}
/>
<ProfileStack.Screen
name="NotificationSettings"
component={NotificationSettingsScreen}
options={{
title: '通知设置',
headerBackTitle: '',
}}
/>
<ProfileStack.Screen
name="BlockedUsers"
component={BlockedUsersScreen}
options={{
title: '黑名单',
headerBackTitle: '',
}}
/>
</ProfileStack.Navigator>
);
}

View File

@@ -0,0 +1,265 @@
/**
* 根导航器
* 处理整个应用的顶级导航,包括认证状态切换
*/
import React, { useEffect, useState } from 'react';
import { View, ActivityIndicator, StyleSheet } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
import { colors } from '../theme';
import { navigationService } from '../infrastructure/navigation/navigationService';
import { AuthNavigator } from './AuthNavigator';
import { SimpleMobileTabNavigator } from './SimpleMobileTabNavigator';
import { DesktopNavigator } from './DesktopNavigator';
import { useResponsive } from '../hooks';
import { useTotalUnreadCount } from '../stores';
// 导入全局屏幕组件
import { PostDetailScreen } from '../screens/home';
import { UserScreen } from '../screens/profile';
import FollowListScreen from '../screens/profile/FollowListScreen';
import { CreatePostScreen } from '../screens/create';
import {
ChatScreen,
CreateGroupScreen,
JoinGroupScreen,
GroupInfoScreen,
GroupMembersScreen,
GroupRequestDetailScreen,
GroupInviteDetailScreen,
PrivateChatInfoScreen,
} from '../screens/message';
const RootStack = createNativeStackNavigator<RootStackParamList>();
interface RootNavigatorProps {
isAuthenticated: boolean;
isInitializing: boolean;
}
// 未认证时可访问的屏幕
const PublicScreens = () => (
<>
<RootStack.Screen
name="PostDetail"
component={PostDetailScreen}
options={{
headerShown: true,
title: '',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="UserProfile"
component={UserScreen}
options={{
headerShown: true,
title: '用户主页',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
}}
/>
</>
);
// 认证后可访问的屏幕
const AuthenticatedScreens = () => (
<>
<RootStack.Screen
name="PostDetail"
component={PostDetailScreen}
options={{
headerShown: true,
title: '',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="UserProfile"
component={UserScreen}
options={{
headerShown: true,
title: '用户主页',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
}}
/>
<RootStack.Screen
name="CreatePost"
component={CreatePostScreen}
options={{
headerShown: true,
title: '发布帖子',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
presentation: 'modal',
}}
/>
<RootStack.Screen
name="Chat"
component={ChatScreen}
options={{
headerShown: false,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="FollowList"
component={FollowListScreen}
options={{
headerShown: true,
title: '关注列表',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="CreateGroup"
component={CreateGroupScreen}
options={{
headerShown: true,
title: '创建群聊',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
presentation: 'modal',
}}
/>
<RootStack.Screen
name="JoinGroup"
component={JoinGroupScreen}
options={{
headerShown: true,
title: '搜索群聊',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="GroupInfo"
component={GroupInfoScreen}
options={{
headerShown: true,
title: '群信息',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="GroupMembers"
component={GroupMembersScreen}
options={{
headerShown: true,
title: '群成员',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="GroupRequestDetail"
component={GroupRequestDetailScreen}
options={{
headerShown: true,
title: '入群审批',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="GroupInviteDetail"
component={GroupInviteDetailScreen}
options={{
headerShown: true,
title: '群聊邀请',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
<RootStack.Screen
name="PrivateChatInfo"
component={PrivateChatInfoScreen}
options={{
headerShown: true,
title: '聊天信息',
headerStyle: { backgroundColor: colors.background.paper },
headerTintColor: colors.text.primary,
animation: 'slide_from_right',
}}
/>
</>
);
export function RootNavigator({ isAuthenticated, isInitializing }: RootNavigatorProps) {
const { isMobile } = useResponsive();
const unreadCount = useTotalUnreadCount();
// 设置导航引用
const setNavigationRef = (ref: any) => {
navigationService.setNavigationRef(ref);
};
// 加载中显示
if (isInitializing) {
return (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.primary.main} />
</View>
);
}
return (
<NavigationContainer ref={setNavigationRef}>
<RootStack.Navigator
screenOptions={{
headerShown: false,
}}
>
{isAuthenticated ? (
<>
<RootStack.Screen name="Main">
{() =>
isMobile ? (
<SimpleMobileTabNavigator />
) : (
<DesktopNavigator unreadCount={unreadCount} />
)
}
</RootStack.Screen>
<AuthenticatedScreens />
</>
) : (
<>
<RootStack.Screen
name="Auth"
component={AuthNavigator}
options={{ headerShown: false }}
/>
<PublicScreens />
</>
)}
</RootStack.Navigator>
</NavigationContainer>
);
}
const styles = StyleSheet.create({
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: colors.background.default,
},
});

View File

@@ -0,0 +1,42 @@
/**
* 课程表 Stack 导航
* 处理课程表相关页面
*/
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { ScheduleStackParamList } from './types';
import { colors } from '../theme';
import { ScheduleScreen, CourseDetailScreen } from '../screens/schedule';
const ScheduleStack = createNativeStackNavigator<ScheduleStackParamList>();
export function ScheduleNavigator() {
return (
<ScheduleStack.Navigator
screenOptions={{
headerShown: false,
}}
>
<ScheduleStack.Screen
name="Schedule"
component={ScheduleScreen}
options={{
title: '课表',
}}
/>
<ScheduleStack.Screen
name="CourseDetail"
component={CourseDetailScreen}
options={{
headerShown: false,
presentation: 'transparentModal',
animation: 'fade',
contentStyle: {
backgroundColor: 'transparent',
},
}}
/>
</ScheduleStack.Navigator>
);
}

View File

@@ -0,0 +1,155 @@
/**
* Tab 导航器
* 处理底部标签导航(移动端)
*/
import React from 'react';
import { View } from 'react-native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import type { MainTabParamList } from './types';
import { colors, shadows } from '../theme';
import { HomeNavigator } from './HomeNavigator';
import { MessageNavigator } from './MessageNavigator';
import { ScheduleNavigator } from './ScheduleNavigator';
import { ProfileNavigator } from './ProfileNavigator';
const Tab = createBottomTabNavigator<MainTabParamList>();
// 常量
const MOBILE_TAB_FLOATING_MARGIN = 12;
interface TabNavigatorProps {
unreadCount: number;
}
export function TabNavigator({ unreadCount }: TabNavigatorProps) {
const insets = useSafeAreaInsets();
return (
<Tab.Navigator
screenOptions={{
tabBarActiveTintColor: colors.primary.main,
tabBarInactiveTintColor: colors.text.secondary,
tabBarHideOnKeyboard: true,
tabBarStyle: {
backgroundColor: colors.background.paper,
borderTopWidth: 1,
borderTopColor: `${colors.divider}88`,
borderRadius: 24,
marginHorizontal: 14,
marginBottom: MOBILE_TAB_FLOATING_MARGIN + insets.bottom,
height: 64,
paddingBottom: 6,
paddingTop: 8,
paddingHorizontal: 8,
position: 'absolute',
...shadows.lg,
},
tabBarItemStyle: {
borderRadius: 18,
paddingVertical: 1,
marginHorizontal: 2,
},
tabBarLabelStyle: {
fontSize: 12,
fontWeight: '600',
marginTop: -2,
letterSpacing: 0.2,
},
tabBarBadgeStyle: {
backgroundColor: colors.error.main,
color: colors.primary.contrast,
fontSize: 10,
fontWeight: '700',
top: 4,
},
headerShown: false,
}}
>
<Tab.Screen
name="HomeTab"
component={HomeNavigator}
options={{
tabBarLabel: '首页',
tabBarIcon: ({ color, size, focused }) => (
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
<MaterialCommunityIcons
name={focused ? 'home' : 'home-outline'}
size={focused ? 26 : 24}
color={color}
/>
</View>
),
}}
/>
<Tab.Screen
name="MessageTab"
component={MessageNavigator}
options={{
tabBarLabel: '消息',
tabBarBadge: unreadCount > 0 ? (unreadCount > 99 ? '99+' : unreadCount) : undefined,
tabBarIcon: ({ color, size, focused }) => (
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
<MaterialCommunityIcons
name={focused ? 'message-text' : 'message-text-outline'}
size={focused ? 26 : 24}
color={color}
/>
</View>
),
}}
/>
<Tab.Screen
name="ScheduleTab"
component={ScheduleNavigator}
options={{
tabBarLabel: '课表',
tabBarIcon: ({ color, size, focused }) => (
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
<MaterialCommunityIcons
name={focused ? 'calendar-today' : 'calendar-today'}
size={focused ? 26 : 24}
color={color}
/>
</View>
),
}}
/>
<Tab.Screen
name="ProfileTab"
component={ProfileNavigator}
options={{
tabBarLabel: '我的',
tabBarIcon: ({ color, size, focused }) => (
<View style={[styles.tabIconContainer, focused && styles.tabIconActive]}>
<MaterialCommunityIcons
name={focused ? 'account' : 'account-outline'}
size={focused ? 26 : 24}
color={color}
/>
</View>
),
}}
/>
</Tab.Navigator>
);
}
import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({
tabIconContainer: {
alignItems: 'center',
justifyContent: 'center',
width: 38,
height: 30,
borderRadius: 15,
},
tabIconActive: {
backgroundColor: `${colors.primary.main}20`,
transform: [{ translateY: -1 }],
},
});

30
src/navigation/index.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* 导出所有导航组件
*/
// 类型
export type {
RootStackParamList,
MainTabParamList,
HomeStackParamList,
MessageStackParamList,
ScheduleStackParamList,
ProfileStackParamList,
AuthStackParamList,
TabName,
NavItemConfig,
} from './types';
// 导航器
export { AuthNavigator } from './AuthNavigator';
export { HomeNavigator } from './HomeNavigator';
export { MessageNavigator } from './MessageNavigator';
export { ScheduleNavigator } from './ScheduleNavigator';
export { ProfileNavigator } from './ProfileNavigator';
export { TabNavigator } from './TabNavigator';
export { DesktopNavigator } from './DesktopNavigator';
export { RootNavigator } from './RootNavigator';
export { MainNavigator } from './MainNavigator';
// 移动端简化导航
export { SimpleMobileTabNavigator } from './SimpleMobileTabNavigator';

View File

@@ -121,3 +121,14 @@ export type MessageScreenNames = keyof MessageStackParamList;
export type ProfileScreenNames = keyof ProfileStackParamList;
export type MainTabScreenNames = keyof MainTabParamList;
export type RootScreenNames = keyof RootStackParamList;
// ==================== Tab 类型 ====================
export type TabName = 'HomeTab' | 'MessageTab' | 'ScheduleTab' | 'ProfileTab';
// ==================== 导航项配置 ====================
export interface NavItemConfig {
name: TabName;
label: string;
icon: string;
iconOutline: string;
}