import { TimeTabSelector, TimeTabType } from '@/components/TimeTabSelector'; import { TimelineSchedule } from '@/components/TimelineSchedule'; import { TodoItem } from '@/components/TodoCard'; import { TodoCarousel } from '@/components/TodoCarousel'; import { Colors } from '@/constants/Colors'; import { useColorScheme } from '@/hooks/useColorScheme'; import { getMonthDaysZh, getMonthTitleZh, getTodayIndexInMonth } from '@/utils/date'; import dayjs from 'dayjs'; import isBetween from 'dayjs/plugin/isBetween'; import { LinearGradient } from 'expo-linear-gradient'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { SafeAreaView, ScrollView, StatusBar, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; dayjs.extend(isBetween); // 模拟数据 const mockTodos: TodoItem[] = [ { id: '1', title: '每日健身训练', description: '完成30分钟普拉提训练', time: dayjs().hour(8).minute(0).toISOString(), category: 'workout', priority: 'high', isCompleted: false, }, { id: '2', title: '支付信用卡账单', description: '本月信用卡账单到期', time: dayjs().hour(10).minute(0).toISOString(), category: 'finance', priority: 'medium', isCompleted: false, }, { id: '3', title: '参加瑜伽课程', description: '晚上瑜伽课程预约', time: dayjs().hour(19).minute(0).toISOString(), category: 'personal', priority: 'low', isCompleted: true, }, ]; const mockTimelineEvents = [ { id: '1', title: '每日健身训练', startTime: dayjs().hour(8).minute(0).toISOString(), endTime: dayjs().hour(8).minute(30).toISOString(), category: 'workout' as const, isCompleted: false, }, { id: '2', title: '支付信用卡账单', startTime: dayjs().hour(10).minute(0).toISOString(), endTime: dayjs().hour(10).minute(15).toISOString(), category: 'finance' as const, isCompleted: false, }, { id: '3', title: '团队会议', startTime: dayjs().hour(10).minute(0).toISOString(), endTime: dayjs().hour(11).minute(0).toISOString(), category: 'work' as const, isCompleted: false, }, { id: '4', title: '午餐时间', startTime: dayjs().hour(12).minute(0).toISOString(), endTime: dayjs().hour(13).minute(0).toISOString(), category: 'personal' as const, isCompleted: true, }, { id: '5', title: '健康检查', startTime: dayjs().hour(14).minute(30).toISOString(), endTime: dayjs().hour(15).minute(30).toISOString(), category: 'health' as const, isCompleted: false, }, { id: '6', title: '参加瑜伽课程', startTime: dayjs().hour(19).minute(0).toISOString(), endTime: dayjs().hour(20).minute(0).toISOString(), category: 'personal' as const, isCompleted: true, }, ]; export default function GoalsScreen() { const theme = (useColorScheme() ?? 'light') as 'light' | 'dark'; const colorTokens = Colors[theme]; const [selectedTab, setSelectedTab] = useState('day'); const [selectedDate, setSelectedDate] = useState(new Date()); const [todos, setTodos] = useState(mockTodos); // tab切换处理函数 const handleTabChange = (tab: TimeTabType) => { setSelectedTab(tab); // 当切换到周或月模式时,如果当前选择的日期不是今天,则重置为今天 const today = new Date(); const currentDate = selectedDate; if (tab === 'week' || tab === 'month') { // 如果当前选择的日期不是今天,重置为今天 if (!dayjs(currentDate).isSame(dayjs(today), 'day')) { setSelectedDate(today); setSelectedIndex(getTodayIndexInMonth()); } } else if (tab === 'day') { // 天模式下也重置为今天 setSelectedDate(today); setSelectedIndex(getTodayIndexInMonth()); } }; // 日期选择器相关状态 (参考 statistics.tsx) const days = getMonthDaysZh(); const [selectedIndex, setSelectedIndex] = useState(getTodayIndexInMonth()); const monthTitle = getMonthTitleZh(); // 日期条自动滚动到选中项 const daysScrollRef = useRef(null); const [scrollWidth, setScrollWidth] = useState(0); const DAY_PILL_WIDTH = 48; const DAY_PILL_SPACING = 8; const scrollToIndex = (index: number, animated = true) => { if (!daysScrollRef.current || scrollWidth === 0) return; const itemWidth = DAY_PILL_WIDTH + DAY_PILL_SPACING; const baseOffset = index * itemWidth; const centerOffset = Math.max(0, baseOffset - (scrollWidth / 2 - DAY_PILL_WIDTH / 2)); // 确保不会滚动超出边界 const maxScrollOffset = Math.max(0, (days.length * itemWidth) - scrollWidth); const finalOffset = Math.min(centerOffset, maxScrollOffset); daysScrollRef.current.scrollTo({ x: finalOffset, animated }); }; useEffect(() => { if (scrollWidth > 0) { scrollToIndex(selectedIndex, false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [scrollWidth]); // 当选中索引变化时,滚动到对应位置 useEffect(() => { if (scrollWidth > 0) { scrollToIndex(selectedIndex, true); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedIndex]); // 日期选择处理 const onSelectDate = (index: number) => { setSelectedIndex(index); const targetDate = days[index]?.date?.toDate(); if (targetDate) { setSelectedDate(targetDate); // 在周模式下,如果用户选择了新日期,更新周的显示范围 if (selectedTab === 'week') { // 自动滚动到新选择的日期 setTimeout(() => { scrollToIndex(index, true); }, 100); } } }; // 上半部分待办卡片始终只显示当日数据 const todayTodos = useMemo(() => { const today = dayjs(); return todos.filter(todo => dayjs(todo.time).isSame(today, 'day') ); }, [todos]); // 下半部分时间轴根据选择的时间范围和日期过滤数据 const filteredTimelineEvents = useMemo(() => { const selected = dayjs(selectedDate); switch (selectedTab) { case 'day': return mockTimelineEvents.filter(event => dayjs(event.startTime).isSame(selected, 'day') ); case 'week': return mockTimelineEvents.filter(event => dayjs(event.startTime).isSame(selected, 'week') ); case 'month': return mockTimelineEvents.filter(event => dayjs(event.startTime).isSame(selected, 'month') ); default: return mockTimelineEvents; } }, [selectedTab, selectedDate]); const handleTodoPress = (item: TodoItem) => { console.log('Todo pressed:', item.title); // 这里可以导航到详情页面或展示编辑模态框 }; const handleToggleComplete = (item: TodoItem) => { setTodos(prevTodos => prevTodos.map(todo => todo.id === item.id ? { ...todo, isCompleted: !todo.isCompleted } : todo ) ); }; const handleEventPress = (event: any) => { console.log('Event pressed:', event.title); // 这里可以处理时间轴事件点击 }; return ( {/* 背景渐变 */} {/* 标题区域 */} 今日 {dayjs().format('YYYY年M月D日 dddd')} {/* 今日待办事项卡片 */} {/* 时间筛选选项卡 */} {/* 日期选择器 - 在周和月模式下显示 */} {(selectedTab === 'week' || selectedTab === 'month') && ( {monthTitle} setScrollWidth(e.nativeEvent.layout.width)} > {days.map((d, i) => { const selected = i === selectedIndex; const isFutureDate = d.date.isAfter(dayjs(), 'day'); // 根据选择的tab模式决定是否显示该日期 let shouldShow = true; if (selectedTab === 'week') { // 周模式:只显示选中日期所在周的日期 const selectedWeekStart = dayjs(selectedDate).startOf('week'); const selectedWeekEnd = dayjs(selectedDate).endOf('week'); shouldShow = d.date.isBetween(selectedWeekStart, selectedWeekEnd, 'day', '[]'); } if (!shouldShow) return null; return ( !isFutureDate && onSelectDate(i)} activeOpacity={isFutureDate ? 1 : 0.8} disabled={isFutureDate} > {d.weekdayZh} {d.dayOfMonth} {selected && } ); })} )} {/* 时间轴安排 */} ); } const styles = StyleSheet.create({ container: { flex: 1, }, backgroundGradient: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, }, content: { flex: 1, }, header: { paddingHorizontal: 20, paddingTop: 20, paddingBottom: 16, }, pageTitle: { fontSize: 28, fontWeight: '800', marginBottom: 4, }, pageSubtitle: { fontSize: 16, fontWeight: '500', }, timelineSection: { flex: 1, backgroundColor: 'rgba(255, 255, 255, 0.95)', borderTopLeftRadius: 24, borderTopRightRadius: 24, marginTop: 8, overflow: 'hidden', }, // 日期选择器样式 (参考 statistics.tsx) dateSelector: { paddingHorizontal: 20, paddingVertical: 16, }, monthTitle: { fontSize: 24, fontWeight: '800', marginBottom: 14, }, daysContainer: { paddingBottom: 8, }, dayItemWrapper: { alignItems: 'center', width: 48, marginRight: 8, }, dayPill: { width: 48, height: 72, borderRadius: 24, alignItems: 'center', justifyContent: 'center', }, dayPillNormal: { backgroundColor: 'transparent', }, dayPillSelected: { backgroundColor: '#FFFFFF', shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 4, elevation: 3, }, dayPillDisabled: { backgroundColor: 'transparent', opacity: 0.4, }, dayLabel: { fontSize: 12, fontWeight: '700', color: 'gray', marginBottom: 2, }, dayLabelSelected: { color: '#192126', }, dayLabelDisabled: { }, dayDate: { fontSize: 14, fontWeight: '800', color: 'gray', }, dayDateSelected: { color: '#192126', }, dayDateDisabled: { }, selectedDot: { width: 5, height: 5, borderRadius: 2.5, marginTop: 6, marginBottom: 2, alignSelf: 'center', }, });