feat: 添加原始睡眠数据列表,优化睡眠详情数据处理逻辑,确保完整的睡眠周期计算
This commit is contained in:
@@ -736,6 +736,109 @@ export default function SleepDetailScreen() {
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
|
||||
{/* Raw Sleep Samples List - 显示所有原始睡眠数据 */}
|
||||
{sleepData && sleepData.rawSleepSamples && sleepData.rawSleepSamples.length > 0 && (
|
||||
<View style={[styles.rawSamplesContainer, { backgroundColor: colorTokens.background }]}>
|
||||
<View style={styles.rawSamplesHeader}>
|
||||
<Text style={[styles.rawSamplesTitle, { color: colorTokens.text }]}>
|
||||
原始睡眠数据 ({sleepData.rawSleepSamples.length} 条记录)
|
||||
</Text>
|
||||
<Text style={[styles.rawSamplesSubtitle, { color: colorTokens.textSecondary }]}>
|
||||
查看数据间隔和可能的gap
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView style={styles.rawSamplesScrollView} nestedScrollEnabled={true}>
|
||||
{sleepData.rawSleepSamples.map((sample, index) => {
|
||||
// 计算与前一个样本的时间间隔
|
||||
const prevSample = index > 0 ? sleepData.rawSleepSamples[index - 1] : null;
|
||||
let gapMinutes = 0;
|
||||
let hasGap = false;
|
||||
|
||||
if (prevSample) {
|
||||
const prevEndTime = new Date(prevSample.endDate).getTime();
|
||||
const currentStartTime = new Date(sample.startDate).getTime();
|
||||
gapMinutes = (currentStartTime - prevEndTime) / (1000 * 60);
|
||||
hasGap = gapMinutes > 1; // 大于1分钟视为有间隔
|
||||
}
|
||||
|
||||
const startTime = formatTime(sample.startDate);
|
||||
const endTime = formatTime(sample.endDate);
|
||||
const duration = Math.round((new Date(sample.endDate).getTime() - new Date(sample.startDate).getTime()) / (1000 * 60));
|
||||
|
||||
// 获取睡眠阶段中文名称
|
||||
const getStageName = (value: string) => {
|
||||
switch (value) {
|
||||
case 'HKCategoryValueSleepAnalysisInBed': return '在床上';
|
||||
case 'HKCategoryValueSleepAnalysisAwake': return '清醒';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepCore': return '核心睡眠';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepDeep': return '深度睡眠';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepREM': return 'REM睡眠';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepUnspecified': return '未指定睡眠';
|
||||
default: return value;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态颜色
|
||||
const getStageColor = (value: string) => {
|
||||
switch (value) {
|
||||
case 'HKCategoryValueSleepAnalysisInBed': return '#9CA3AF';
|
||||
case 'HKCategoryValueSleepAnalysisAwake': return '#F59E0B';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepCore': return '#8B5CF6';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepDeep': return '#3B82F6';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepREM': return '#EC4899';
|
||||
case 'HKCategoryValueSleepAnalysisAsleepUnspecified': return '#6B7280';
|
||||
default: return '#6B7280';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<View key={index}>
|
||||
{/* 显示数据间隔 */}
|
||||
{hasGap && (
|
||||
<View style={[styles.gapIndicator, { backgroundColor: colorTokens.pageBackgroundEmphasis }]}>
|
||||
<Ionicons name="alert-circle-outline" size={14} color="#F59E0B" />
|
||||
<Text style={[styles.gapText, { color: '#F59E0B' }]}>
|
||||
数据间隔: {Math.round(gapMinutes)}分钟
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 睡眠样本条目 */}
|
||||
<View style={[styles.rawSampleItem, { borderColor: colorTokens.border }]}>
|
||||
<View style={styles.sampleHeader}>
|
||||
<View style={styles.sampleLeft}>
|
||||
<View
|
||||
style={[
|
||||
styles.stageDot,
|
||||
{ backgroundColor: getStageColor(sample.value) }
|
||||
]}
|
||||
/>
|
||||
<Text style={[styles.sampleStage, { color: colorTokens.text }]}>
|
||||
{getStageName(sample.value)}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={[styles.sampleDuration, { color: colorTokens.textSecondary }]}>
|
||||
{duration}分钟
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.sampleTimeRange}>
|
||||
<Text style={[styles.sampleTime, { color: colorTokens.textSecondary }]}>
|
||||
{startTime} - {endTime}
|
||||
</Text>
|
||||
<Text style={[styles.sampleIndex, { color: colorTokens.textMuted }]}>
|
||||
#{index + 1}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
{infoModal.type && (
|
||||
@@ -1388,4 +1491,92 @@ const styles = StyleSheet.create({
|
||||
lineHeight: 16,
|
||||
marginBottom: 4,
|
||||
},
|
||||
// Raw Sleep Samples List 样式
|
||||
rawSamplesContainer: {
|
||||
borderRadius: 16,
|
||||
padding: 16,
|
||||
marginBottom: 24,
|
||||
marginHorizontal: 4,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
elevation: 3,
|
||||
},
|
||||
rawSamplesHeader: {
|
||||
marginBottom: 16,
|
||||
},
|
||||
rawSamplesTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
marginBottom: 4,
|
||||
},
|
||||
rawSamplesSubtitle: {
|
||||
fontSize: 12,
|
||||
fontWeight: '500',
|
||||
},
|
||||
rawSamplesScrollView: {
|
||||
maxHeight: 400, // 限制高度,避免列表过长
|
||||
},
|
||||
rawSampleItem: {
|
||||
paddingVertical: 12,
|
||||
paddingHorizontal: 16,
|
||||
borderLeftWidth: 3,
|
||||
borderLeftColor: 'transparent',
|
||||
marginBottom: 8,
|
||||
borderRadius: 8,
|
||||
backgroundColor: 'rgba(248, 250, 252, 0.5)',
|
||||
},
|
||||
sampleHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 4,
|
||||
},
|
||||
sampleLeft: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
stageDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
marginRight: 8,
|
||||
},
|
||||
sampleStage: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
flex: 1,
|
||||
},
|
||||
sampleDuration: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
sampleTimeRange: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
},
|
||||
sampleTime: {
|
||||
fontSize: 12,
|
||||
},
|
||||
sampleIndex: {
|
||||
fontSize: 10,
|
||||
fontWeight: '500',
|
||||
},
|
||||
gapIndicator: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
marginVertical: 4,
|
||||
borderRadius: 8,
|
||||
gap: 6,
|
||||
},
|
||||
gapText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user