feat: 更新训练计划和打卡功能

- 在训练计划中新增训练项目的添加、更新和删除功能,支持用户灵活管理训练内容
- 优化训练计划排课界面,提升用户体验
- 更新打卡功能,支持按日期加载和展示打卡记录
- 删除不再使用的打卡相关页面,简化代码结构
- 新增今日训练页面,集成今日训练计划和动作展示
- 更新样式以适应新功能的展示和交互
This commit is contained in:
richarjiang
2025-08-15 17:01:33 +08:00
parent f95401c1ce
commit dacbee197c
19 changed files with 3052 additions and 1197 deletions

223
components/MyPlanCard.tsx Normal file
View File

@@ -0,0 +1,223 @@
import { type TrainingPlan } from '@/store/trainingPlanSlice';
import { Ionicons } from '@expo/vector-icons';
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
interface MyPlanCardProps {
plan: TrainingPlan;
onPress: () => void;
}
const GOAL_TEXT: Record<string, { title: string; color: string; description: string }> = {
postpartum_recovery: { title: '产后恢复', color: '#9BE370', description: '温和激活,核心重建' },
fat_loss: { title: '减脂塑形', color: '#FFB86B', description: '全身燃脂,线条雕刻' },
posture_correction: { title: '体态矫正', color: '#95CCE3', description: '打开胸肩,改善圆肩驼背' },
core_strength: { title: '核心力量', color: '#A48AED', description: '核心稳定,提升运动表现' },
flexibility: { title: '柔韧灵活', color: '#B0F2A7', description: '拉伸延展,释放紧张' },
rehab: { title: '康复保健', color: '#FF8E9E', description: '循序渐进,科学修复' },
stress_relief: { title: '释压放松', color: '#9BD1FF', description: '舒缓身心,改善睡眠' },
};
export function MyPlanCard({ plan, onPress }: MyPlanCardProps) {
const goalConfig = GOAL_TEXT[plan.goal] || { title: '训练计划', color: '#FF6B47', description: '开始你的训练之旅' };
// 格式化日期
const formatDate = (dateStr: string) => {
const date = new Date(dateStr);
const months = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
return `${months[date.getMonth()]}, ${date.getFullYear()}`;
};
// 获取训练类型显示文本
const getWorkoutTypeText = () => {
if (plan.goal === 'core_strength') return 'Body Weight';
if (plan.goal === 'flexibility') return 'Flexibility';
if (plan.goal === 'posture_correction') return 'Posture';
return 'Body Weight';
};
// 获取周数和训练次数
const getWeekInfo = () => {
const startDate = new Date(plan.startDate);
const now = new Date();
const diffTime = Math.abs(now.getTime() - startDate.getTime());
const diffWeeks = Math.ceil(diffTime / (1000 * 60 * 60 * 24 * 7));
const currentWeek = Math.min(diffWeeks, 12); // 假设最多12周
const totalSessions = plan.mode === 'daysOfWeek'
? plan.daysOfWeek.length * currentWeek
: plan.sessionsPerWeek * currentWeek;
return {
week: currentWeek,
session: Math.min(totalSessions, 60), // 假设最多60次训练
totalSessions: 60
};
};
const weekInfo = getWeekInfo();
return (
<View style={styles.container}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.title}></Text>
<Pressable style={styles.menuButton}>
<View style={styles.menuDot} />
<View style={styles.menuDot} />
<View style={styles.menuDot} />
</Pressable>
</View>
{/* Date */}
<Text style={styles.date}>{formatDate(plan.startDate)}</Text>
{/* Main Card */}
<Pressable style={styles.card} onPress={onPress}>
{/* Icon */}
<View style={styles.iconContainer}>
<View style={[styles.iconCircle, { backgroundColor: goalConfig.color }]}>
<Ionicons name="flash" size={24} color="#FFFFFF" />
</View>
</View>
{/* Content */}
<View style={styles.content}>
{/* Week indicator */}
<Text style={styles.weekText}> {weekInfo.week}</Text>
{/* Workout type */}
<Text style={styles.workoutType}>{getWorkoutTypeText()}</Text>
{/* Session info */}
<Text style={styles.sessionInfo}> {weekInfo.session} </Text>
{/* Next exercise section */}
<View style={styles.nextExerciseContainer}>
<View style={styles.nextExerciseIcon}>
<Ionicons name="play" size={16} color="#666666" />
<Ionicons name="play" size={16} color="#666666" style={{ marginLeft: -4 }} />
</View>
<View style={styles.nextExerciseText}>
<Text style={styles.nextExerciseLabel}></Text>
<Text style={styles.nextExerciseName}></Text>
</View>
</View>
</View>
</Pressable>
</View>
);
}
const styles = StyleSheet.create({
container: {
marginHorizontal: 24,
marginBottom: 24,
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 8,
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#1A1A1A',
paddingHorizontal: 24,
marginBottom: 18,
},
menuButton: {
flexDirection: 'row',
alignItems: 'center',
gap: 4,
},
menuDot: {
width: 4,
height: 4,
borderRadius: 2,
backgroundColor: '#999999',
},
date: {
fontSize: 16,
color: '#999999',
marginBottom: 20,
},
card: {
backgroundColor: '#F5E6E0',
borderRadius: 24,
padding: 24,
flexDirection: 'row',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.1,
shadowRadius: 12,
elevation: 5,
},
iconContainer: {
marginRight: 20,
},
iconCircle: {
width: 64,
height: 64,
borderRadius: 32,
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 4,
},
shadowOpacity: 0.2,
shadowRadius: 8,
elevation: 4,
},
content: {
flex: 1,
},
weekText: {
fontSize: 12,
color: '#999999',
fontWeight: '600',
letterSpacing: 1,
marginBottom: 4,
},
workoutType: {
fontSize: 24,
fontWeight: 'bold',
color: '#1A1A1A',
marginBottom: 4,
},
sessionInfo: {
fontSize: 14,
color: '#666666',
marginBottom: 20,
},
nextExerciseContainer: {
flexDirection: 'row',
alignItems: 'center',
},
nextExerciseIcon: {
flexDirection: 'row',
alignItems: 'center',
marginRight: 12,
},
nextExerciseText: {
flex: 1,
},
nextExerciseLabel: {
fontSize: 12,
color: '#999999',
marginBottom: 2,
},
nextExerciseName: {
fontSize: 16,
fontWeight: '600',
color: '#1A1A1A',
},
});