import React, { useEffect, useMemo, useRef, useState } from 'react'; import { Animated, StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native'; import { fetchHourlyStepSamples, fetchStepCount, HourlyStepData } from '@/utils/health'; import { logger } from '@/utils/logger'; import { useRouter } from 'expo-router'; import { AnimatedNumber } from './AnimatedNumber'; import dayjs from 'dayjs'; // 使用原生View来替代SVG,避免导入问题 // import Svg, { Rect } from 'react-native-svg'; interface StepsCardProps { curDate: Date stepGoal: number; style?: ViewStyle; } const StepsCard: React.FC = ({ curDate, style, }) => { const router = useRouter(); const [stepCount, setStepCount] = useState(0) const [hourlySteps, setHourSteps] = useState([]) const getStepData = async (date: Date) => { try { logger.info('获取步数数据...'); const [steps, hourly] = await Promise.all([ fetchStepCount(date), fetchHourlyStepSamples(date) ]) setStepCount(steps) setHourSteps(hourly) } catch (error) { logger.error('获取步数数据失败:', error); } } useEffect(() => { if (curDate) { getStepData(curDate); } }, [curDate]); // 为每个柱体创建独立的动画值 const animatedValues = useRef( Array.from({ length: 24 }, () => new Animated.Value(0)) ).current; // 计算柱状图数据 const chartData = useMemo(() => { if (!hourlySteps || hourlySteps.length === 0) { return Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0, height: 0 })); } // 找到最大步数用于计算高度比例 const maxSteps = Math.max(...hourlySteps.map(data => data.steps), 1); const maxHeight = 20; // 柱状图最大高度(缩小一半) return hourlySteps.map(data => ({ ...data, height: maxSteps > 0 ? (data.steps / maxSteps) * maxHeight : 0 })); }, [hourlySteps]); // 获取当前小时 const currentHour = new Date().getHours(); // 触发柱体动画 useEffect(() => { // 检查是否有实际数据(不只是空数组) const hasData = chartData && chartData.length > 0 && chartData.some(data => data.steps > 0); if (hasData) { // 重置所有动画值 animatedValues.forEach(animValue => animValue.setValue(0)); // 使用 setTimeout 确保在下一个事件循环中执行动画,保证组件已完全渲染 const timeoutId = setTimeout(() => { // 同时启动所有柱体的弹性动画,有步数的柱体才执行动画 chartData.forEach((data, index) => { if (data.steps > 0) { Animated.spring(animatedValues[index], { toValue: 1, tension: 150, friction: 8, useNativeDriver: false, }).start(); } }); }, 50); // 添加小延迟确保渲染完成 return () => clearTimeout(timeoutId); } }, [chartData, animatedValues]); const CardContent = () => ( <> {/* 标题和步数显示 */} 步数 {/* 柱状图 */} {chartData.map((data, index) => { // 判断是否是当前小时或者有活动的小时 const isActive = data.steps > 0; const isCurrent = index <= currentHour; // 动画变换:缩放从0到实际高度 const animatedScale = animatedValues[index].interpolate({ inputRange: [0, 1], outputRange: [0, 1], }); // 动画变换:透明度从0到1 const animatedOpacity = animatedValues[index].interpolate({ inputRange: [0, 1], outputRange: [0, 1], }); return ( {/* 背景柱体 - 始终显示,使用相似色系的淡色 */} {/* 数据柱体 - 只有当有数据时才显示并执行动画 */} {isActive && ( )} ); })} {/* 步数和目标显示 */} stepCount !== null ? `${Math.round(v)}` : '——'} resetToken={stepCount} /> ); return ( { // 传递当前日期参数到详情页 const dateParam = dayjs(curDate).format('YYYY-MM-DD'); router.push(`/steps/detail?date=${dateParam}`); }} activeOpacity={0.8} > ); }; const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'space-between', borderRadius: 20, padding: 16, shadowColor: '#000', shadowOffset: { width: 0, height: 4, }, shadowOpacity: 0.08, shadowRadius: 20, elevation: 8, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, title: { fontSize: 14, color: '#192126', }, footprintIcons: { flexDirection: 'row', alignItems: 'center', gap: 6, }, chartContainer: { flex: 1, justifyContent: 'center', }, chartWrapper: { width: '100%', alignItems: 'center', }, chartArea: { flexDirection: 'row', alignItems: 'flex-end', height: 20, width: '100%', maxWidth: 240, justifyContent: 'space-between', paddingHorizontal: 4, }, barContainer: { width: 4, height: 20, alignItems: 'center', justifyContent: 'flex-end', position: 'relative', }, chartBar: { width: 4, borderRadius: 1, position: 'absolute', bottom: 0, }, statsContainer: { alignItems: 'flex-start', marginTop: 6 }, stepCount: { fontSize: 18, fontWeight: '600', color: '#192126', }, }); export default StepsCard;