feat: 初始提交(仅核心代码,已排除大文件)

This commit is contained in:
yangxiangyuan
2026-07-08 10:29:50 +08:00
commit be7c5842f1
623 changed files with 332897 additions and 0 deletions
@@ -0,0 +1,267 @@
import React, { useState, useCallback, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
FlatList,
TouchableOpacity,
Alert,
ActivityIndicator,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useNavigation, useFocusEffect } from '@react-navigation/native';
import { theme } from '../theme';
import { Event } from '../types';
import { eventApi } from '../api';
import EventModal from '../components/EventModal';
import EventCard from '../components/EventCard';
import { shouldShowEventOnDate, safeParseDate } from '../utils/recurrence';
const showError = (title: string, error: any) => {
const message = error instanceof Error ? error.message : '加载事件失败';
Alert.alert(title, message);
};
export default function TodayScreen() {
const navigation = useNavigation();
const [events, setEvents] = useState<Event[]>([]);
const [loading, setLoading] = useState(true);
const [modalVisible, setModalVisible] = useState(false);
const [selectedEvent, setSelectedEvent] = useState<Event | null>(null);
const loadEvents = useCallback(async () => {
try {
setLoading(true);
const today = new Date();
const allEvents = await eventApi.getEvents(today.getFullYear(), today.getMonth() + 1);
const todayEvents = allEvents.filter(event => {
if (event.deleted) return false;
return shouldShowEventOnDate(event, today);
}).sort((a, b) => safeParseDate(a.start).getTime() - safeParseDate(b.start).getTime());
setEvents(todayEvents);
} catch (error) {
showError('加载失败', error);
} finally {
setLoading(false);
}
}, []);
useFocusEffect(
useCallback(() => {
loadEvents();
}, [loadEvents])
);
const handleAddEvent = () => {
setSelectedEvent(null);
setModalVisible(true);
};
const handleEditEvent = (event: Event) => {
setSelectedEvent(event);
setModalVisible(true);
};
const handleToggleComplete = async (event: Event) => {
try {
await eventApi.updateEvent(event.id, { completed: !event.completed });
await loadEvents();
} catch (error) {
showError('更新失败', error);
}
};
const handleDeleteEvent = async (event: Event) => {
Alert.alert(
'确认删除',
'确定要删除这个事件吗?',
[
{ text: '取消', style: 'cancel' },
{
text: '删除',
style: 'destructive',
onPress: async () => {
try {
await eventApi.deleteEvent(event.id);
await loadEvents();
} catch (error) {
showError('删除失败', error);
}
},
},
]
);
};
const handleSaveEvent = async (eventData: Partial<Event>) => {
try {
if (selectedEvent) {
await eventApi.updateEvent(selectedEvent.id, eventData);
} else {
await eventApi.createEvent({
...eventData,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
deleted: false,
} as Event);
}
setModalVisible(false);
await loadEvents();
} catch (error) {
showError('保存失败', error);
}
};
const getGreeting = () => {
const hour = new Date().getHours();
if (hour < 12) return '早上好';
if (hour < 18) return '下午好';
return '晚上好';
};
const pendingEvents = events.filter(e => !e.completed);
const completedEvents = events.filter(e => e.completed);
return (
<View style={styles.container}>
<View style={styles.header}>
<View style={styles.headerLeft}>
<Text style={styles.greeting}>{getGreeting()} 👋</Text>
<Text style={styles.date}>
{new Date().toLocaleDateString('zh-CN', {
year: 'numeric',
month: 'long',
day: 'numeric',
})}
</Text>
</View>
<TouchableOpacity style={styles.addButton} onPress={handleAddEvent}>
<Ionicons name="add" size={28} color={theme.colors.background} />
</TouchableOpacity>
</View>
<View style={styles.statsContainer}>
<View style={styles.statCard}>
<Text style={styles.statNumber}>{pendingEvents.length}</Text>
<Text style={styles.statLabel}>待完成</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statNumber}>{completedEvents.length}</Text>
<Text style={styles.statLabel}>已完成</Text>
</View>
<View style={styles.statCard}>
<Text style={styles.statNumber}>{events.length}</Text>
<Text style={styles.statLabel}>共计</Text>
</View>
</View>
<Text style={styles.sectionTitle}>待完成</Text>
{loading ? (
<ActivityIndicator style={styles.loader} color={theme.colors.primary} />
) : (
<FlatList
data={pendingEvents}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<EventCard
event={item}
onEdit={handleEditEvent}
onToggleComplete={handleToggleComplete}
onDelete={handleDeleteEvent}
/>
)}
contentContainerStyle={styles.list}
showsVerticalScrollIndicator={false}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Text style={styles.emptyText}>今天没有待办事项 🎉</Text>
</View>
}
/>
)}
<EventModal
visible={modalVisible}
event={selectedEvent}
onClose={() => setModalVisible(false)}
onSave={handleSaveEvent}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: theme.colors.background,
padding: theme.spacing.md,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: theme.spacing.lg,
},
headerLeft: {
flex: 1,
},
greeting: {
...theme.typography.h1,
color: theme.colors.text,
marginBottom: theme.spacing.xs,
},
date: {
...theme.typography.bodySmall,
color: theme.colors.textSecondary,
},
addButton: {
backgroundColor: theme.colors.primary,
width: 56,
height: 56,
borderRadius: 28,
justifyContent: 'center',
alignItems: 'center',
},
statsContainer: {
flexDirection: 'row',
gap: theme.spacing.sm,
marginBottom: theme.spacing.lg,
},
statCard: {
flex: 1,
backgroundColor: theme.colors.surface,
padding: theme.spacing.md,
borderRadius: theme.borderRadius.lg,
alignItems: 'center',
},
statNumber: {
...theme.typography.h2,
color: theme.colors.primary,
},
statLabel: {
...theme.typography.bodySmall,
color: theme.colors.textSecondary,
},
sectionTitle: {
...theme.typography.h3,
color: theme.colors.text,
marginBottom: theme.spacing.sm,
},
list: {
paddingBottom: 20,
},
loader: {
flex: 1,
justifyContent: 'center',
},
emptyContainer: {
padding: theme.spacing.xl,
alignItems: 'center',
},
emptyText: {
...theme.typography.body,
color: theme.colors.textSecondary,
textAlign: 'center',
},
});