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 && (
{/* 图表底部信息 */}
当前体重
{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',
},
});