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

@@ -5,6 +5,7 @@
// 导出DTO类型作为主要类型
export * from './dto';
export * from './schedule';
// 兼容旧类型(用于内部组件)
import type {

125
src/types/schedule.ts Normal file
View File

@@ -0,0 +1,125 @@
/**
* 课程表类型定义
*/
// 课程数据接口
export interface Course {
id: string;
name: string;
location?: string;
teacher?: string;
dayOfWeek: number; // 0-6 周一到周日
startSection: number; // 开始节次 (1-12)
endSection: number; // 结束节次 (1-12)
weeks: number[]; // 上课周数
color?: string; // 卡片颜色
}
// 时间段配置
export interface TimeSlot {
section: number; // 节次
startTime: string; // 如 "08:00"
endTime: string; // 如 "09:45"
}
// 周配置
export interface WeekConfig {
currentWeek: number; // 当前周
totalWeeks: number; // 总周数 (通常16-20周)
semesterStartDate: string; // 学期开始日期
}
// 课程表状态
export interface ScheduleState {
courses: Course[];
currentWeek: number;
selectedWeek: number;
timeSlots: TimeSlot[];
isLoading: boolean;
error: string | null;
}
// 课程卡片颜色预设
export const COURSE_COLORS = [
'#FF6B6B',
'#FF8E72',
'#FFA552',
'#FFB347',
'#FFD166',
'#F6E05E',
'#D9ED92',
'#B5E48C',
'#99D98C',
'#76C893',
'#52B69A',
'#34A0A4',
'#4ECDC4',
'#2EC4B6',
'#45B7D1',
'#48CAE4',
'#4EA8DE',
'#5390D9',
'#4361EE',
'#3A0CA3',
'#5E60CE',
'#6930C3',
'#7400B8',
'#9D4EDD',
'#C77DFF',
'#DDA0DD',
'#E0AAFF',
'#F15BB5',
'#FF5D8F',
'#EF476F',
'#E76F51',
'#A8DADC',
'#8EC5FC',
'#98D8C8',
'#96CEB4',
'#85C1E2',
'#70D6FF',
'#7BDFF2',
'#B2F7EF',
'#F7D6E0',
];
// 默认时间段配置 (12节)
export const DEFAULT_TIME_SLOTS: TimeSlot[] = [
{ section: 1, startTime: '08:00', endTime: '08:45' },
{ section: 2, startTime: '08:55', endTime: '09:40' },
{ section: 3, startTime: '10:00', endTime: '10:45' },
{ section: 4, startTime: '10:55', endTime: '11:40' },
{ section: 5, startTime: '14:00', endTime: '14:45' },
{ section: 6, startTime: '14:55', endTime: '15:40' },
{ section: 7, startTime: '16:00', endTime: '16:45' },
{ section: 8, startTime: '16:55', endTime: '17:40' },
{ section: 9, startTime: '19:00', endTime: '19:45' },
{ section: 10, startTime: '19:55', endTime: '20:40' },
{ section: 11, startTime: '20:50', endTime: '21:35' },
{ section: 12, startTime: '21:45', endTime: '22:30' },
];
// 星期名称
export const WEEKDAY_NAMES = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
// 获取当前是第几周
export function getCurrentWeek(semesterStartDate: string, totalWeeks: number = 20): number {
const start = new Date(semesterStartDate);
const now = new Date();
const diffTime = now.getTime() - start.getTime();
const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
const week = Math.floor(diffDays / 7) + 1;
return Math.min(Math.max(week, 1), totalWeeks);
}
// 获取课程颜色
export function getCourseColor(courseId: string, color?: string): string {
if (color) return color;
const hash = courseId.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
return COURSE_COLORS[hash % COURSE_COLORS.length];
}
// 检查课程在某周是否有课
export function hasCourseInWeek(course: Course, week: number): boolean {
return course.weeks.includes(week);
}