feat(navigation): add schedule tab and stack navigator

Add new ScheduleTab to main navigation with ScheduleStackNavigator for managing course schedule screens. Includes CourseDetailScreen with modal presentation. Also exports scheduleService and related types for the new schedule feature.

fix(message): add group_id parameter to invite and request handlers

Pass group_id from extra_data when calling respondInvite and reviewJoinRequest APIs, as the backend now requires this parameter.

refactor(message): extract mergeMessagesById helper method

Centralize message merging logic into a reusable private method to avoid duplication and ensure consistent message deduplication behavior.
This commit is contained in:
2026-03-12 08:37:08 +08:00
parent f10c9cc1d7
commit 798dd7c9a0
12 changed files with 1606 additions and 16 deletions

View File

@@ -40,12 +40,14 @@ import type {
MainTabParamList,
HomeStackParamList,
MessageStackParamList,
ScheduleStackParamList,
ProfileStackParamList,
AuthStackParamList,
} from './types';
// ==================== 导入屏幕组件 ====================
import { HomeScreen, PostDetailScreen, SearchScreen } from '../screens/home';
import { ScheduleScreen, CourseDetailScreen } from '../screens/schedule';
import {
MessageListScreen,
ChatScreen,
@@ -69,6 +71,7 @@ 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>();
@@ -79,7 +82,7 @@ const SIDEBAR_COLLAPSED_WIDTH = 72;
const MOBILE_TAB_FLOATING_MARGIN = 12;
// ==================== 导航项类型 ====================
type TabName = 'HomeTab' | 'MessageTab' | 'ProfileTab';
type TabName = 'HomeTab' | 'MessageTab' | 'ScheduleTab' | 'ProfileTab';
type IconName = React.ComponentProps<typeof MaterialCommunityIcons>['name'];
@@ -306,6 +309,7 @@ function Sidebar({
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' },
];
@@ -418,6 +422,37 @@ function Text({ style, children, numberOfLines }: { style?: any; children: React
);
}
// ==================== 课程表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;
@@ -441,7 +476,7 @@ function ResponsiveTabNavigator({ children }: ResponsiveTabNavigatorProps) {
useEffect(() => {
const targetTab = route.params?.screen;
if (!targetTab) return;
if ((targetTab === 'HomeTab' || targetTab === 'MessageTab' || targetTab === 'ProfileTab') && targetTab !== activeTab) {
if ((targetTab === 'HomeTab' || targetTab === 'MessageTab' || targetTab === 'ScheduleTab' || targetTab === 'ProfileTab') && targetTab !== activeTab) {
setActiveTab(targetTab);
}
}, [route.params?.screen, activeTab]);
@@ -482,6 +517,8 @@ function ResponsiveTabNavigator({ children }: ResponsiveTabNavigatorProps) {
return <HomeStackNavigator />;
case 'MessageTab':
return <MessageStackNavigator />;
case 'ScheduleTab':
return <ScheduleStackNavigator />;
case 'ProfileTab':
return <ProfileStackNavigator />;
default:
@@ -609,6 +646,22 @@ function MainTabNavigator() {
),
}}
/>
<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}

View File

@@ -5,11 +5,13 @@
import { NavigatorScreenParams } from '@react-navigation/native';
import type { SystemMessageResponse } from '../types/dto';
import type { Course } from '../types/schedule';
// ==================== 主Tab导航参数 ====================
export type MainTabParamList = {
HomeTab: NavigatorScreenParams<HomeStackParamList>;
MessageTab: NavigatorScreenParams<MessageStackParamList>;
ScheduleTab: NavigatorScreenParams<ScheduleStackParamList>;
ProfileTab: NavigatorScreenParams<ProfileStackParamList>;
};
@@ -59,6 +61,12 @@ export type ProfileStackParamList = {
BlockedUsers: undefined;
};
// ==================== 课程表Stack ====================
export type ScheduleStackParamList = {
Schedule: undefined;
CourseDetail: { course: Course; relatedCourses: Course[] };
};
// ==================== 认证Stack ====================
export type AuthStackParamList = {
Login: undefined;