- 创建目标管理演示页面,展示高保真的目标管理界面 - 实现待办事项卡片的横向滑动展示,支持时间筛选功能 - 新增时间轴组件,展示选中日期的具体任务 - 更新底部导航,添加目标管理和演示页面的路由 - 优化个人页面菜单项,提供目标管理的快速访问 - 编写目标管理功能实现文档,详细描述功能和组件架构
249 lines
6.1 KiB
TypeScript
249 lines
6.1 KiB
TypeScript
import { Colors } from '@/constants/Colors';
|
|
import { useColorScheme } from '@/hooks/useColorScheme';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import dayjs from 'dayjs';
|
|
import React from 'react';
|
|
import { Dimensions, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
|
|
export interface TodoItem {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
time: string;
|
|
category: 'workout' | 'finance' | 'personal' | 'work' | 'health';
|
|
isCompleted?: boolean;
|
|
priority?: 'high' | 'medium' | 'low';
|
|
}
|
|
|
|
interface TodoCardProps {
|
|
item: TodoItem;
|
|
onPress?: (item: TodoItem) => void;
|
|
onToggleComplete?: (item: TodoItem) => void;
|
|
}
|
|
|
|
const { width: screenWidth } = Dimensions.get('window');
|
|
const CARD_WIDTH = (screenWidth - 60) * 0.65; // 显示1.5张卡片
|
|
|
|
const getCategoryIcon = (category: TodoItem['category']) => {
|
|
switch (category) {
|
|
case 'workout':
|
|
return 'fitness-outline';
|
|
case 'finance':
|
|
return 'card-outline';
|
|
case 'personal':
|
|
return 'person-outline';
|
|
case 'work':
|
|
return 'briefcase-outline';
|
|
case 'health':
|
|
return 'heart-outline';
|
|
default:
|
|
return 'checkmark-circle-outline';
|
|
}
|
|
};
|
|
|
|
const getCategoryColor = (category: TodoItem['category']) => {
|
|
switch (category) {
|
|
case 'workout':
|
|
return '#FF6B6B';
|
|
case 'finance':
|
|
return '#4ECDC4';
|
|
case 'personal':
|
|
return '#45B7D1';
|
|
case 'work':
|
|
return '#96CEB4';
|
|
case 'health':
|
|
return '#FFEAA7';
|
|
default:
|
|
return '#DDA0DD';
|
|
}
|
|
};
|
|
|
|
const getPriorityColor = (priority: TodoItem['priority']) => {
|
|
switch (priority) {
|
|
case 'high':
|
|
return '#FF4757';
|
|
case 'medium':
|
|
return '#FFA502';
|
|
case 'low':
|
|
return '#2ED573';
|
|
default:
|
|
return '#747D8C';
|
|
}
|
|
};
|
|
|
|
export function TodoCard({ item, onPress, onToggleComplete }: TodoCardProps) {
|
|
const theme = useColorScheme() ?? 'light';
|
|
const colorTokens = Colors[theme];
|
|
|
|
const categoryColor = getCategoryColor(item.category);
|
|
const categoryIcon = getCategoryIcon(item.category);
|
|
const priorityColor = getPriorityColor(item.priority);
|
|
|
|
const timeFormatted = dayjs(item.time).format('HH:mm');
|
|
const isToday = dayjs(item.time).isSame(dayjs(), 'day');
|
|
|
|
return (
|
|
<TouchableOpacity
|
|
style={[styles.container, { backgroundColor: colorTokens.card }]}
|
|
onPress={() => onPress?.(item)}
|
|
activeOpacity={0.8}
|
|
>
|
|
{/* 顶部标签和优先级 */}
|
|
<View style={styles.header}>
|
|
<View style={[styles.categoryBadge, { backgroundColor: categoryColor }]}>
|
|
<Ionicons name={categoryIcon as any} size={12} color="#fff" />
|
|
<Text style={styles.categoryText}>{item.category}</Text>
|
|
</View>
|
|
|
|
{item.priority && (
|
|
<View style={[styles.priorityDot, { backgroundColor: priorityColor }]} />
|
|
)}
|
|
</View>
|
|
|
|
{/* 主要内容 */}
|
|
<View style={styles.content}>
|
|
<Text style={[styles.title, { color: colorTokens.text }]} numberOfLines={2}>
|
|
{item.title}
|
|
</Text>
|
|
|
|
{item.description && (
|
|
<Text style={[styles.description, { color: colorTokens.textSecondary }]} numberOfLines={2}>
|
|
{item.description}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
|
|
{/* 底部时间和完成状态 */}
|
|
<View style={styles.footer}>
|
|
<View style={styles.timeContainer}>
|
|
<Ionicons
|
|
name={isToday ? "time" : "calendar-outline"}
|
|
size={14}
|
|
color={colorTokens.textMuted}
|
|
/>
|
|
<Text style={[styles.timeText, { color: colorTokens.textMuted }]}>
|
|
{timeFormatted}
|
|
</Text>
|
|
</View>
|
|
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.completeButton,
|
|
{
|
|
backgroundColor: item.isCompleted ? colorTokens.primary : 'transparent',
|
|
borderColor: item.isCompleted ? colorTokens.primary : colorTokens.border
|
|
}
|
|
]}
|
|
onPress={() => onToggleComplete?.(item)}
|
|
>
|
|
{item.isCompleted && (
|
|
<Ionicons name="checkmark" size={16} color={colorTokens.onPrimary} />
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
|
|
{/* 完成状态遮罩 */}
|
|
{item.isCompleted && (
|
|
<View style={styles.completedOverlay}>
|
|
<Ionicons name="checkmark-circle" size={24} color={colorTokens.primary} />
|
|
</View>
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
width: CARD_WIDTH,
|
|
height: 140,
|
|
marginHorizontal: 8,
|
|
borderRadius: 20,
|
|
padding: 16,
|
|
shadowColor: '#000',
|
|
shadowOffset: {
|
|
width: 0,
|
|
height: 4,
|
|
},
|
|
shadowOpacity: 0.1,
|
|
shadowRadius: 8,
|
|
elevation: 6,
|
|
position: 'relative',
|
|
},
|
|
header: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginBottom: 12,
|
|
},
|
|
categoryBadge: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 4,
|
|
borderRadius: 12,
|
|
backgroundColor: '#4ECDC4',
|
|
},
|
|
categoryText: {
|
|
fontSize: 10,
|
|
fontWeight: '600',
|
|
color: '#fff',
|
|
marginLeft: 4,
|
|
textTransform: 'capitalize',
|
|
},
|
|
priorityDot: {
|
|
width: 8,
|
|
height: 8,
|
|
borderRadius: 4,
|
|
backgroundColor: '#FF4757',
|
|
},
|
|
content: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
},
|
|
title: {
|
|
fontSize: 16,
|
|
fontWeight: '700',
|
|
lineHeight: 20,
|
|
marginBottom: 4,
|
|
},
|
|
description: {
|
|
fontSize: 12,
|
|
lineHeight: 16,
|
|
opacity: 0.7,
|
|
},
|
|
footer: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
marginTop: 12,
|
|
},
|
|
timeContainer: {
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
},
|
|
timeText: {
|
|
fontSize: 12,
|
|
fontWeight: '500',
|
|
marginLeft: 4,
|
|
},
|
|
completeButton: {
|
|
width: 24,
|
|
height: 24,
|
|
borderRadius: 12,
|
|
borderWidth: 1.5,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
completedOverlay: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
|
borderRadius: 20,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
});
|