feat: 支持步数卡片; 优化数据分析各类卡片样式

This commit is contained in:
richarjiang
2025-08-30 17:07:04 +08:00
parent 465d5350f3
commit 741688065d
9 changed files with 462 additions and 103 deletions

152
components/StepsCard.tsx Normal file
View File

@@ -0,0 +1,152 @@
import React, { useMemo } from 'react';
import {
StyleSheet,
Text,
View,
ViewStyle
} from 'react-native';
import { HourlyStepData } from '@/utils/health';
// 使用原生View来替代SVG避免导入问题
// import Svg, { Rect } from 'react-native-svg';
interface StepsCardProps {
stepCount: number | null;
stepGoal: number;
hourlySteps: HourlyStepData[];
style?: ViewStyle;
}
const StepsCard: React.FC<StepsCardProps> = ({
stepCount,
stepGoal,
hourlySteps,
style
}) => {
// 计算柱状图数据
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();
return (
<View style={[styles.container, style]}>
{/* 标题和步数显示 */}
<View style={styles.header}>
<Text style={styles.title}></Text>
</View>
{/* 柱状图 */}
<View style={styles.chartContainer}>
<View style={styles.chartWrapper}>
<View style={styles.chartArea}>
{chartData.map((data, index) => {
// 判断是否是当前小时或者有活动的小时
const isActive = data.steps > 0;
const isCurrent = index <= currentHour;
return (
<View
key={`bar-${index}`}
style={[
styles.chartBar,
{
height: data.height || 2, // 最小高度2px
backgroundColor: isCurrent && isActive ? '#FFC365' : '#FFEBCB',
}
]}
/>
);
})}
</View>
</View>
</View>
{/* 步数和目标显示 */}
<View style={styles.statsContainer}>
<Text style={styles.stepCount}>
{stepCount !== null ? stepCount.toLocaleString() : '——'}
</Text>
</View>
</View>
);
};
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: 16,
fontWeight: '700',
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,
},
chartBar: {
width: 4,
borderRadius: 1,
alignSelf: 'flex-end',
},
statsContainer: {
alignItems: 'flex-start',
marginTop: 6
},
stepCount: {
fontSize: 18,
fontWeight: '600',
color: '#192126',
},
});
export default StepsCard;