import { useAppDispatch, useAppSelector } from '@/hooks/redux'; import { fetchWeightHistory } from '@/store/userSlice'; import { Ionicons } from '@expo/vector-icons'; import dayjs from 'dayjs'; import { useRouter } from 'expo-router'; import React, { useEffect, useState } from 'react'; import { Dimensions, StyleSheet, Text, TouchableOpacity, View, } from 'react-native'; import Svg, { Circle, Line, Path, Text as SvgText } from 'react-native-svg'; const { width: screenWidth } = Dimensions.get('window'); const CARD_WIDTH = screenWidth - 40; // 减去左右边距 const CHART_WIDTH = CARD_WIDTH - 36; // 减去卡片内边距 const CHART_HEIGHT = 100; const PADDING = 10; type WeightHistoryItem = { weight: string; source: string; createdAt: string; }; export function WeightHistoryCard() { const router = useRouter(); const dispatch = useAppDispatch(); const userProfile = useAppSelector((s) => s.user.profile); const weightHistory = useAppSelector((s) => s.user.weightHistory); const [isLoading, setIsLoading] = useState(false); const [showChart, setShowChart] = useState(false); const hasWeight = userProfile?.weight && parseFloat(userProfile.weight) > 0; useEffect(() => { if (hasWeight) { loadWeightHistory(); } }, [hasWeight]); const loadWeightHistory = async () => { try { setIsLoading(true); await dispatch(fetchWeightHistory() as any); } catch (error) { console.error('加载体重历史失败:', error); } finally { setIsLoading(false); } }; const navigateToCoach = () => { router.push('/(tabs)/coach'); }; // 如果正在加载,显示加载状态 if (isLoading) { return ( 体重记录 加载中... ); } // 如果没有体重数据,显示引导卡片 if (!hasWeight) { return ( 体重记录 开始记录你的体重变化 记录体重变化,追踪你的健康进展 去记录体重 ); } // 处理体重历史数据 const sortedHistory = [...weightHistory] .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()) .slice(-7); // 只显示最近7条记录 if (sortedHistory.length === 0) { return ( 体重记录 暂无体重记录,点击下方按钮开始记录 记录体重 ); } // 生成图表数据 const weights = sortedHistory.map(item => parseFloat(item.weight)); const minWeight = Math.min(...weights); const maxWeight = Math.max(...weights); const weightRange = maxWeight - minWeight || 1; const points = sortedHistory.map((item, index) => { const x = PADDING + (index / Math.max(sortedHistory.length - 1, 1)) * (CHART_WIDTH - 2 * PADDING); const normalizedWeight = (parseFloat(item.weight) - minWeight) / weightRange; // 减少顶部边距,压缩留白 const y = PADDING + 15 + (1 - normalizedWeight) * (CHART_HEIGHT - 2 * PADDING - 30); return { x, y, weight: item.weight, date: item.createdAt }; }); // 生成路径 const pathData = points.map((point, index) => { if (index === 0) return `M ${point.x} ${point.y}`; return `L ${point.x} ${point.y}`; }).join(' '); // 如果只有一个数据点,显示为水平线 const singlePointPath = points.length === 1 ? `M ${PADDING} ${points[0].y} L ${CHART_WIDTH - PADDING} ${points[0].y}` : pathData; return ( 体重记录 setShowChart(!showChart)} activeOpacity={0.8} > {/* 默认信息显示 */} {!showChart && sortedHistory.length > 0 && ( 当前体重 {userProfile.weight}kg 记录天数 {sortedHistory.length}天 变化范围 {minWeight.toFixed(1)}-{maxWeight.toFixed(1)}kg setShowChart(true)} activeOpacity={0.8} > 查看趋势图 )} {/* 图表容器 - 可折叠 */} {showChart && ( {/* 背景网格线 */} {[0, 1, 2, 3, 4].map(i => ( ))} {/* 折线 */} {/* 数据点和标签 */} {points.map((point, index) => { const isLastPoint = index === points.length - 1; const isFirstPoint = index === 0; const showLabel = isFirstPoint || isLastPoint || points.length <= 3 || points.length === 1; return ( {/* 体重标签 - 只在关键点显示 */} {showLabel && ( <> {point.weight} )} ); })} {/* 图表底部信息 */} 当前体重 {userProfile.weight}kg 记录天数 {sortedHistory.length}天 变化范围 {minWeight.toFixed(1)}-{maxWeight.toFixed(1)}kg {/* 最近记录时间 */} {sortedHistory.length > 0 && ( 最近记录:{dayjs(sortedHistory[sortedHistory.length - 1].createdAt).format('MM/DD HH:mm')} )} )} ); } const styles = StyleSheet.create({ card: { backgroundColor: '#FFFFFF', borderRadius: 22, padding: 18, marginBottom: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.1, shadowRadius: 8, elevation: 3, }, cardHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: 16, }, iconSquare: { width: 30, height: 30, borderRadius: 8, backgroundColor: '#F0F8E0', alignItems: 'center', justifyContent: 'center', marginRight: 10, }, cardTitle: { fontSize: 18, fontWeight: '800', color: '#192126', flex: 1, }, headerButtons: { flexDirection: 'row', alignItems: 'center', gap: 8, }, chartToggleButton: { width: 28, height: 28, borderRadius: 14, backgroundColor: '#F0F8E0', alignItems: 'center', justifyContent: 'center', }, addButton: { width: 28, height: 28, borderRadius: 14, backgroundColor: '#F0F8E0', alignItems: 'center', justifyContent: 'center', }, emptyContent: { alignItems: 'center', paddingVertical: 20, }, emptyIconContainer: { width: 60, height: 60, borderRadius: 30, backgroundColor: '#F0F8E0', alignItems: 'center', justifyContent: 'center', marginBottom: 12, }, emptyTitle: { fontSize: 16, fontWeight: '700', color: '#192126', marginBottom: 6, }, emptyDescription: { fontSize: 14, color: '#687076', textAlign: 'center', marginBottom: 16, lineHeight: 20, }, recordButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#BBF246', paddingHorizontal: 16, paddingVertical: 10, borderRadius: 20, gap: 6, }, recordButtonText: { color: '#192126', fontSize: 14, fontWeight: '700', }, chartContainer: { alignItems: 'center', minHeight: 100, }, chartInfo: { flexDirection: 'row', justifyContent: 'space-around', width: '100%', paddingTop: 16, borderTopWidth: 1, borderTopColor: '#F0F0F0', }, infoItem: { alignItems: 'center', }, infoLabel: { fontSize: 12, color: '#687076', marginBottom: 4, }, infoValue: { fontSize: 14, fontWeight: '700', color: '#192126', }, lastRecordText: { fontSize: 12, color: '#687076', textAlign: 'center', marginTop: 8, }, summaryInfo: { paddingVertical: 12, }, summaryRow: { flexDirection: 'row', justifyContent: 'space-around', marginBottom: 12, }, summaryItem: { alignItems: 'center', }, summaryLabel: { fontSize: 12, color: '#687076', marginBottom: 4, }, summaryValue: { fontSize: 14, fontWeight: '700', color: '#192126', }, viewTrendButton: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', backgroundColor: '#F0F8E0', paddingHorizontal: 16, paddingVertical: 8, borderRadius: 16, gap: 6, alignSelf: 'center', }, viewTrendText: { fontSize: 13, fontWeight: '600', color: '#192126', }, });