feat: 添加健康咨询页面及相关功能
- 在应用中新增健康咨询页面,展示用户健康数据和建议 - 集成 expo-linear-gradient 以实现页面背景渐变效果 - 更新 package.json 和 package-lock.json,添加 expo-linear-gradient 依赖 - 在首页中添加健康咨询的导航链接 - 修改布局以支持新页面的显示和交互
This commit is contained in:
@@ -56,6 +56,8 @@ export default function HomeScreen() {
|
||||
onPress={() => {
|
||||
if (workout.title === 'AI体态评估') {
|
||||
router.push('/ai-posture-assessment');
|
||||
} else if (workout.title === '认证教练') {
|
||||
router.push('/health-consultation' as any);
|
||||
} else {
|
||||
console.log(`Pressed ${workout.title}`);
|
||||
}
|
||||
|
||||
481
app/health-consultation.tsx
Normal file
481
app/health-consultation.tsx
Normal file
@@ -0,0 +1,481 @@
|
||||
import { ThemedText } from '@/components/ThemedText';
|
||||
import { ThemedView } from '@/components/ThemedView';
|
||||
import { useThemeColor } from '@/hooks/useThemeColor';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { LinearGradient } from 'expo-linear-gradient';
|
||||
import { useRouter } from 'expo-router';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Dimensions,
|
||||
Pressable,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
const { width: screenWidth } = Dimensions.get('window');
|
||||
|
||||
// 健康数据项类型
|
||||
interface HealthItem {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
status: 'warning' | 'good' | 'info';
|
||||
icon: string;
|
||||
recommendation: string;
|
||||
value?: string;
|
||||
color: string;
|
||||
bgColor: string;
|
||||
}
|
||||
|
||||
// 健康数据
|
||||
const healthData: HealthItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: '运动状态',
|
||||
subtitle: '本周运动不足',
|
||||
status: 'warning',
|
||||
icon: '🏃♀️',
|
||||
recommendation: '建议每天进行30分钟普拉提训练',
|
||||
value: '2天/周',
|
||||
color: '#FF6B6B',
|
||||
bgColor: '#FFE5E5',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: '体态评估',
|
||||
subtitle: '需要进行评估',
|
||||
status: 'info',
|
||||
icon: '🧘♀️',
|
||||
recommendation: '进行AI体态评估,了解身体状况',
|
||||
color: '#4ECDC4',
|
||||
bgColor: '#E5F9F7',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: '核心力量',
|
||||
subtitle: '待加强',
|
||||
status: 'warning',
|
||||
icon: '💪',
|
||||
recommendation: '推荐核心训练课程',
|
||||
value: '初级',
|
||||
color: '#FFB84D',
|
||||
bgColor: '#FFF4E5',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: '柔韧性',
|
||||
subtitle: '良好',
|
||||
status: 'good',
|
||||
icon: '🤸♀️',
|
||||
recommendation: '保持每日拉伸习惯',
|
||||
value: '良好',
|
||||
color: '#95E1D3',
|
||||
bgColor: '#E5F9F5',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: '平衡能力',
|
||||
subtitle: '需要提升',
|
||||
status: 'info',
|
||||
icon: '⚖️',
|
||||
recommendation: '尝试单腿站立训练',
|
||||
color: '#A8E6CF',
|
||||
bgColor: '#E8F8F0',
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: '呼吸质量',
|
||||
subtitle: '待改善',
|
||||
status: 'warning',
|
||||
icon: '🌬️',
|
||||
recommendation: '学习普拉提呼吸法',
|
||||
color: '#C7CEEA',
|
||||
bgColor: '#F0F1F8',
|
||||
},
|
||||
];
|
||||
|
||||
export default function HealthConsultationScreen() {
|
||||
const router = useRouter();
|
||||
const primaryColor = useThemeColor({}, 'primary');
|
||||
const backgroundColor = useThemeColor({}, 'background');
|
||||
const textColor = useThemeColor({}, 'text');
|
||||
const [greeting, setGreeting] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
const hour = new Date().getHours();
|
||||
if (hour < 12) {
|
||||
setGreeting('早上好');
|
||||
} else if (hour < 18) {
|
||||
setGreeting('下午好');
|
||||
} else {
|
||||
setGreeting('晚上好');
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleHealthItemPress = (item: HealthItem) => {
|
||||
// 根据不同的健康项导航到相应页面
|
||||
if (item.title === '体态评估') {
|
||||
router.push('/ai-posture-assessment');
|
||||
} else {
|
||||
console.log(`点击了 ${item.title}`);
|
||||
// 可以添加更多导航逻辑
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<SafeAreaView style={[styles.safeArea, { backgroundColor }]}>
|
||||
<ThemedView style={styles.container}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{/* 顶部导航栏 */}
|
||||
<View style={styles.header}>
|
||||
<Pressable onPress={() => router.back()} style={styles.backButton}>
|
||||
<Ionicons name="chevron-back" size={24} color={textColor} />
|
||||
</Pressable>
|
||||
<ThemedText style={styles.headerTitle}>健康咨询</ThemedText>
|
||||
<Pressable style={styles.notificationButton}>
|
||||
<Ionicons name="notifications-outline" size={24} color={textColor} />
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* 教练问候卡片 */}
|
||||
<LinearGradient
|
||||
colors={[primaryColor, '#A8E063']}
|
||||
start={{ x: 0, y: 0 }}
|
||||
end={{ x: 1, y: 1 }}
|
||||
style={styles.coachCard}
|
||||
>
|
||||
<View style={styles.coachContent}>
|
||||
<View style={styles.coachInfo}>
|
||||
<View style={styles.coachAvatar}>
|
||||
<Text style={styles.coachAvatarEmoji}>👩⚕️</Text>
|
||||
</View>
|
||||
<View style={styles.coachTextContainer}>
|
||||
<Text style={styles.coachGreeting}>{greeting},</Text>
|
||||
<Text style={styles.coachName}>我是您的普拉提教练 Sarah</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.coachQuestion}>今天感觉怎么样?</Text>
|
||||
<Text style={styles.coachSubtext}>让我们一起了解您的身体状况</Text>
|
||||
</View>
|
||||
</LinearGradient>
|
||||
|
||||
{/* 快速操作按钮 */}
|
||||
<View style={styles.quickActions}>
|
||||
<Pressable style={[styles.actionButton, { backgroundColor: primaryColor }]}>
|
||||
<Ionicons name="body-outline" size={20} color="#192126" />
|
||||
<Text style={styles.actionButtonText}>体态检测</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.actionButton, styles.actionButtonOutline]}>
|
||||
<Ionicons name="chatbubble-outline" size={20} color={textColor} />
|
||||
<Text style={[styles.actionButtonText, { color: textColor }]}>咨询教练</Text>
|
||||
</Pressable>
|
||||
<Pressable style={[styles.actionButton, styles.actionButtonOutline]}>
|
||||
<Ionicons name="calendar-outline" size={20} color={textColor} />
|
||||
<Text style={[styles.actionButtonText, { color: textColor }]}>预约课程</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
{/* 健康状况标题 */}
|
||||
<View style={styles.sectionHeader}>
|
||||
<ThemedText style={styles.sectionTitle}>您的健康状况</ThemedText>
|
||||
<View style={[styles.healthBadge, { backgroundColor: primaryColor }]}>
|
||||
<Text style={styles.healthBadgeText}>需要关注</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 健康数据网格 */}
|
||||
<View style={styles.healthGrid}>
|
||||
{healthData.map((item) => (
|
||||
<Pressable
|
||||
key={item.id}
|
||||
style={[styles.healthCard, { backgroundColor: item.bgColor }]}
|
||||
onPress={() => handleHealthItemPress(item)}
|
||||
>
|
||||
<View style={styles.healthCardHeader}>
|
||||
<Text style={styles.healthIcon}>{item.icon}</Text>
|
||||
{item.value && (
|
||||
<Text style={[styles.healthValue, { color: item.color }]}>
|
||||
{item.value}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
<Text style={styles.healthTitle}>{item.title}</Text>
|
||||
<Text style={styles.healthSubtitle}>{item.subtitle}</Text>
|
||||
<View style={styles.healthRecommendation}>
|
||||
<Ionicons name="bulb-outline" size={12} color={item.color} />
|
||||
<Text style={[styles.recommendationText, { color: item.color }]} numberOfLines={2}>
|
||||
{item.recommendation}
|
||||
</Text>
|
||||
</View>
|
||||
<Ionicons
|
||||
name="arrow-forward-circle"
|
||||
size={20}
|
||||
color={item.color}
|
||||
style={styles.cardArrow}
|
||||
/>
|
||||
</Pressable>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 今日建议 */}
|
||||
<View style={styles.suggestionSection}>
|
||||
<ThemedText style={styles.suggestionTitle}>今日建议</ThemedText>
|
||||
<View style={[styles.suggestionCard, { backgroundColor: '#F0F8FF' }]}>
|
||||
<View style={styles.suggestionIcon}>
|
||||
<Text style={{ fontSize: 24 }}>💡</Text>
|
||||
</View>
|
||||
<View style={styles.suggestionContent}>
|
||||
<Text style={styles.suggestionMainText}>
|
||||
根据您的身体状况,建议今天进行轻度核心训练
|
||||
</Text>
|
||||
<Text style={styles.suggestionSubText}>
|
||||
配合呼吸练习,效果更佳
|
||||
</Text>
|
||||
<Pressable style={[styles.startButton, { backgroundColor: primaryColor }]}>
|
||||
<Text style={styles.startButtonText}>开始训练</Text>
|
||||
<Ionicons name="arrow-forward" size={16} color="#192126" />
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 底部间距 */}
|
||||
<View style={styles.bottomSpacing} />
|
||||
</ScrollView>
|
||||
</ThemedView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
safeArea: {
|
||||
flex: 1,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
header: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 12,
|
||||
},
|
||||
backButton: {
|
||||
padding: 8,
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
},
|
||||
notificationButton: {
|
||||
padding: 8,
|
||||
},
|
||||
coachCard: {
|
||||
marginHorizontal: 20,
|
||||
marginTop: 12,
|
||||
borderRadius: 20,
|
||||
padding: 24,
|
||||
elevation: 5,
|
||||
shadowColor: '#000',
|
||||
shadowOffset: { width: 0, height: 2 },
|
||||
shadowOpacity: 0.1,
|
||||
shadowRadius: 8,
|
||||
},
|
||||
coachContent: {
|
||||
gap: 16,
|
||||
},
|
||||
coachInfo: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
},
|
||||
coachAvatar: {
|
||||
width: 50,
|
||||
height: 50,
|
||||
borderRadius: 25,
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
coachAvatarEmoji: {
|
||||
fontSize: 30,
|
||||
},
|
||||
coachTextContainer: {
|
||||
flex: 1,
|
||||
},
|
||||
coachGreeting: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
opacity: 0.8,
|
||||
},
|
||||
coachName: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#192126',
|
||||
},
|
||||
coachQuestion: {
|
||||
fontSize: 28,
|
||||
fontWeight: 'bold',
|
||||
color: '#192126',
|
||||
marginTop: 8,
|
||||
},
|
||||
coachSubtext: {
|
||||
fontSize: 14,
|
||||
color: '#192126',
|
||||
opacity: 0.7,
|
||||
},
|
||||
quickActions: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 20,
|
||||
marginTop: 20,
|
||||
gap: 12,
|
||||
},
|
||||
actionButton: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 12,
|
||||
borderRadius: 12,
|
||||
gap: 6,
|
||||
},
|
||||
actionButtonOutline: {
|
||||
borderWidth: 1,
|
||||
borderColor: '#E0E0E0',
|
||||
},
|
||||
actionButtonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
sectionHeader: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
paddingHorizontal: 20,
|
||||
marginTop: 32,
|
||||
marginBottom: 16,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
healthBadge: {
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 6,
|
||||
borderRadius: 12,
|
||||
},
|
||||
healthBadgeText: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
color: '#192126',
|
||||
},
|
||||
healthGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
paddingHorizontal: 16,
|
||||
gap: 12,
|
||||
},
|
||||
healthCard: {
|
||||
width: (screenWidth - 44) / 2,
|
||||
padding: 16,
|
||||
borderRadius: 16,
|
||||
position: 'relative',
|
||||
},
|
||||
healthCardHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: 8,
|
||||
},
|
||||
healthIcon: {
|
||||
fontSize: 28,
|
||||
},
|
||||
healthValue: {
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
},
|
||||
healthTitle: {
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: '#192126',
|
||||
marginBottom: 4,
|
||||
},
|
||||
healthSubtitle: {
|
||||
fontSize: 13,
|
||||
color: '#666',
|
||||
marginBottom: 12,
|
||||
},
|
||||
healthRecommendation: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
gap: 6,
|
||||
paddingRight: 20,
|
||||
},
|
||||
recommendationText: {
|
||||
fontSize: 11,
|
||||
flex: 1,
|
||||
},
|
||||
cardArrow: {
|
||||
position: 'absolute',
|
||||
bottom: 12,
|
||||
right: 12,
|
||||
},
|
||||
suggestionSection: {
|
||||
paddingHorizontal: 20,
|
||||
marginTop: 32,
|
||||
},
|
||||
suggestionTitle: {
|
||||
fontSize: 22,
|
||||
fontWeight: 'bold',
|
||||
marginBottom: 16,
|
||||
},
|
||||
suggestionCard: {
|
||||
flexDirection: 'row',
|
||||
padding: 20,
|
||||
borderRadius: 16,
|
||||
gap: 16,
|
||||
},
|
||||
suggestionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: '#fff',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
suggestionContent: {
|
||||
flex: 1,
|
||||
gap: 8,
|
||||
},
|
||||
suggestionMainText: {
|
||||
fontSize: 15,
|
||||
fontWeight: '500',
|
||||
color: '#192126',
|
||||
},
|
||||
suggestionSubText: {
|
||||
fontSize: 13,
|
||||
color: '#666',
|
||||
},
|
||||
startButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
alignSelf: 'flex-start',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 8,
|
||||
borderRadius: 20,
|
||||
marginTop: 8,
|
||||
gap: 6,
|
||||
},
|
||||
startButtonText: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#192126',
|
||||
},
|
||||
bottomSpacing: {
|
||||
height: 100,
|
||||
},
|
||||
});
|
||||
@@ -51,6 +51,8 @@ PODS:
|
||||
- SDWebImageWebPCoder (~> 0.14.6)
|
||||
- ExpoKeepAwake (14.1.4):
|
||||
- ExpoModulesCore
|
||||
- ExpoLinearGradient (14.1.5):
|
||||
- ExpoModulesCore
|
||||
- ExpoLinking (7.1.7):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (2.5.0):
|
||||
@@ -2107,6 +2109,7 @@ DEPENDENCIES:
|
||||
- ExpoHead (from `../node_modules/expo-router/ios`)
|
||||
- ExpoImage (from `../node_modules/expo-image/ios`)
|
||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
|
||||
- ExpoLinking (from `../node_modules/expo-linking/ios`)
|
||||
- ExpoModulesCore (from `../node_modules/expo-modules-core`)
|
||||
- ExpoSplashScreen (from `../node_modules/expo-splash-screen/ios`)
|
||||
@@ -2227,6 +2230,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/expo-image/ios"
|
||||
ExpoKeepAwake:
|
||||
:path: "../node_modules/expo-keep-awake/ios"
|
||||
ExpoLinearGradient:
|
||||
:path: "../node_modules/expo-linear-gradient/ios"
|
||||
ExpoLinking:
|
||||
:path: "../node_modules/expo-linking/ios"
|
||||
ExpoModulesCore:
|
||||
@@ -2404,6 +2409,7 @@ SPEC CHECKSUMS:
|
||||
ExpoHead: a7b66cbaeeb51f4a85338d335a0f5467e29a2c90
|
||||
ExpoImage: e4102c93d1dbe99ff54b075452d1bc9d6ec21b7c
|
||||
ExpoKeepAwake: bf0811570c8da182bfb879169437d4de298376e7
|
||||
ExpoLinearGradient: 7734c8059972fcf691fb4330bcdf3390960a152d
|
||||
ExpoLinking: d5c183998ca6ada66ff45e407e0f965b398a8902
|
||||
ExpoModulesCore: 00a1b5c73248465bd0b93f59f8538c4573dac579
|
||||
ExpoSplashScreen: 0ad5acac1b5d2953c6e00d4319f16d616f70d4dd
|
||||
|
||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -20,6 +20,7 @@
|
||||
"expo-font": "~13.3.2",
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image": "~2.4.0",
|
||||
"expo-linear-gradient": "^14.1.5",
|
||||
"expo-linking": "~7.1.7",
|
||||
"expo-router": "~5.1.4",
|
||||
"expo-splash-screen": "~0.30.10",
|
||||
@@ -6310,6 +6311,17 @@
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-linear-gradient": {
|
||||
"version": "14.1.5",
|
||||
"resolved": "https://mirrors.tencent.com/npm/expo-linear-gradient/-/expo-linear-gradient-14.1.5.tgz",
|
||||
"integrity": "sha512-BSN3MkSGLZoHMduEnAgfhoj3xqcDWaoICgIr4cIYEx1GcHfKMhzA/O4mpZJ/WC27BP1rnAqoKfbclk1eA70ndQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"expo": "*",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-linking": {
|
||||
"version": "7.1.7",
|
||||
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"expo-font": "~13.3.2",
|
||||
"expo-haptics": "~14.1.4",
|
||||
"expo-image": "~2.4.0",
|
||||
"expo-linear-gradient": "^14.1.5",
|
||||
"expo-linking": "~7.1.7",
|
||||
"expo-router": "~5.1.4",
|
||||
"expo-splash-screen": "~0.30.10",
|
||||
|
||||
Reference in New Issue
Block a user