Files
digital-pilates/components/StressMeter.tsx
richarjiang d76ba48424 feat(ui): 统一应用主题色为天空蓝并优化渐变背景
将应用主色调从 '#BBF246' 更改为 '#87CEEB'(天空蓝),并更新所有相关组件和页面中的颜色引用。同时为多个页面添加统一的渐变背景,提升视觉效果和用户体验。新增压力分析模态框组件,并优化压力计组件的交互与显示逻辑。更新应用图标和启动图资源。
2025-08-20 09:38:25 +08:00

216 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Ionicons } from '@expo/vector-icons';
import { LinearGradient } from 'expo-linear-gradient';
import React from 'react';
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
interface StressMeterProps {
value: number | null;
updateTime?: Date;
style?: any;
onPress?: () => void;
}
export function StressMeter({ value, updateTime, style, onPress }: StressMeterProps) {
// 将HRV值转换为压力指数0-100
// HRV值范围30-110ms映射到压力指数100-0
// HRV值越高压力越小HRV值越低压力越大
const stressIndex = value ? Math.round(Math.min(100, Math.max(0, 100 - ((value - 30) / 80) * 100))) : null;
// 根据压力指数计算状态
const getStressStatus = () => {
if (stressIndex === null) {
return '未知';
} else if (stressIndex <= 30) {
return '放松';
} else if (stressIndex <= 70) {
return '正常';
} else {
return '紧张';
}
};
// 根据状态获取表情
const getStatusEmoji = () => {
// 当HRV值为null或0时不展示表情
if (value === null || value === 0) {
return '';
}
const status = getStressStatus();
switch (status) {
case '未知': return '';
case '放松': return '😌';
case '正常': return '😊';
case '紧张': return '😰';
default: return '😊';
}
};
// 计算进度条位置0-100%
// 压力指数越高,进度条越满
const progressPercentage = stressIndex === null ? 0 : stressIndex;
return (
<TouchableOpacity
style={[styles.container, style]}
onPress={onPress}
activeOpacity={0.8}
>
{/* 头部区域 */}
<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}>{stressIndex === null ? '--' : stressIndex}</Text>
<Text style={styles.unit}></Text>
</View>
{/* 进度条区域 */}
<View style={styles.progressContainer}>
<View style={styles.progressTrack}>
{/* 渐变背景进度条 */}
<View style={[styles.progressBar, { width: `${progressPercentage}%` }]}>
<LinearGradient
colors={['#10B981', '#FCD34D', '#F97316']}
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
style={styles.gradientBar}
/>
</View>
{/* 白色圆形指示器 */}
<View style={[styles.indicator, { left: `${Math.max(0, Math.min(100, progressPercentage - 2))}%` }]} />
</View>
</View>
{/* 更新时间
{updateTime && (
<Text style={styles.updateTime}>{formatUpdateTime(updateTime)}</Text>
)} */}
</TouchableOpacity>
);
}
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',
},
progressBar: {
height: '100%',
borderRadius: 4,
overflow: 'hidden',
},
gradientBar: {
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,
},
});