diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 1d3e97b..a81bbe4 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -57,7 +57,7 @@ export default function TabLayout() {
};
const { icon, title } = getIconAndTitle();
- const activeContentColor = colorTokens.onPrimary;
+ const activeContentColor = colorTokens.tabIconSelected; // 使用专门为Tab定义的选中颜色
const inactiveContentColor = colorTokens.tabIconDefault;
return (
diff --git a/app/(tabs)/coach.tsx b/app/(tabs)/coach.tsx
index 9740c61..aa1f4cc 100644
--- a/app/(tabs)/coach.tsx
+++ b/app/(tabs)/coach.tsx
@@ -27,6 +27,7 @@ import { Colors } from '@/constants/Colors';
import { getTabBarBottomPadding } from '@/constants/TabBar';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { useAuthGuard } from '@/hooks/useAuthGuard';
+import { useColorScheme } from '@/hooks/useColorScheme';
import { useCosUpload } from '@/hooks/useCosUpload';
import { deleteConversation, getConversationDetail, listConversations, type AiConversationListItem } from '@/services/aiCoach';
import { loadAiCoachSessionCache, saveAiCoachSessionCache } from '@/services/aiCoachSession';
@@ -124,7 +125,8 @@ export default function CoachScreen() {
const { isLoggedIn, pushIfAuthedElseLogin } = useAuthGuard();
// 为了让页面更贴近品牌主题与更亮的观感,这里使用亮色系配色
- const theme = Colors.light;
+ const colorScheme = useColorScheme();
+ const theme = Colors[colorScheme ?? 'light'];
const botName = (params?.name || 'Seal').toString();
const [input, setInput] = useState('');
const [isSending, setIsSending] = useState(false);
@@ -282,6 +284,16 @@ export default function CoachScreen() {
{ key: 'weight', label: '#记体重', action: () => insertWeightInputCard() },
{ key: 'diet', label: '#记饮食', action: () => insertDietInputCard() },
{ key: 'dietPlan', label: '#饮食方案', action: () => insertDietPlanCard() },
+ {
+ key: 'mood',
+ label: '#记心情',
+ action: () => {
+ if (Platform.OS === 'ios') {
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
+ }
+ router.push('/mood/calendar');
+ }
+ },
], [router, planDraft, checkin]);
const scrollToEnd = useCallback(() => {
@@ -1333,7 +1345,7 @@ export default function CoachScreen() {
{/* 标题部分 */}
-
+
我的饮食方案
MY DIET PLAN
@@ -1522,7 +1534,7 @@ export default function CoachScreen() {
)}
{isSelected && isPending && (
-
+
)}
{isSelected && !isPending && (
@@ -1829,7 +1841,7 @@ export default function CoachScreen() {
{/* 背景渐变 */}
-
+
{userProfile?.isVip ? '不限' : `${userProfile?.freeUsageCount || 0}/${userProfile?.maxUsageCount || 0}`}
@@ -1876,16 +1888,16 @@ export default function CoachScreen() {
-
+
-
+
@@ -1948,8 +1960,18 @@ export default function CoachScreen() {
contentContainerStyle={{ paddingHorizontal: 6, gap: 8 }}
>
{chips.map((c) => (
-
- {c.label}
+
+ {c.label}
))}
@@ -1990,18 +2012,18 @@ export default function CoachScreen() {
)}
-
+
-
+
0) || (isSending || isStreaming)) ? 1 : 0.5
}
]}
@@ -2144,8 +2166,8 @@ const styles = StyleSheet.create({
width: 60,
height: 60,
borderRadius: 30,
- backgroundColor: '#0EA5E9',
- opacity: 0.1,
+ backgroundColor: '#7a5af8', // 紫色主题
+ opacity: 0.08,
},
decorativeCircle2: {
position: 'absolute',
@@ -2154,8 +2176,8 @@ const styles = StyleSheet.create({
width: 40,
height: 40,
borderRadius: 20,
- backgroundColor: '#0EA5E9',
- opacity: 0.05,
+ backgroundColor: '#7a5af8', // 紫色主题
+ opacity: 0.04,
},
headerLeft: {
flexDirection: 'row',
@@ -2177,6 +2199,11 @@ const styles = StyleSheet.create({
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
+ shadowColor: '#7a5af8',
+ shadowOffset: { width: 0, height: 2 },
+ shadowOpacity: 0.1,
+ shadowRadius: 4,
+ elevation: 2,
},
historyButton: {
width: 32,
@@ -2237,7 +2264,7 @@ const styles = StyleSheet.create({
borderRadius: 8,
alignItems: 'center',
justifyContent: 'center',
- backgroundColor: `${Colors.light.accentGreen}99` // 60% opacity
+ backgroundColor: '#7a5af899' // 紫色主题 60% opacity
},
dietOptionsContainer: {
gap: 8,
@@ -2249,13 +2276,13 @@ const styles = StyleSheet.create({
borderRadius: 12,
backgroundColor: 'rgba(255,255,255,0.9)',
borderWidth: 1,
- borderColor: `${Colors.light.accentGreen}4D`, // 30% opacity
+ borderColor: '#7a5af84d', // 紫色主题 30% opacity
},
dietOptionIconContainer: {
width: 40,
height: 40,
borderRadius: 20,
- backgroundColor: `${Colors.light.accentGreen}33`, // 20% opacity
+ backgroundColor: '#7a5af833', // 紫色主题 20% opacity
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
@@ -2304,7 +2331,7 @@ const styles = StyleSheet.create({
borderRadius: 10,
alignItems: 'center',
justifyContent: 'center',
- backgroundColor: `${Colors.light.accentGreen}99`, // 60% opacity
+ backgroundColor: '#7a5af899', // 紫色主题 60% opacity
alignSelf: 'flex-end',
},
// markdown 基础样式承载容器的字体尺寸保持与气泡一致
@@ -2349,7 +2376,7 @@ const styles = StyleSheet.create({
borderRadius: 12,
overflow: 'hidden',
position: 'relative',
- backgroundColor: 'rgba(0,0,0,0.06)'
+ backgroundColor: 'rgba(122,90,248,0.08)' // 使用紫色主题的浅色背景
},
imageThumb: {
width: '100%',
@@ -2404,7 +2431,7 @@ const styles = StyleSheet.create({
padding: 8,
borderWidth: 1,
borderRadius: 16,
- backgroundColor: 'rgba(0,0,0,0.04)'
+ backgroundColor: 'rgba(122,90,248,0.04)' // 使用紫色主题的极浅色背景
},
mediaBtn: {
width: 40,
@@ -2617,17 +2644,17 @@ const styles = StyleSheet.create({
choiceButton: {
backgroundColor: 'rgba(255,255,255,0.9)',
borderWidth: 1,
- borderColor: `${Colors.light.accentGreen}4D`, // 30% opacity
+ borderColor: '#7a5af84d', // 紫色主题 30% opacity
borderRadius: 12,
padding: 12,
},
choiceButtonRecommended: {
- borderColor: `${Colors.light.accentGreen}99`, // 60% opacity
- backgroundColor: `${Colors.light.accentGreen}1A`, // 10% opacity
+ borderColor: '#7a5af899', // 紫色主题 60% opacity
+ backgroundColor: '#7a5af81a', // 紫色主题 10% opacity
},
choiceButtonSelected: {
- borderColor: Colors.light.accentGreenDark,
- backgroundColor: `${Colors.light.accentGreen}33`, // 20% opacity
+ borderColor: '#19b36e', // success[500]
+ backgroundColor: '#19b36e33', // 20% opacity
borderWidth: 2,
},
choiceButtonDisabled: {
@@ -2647,10 +2674,10 @@ const styles = StyleSheet.create({
flex: 1,
},
choiceLabelRecommended: {
- color: Colors.light.accentGreenDark,
+ color: '#19b36e', // success[500]
},
choiceLabelSelected: {
- color: Colors.light.accentGreenDark,
+ color: '#19b36e', // success[500]
fontWeight: '700',
},
choiceLabelDisabled: {
@@ -2662,7 +2689,7 @@ const styles = StyleSheet.create({
gap: 8,
},
recommendedBadge: {
- backgroundColor: `${Colors.light.accentGreen}CC`, // 80% opacity
+ backgroundColor: '#7a5af8cc', // 紫色主题 80% opacity
borderRadius: 6,
paddingHorizontal: 8,
paddingVertical: 2,
@@ -2670,10 +2697,10 @@ const styles = StyleSheet.create({
recommendedText: {
fontSize: 12,
fontWeight: '700',
- color: Colors.light.accentGreenDark,
+ color: '#19b36e', // success[500]
},
selectedBadge: {
- backgroundColor: Colors.light.accentGreenDark,
+ backgroundColor: '#19b36e', // success[500]
borderRadius: 6,
paddingHorizontal: 8,
paddingVertical: 2,
@@ -2714,7 +2741,7 @@ const styles = StyleSheet.create({
padding: 16,
gap: 16,
borderWidth: 1,
- borderColor: `${Colors.light.accentGreen}33`, // 20% opacity
+ borderColor: '#7a5af833', // 紫色主题 20% opacity
},
dietPlanHeader: {
gap: 4,
@@ -2834,7 +2861,7 @@ const styles = StyleSheet.create({
caloriesValue: {
fontSize: 18,
fontWeight: '800',
- color: Colors.light.accentGreenDark,
+ color: '#19b36e', // success[500]
},
nutritionGrid: {
flexDirection: 'row',
@@ -2871,7 +2898,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'center',
gap: 8,
- backgroundColor: Colors.light.accentGreenDark,
+ backgroundColor: '#19b36e', // success[500]
paddingVertical: 12,
paddingHorizontal: 16,
borderRadius: 12,
@@ -2889,7 +2916,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 12,
- backgroundColor: 'rgba(0,0,0,0.06)',
+ backgroundColor: 'rgba(122,90,248,0.08)', // 紫色主题浅色背景
},
usageIcon: {
width: 16,
@@ -2898,7 +2925,7 @@ const styles = StyleSheet.create({
usageText: {
fontSize: 12,
fontWeight: '600',
- color: '#687076',
+ color: '#7a5af8', // 紫色主题文字颜色
},
});
diff --git a/app/(tabs)/personal.tsx b/app/(tabs)/personal.tsx
index 32464d7..b63fbb0 100644
--- a/app/(tabs)/personal.tsx
+++ b/app/(tabs)/personal.tsx
@@ -9,7 +9,6 @@ import { DEFAULT_MEMBER_NAME, fetchActivityHistory, fetchMyProfile } from '@/sto
import { Ionicons } from '@expo/vector-icons';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useFocusEffect } from '@react-navigation/native';
-import { LinearGradient } from 'expo-linear-gradient';
import React, { useMemo, useState } from 'react';
import { Image, Linking, SafeAreaView, ScrollView, StatusBar, StyleSheet, Switch, Text, TouchableOpacity, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@@ -31,7 +30,6 @@ export default function PersonalScreen() {
// 颜色主题
const colors = Colors[colorScheme ?? 'light'];
- const theme = (colorScheme ?? 'light') as 'light' | 'dark';
// 直接使用 Redux 中的用户信息,避免重复状态管理
const userProfile = useAppSelector((state) => state.user.profile);
@@ -66,129 +64,100 @@ export default function PersonalScreen() {
// 显示名称
const displayName = (userProfile.name?.trim()) ? userProfile.name : DEFAULT_MEMBER_NAME;
- // 颜色令牌
- const colorTokens = colors;
-
- const UserInfoSection = () => (
-
-
- {/* 头像 */}
-
-
-
+ // 用户信息头部
+ const UserHeader = () => (
+
+ 个人信息
+
+
+
+
+
+ {displayName}
+
+ pushIfAuthedElseLogin('/profile/edit')}>
+ 编辑
+
-
- {/* 用户信息 */}
-
- {displayName}
-
-
- {/* 编辑按钮 */}
- pushIfAuthedElseLogin('/profile/edit')}>
- 编辑
-
);
+ // 数据统计部分
const StatsSection = () => (
-
-
- {formatHeight()}
- 身高
-
-
- {formatWeight()}
- 体重
-
-
- {formatAge()}
- 年龄
+
+ 身体数据
+
+
+
+ {formatHeight()}
+ 身高
+
+
+ {formatWeight()}
+ 体重
+
+
+ {formatAge()}
+ 年龄
+
+
);
// 菜单项组件
const MenuSection = ({ title, items }: { title: string; items: any[] }) => (
-
- {title}
- {items.map((item, index) => (
-
-
-
-
+
+ {title}
+
+ {items.map((item, index) => (
+
+
+
+
+
+ {item.title}
- {item.title}
-
- {item.type === 'switch' ? (
- {
- if (!isLoggedIn) {
- pushIfAuthedElseLogin('/profile/notification-settings');
- return;
- }
- setNotificationEnabled(value);
- }}
- trackColor={{ false: '#E5E5E5', true: colors.primary }}
- thumbColor="#FFFFFF"
- style={styles.switch}
- />
- ) : (
-
- )}
-
- ))}
+ {item.type === 'switch' ? (
+ {
+ if (!isLoggedIn) {
+ pushIfAuthedElseLogin('/profile/notification-settings');
+ return;
+ }
+ setNotificationEnabled(value);
+ }}
+ trackColor={{ false: '#E5E5E5', true: '#9370DB' }}
+ thumbColor="#FFFFFF"
+ style={styles.switch}
+ />
+ ) : (
+
+ )}
+
+ ))}
+
);
- // 动态样式
- const dynamicStyles = StyleSheet.create({
- editButton: {
- backgroundColor: colors.primary,
- paddingHorizontal: 20,
- paddingVertical: 10,
- borderRadius: 20,
- },
- editButtonText: {
- color: colors.onPrimary,
- fontSize: 14,
- fontWeight: '600',
- },
- statValue: {
- fontSize: 18,
- fontWeight: 'bold',
- color: colors.primary,
- marginBottom: 4,
- },
- floatingButton: {
- width: 56,
- height: 56,
- borderRadius: 28,
- backgroundColor: colors.primary,
- alignItems: 'center',
- justifyContent: 'center',
- shadowColor: colors.primary,
- shadowOffset: { width: 0, height: 4 },
- shadowOpacity: 0.3,
- shadowRadius: 8,
- elevation: 8,
- },
- });
-
// 菜单项配置
const menuSections = [
{
@@ -197,25 +166,8 @@ export default function PersonalScreen() {
{
icon: 'flag-outline' as const,
title: '目标管理',
- onPress: () => pushIfAuthedElseLogin('/goals'),
+ onPress: () => pushIfAuthedElseLogin('/profile/goals'),
},
- {
- icon: 'telescope-outline' as const,
- title: '目标管理演示',
- onPress: () => pushIfAuthedElseLogin('/goal-demo'),
- },
- // {
- // icon: 'stats-chart-outline' as const,
- // title: '训练进度',
- // onPress: () => {
- // // 训练进度页面暂未实现,先显示提示
- // if (isLoggedIn) {
- // Alert.alert('提示', '训练进度功能正在开发中');
- // } else {
- // pushIfAuthedElseLogin('/profile/training-progress');
- // }
- // },
- // },
],
},
{
@@ -265,12 +217,6 @@ export default function PersonalScreen() {
return (
-
-
+
{menuSections.map((section, index) => (
))}
-
@@ -294,48 +239,53 @@ export default function PersonalScreen() {
const styles = StyleSheet.create({
container: {
flex: 1,
- },
- gradientBackground: {
- position: 'absolute',
- left: 0,
- right: 0,
- top: 0,
- bottom: 0,
+ backgroundColor: '#FAFAFA',
},
safeArea: {
flex: 1,
},
scrollView: {
flex: 1,
- paddingHorizontal: 20,
+ paddingHorizontal: 16,
+ paddingTop: 16,
+ },
+ // 部分容器
+ sectionContainer: {
+ marginBottom: 20,
+ },
+ sectionTitle: {
+ fontSize: 16,
+ fontWeight: 'bold',
+ color: '#2C3E50',
+ marginBottom: 10,
+ paddingHorizontal: 4,
+ },
+ // 卡片容器
+ cardContainer: {
+ backgroundColor: '#FFFFFF',
+ borderRadius: 12,
+ shadowColor: '#000',
+ shadowOffset: { width: 0, height: 1 },
+ shadowOpacity: 0.05,
+ shadowRadius: 4,
+ elevation: 2,
+ overflow: 'hidden',
},
// 用户信息区域
- userInfoCard: {
- borderRadius: 16,
- marginBottom: 20,
- shadowColor: 'rgba(135,206,235,0.3)',
- shadowOffset: { width: 0, height: 4 },
- shadowOpacity: 0.15,
- shadowRadius: 8,
- elevation: 3,
- borderWidth: 1,
- borderColor: 'rgba(135,206,235,0.1)',
- },
userInfoContainer: {
flexDirection: 'row',
alignItems: 'center',
- padding: 20,
+ padding: 16,
},
avatarContainer: {
- marginRight: 15,
+ marginRight: 12,
},
avatar: {
- width: 80,
- height: 80,
- borderRadius: 40,
- alignItems: 'center',
- justifyContent: 'center',
- overflow: 'hidden',
+ width: 60,
+ height: 60,
+ borderRadius: 30,
+ borderWidth: 2,
+ borderColor: '#9370DB',
},
userDetails: {
flex: 1,
@@ -343,86 +293,75 @@ const styles = StyleSheet.create({
userName: {
fontSize: 18,
fontWeight: 'bold',
+ color: '#2C3E50',
marginBottom: 4,
},
+ userRole: {
+ fontSize: 14,
+ color: '#9370DB',
+ fontWeight: '500',
+ },
+ editButton: {
+ backgroundColor: '#9370DB',
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderRadius: 16,
+ },
+ editButtonText: {
+ color: '#FFFFFF',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ // 数据统计
statsContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
- borderRadius: 16,
- padding: 20,
- marginBottom: 20,
- shadowColor: 'rgba(135,206,235,0.25)',
- shadowOffset: { width: 0, height: 3 },
- shadowOpacity: 0.12,
- shadowRadius: 6,
- elevation: 3,
- borderWidth: 1,
- borderColor: 'rgba(135,206,235,0.08)',
+ padding: 16,
},
statItem: {
alignItems: 'center',
flex: 1,
},
-
+ statValue: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#9370DB',
+ marginBottom: 4,
+ },
statLabel: {
fontSize: 12,
+ color: '#6C757D',
+ fontWeight: '500',
},
- menuSection: {
- marginBottom: 20,
- padding: 16,
- borderRadius: 16,
- shadowColor: 'rgba(135,206,235,0.2)',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.1,
- shadowRadius: 4,
- elevation: 2,
- borderWidth: 1,
- borderColor: 'rgba(135,206,235,0.06)',
- },
- sectionTitle: {
- fontSize: 20,
- fontWeight: '800',
- marginBottom: 12,
- paddingHorizontal: 4,
- },
+ // 菜单项
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
- paddingVertical: 16,
+ paddingVertical: 14,
paddingHorizontal: 16,
- borderRadius: 12,
- marginBottom: 8,
+ borderBottomWidth: 1,
+ borderBottomColor: '#F1F3F4',
},
menuItemLeft: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
- menuIcon: {
- width: 36,
- height: 36,
- borderRadius: 8,
+ iconContainer: {
+ width: 32,
+ height: 32,
+ borderRadius: 6,
alignItems: 'center',
justifyContent: 'center',
marginRight: 12,
},
menuItemText: {
- fontSize: 16,
- flex: 1,
- fontWeight: '600',
+ fontSize: 15,
+ color: '#2C3E50',
+ fontWeight: '500',
},
switch: {
transform: [{ scaleX: 0.8 }, { scaleY: 0.8 }],
},
- // 浮动按钮
- floatingButtonContainer: {
- position: 'absolute',
- bottom: 30,
- left: 0,
- right: 0,
- alignItems: 'center',
- pointerEvents: 'box-none',
- },
-
});
diff --git a/app/(tabs)/statistics.tsx b/app/(tabs)/statistics.tsx
index 385280c..57427f7 100644
--- a/app/(tabs)/statistics.tsx
+++ b/app/(tabs)/statistics.tsx
@@ -22,7 +22,6 @@ import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useFocusEffect } from '@react-navigation/native';
import dayjs from 'dayjs';
import { LinearGradient } from 'expo-linear-gradient';
-import { router } from 'expo-router';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import {
Animated,
@@ -120,6 +119,9 @@ export default function ExploreScreen() {
standHours: 0,
standHoursGoal: 12
});
+ // 血氧饱和度和心率数据
+ const [oxygenSaturation, setOxygenSaturation] = useState(null);
+ const [heartRate, setHeartRate] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// 用于触发动画重置的 token(当日期或数据变化时更新)
@@ -225,6 +227,13 @@ export default function ExploreScreen() {
// 更新HRV数据时间
setHrvUpdateTime(new Date());
+ // 设置血氧饱和度和心率数据
+ setOxygenSaturation(data.oxygenSaturation ?? null);
+ setHeartRate(data.heartRate ?? null);
+
+ console.log('血氧饱和度数据:', data.oxygenSaturation);
+ console.log('心率数据:', data.heartRate);
+
setAnimToken((t) => t + 1);
} else {
console.log('忽略过期健康数据请求结果,key=', requestKey, '最新key=', latestRequestKeyRef.current);
@@ -233,6 +242,9 @@ export default function ExploreScreen() {
} catch (error) {
console.error('HealthKit流程出现异常:', error);
+ // 重置血氧饱和度和心率数据
+ setOxygenSaturation(null);
+ setHeartRate(null);
} finally {
setIsLoading(false);
}
@@ -389,7 +401,7 @@ export default function ExploreScreen() {
router.push('/mood/calendar')}
+ onPress={() => pushIfAuthedElseLogin('/mood/calendar')}
isLoading={isMoodLoading}
/>
@@ -436,6 +448,7 @@ export default function ExploreScreen() {
@@ -444,6 +457,7 @@ export default function ExploreScreen() {
diff --git a/app/goal-demo.tsx b/app/goal-demo.tsx
deleted file mode 100644
index 4350148..0000000
--- a/app/goal-demo.tsx
+++ /dev/null
@@ -1,202 +0,0 @@
-import { Colors } from '@/constants/Colors';
-import { useColorScheme } from '@/hooks/useColorScheme';
-import { Ionicons } from '@expo/vector-icons';
-import { LinearGradient } from 'expo-linear-gradient';
-import { useRouter } from 'expo-router';
-import React from 'react';
-import { SafeAreaView, StyleSheet, Text, TouchableOpacity, View } from 'react-native';
-
-export default function GoalDemoScreen() {
- const router = useRouter();
- const theme = useColorScheme() ?? 'light';
- const colorTokens = Colors[theme];
-
- return (
-
-
-
-
-
- router.back()}
- >
-
-
-
-
- 目标管理演示
-
-
-
-
-
-
-
-
-
-
- 智能目标管理系统
-
-
-
- 体验高保真的目标管理界面,包含待办事项卡片滑动、时间筛选器和可滚动时间轴。界面完全按照您的需求设计,支持:
-
-
-
-
-
-
- 横向滑动的待办事项卡片(首屏1.5张)
-
-
-
-
-
-
- 天/周/月时间筛选选择器
-
-
-
-
-
-
- 可滚动的时间轴和任务显示
-
-
-
-
-
-
- 支持同一时间多任务的左右上下滑动
-
-
-
-
- router.push('/goals')}
- >
-
- 进入目标管理页面
-
-
-
-
-
-
-
- );
-}
-
-const styles = StyleSheet.create({
- container: {
- flex: 1,
- },
- backgroundGradient: {
- position: 'absolute',
- left: 0,
- right: 0,
- top: 0,
- bottom: 0,
- },
- content: {
- flex: 1,
- padding: 20,
- },
- header: {
- flexDirection: 'row',
- alignItems: 'center',
- marginBottom: 30,
- marginTop: 20,
- },
- backButton: {
- width: 40,
- height: 40,
- borderRadius: 20,
- justifyContent: 'center',
- alignItems: 'center',
- marginRight: 16,
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 2 },
- shadowOpacity: 0.1,
- shadowRadius: 4,
- elevation: 3,
- },
- title: {
- fontSize: 24,
- fontWeight: '800',
- },
- demoContainer: {
- flex: 1,
- justifyContent: 'center',
- },
- demoCard: {
- borderRadius: 24,
- padding: 32,
- shadowColor: '#000',
- shadowOffset: { width: 0, height: 8 },
- shadowOpacity: 0.15,
- shadowRadius: 20,
- elevation: 10,
- alignItems: 'center',
- },
- iconContainer: {
- width: 80,
- height: 80,
- borderRadius: 40,
- backgroundColor: '#E6F3FF',
- justifyContent: 'center',
- alignItems: 'center',
- marginBottom: 24,
- },
- demoTitle: {
- fontSize: 24,
- fontWeight: '700',
- marginBottom: 16,
- textAlign: 'center',
- },
- demoDescription: {
- fontSize: 16,
- lineHeight: 24,
- textAlign: 'center',
- marginBottom: 24,
- },
- featureList: {
- alignSelf: 'stretch',
- marginBottom: 32,
- },
- featureItem: {
- flexDirection: 'row',
- alignItems: 'center',
- marginBottom: 12,
- },
- featureText: {
- fontSize: 14,
- marginLeft: 8,
- flex: 1,
- },
- enterButton: {
- flexDirection: 'row',
- alignItems: 'center',
- paddingHorizontal: 32,
- paddingVertical: 16,
- borderRadius: 28,
- shadowColor: '#87CEEB',
- shadowOffset: { width: 0, height: 4 },
- shadowOpacity: 0.3,
- shadowRadius: 8,
- elevation: 6,
- },
- enterButtonText: {
- fontSize: 16,
- fontWeight: '700',
- marginRight: 8,
- },
-});
diff --git a/app/mood/calendar.tsx b/app/mood/calendar.tsx
index b4d7080..9f02f92 100644
--- a/app/mood/calendar.tsx
+++ b/app/mood/calendar.tsx
@@ -195,11 +195,15 @@ export default function MoodCalendarScreen() {
return (
+ {/* 装饰性圆圈 */}
+
+
+
+
+ {/* 装饰性圆圈 */}
+
+
+
+ {/* HeaderBar 放在 ScrollView 外部,确保全宽显示 */}
+ router.back()}
+ withSafeTop={false}
+ transparent={true}
+ variant="elevated"
+ />
+
- router.back()} withSafeTop={false} transparent />
{/* 头像(带相机蒙层,点击从相册选择) */}
@@ -561,7 +570,7 @@ const styles = StyleSheet.create({
backgroundColor: '#F1F5F9',
},
modalBtnPrimary: {
- backgroundColor: palette.primary,
+ backgroundColor: '#7a5af8',
},
modalBtnText: {
color: '#334155',
@@ -571,15 +580,6 @@ const styles = StyleSheet.create({
color: '#0F172A',
fontWeight: '700',
},
- header: {
- flexDirection: 'row',
- alignItems: 'center',
- justifyContent: 'space-between',
- paddingHorizontal: 0,
- marginBottom: 8,
- },
- backButton: { padding: 4, width: 32 },
- headerTitle: { fontSize: 18, fontWeight: '700', color: '#192126' },
});
diff --git a/components/ActivityHeatMap.tsx b/components/ActivityHeatMap.tsx
index e66da9b..95cfcf4 100644
--- a/components/ActivityHeatMap.tsx
+++ b/components/ActivityHeatMap.tsx
@@ -59,19 +59,24 @@ const ActivityHeatMap = () => {
console.log('generateActivityData', generateActivityData);
- // 根据活跃度计算颜色
+ // 根据活跃度计算颜色 - 优化配色方案
const getActivityColor = (level: number): string => {
- // 由于useColorScheme总是返回'light',我们直接使用浅色主题的颜色
switch (level) {
case 0:
- return colors.separator; // 使用主题分隔线色
+ // 无活动:使用主题适配的背景色
+ return colors.separator;
case 1:
+ // 低活动:使用主题主色的浅色版本
+ return 'rgba(122, 90, 248, 0.15)'; // 浅色模式下的浅紫色
case 2:
- return 'rgba(135,206,235,0.4)';
+ // 中等活动:使用主题主色的中等透明度
+ return 'rgba(122, 90, 248, 0.35)'; // 浅色模式下的中等紫色
case 3:
- return 'rgba(135,206,235,0.65)';
+ // 高活动:使用主题主色的较高透明度
+ return 'rgba(122, 90, 248, 0.55)'; // 浅色模式下的较深紫色
case 4:
default:
+ // 最高活动:使用主题主色
return colors.primary;
}
};
@@ -143,14 +148,20 @@ const ActivityHeatMap = () => {
}, [generateActivityData]);
return (
-
+
{/* 标题和统计 */}
最近6个月活跃 {activityStats.activeDays} 天
-
+
{activityStats.activeRate}%
@@ -238,13 +249,10 @@ const styles = StyleSheet.create({
borderRadius: 16,
padding: 20,
marginBottom: 20,
- shadowColor: 'rgba(135,206,235,0.25)',
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.12,
shadowRadius: 6,
elevation: 3,
- borderWidth: 1,
- borderColor: 'rgba(135,206,235,0.08)',
},
header: {
marginBottom: 8,
diff --git a/components/statistic/HealthDataCard.tsx b/components/statistic/HealthDataCard.tsx
index d97aac4..c1ccf1d 100644
--- a/components/statistic/HealthDataCard.tsx
+++ b/components/statistic/HealthDataCard.tsx
@@ -24,6 +24,9 @@ const HealthDataCard: React.FC = ({
style={[styles.card, style]}
>
+
+ {icon}
+
{title}
@@ -53,14 +56,18 @@ const styles = StyleSheet.create({
},
iconContainer: {
marginRight: 16,
+ alignItems: 'center',
+ justifyContent: 'center',
},
content: {
flex: 1,
+ justifyContent: 'center',
},
title: {
fontSize: 14,
color: '#666',
marginBottom: 4,
+ fontWeight: '600',
},
valueContainer: {
flexDirection: 'row',
@@ -68,14 +75,15 @@ const styles = StyleSheet.create({
},
value: {
fontSize: 24,
- fontWeight: 'bold',
- color: '#333',
+ fontWeight: '800',
+ color: '#192126',
},
unit: {
fontSize: 14,
color: '#666',
marginLeft: 4,
marginBottom: 2,
+ fontWeight: '500',
},
});
diff --git a/components/statistic/HeartRateCard.tsx b/components/statistic/HeartRateCard.tsx
index 424987f..5a77ba4 100644
--- a/components/statistic/HeartRateCard.tsx
+++ b/components/statistic/HeartRateCard.tsx
@@ -1,29 +1,19 @@
import { Ionicons } from '@expo/vector-icons';
-import React, { useEffect, useState } from 'react';
+import React from 'react';
import { StyleSheet } from 'react-native';
-import HealthDataService from '../../services/healthData';
import HealthDataCard from './HealthDataCard';
interface HeartRateCardProps {
resetToken: number;
style?: object;
+ heartRate?: number | null;
}
const HeartRateCard: React.FC = ({
resetToken,
- style
+ style,
+ heartRate
}) => {
- const [heartRate, setHeartRate] = useState(null);
-
- useEffect(() => {
- const fetchHeartRate = async () => {
- const data = await HealthDataService.getHeartRate();
- setHeartRate(data);
- };
-
- fetchHeartRate();
- }, [resetToken]);
-
const heartIcon = (
);
@@ -31,7 +21,7 @@ const HeartRateCard: React.FC = ({
return (
= ({
resetToken,
- style
+ style,
+ oxygenSaturation
}) => {
- const [oxygenSaturation, setOxygenSaturation] = useState(null);
-
- useEffect(() => {
- const fetchOxygenSaturation = async () => {
- const data = await HealthDataService.getOxygenSaturation();
- setOxygenSaturation(data);
- };
-
- fetchOxygenSaturation();
- }, [resetToken]);
-
const oxygenIcon = (
);
@@ -31,7 +21,7 @@ const OxygenSaturationCard: React.FC = ({
return (
{
+ if (transparent) return 'transparent';
+
+ switch (variant) {
+ case 'elevated':
+ return theme.background;
+ case 'minimal':
+ return theme.background;
+ default:
+ return theme.card;
+ }
+ };
+
+ const getBackButtonStyle = () => {
+ const baseStyle = [styles.backButton];
+
+ switch (variant) {
+ case 'elevated':
+ return [...baseStyle, {
+ backgroundColor: `${theme.primary}15`, // 15% 透明度
+ borderWidth: 1,
+ borderColor: `${theme.primary}20`, // 20% 透明度
+ }];
+ case 'minimal':
+ return [...baseStyle, {
+ backgroundColor: `${theme.neutral100}80`, // 80% 透明度
+ }];
+ default:
+ return [...baseStyle, {
+ backgroundColor: `${theme.accentGreen}20`, // 20% 透明度
+ }];
+ }
+ };
+
+ const getBackButtonIconColor = () => {
+ switch (variant) {
+ case 'elevated':
+ return theme.primary;
+ case 'minimal':
+ return theme.textSecondary;
+ default:
+ return theme.onPrimary;
+ }
+ };
+
+ const getBorderStyle = () => {
+ if (!showBottomBorder) return {};
+
+ return {
+ borderBottomWidth: 1,
+ borderBottomColor: variant === 'elevated'
+ ? theme.border
+ : `${theme.border}40`, // 40% 透明度
+ };
+ };
+
return (
{onBack ? (
-
-
+
+
) : (
)}
-
+
{typeof title === 'string' ? (
- {title}
+
+ {title}
+
) : (
title
)}
@@ -68,7 +150,9 @@ const styles = StyleSheet.create({
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
- paddingBottom: 10,
+ paddingBottom: 12,
+ minHeight: 44,
+ width: '100%',
},
backButton: {
width: 32,
@@ -76,10 +160,25 @@ const styles = StyleSheet.create({
borderRadius: 16,
alignItems: 'center',
justifyContent: 'center',
+ shadowColor: '#000',
+ shadowOffset: {
+ width: 0,
+ height: 1,
+ },
+ shadowOpacity: 0.1,
+ shadowRadius: 2,
+ elevation: 2,
+ },
+ titleContainer: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingHorizontal: 8,
},
title: {
- fontSize: 20,
- fontWeight: '800',
+ fontSize: 18,
+ textAlign: 'center',
+ letterSpacing: -0.3,
},
});
diff --git a/constants/Colors.ts b/constants/Colors.ts
index 37b61d3..dd2e6df 100644
--- a/constants/Colors.ts
+++ b/constants/Colors.ts
@@ -1,138 +1,212 @@
/**
- * 应用全局配色规范(来自设计规范图)。
- * 说明:保持原有导出结构不变,同时扩展更完整的语义令牌与原子调色板。
+ * 应用全局配色规范(基于设计规范图)。
+ * 包含完整的语义化颜色系统:灰色、紫色、成功色、错误色、警告色和基础色。
*/
// 原子调色板(与设计图一致)
export const palette = {
- // Primary
- primary: '#7A5AF8',
- ink: '#FFFFFF',
-
- // Secondary / Neutrals
- neutral100: '#888F92',
- neutral200: '#5E6468',
- neutral300: '#384046',
-
- // Accents
- purple: '#A48AED',
- red: '#ED4747',
- orange: '#FCC46F',
- blue: '#7A5AF8', // 更贴近logo背景的天空蓝
- blueSecondary: '#4682B4', // 钢蓝色,用于选中状态
- green: '#9ceb87', // 温暖的绿色,用于心情日历等
+ // 灰色系统 - 中性色,UI设计的基础
+ gray: {
+ 25: '#fcfcfd',
+ 50: '#ebecee',
+ 100: '#c0c4ca',
+ 200: '#a2a7b0',
+ 300: '#777f8c',
+ 400: '#5d6676',
+ 500: '#344054',
+ 600: '#2f3a4c',
+ 700: '#252d3c',
+ 800: '#1d232e',
+ 900: '#161b23',
+ },
+
+ // 紫色系统 - 品牌主色,用于交互元素
+ purple: {
+ 25: '#fafaff',
+ 50: '#f4f3ff',
+ 100: '#ebe9fe',
+ 200: '#d9d6fe',
+ 300: '#bdb4fe',
+ 400: '#9b8afb',
+ 500: '#7a5af8',
+ 600: '#6938ef',
+ 700: '#5925dc',
+ 800: '#4a1fb8',
+ 900: '#3e1c96',
+ },
+
+ // 成功色系统 - 绿色,用于正面反馈
+ success: {
+ 25: '#f6fef9',
+ 50: '#e8f7f1',
+ 100: '#b8e7d2',
+ 200: '#95dcbc',
+ 300: '#65cc9e',
+ 400: '#47c28b',
+ 500: '#19b36e',
+ 600: '#17a364',
+ 700: '#127f4e',
+ 800: '#0e623d',
+ 900: '#0b4b2e',
+ },
+
+ // 错误色系统 - 红色,用于错误状态和破坏性操作
+ error: {
+ 25: '#fffbfa',
+ 50: '#feeeee',
+ 100: '#fdcaca',
+ 200: '#fcb1b1',
+ 300: '#fb8d8d',
+ 400: '#fa7777',
+ 500: '#f95555',
+ 600: '#e34d4d',
+ 700: '#b13c3c',
+ 800: '#892f2f',
+ 900: '#692424',
+ },
+
+ // 警告色系统 - 黄色/橙色,用于警告和确认
+ warning: {
+ 25: '#fff7ec',
+ 50: '#fff7ec',
+ 100: '#ffe6c4',
+ 200: '#ffd9a8',
+ 300: '#ffc880',
+ 400: '#ffbd68',
+ 500: '#ffad42',
+ 600: '#e89d3c',
+ 700: '#b57b2f',
+ 800: '#8c5f24',
+ 900: '#6b491c',
+ },
+
+ // 基础色
+ base: {
+ white: '#ffffff',
+ black: '#1d232e',
+ }
} as const;
-const primaryColor = palette.blue; // 应用主题色
+// 主色调定义
+const primaryColor = palette.purple[500]; // 紫色500作为主色
const tintColorLight = primaryColor;
-const tintColorDark = '#FFFFFF';
+const tintColorDark = palette.base.white;
export const Colors = {
light: {
- // 基础文本/背景(优化对比度)
- text: '#1A2027', // 更深的文本色,提高可读性
- textSecondary: '#4A5568', // 温和的次要文本色
- textMuted: '#718096', // 柔和的静音文本色
- background: '#FFFFFF',
- surface: '#FFFFFF',
- card: 'rgba(255,255,255,0.95)', // 半透明卡片,与渐变背景融合
-
- buttonBackground: palette.blue,
+ // 基础文本/背景
+ text: palette.gray[900], // 最深灰色用于主要文本
+ textSecondary: palette.gray[600], // 中等灰色用于次要文本
+ textMuted: palette.gray[400], // 浅灰色用于静音文本
+ background: palette.base.white,
+ surface: palette.base.white,
+ card: palette.gray[25], // 最浅灰色用于卡片背景
// 品牌与可交互主色
tint: tintColorLight,
primary: primaryColor,
- onPrimary: palette.ink, // 与主色搭配的前景色(按钮文字/图标)
+ onPrimary: palette.base.white, // 主色上的文字/图标颜色
// 中性色与辅助
- neutral100: palette.neutral100,
- neutral200: palette.neutral200,
- neutral300: palette.neutral300,
+ neutral100: palette.gray[100],
+ neutral200: palette.gray[200],
+ neutral300: palette.gray[300],
// 状态/反馈色
- success: palette.primary,
- warning: palette.orange,
- danger: palette.red,
- info: palette.blue,
- accentPurple: palette.purple,
- accentGreen: palette.green, // 温暖的绿色
- accentGreenDark: palette.blueSecondary, // 深绿色,用于文本和强调
+ success: palette.success[500],
+ successLight: palette.success[100],
+ successDark: palette.success[700],
+ warning: palette.warning[500],
+ warningLight: palette.warning[100],
+ warningDark: palette.warning[700],
+ danger: palette.error[500],
+ dangerLight: palette.error[100],
+ dangerDark: palette.error[700],
+ info: palette.purple[500],
+ accentPurple: palette.purple[400],
+ accentGreen: palette.success[400],
// 日期选择器主题色
- datePickerNormal: palette.blue,
- datePickerSelected: palette.green, // 使用温暖的绿色作为选中状态
+ datePickerNormal: palette.purple[500],
+ datePickerSelected: palette.success[500],
- // 结构色(优化后的蓝色主题)
- border: 'rgba(135,206,235,0.2)', // 蓝色调边框
- separator: 'rgba(135,206,235,0.15)', // 更淡的分隔线
- icon: '#5A6C7D', // 蓝灰色图标
+ // 结构色
+ border: palette.gray[200], // 浅灰色边框
+ separator: palette.gray[100], // 更浅的分隔线
+ icon: palette.gray[500], // 中等灰色图标
- // Tab 相关(保持兼容)
- tabIconDefault: '#687076',
- tabIconSelected: palette.ink, // tab 激活时的文字/图标颜色(深色,在亮色背景上显示)
- tabBarBackground: palette.ink, // tab 栏背景色
- tabBarActiveBackground: primaryColor, // tab 激活时的背景色
+ // Tab 相关
+ tabIconDefault: palette.gray[400],
+ tabIconSelected: palette.base.white, // 选中时使用白色文字/图标,确保在紫色背景上清晰可见
+ tabBarBackground: palette.base.white,
+ tabBarActiveBackground: palette.purple[500], // 使用主色作为选中背景
- // 页面氛围与装饰(优化后的蓝色配色方案)
- pageBackgroundEmphasis: '#F0F8FF', // 淡蓝色背景强调
- heroSurfaceTint: 'rgba(135,206,235,0.12)', // 更柔和的蓝色调表面色彩
- ornamentPrimary: 'rgba(135,206,235,0.15)', // 与主色调和的装饰色
- ornamentAccent: 'rgba(70,130,180,0.12)', // 钢蓝色装饰,增加层次
+ // 页面氛围与装饰
+ pageBackgroundEmphasis: palette.gray[25],
+ heroSurfaceTint: palette.purple[25],
+ ornamentPrimary: palette.purple[100],
+ ornamentAccent: palette.success[100],
- // 优化的背景渐变色(更柔和的蓝色过渡)
- backgroundGradientStart: '#E6F3FF', // 更柔和的浅蓝色起始
- backgroundGradientEnd: '#FAFCFF', // 带有微蓝调的白色结束
+ // 背景渐变色
+ backgroundGradientStart: palette.gray[25],
+ backgroundGradientEnd: palette.base.white,
},
dark: {
// 基础文本/背景
- text: '#ECEDEE',
- textSecondary: palette.neutral100,
- textMuted: '#9BA1A6',
- background: '#151718',
- surface: '#1A1D1E',
- card: '#1A1D1E',
+ text: palette.gray[25], // 最浅灰色用于主要文本
+ textSecondary: palette.gray[100], // 浅灰色用于次要文本
+ textMuted: palette.gray[300], // 中等灰色用于静音文本
+ background: palette.gray[900],
+ surface: palette.gray[800],
+ card: palette.gray[800],
// 品牌与可交互主色
tint: tintColorDark,
primary: primaryColor,
- onPrimary: palette.ink,
+ onPrimary: palette.base.white,
// 中性色与辅助
- neutral100: palette.neutral100,
- neutral200: palette.neutral200,
- neutral300: palette.neutral300,
+ neutral100: palette.gray[100],
+ neutral200: palette.gray[200],
+ neutral300: palette.gray[300],
// 状态/反馈色
- success: palette.primary,
- warning: palette.orange,
- danger: palette.red,
- info: palette.blue,
- accentPurple: palette.purple,
- accentGreenDark: '#2D5016', // 深绿色,用于文本和强调
+ success: palette.success[400], // 深色模式下使用较亮的成功色
+ successLight: palette.success[200],
+ successDark: palette.success[600],
+ warning: palette.warning[400],
+ warningLight: palette.warning[200],
+ warningDark: palette.warning[600],
+ danger: palette.error[400],
+ dangerLight: palette.error[200],
+ dangerDark: palette.error[600],
+ info: palette.purple[400],
+ accentPurple: palette.purple[300],
+ accentGreen: palette.success[300],
// 日期选择器主题色
- datePickerNormal: palette.blue,
- datePickerSelected: palette.green, // 使用温暖的绿色作为选中状态
+ datePickerNormal: palette.purple[400],
+ datePickerSelected: palette.success[400],
// 结构色
- border: '#2A2F32',
- separator: '#2A2F32',
- icon: '#9BA1A6',
+ border: palette.gray[700],
+ separator: palette.gray[700],
+ icon: palette.gray[300],
- // Tab 相关(保持兼容)
- tabIconDefault: '#9BA1A6',
- tabIconSelected: palette.ink, // 在亮色背景上使用深色文字
- tabBarBackground: palette.ink,
- tabBarActiveBackground: primaryColor,
+ // Tab 相关
+ tabIconDefault: palette.gray[400],
+ tabIconSelected: palette.base.white, // 选中时使用白色文字/图标,确保在紫色背景上清晰可见
+ tabBarBackground: palette.gray[800],
+ tabBarActiveBackground: palette.purple[500], // 使用主色作为选中背景,在深色模式下更突出
- // 页面氛围与装饰(新)
- pageBackgroundEmphasis: '#151718',
- heroSurfaceTint: 'rgba(187,242,70,0.12)',
- ornamentPrimary: 'rgba(187,242,70,0.18)',
- ornamentAccent: 'rgba(164,138,237,0.14)',
+ // 页面氛围与装饰
+ pageBackgroundEmphasis: palette.gray[800],
+ heroSurfaceTint: palette.purple[900],
+ ornamentPrimary: palette.purple[800],
+ ornamentAccent: palette.success[800],
- // 统一背景渐变色(深色模式)
- backgroundGradientStart: '#0A0B0C', // 深黑色起始
- backgroundGradientEnd: '#151718', // 背景色结束
+ // 背景渐变色
+ backgroundGradientStart: palette.gray[900],
+ backgroundGradientEnd: palette.gray[800],
},
} as const;
diff --git a/docs/headerbar-design-system.md b/docs/headerbar-design-system.md
new file mode 100644
index 0000000..51db480
--- /dev/null
+++ b/docs/headerbar-design-system.md
@@ -0,0 +1,201 @@
+# HeaderBar 组件设计系统
+
+## 概述
+
+HeaderBar 组件已经与 `Colors.ts` 中的设计系统完全集成,提供了多种变体和优美的 UI 效果。
+
+## 设计理念
+
+### 颜色系统集成
+- 完全基于 `Colors.ts` 中定义的语义化颜色系统
+- 支持浅色和深色主题自动切换
+- 使用透明度创建层次感和视觉深度
+
+### 三种变体设计
+
+#### 1. Default 变体 (默认)
+- **背景**: 透明或卡片背景
+- **返回按钮**: 绿色强调色背景 (20% 透明度)
+- **图标颜色**: 白色 (确保在绿色背景上清晰可见)
+- **适用场景**: 标准页面导航
+
+#### 2. Elevated 变体 (提升)
+- **背景**: 卡片背景
+- **返回按钮**: 主色背景 (15% 透明度) + 边框 (20% 透明度)
+- **图标颜色**: 主色
+- **边框**: 实线边框
+- **适用场景**: 重要页面、模态框、需要突出显示的导航
+
+#### 3. Minimal 变体 (简约)
+- **背景**: 页面背景色
+- **返回按钮**: 中性色背景 (80% 透明度)
+- **图标颜色**: 次要文本色
+- **适用场景**: 内容页面、列表页面、需要最小化视觉干扰的场景
+
+## 颜色搭配详解
+
+### 背景色系统
+```typescript
+// 透明背景
+transparent: 'transparent'
+
+// 卡片背景 (浅色主题)
+card: palette.gray[25] // #fcfcfd
+
+// 页面背景 (浅色主题)
+background: palette.base.white // #ffffff
+```
+
+### 按钮背景色
+```typescript
+// Default 变体
+backgroundColor: `${theme.accentGreen}20` // 绿色 20% 透明度
+
+// Elevated 变体
+backgroundColor: `${theme.primary}15` // 主色 15% 透明度
+borderColor: `${theme.primary}20` // 主色 20% 透明度
+
+// Minimal 变体
+backgroundColor: `${theme.neutral100}80` // 中性色 80% 透明度
+```
+
+### 文本颜色
+```typescript
+// 主标题
+color: theme.text // 主要文本色
+
+// 次要文本
+color: theme.textSecondary // 次要文本色
+
+// 按钮图标
+color: theme.onPrimary // 主色上的文字/图标颜色
+```
+
+## 使用示例
+
+### 基础用法
+```tsx
+// 默认透明背景
+ navigation.goBack()}
+/>
+
+// 卡片背景
+ navigation.goBack()}
+ transparent={false}
+ showBottomBorder={true}
+/>
+```
+
+### 变体使用
+```tsx
+// Elevated 变体 - 重要页面
+ navigation.goBack()}
+ variant="elevated"
+ showBottomBorder={true}
+/>
+
+// Minimal 变体 - 内容页面
+ navigation.goBack()}
+ variant="minimal"
+/>
+```
+
+### 自定义右侧内容
+```tsx
+ navigation.goBack()}
+ right={
+
+ 保存
+
+ }
+ variant="elevated"
+/>
+```
+
+### 自定义标题
+```tsx
+
+
+ 主标题
+
+
+ 副标题
+
+
+ }
+ onBack={() => navigation.goBack()}
+ variant="elevated"
+/>
+```
+
+## 视觉层次
+
+### 阴影效果
+- 返回按钮添加了微妙的阴影效果
+- 增强按钮的立体感和可点击性
+- 在浅色和深色主题下都有良好的表现
+
+### 间距系统
+- 使用 8px 的基础间距单位
+- 标题容器有适当的内边距确保居中
+- 按钮有足够的触摸区域 (32x32)
+
+### 字体系统
+- 标题字体大小: 18px
+- 字重根据变体调整 (700-800)
+- 字母间距优化 (-0.3) 提升可读性
+
+## 主题适配
+
+### 浅色主题
+- 使用 `palette.gray[900]` 作为主要文本色
+- 使用 `palette.gray[25]` 作为卡片背景
+- 绿色强调色用于默认按钮
+
+### 深色主题
+- 使用 `palette.gray[25]` 作为主要文本色
+- 使用 `palette.gray[800]` 作为卡片背景
+- 自动调整透明度和对比度
+
+## 最佳实践
+
+1. **选择合适的变体**: 根据页面重要性和视觉需求选择变体
+2. **保持一致性**: 在同一应用中保持 HeaderBar 风格的一致性
+3. **考虑可访问性**: 确保颜色对比度符合可访问性标准
+4. **响应式设计**: 在不同屏幕尺寸下测试显示效果
+5. **性能优化**: 避免在 HeaderBar 中放置复杂的交互元素
+
+## 技术实现
+
+### 颜色计算
+使用模板字符串和透明度后缀来创建半透明颜色:
+```typescript
+`${theme.primary}15` // 15% 透明度
+`${theme.accentGreen}20` // 20% 透明度
+```
+
+### 动态样式
+根据变体和主题动态计算样式,确保视觉一致性:
+```typescript
+const getBackButtonStyle = () => {
+ switch (variant) {
+ case 'elevated':
+ return { backgroundColor: `${theme.primary}15` };
+ // ...
+ }
+};
+```
+
+这种设计确保了 HeaderBar 组件与整个应用的设计系统完美融合,提供了优美且一致的用户体验。
diff --git a/utils/health.ts b/utils/health.ts
index 357d974..cf0229a 100644
--- a/utils/health.ts
+++ b/utils/health.ts
@@ -11,6 +11,8 @@ const PERMISSIONS: HealthKitPermissions = {
AppleHealthKit.Constants.Permissions.SleepAnalysis,
AppleHealthKit.Constants.Permissions.HeartRateVariability,
AppleHealthKit.Constants.Permissions.ActivitySummary,
+ AppleHealthKit.Constants.Permissions.OxygenSaturation,
+ AppleHealthKit.Constants.Permissions.HeartRate,
],
write: [
// 支持体重写入
@@ -32,6 +34,9 @@ export type TodayHealthData = {
exerciseMinutesGoal: number;
standHours: number;
standHoursGoal: number;
+ // 新增血氧饱和度和心率数据
+ oxygenSaturation: number | null;
+ heartRate: number | null;
};
export async function ensureHealthPermissions(): Promise {
@@ -74,8 +79,8 @@ export async function fetchHealthDataForDate(date: Date): Promise((resolve) => {
AppleHealthKit.getStepCount({
@@ -198,10 +203,68 @@ export async function fetchHealthDataForDate(date: Date): Promise((resolve) => {
+ AppleHealthKit.getOxygenSaturationSamples(options, (err, res) => {
+ if (err) {
+ console.error('获取血氧饱和度失败:', err);
+ return resolve(null);
+ }
+ if (!res || !Array.isArray(res) || res.length === 0) {
+ console.warn('血氧饱和度数据为空或格式错误');
+ return resolve(null);
+ }
+ console.log('血氧饱和度数据:', res);
+ // 获取最新的血氧饱和度值
+ const latestOxygen = res[res.length - 1];
+ if (latestOxygen && latestOxygen.value !== undefined && latestOxygen.value !== null) {
+ // 血氧饱和度通常在0-100之间,验证数据有效性
+ const value = Number(latestOxygen.value);
+ if (value >= 0 && value <= 100) {
+ resolve(Number(value.toFixed(1)));
+ } else {
+ console.warn('血氧饱和度数据异常:', value);
+ resolve(null);
+ }
+ } else {
+ resolve(null);
+ }
+ });
+ }),
+
+ // 获取心率数据
+ new Promise((resolve) => {
+ AppleHealthKit.getHeartRateSamples(options, (err, res) => {
+ if (err) {
+ console.error('获取心率失败:', err);
+ return resolve(null);
+ }
+ if (!res || !Array.isArray(res) || res.length === 0) {
+ console.warn('心率数据为空或格式错误');
+ return resolve(null);
+ }
+ console.log('心率数据:', res);
+ // 获取最新的心率值
+ const latestHeartRate = res[res.length - 1];
+ if (latestHeartRate && latestHeartRate.value !== undefined && latestHeartRate.value !== null) {
+ // 心率通常在30-200之间,验证数据有效性
+ const value = Number(latestHeartRate.value);
+ if (value >= 30 && value <= 200) {
+ resolve(Math.round(value));
+ } else {
+ console.warn('心率数据异常:', value);
+ resolve(null);
+ }
+ } else {
+ resolve(null);
+ }
+ });
})
]);
- console.log('指定日期健康数据获取完成:', { steps, calories, basalMetabolism, sleepDuration, hrv, activitySummary });
+ console.log('指定日期健康数据获取完成:', { steps, calories, basalMetabolism, sleepDuration, hrv, activitySummary, oxygenSaturation, heartRate });
return {
steps,
@@ -215,7 +278,10 @@ export async function fetchHealthDataForDate(date: Date): Promise