Files
frontend/src/navigation/MainNavigator.tsx

1093 lines
30 KiB
TypeScript
Raw Normal View History

/**
*
* 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 type {
RootStackParamList,
MainTabParamList,
HomeStackParamList,
MessageStackParamList,
ScheduleStackParamList,
ProfileStackParamList,
AuthStackParamList,
} from './types';
// ==================== 导入屏幕组件 ====================
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<RootStackParamList>();
const AuthStack = createNativeStackNavigator<AuthStackParamList>();
const HomeStack = createNativeStackNavigator<HomeStackParamList>();
const MessageStack = createNativeStackNavigator<MessageStackParamList>();
const ScheduleStack = createNativeStackNavigator<ScheduleStackParamList>();
const ProfileStack = createNativeStackNavigator<ProfileStackParamList>();
const Tab = createBottomTabNavigator<MainTabParamList>();
// ==================== 导航栏宽度常量 ====================
const SIDEBAR_WIDTH_DESKTOP = 240;
const SIDEBAR_WIDTH_TABLET = 200;
const SIDEBAR_COLLAPSED_WIDTH = 72;
const MOBILE_TAB_FLOATING_MARGIN = 12;
// ==================== 导航项类型 ====================
type TabName = 'HomeTab' | 'MessageTab' | 'ScheduleTab' | 'ProfileTab';
type IconName = React.ComponentProps<typeof MaterialCommunityIcons>['name'];
interface NavItem {
name: TabName;
label: string;
icon: IconName;
iconOutline: IconName;
badge?: number;
}
// ==================== AuthStack导航 ====================
function AuthStackNavigator() {
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>
);
}
// ==================== 首页Stack导航 ====================
function HomeStackNavigator() {
const paperTheme = useTheme();
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>
);
}
// ==================== 消息Stack导航 ====================
function MessageStackNavigator() {
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>
);
}
// ==================== 个人中心Stack导航 ====================
function ProfileStackNavigator() {
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>
);
}
// ==================== 侧边导航栏组件 ====================
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();
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 (
<SafeAreaView
style={[
styles.sidebar,
{
width,
paddingTop: insets.top,
paddingBottom: insets.bottom,
},
]}
>
{/* Logo/品牌区域 */}
<View style={styles.sidebarHeader}>
{!isCollapsed && (
<View style={styles.logoContainer}>
<MaterialCommunityIcons
name="carrot"
size={32}
color={colors.primary.main}
/>
<Animated.Text style={styles.logoText}>
BBS
</Animated.Text>
</View>
)}
{isCollapsed && (
<MaterialCommunityIcons
name="carrot"
size={32}
color={colors.primary.main}
/>
)}
</View>
{/* 导航项 */}
<ScrollView style={styles.sidebarContent} showsVerticalScrollIndicator={false}>
{navItems.map((item) => {
const isActive = activeTab === item.name;
const showBadge = !!(item.badge && item.badge > 0);
return (
<TouchableOpacity
key={item.name}
style={[
styles.sidebarItem,
isActive && styles.sidebarItemActive,
isCollapsed && styles.sidebarItemCollapsed,
]}
onPress={() => onTabChange(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}>
{item.badge! > 99 ? '99+' : item.badge}
</Text>
</View>
)}
</View>
{!isCollapsed && (
<Text
style={[
styles.sidebarLabel,
isActive && styles.sidebarLabelActive,
]}
numberOfLines={1}
>
{item.label}
</Text>
)}
</TouchableOpacity>
);
})}
</ScrollView>
{/* 底部折叠按钮 */}
<TouchableOpacity
style={[
styles.collapseButton,
isCollapsed && styles.collapseButtonCollapsed,
]}
onPress={onToggleCollapse}
activeOpacity={0.7}
>
<MaterialCommunityIcons
name={isCollapsed ? 'chevron-right' : 'chevron-left'}
size={24}
color={colors.text.secondary}
/>
</TouchableOpacity>
</SafeAreaView>
);
}
// ==================== Text 组件 ====================
function Text({ style, children, numberOfLines }: { style?: any; children: React.ReactNode; numberOfLines?: number }) {
return (
<RNText style={style} numberOfLines={numberOfLines}>
{children}
</RNText>
);
}
// ==================== 课程表Stack导航 ====================
function ScheduleStackNavigator() {
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>
);
}
// ==================== 响应式 Tab Navigator ====================
interface ResponsiveTabNavigatorProps {
children?: React.ReactNode;
}
function ResponsiveTabNavigator({ children }: ResponsiveTabNavigatorProps) {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const route = useRoute<RouteProp<RootStackParamList, 'Main'>>();
const { isMobile, isTablet, isDesktop } = useResponsive();
const insets = useSafeAreaInsets();
const [activeTab, setActiveTab] = useState<TabName>('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 (isCollapsed) return SIDEBAR_COLLAPSED_WIDTH;
if (isDesktop) return SIDEBAR_WIDTH_DESKTOP;
if (isTablet) return SIDEBAR_WIDTH_TABLET;
return 0;
}, [isCollapsed, isDesktop, isTablet]);
// 切换 Tab
const handleTabChange = useCallback((tab: TabName) => {
setActiveTab(tab);
if (Platform.OS === 'web') {
let tabTarget: NavigatorScreenParams<MainTabParamList>;
if (tab === 'HomeTab') {
tabTarget = { screen: 'HomeTab', params: { screen: 'Home' } };
} else if (tab === 'MessageTab') {
tabTarget = { screen: 'MessageTab', params: { screen: 'MessageList' } };
} else {
tabTarget = { screen: 'ProfileTab', params: { screen: 'Profile' } };
}
navigation.navigate('Main', tabTarget);
}
}, [navigation]);
// 切换折叠状态
const handleToggleCollapse = useCallback(() => {
setIsCollapsed(prev => !prev);
}, []);
// 渲染当前 Tab 的内容
const renderTabContent = () => {
switch (activeTab) {
case 'HomeTab':
return <HomeStackNavigator />;
case 'MessageTab':
return <MessageStackNavigator />;
case 'ScheduleTab':
return <ScheduleStackNavigator />;
case 'ProfileTab':
return <ProfileStackNavigator />;
default:
return <HomeStackNavigator />;
}
};
// 移动端:使用底部 Tab 导航
if (isMobile) {
return <MainTabNavigator />;
}
// 平板/桌面端:使用侧边导航
const currentSidebarWidth = sidebarWidth();
return (
<View style={styles.responsiveContainer}>
{/* 侧边导航栏 */}
<Sidebar
activeTab={activeTab}
onTabChange={handleTabChange}
unreadCount={messageUnreadCount}
isCollapsed={isCollapsed}
onToggleCollapse={handleToggleCollapse}
width={currentSidebarWidth}
/>
{/* 主内容区域 */}
<View
style={[
styles.mainContent,
{
marginLeft: 0,
flex: 1,
},
]}
>
{renderTabContent()}
</View>
</View>
);
}
// ==================== 底部Tab导航移动端====================
function MainTabNavigator() {
const insets = useSafeAreaInsets();
const messageUnreadCount = useTotalUnreadCount();
// 【新架构】MessageManager 自动处理 WebSocket 消息和未读数更新
useEffect(() => {
messageManager.initialize();
}, []);
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={HomeStackNavigator}
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={MessageStackNavigator}
options={{
tabBarLabel: '消息',
tabBarBadge: messageUnreadCount > 0 ? (messageUnreadCount > 99 ? '99+' : messageUnreadCount) : 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={ScheduleStackNavigator}
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={ProfileStackNavigator}
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>
);
}
// ==================== 根导航 ====================
export default function MainNavigator() {
const { isAuthenticated, fetchCurrentUser } = useAuthStore();
const [initializing, setInitializing] = useState(true);
const linking: LinkingOptions<RootStackParamList> = {
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 (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color={colors.primary.main} />
</View>
);
}
return (
<NavigationContainer linking={Platform.OS === 'web' ? linking : undefined}>
<RootStack.Navigator
screenOptions={{
headerShown: false,
}}
>
{/* 根据登录状态显示不同Stack */}
{isAuthenticated ? (
<>
<RootStack.Screen
name="Main"
component={ResponsiveTabNavigator}
/>
<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',
}}
/>
</>
) : (
<>
<RootStack.Screen
name="Auth"
component={AuthStackNavigator}
options={{
headerShown: false,
}}
/>
{/* 未登录时也可以查看帖子详情 */}
<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.Navigator>
</NavigationContainer>
);
}
// ==================== 样式 ====================
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,
},
});