feat: 更新统计标签和标题,优化健康数据卡片样式,调整步数和健康相关组件的样式

This commit is contained in:
2025-08-30 22:37:27 +08:00
parent f4dd40ed46
commit 6bdfda9fd3
11 changed files with 68 additions and 118 deletions

View File

@@ -48,7 +48,7 @@ export default function TabLayout() {
case 'goals': case 'goals':
return { icon: 'flag.fill', title: '习惯' } as const; return { icon: 'flag.fill', title: '习惯' } as const;
case 'statistics': case 'statistics':
return { icon: 'chart.pie.fill', title: '统计' } as const; return { icon: 'chart.pie.fill', title: '健康' } as const;
case 'personal': case 'personal':
return { icon: 'person.fill', title: '个人' } as const; return { icon: 'person.fill', title: '个人' } as const;
default: default:
@@ -132,15 +132,15 @@ export default function TabLayout() {
}}> }}>
<Tabs.Screen <Tabs.Screen
name="coach" name="statistics"
options={{ options={{
title: 'Seal', title: '统计',
tabBarIcon: ({ color }) => { tabBarIcon: ({ color }) => {
const isCoachSelected = pathname === '/coach'; const isStatisticsSelected = pathname === '/statistics';
return ( return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}>
<IconSymbol size={22} name="person.3.fill" color={color} /> <IconSymbol size={22} name="chart.pie.fill" color={color} />
{isCoachSelected && ( {isStatisticsSelected && (
<Text <Text
numberOfLines={1} numberOfLines={1}
style={{ style={{
@@ -151,7 +151,7 @@ export default function TabLayout() {
textAlign: 'center', textAlign: 'center',
flexShrink: 0, flexShrink: 0,
}}> }}>
Seal
</Text> </Text>
)} )}
</View> </View>
@@ -188,17 +188,16 @@ export default function TabLayout() {
}, },
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="statistics" name="coach"
options={{ options={{
title: '统计', title: 'Seal',
tabBarIcon: ({ color }) => { tabBarIcon: ({ color }) => {
const isStatisticsSelected = pathname === '/statistics'; const isCoachSelected = pathname === '/coach';
return ( return (
<View style={{ flexDirection: 'row', alignItems: 'center' }}> <View style={{ flexDirection: 'row', alignItems: 'center' }}>
<IconSymbol size={22} name="chart.pie.fill" color={color} /> <IconSymbol size={22} name="person.3.fill" color={color} />
{isStatisticsSelected && ( {isCoachSelected && (
<Text <Text
numberOfLines={1} numberOfLines={1}
style={{ style={{
@@ -209,7 +208,7 @@ export default function TabLayout() {
textAlign: 'center', textAlign: 'center',
flexShrink: 0, flexShrink: 0,
}}> }}>
Seal
</Text> </Text>
)} )}
</View> </View>
@@ -217,6 +216,7 @@ export default function TabLayout() {
}, },
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="goals" name="goals"
options={{ options={{

View File

@@ -300,8 +300,6 @@ export default function ExploreScreen() {
contentContainerStyle={{ paddingBottom: bottomPadding }} contentContainerStyle={{ paddingBottom: bottomPadding }}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
> >
{/* 体重历史记录卡片 */}
<Text style={styles.sectionTitle}></Text>
<WeightHistoryCard /> <WeightHistoryCard />
{/* 日期选择器 */} {/* 日期选择器 */}
@@ -617,7 +615,6 @@ const styles = StyleSheet.create({
}, },
cardTitle: { cardTitle: {
fontSize: 14, fontSize: 14,
fontWeight: '800',
color: '#192126', color: '#192126',
}, },
heartCard: { heartCard: {

View File

@@ -115,8 +115,7 @@ const styles = StyleSheet.create({
borderBottomRightRadius: 2, borderBottomRightRadius: 2,
}, },
title: { title: {
fontSize: 16, fontSize: 14,
fontWeight: '700',
color: '#0F172A', color: '#0F172A',
}, },
statusBadge: { statusBadge: {

View File

@@ -185,7 +185,7 @@ const styles = StyleSheet.create({
paddingVertical: 8, paddingVertical: 8,
}, },
monthTitle: { monthTitle: {
fontSize: 24, fontSize: 18,
fontWeight: '800', fontWeight: '800',
color: '#192126', color: '#192126',
marginBottom: 14, marginBottom: 14,
@@ -234,7 +234,7 @@ const styles = StyleSheet.create({
}, },
dayDate: { dayDate: {
fontSize: 12, fontSize: 12,
fontWeight: '800', fontWeight: '600',
color: 'gray', color: 'gray',
}, },
dayDateSelected: { dayDateSelected: {

View File

@@ -42,7 +42,6 @@ const styles = StyleSheet.create({
cardTitle: { cardTitle: {
fontSize: 14, fontSize: 14,
fontWeight: '800',
color: '#192126', color: '#192126',
}, },

View File

@@ -100,7 +100,7 @@ export function NutritionRadarCard({
<View style={styles.cardRightContainer}> <View style={styles.cardRightContainer}>
<Text style={styles.cardSubtitle}>: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')}</Text> <Text style={styles.cardSubtitle}>: {dayjs(nutritionSummary?.updatedAt).format('MM-DD HH:mm')}</Text>
<TouchableOpacity style={styles.addButton} onPress={handleAddFood}> <TouchableOpacity style={styles.addButton} onPress={handleAddFood}>
<Ionicons name="add" size={16} color="#FFFFFF" /> <Ionicons name="add" size={12} color="#514b4bff" />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
</View> </View>
@@ -185,11 +185,10 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
marginBottom: 16, marginBottom: 8,
}, },
cardTitle: { cardTitle: {
fontSize: 18, fontSize: 14,
fontWeight: '800',
color: '#192126', color: '#192126',
}, },
cardRightContainer: { cardRightContainer: {
@@ -198,7 +197,7 @@ const styles = StyleSheet.create({
gap: 4, gap: 4,
}, },
cardSubtitle: { cardSubtitle: {
fontSize: 12, fontSize: 10,
color: '#9AA3AE', color: '#9AA3AE',
fontWeight: '600', fontWeight: '600',
}, },
@@ -230,15 +229,14 @@ const styles = StyleSheet.create({
marginRight: 8, marginRight: 8,
}, },
statLabel: { statLabel: {
fontSize: 12, fontSize: 10,
color: '#9AA3AE', color: '#9AA3AE',
fontWeight: '600',
flex: 1, flex: 1,
}, },
statValue: { statValue: {
fontSize: 12, fontSize: 12,
color: '#192126', color: '#192126',
fontWeight: '700', fontWeight: '600',
}, },
// 卡路里相关样式 // 卡路里相关样式
calorieSection: { calorieSection: {
@@ -272,12 +270,12 @@ const styles = StyleSheet.create({
gap: 4, gap: 4,
}, },
mainValue: { mainValue: {
fontSize: 24, fontSize: 14,
fontWeight: '800', fontWeight: '600',
color: '#192126', color: '#192126',
}, },
calculationText: { calculationText: {
fontSize: 14, fontSize: 12,
fontWeight: '600', fontWeight: '600',
color: '#64748B', color: '#64748B',
}, },
@@ -287,12 +285,12 @@ const styles = StyleSheet.create({
gap: 2, gap: 2,
}, },
calculationLabel: { calculationLabel: {
fontSize: 10, fontSize: 8,
color: '#64748B', color: '#64748B',
fontWeight: '500', fontWeight: '500',
}, },
calculationValue: { calculationValue: {
fontSize: 12, fontSize: 10,
fontWeight: '700', fontWeight: '700',
color: '#192126', color: '#192126',
}, },
@@ -315,10 +313,10 @@ const styles = StyleSheet.create({
fontSize: 24, fontSize: 24,
}, },
addButton: { addButton: {
width: 18, width: 16,
height: 18, height: 16,
borderRadius: 8, borderRadius: 8,
backgroundColor: '#9AA3AE', backgroundColor: '#e5e8ecff',
marginLeft: 8, marginLeft: 8,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',

View File

@@ -106,8 +106,7 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
title: { title: {
fontSize: 16, fontSize: 14,
fontWeight: '700',
color: '#192126', color: '#192126',
}, },
footprintIcons: { footprintIcons: {

View File

@@ -145,7 +145,6 @@ const styles = StyleSheet.create({
}, },
title: { title: {
fontSize: 14, fontSize: 14,
fontWeight: '800',
color: '#192126', color: '#192126',
}, },
valueSection: { valueSection: {

View File

@@ -55,7 +55,6 @@ const styles = StyleSheet.create({
fontSize: 14, fontSize: 14,
color: '#192126', color: '#192126',
marginBottom: 14, marginBottom: 14,
fontWeight: '800',
}, },
valueContainer: { valueContainer: {
flexDirection: 'row', flexDirection: 'row',

View File

@@ -11,13 +11,12 @@ import { LinearGradient } from 'expo-linear-gradient';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { import {
Dimensions, Dimensions,
Image,
Modal, Modal,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
Text, Text,
TouchableOpacity, TouchableOpacity,
View, View
} from 'react-native'; } from 'react-native';
import Animated, { import Animated, {
Extrapolation, Extrapolation,
@@ -201,9 +200,6 @@ export function WeightHistoryCard() {
return ( return (
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}> <TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<View style={styles.iconSquare}>
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
</View>
<Text style={styles.cardTitle}></Text> <Text style={styles.cardTitle}></Text>
</View> </View>
<View style={styles.emptyContent}> <View style={styles.emptyContent}>
@@ -218,9 +214,6 @@ export function WeightHistoryCard() {
return ( return (
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}> <TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<View style={styles.iconSquare}>
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
</View>
<Text style={styles.cardTitle}></Text> <Text style={styles.cardTitle}></Text>
</View> </View>
@@ -254,9 +247,6 @@ export function WeightHistoryCard() {
return ( return (
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}> <TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<View style={styles.iconSquare}>
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
</View>
<Text style={styles.cardTitle}></Text> <Text style={styles.cardTitle}></Text>
</View> </View>
@@ -308,9 +298,6 @@ export function WeightHistoryCard() {
return ( return (
<TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}> <TouchableOpacity style={styles.card} onPress={navigateToWeightRecords} activeOpacity={0.8}>
<View style={styles.cardHeader}> <View style={styles.cardHeader}>
<View style={styles.iconSquare}>
<Image source={require('@/assets/images/icons/iconWeight.png')} style={{ width: 18, height: 18 }} />
</View>
<Text style={styles.cardTitle}></Text> <Text style={styles.cardTitle}></Text>
<View style={styles.headerButtons}> <View style={styles.headerButtons}>
<TouchableOpacity <TouchableOpacity
@@ -580,8 +567,8 @@ const styles = StyleSheet.create({
card: { card: {
backgroundColor: '#FFFFFF', backgroundColor: '#FFFFFF',
borderRadius: 22, borderRadius: 22,
padding: 18, padding: 16,
marginBottom: 8, marginBottom: 4,
shadowColor: '#000', shadowColor: '#000',
shadowOffset: { width: 0, height: 2 }, shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1, shadowOpacity: 0.1,
@@ -591,7 +578,6 @@ const styles = StyleSheet.create({
cardHeader: { cardHeader: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginBottom: 16,
}, },
iconSquare: { iconSquare: {
width: 30, width: 30,
@@ -603,7 +589,6 @@ const styles = StyleSheet.create({
}, },
cardTitle: { cardTitle: {
fontSize: 14, fontSize: 14,
fontWeight: '800',
color: '#192126', color: '#192126',
flex: 1, flex: 1,
}, },
@@ -675,9 +660,6 @@ const styles = StyleSheet.create({
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-around', justifyContent: 'space-around',
width: '100%', width: '100%',
paddingTop: 16,
borderTopWidth: 1,
borderTopColor: '#F0F0F0',
}, },
infoItem: { infoItem: {
alignItems: 'center', alignItems: 'center',

View File

@@ -162,39 +162,11 @@ async function fetchStepCount(date: Date): Promise<number> {
}); });
} }
// 获取指定日期每小时步数数据 // 获取指定日期每小时步数数据 (已弃用,使用 fetchHourlyStepSamples 替代)
// 保留此函数以防后向兼容需求
async function fetchHourlyStepCount(date: Date): Promise<HourlyStepData[]> { async function fetchHourlyStepCount(date: Date): Promise<HourlyStepData[]> {
return new Promise((resolve) => { // 直接调用更准确的样本数据获取函数
const startOfDay = dayjs(date).startOf('day'); return fetchHourlyStepSamples(date);
const endOfDay = dayjs(date).endOf('day');
AppleHealthKit.getStepCount({
startDate: startOfDay.toDate().toISOString(),
endDate: endOfDay.toDate().toISOString(),
includeManuallyAdded: false,
}, (err, res) => {
if (err) {
logError('每小时步数', err);
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
}
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('每小时步数', '为空');
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
}
logSuccess('每小时步数', res);
// 初始化24小时数据
const hourlyData: HourlyStepData[] = Array.from({ length: 24 }, (_, i) => ({
hour: i,
steps: 0
}));
// 如果返回的是累计数据,我们需要获取样本数据
resolve(hourlyData);
});
});
} }
// 使用样本数据获取每小时步数 // 使用样本数据获取每小时步数
@@ -203,21 +175,22 @@ async function fetchHourlyStepSamples(date: Date): Promise<HourlyStepData[]> {
const startOfDay = dayjs(date).startOf('day'); const startOfDay = dayjs(date).startOf('day');
const endOfDay = dayjs(date).endOf('day'); const endOfDay = dayjs(date).endOf('day');
AppleHealthKit.getSamples( // 使用正确的 getDailyStepCountSamples 方法,设置 period 为 60 分钟获取每小时数据
{ const options = {
startDate: startOfDay.toDate().toISOString(), startDate: startOfDay.toDate().toISOString(),
endDate: endOfDay.toDate().toISOString(), endDate: endOfDay.toDate().toISOString(),
type: 'StepCount', ascending: false,
}, period: 60, // 60分钟为一个时间段获取每小时数据
includeManuallyAdded: false,
};
AppleHealthKit.getDailyStepCountSamples(
options,
(err: any, res: any[]) => { (err: any, res: any[]) => {
if (err) { if (err) {
logError('每小时步数样本', err); logError('每小时步数样本', err);
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 }))); // 如果主方法失败,尝试使用备用方法
} return null
if (!res || !Array.isArray(res) || res.length === 0) {
logWarning('每小时步数样本', '为空');
return resolve(Array.from({ length: 24 }, (_, i) => ({ hour: i, steps: 0 })));
} }
logSuccess('每小时步数样本', res); logSuccess('每小时步数样本', res);
@@ -228,12 +201,17 @@ async function fetchHourlyStepSamples(date: Date): Promise<HourlyStepData[]> {
steps: 0 steps: 0
})); }));
// 将样本数据按小时分组并累加 // 将每小时的步数样本数据映射到对应的小时
res.forEach((sample: any) => { res.forEach((sample: any) => {
if (sample && sample.startDate && sample.value) { if (sample && sample.startDate && sample.value !== undefined) {
const hour = dayjs(sample.startDate).hour(); const hour = dayjs(sample.startDate).hour();
if (hour >= 0 && hour < 24) { if (hour >= 0 && hour < 24) {
hourlyData[hour].steps += Math.round(sample.value); // 使用样本中的步数值,如果有 metadata优先使用 metadata 中的数据
const stepValue = sample.metadata && sample.metadata.length > 0
? sample.metadata.reduce((total: number, meta: any) => total + (meta.quantity || 0), 0)
: sample.value;
hourlyData[hour].steps = Math.round(stepValue);
} }
} }
}); });