feat: 支持步数卡片; 优化数据分析各类卡片样式
This commit is contained in:
152
components/StepsCard.tsx
Normal file
152
components/StepsCard.tsx
Normal 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;
|
||||
Reference in New Issue
Block a user