- 移除模拟HRV数据,改为从健康数据中获取实际HRV值 - 新增HRV更新时间显示,提升用户信息获取体验 - 优化日期推导逻辑,确保数据加载一致性 - 更新BMI卡片和营养雷达图组件,支持紧凑模式展示 - 移除不再使用的图片资源,简化项目结构
212 lines
4.8 KiB
TypeScript
212 lines
4.8 KiB
TypeScript
import { Ionicons } from '@expo/vector-icons';
|
||
import { LinearGradient } from 'expo-linear-gradient';
|
||
import React from 'react';
|
||
import { StyleSheet, Text, View } from 'react-native';
|
||
|
||
interface StressMeterProps {
|
||
value: number;
|
||
updateTime?: Date;
|
||
style?: any;
|
||
}
|
||
|
||
export function StressMeter({ value, updateTime, style }: StressMeterProps) {
|
||
|
||
// 计算进度条位置(0-100%)
|
||
// HRV值范围:30-110ms,对应进度条0-100%
|
||
const progressPercentage = Math.min(100, Math.max(0, ((value - 30) / 80) * 100));
|
||
|
||
// 根据HRV值计算状态
|
||
const getHrvStatus = () => {
|
||
if (value >= 70) {
|
||
return '放松';
|
||
} else if (value >= 50) {
|
||
return '正常';
|
||
} else {
|
||
return '紧张';
|
||
}
|
||
};
|
||
|
||
// 根据状态获取表情
|
||
const getStatusEmoji = () => {
|
||
// 当HRV值为0时,不展示表情
|
||
if (value === 0) {
|
||
return '';
|
||
}
|
||
|
||
const status = getHrvStatus();
|
||
switch (status) {
|
||
case '放松': return '😌';
|
||
case '正常': return '😊';
|
||
case '紧张': return '😰';
|
||
default: return '😊';
|
||
}
|
||
};
|
||
|
||
// 格式化更新时间
|
||
const formatUpdateTime = (date?: Date) => {
|
||
if (!date) return '';
|
||
const hours = date.getHours().toString().padStart(2, '0');
|
||
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||
return `${hours}:${minutes}`;
|
||
};
|
||
|
||
return (
|
||
<View style={[styles.container, style]}>
|
||
{/* 渐变背景 */}
|
||
<LinearGradient
|
||
colors={['#F8F9FF', '#F0F4FF']}
|
||
start={{ x: 0, y: 0 }}
|
||
end={{ x: 1, y: 1 }}
|
||
style={styles.gradientBackground}
|
||
/>
|
||
|
||
{/* 头部区域 */}
|
||
<View style={styles.header}>
|
||
<View style={styles.leftSection}>
|
||
<View style={styles.iconContainer}>
|
||
<Ionicons name="heart" size={16} color="#3B82F6" />
|
||
</View>
|
||
<Text style={styles.title}>压力</Text>
|
||
</View>
|
||
<Text style={styles.emoji}>{getStatusEmoji()}</Text>
|
||
</View>
|
||
|
||
{/* 数值显示区域 */}
|
||
<View style={styles.valueSection}>
|
||
<Text style={styles.value}>{value}</Text>
|
||
<Text style={styles.unit}>毫秒</Text>
|
||
</View>
|
||
|
||
{/* 进度条区域 */}
|
||
<View style={styles.progressContainer}>
|
||
<View style={styles.progressTrack}>
|
||
{/* 渐变背景进度条 */}
|
||
<LinearGradient
|
||
colors={['#FFD700', '#87CEEB', '#98FB98']}
|
||
start={{ x: 0, y: 0 }}
|
||
end={{ x: 1, y: 0 }}
|
||
style={styles.gradientTrack}
|
||
/>
|
||
{/* 白色圆形指示器 */}
|
||
<View style={[styles.indicator, { left: `${Math.max(0, Math.min(100, progressPercentage - 2))}%` }]} />
|
||
</View>
|
||
</View>
|
||
|
||
{/* 更新时间 */}
|
||
{updateTime && (
|
||
<Text style={styles.updateTime}>{formatUpdateTime(updateTime)}</Text>
|
||
)}
|
||
</View>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: {
|
||
backgroundColor: '#FFFFFF',
|
||
borderRadius: 16,
|
||
padding: 14,
|
||
marginBottom: 12,
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 2,
|
||
},
|
||
shadowOpacity: 0.08,
|
||
shadowRadius: 8,
|
||
elevation: 3,
|
||
position: 'relative',
|
||
overflow: 'hidden',
|
||
minHeight: 110,
|
||
width: 140,
|
||
},
|
||
gradientBackground: {
|
||
position: 'absolute',
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
borderRadius: 16,
|
||
},
|
||
header: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
marginBottom: 8,
|
||
},
|
||
leftSection: {
|
||
flexDirection: 'row',
|
||
alignItems: 'center',
|
||
},
|
||
iconContainer: {
|
||
width: 24,
|
||
height: 24,
|
||
borderRadius: 6,
|
||
backgroundColor: '#EBF4FF',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
marginRight: 6,
|
||
},
|
||
title: {
|
||
fontSize: 14,
|
||
fontWeight: '700',
|
||
color: '#192126',
|
||
},
|
||
emoji: {
|
||
fontSize: 16,
|
||
},
|
||
valueSection: {
|
||
flexDirection: 'row',
|
||
alignItems: 'baseline',
|
||
marginBottom: 12,
|
||
},
|
||
value: {
|
||
fontSize: 28,
|
||
fontWeight: '800',
|
||
color: '#192126',
|
||
lineHeight: 32,
|
||
},
|
||
unit: {
|
||
fontSize: 12,
|
||
fontWeight: '500',
|
||
color: '#9AA3AE',
|
||
marginLeft: 4,
|
||
},
|
||
progressContainer: {
|
||
height: 16,
|
||
marginBottom: 4,
|
||
},
|
||
progressTrack: {
|
||
height: 8,
|
||
borderRadius: 4,
|
||
position: 'relative',
|
||
overflow: 'visible',
|
||
},
|
||
gradientTrack: {
|
||
height: '100%',
|
||
borderRadius: 4,
|
||
},
|
||
indicator: {
|
||
position: 'absolute',
|
||
top: -4,
|
||
width: 16,
|
||
height: 16,
|
||
borderRadius: 8,
|
||
backgroundColor: '#FFFFFF',
|
||
shadowColor: '#000',
|
||
shadowOffset: {
|
||
width: 0,
|
||
height: 1,
|
||
},
|
||
shadowOpacity: 0.1,
|
||
shadowRadius: 2,
|
||
elevation: 2,
|
||
borderWidth: 1.5,
|
||
borderColor: '#E5E7EB',
|
||
},
|
||
updateTime: {
|
||
fontSize: 10,
|
||
color: '#9AA3AE',
|
||
textAlign: 'right',
|
||
marginTop: 2,
|
||
},
|
||
}); |