You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
11 KiB
11 KiB
04-首页(原型)
目标
实现 APP 首页原型,展示用户体质信息、快捷入口和健康提示。
UI 设计参考
参考设计稿:
files/ui/首页.png
页面布局
| 区域 | 设计要点 |
|---|---|
| 顶部 | 绿色背景 #10B981,用户问候语 |
| 体质卡片 | 白色卡片,显示当前体质类型和简介 |
| 快捷入口 | 4个功能入口(AI问诊、体质测试、健康档案、商城) |
| 健康提示 | 每日健康小贴士 |
| 推荐产品 | 根据体质推荐的保健品(横向滚动) |
前置要求
- 导航配置完成
- 模拟数据服务已创建
- 登录页面完成
实施步骤
步骤 1:创建体质状态 Store
创建 src/stores/useConstitutionStore.ts:
import { create } from 'zustand'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { ConstitutionResult } from '../types'
interface ConstitutionState {
result: ConstitutionResult | null
setResult: (result: ConstitutionResult) => void
clearResult: () => void
}
export const useConstitutionStore = create<ConstitutionState>((set) => ({
result: null,
setResult: (result) => {
AsyncStorage.setItem('constitution_result', JSON.stringify(result))
set({ result })
},
clearResult: () => {
AsyncStorage.removeItem('constitution_result')
set({ result: null })
},
}))
步骤 2:创建首页
创建 src/screens/home/HomeScreen.tsx:
import React from 'react'
import {
View,
ScrollView,
StyleSheet,
TouchableOpacity,
Linking,
} from 'react-native'
import { Text, Card, Avatar } from 'react-native-paper'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
import { useNavigation } from '@react-navigation/native'
import { useAuthStore } from '../../stores/useAuthStore'
import { useConstitutionStore } from '../../stores/useConstitutionStore'
import { constitutionNames, constitutionDescriptions } from '../../mock/constitution'
import { getProductsByConstitution, mockProducts } from '../../mock/products'
const HomeScreen = () => {
const navigation = useNavigation<any>()
const { user } = useAuthStore()
const { result } = useConstitutionStore()
// 获取当前时间段问候语
const getGreeting = () => {
const hour = new Date().getHours()
if (hour < 12) return '早上好'
if (hour < 18) return '下午好'
return '晚上好'
}
// 获取推荐产品
const recommendedProducts = result
? getProductsByConstitution(result.primaryType)
: mockProducts.slice(0, 4)
// 快捷入口数据
const quickActions = [
{ icon: 'chat-processing', label: 'AI问诊', color: '#3B82F6', onPress: () => navigation.navigate('ChatTab') },
{ icon: 'heart-pulse', label: '体质测试', color: '#10B981', onPress: () => navigation.navigate('ConstitutionTab') },
{ icon: 'file-document', label: '健康档案', color: '#8B5CF6', onPress: () => navigation.navigate('ProfileTab', { screen: 'HealthRecord' }) },
{ icon: 'store', label: '健康商城', color: '#F59E0B', onPress: () => Linking.openURL('https://mall.example.com') },
]
return (
<ScrollView style={styles.container}>
{/* 顶部问候 */}
<View style={styles.header}>
<View style={styles.greeting}>
<Text style={styles.greetingText}>
{getGreeting()},{user?.nickname || '用户'}
</Text>
<Text style={styles.greetingSubtext}>今天也要保持健康哦~</Text>
</View>
<Avatar.Text
size={48}
label={user?.nickname?.charAt(0) || 'U'}
style={styles.avatar}
/>
</View>
{/* 体质卡片 */}
<Card style={styles.constitutionCard}>
<Card.Content>
{result ? (
<>
<View style={styles.constitutionHeader}>
<Text style={styles.constitutionLabel}>我的体质</Text>
<TouchableOpacity
onPress={() => navigation.navigate('ConstitutionTab', { screen: 'ConstitutionResult' })}
>
<Text style={styles.viewMore}>查看详情 →</Text>
</TouchableOpacity>
</View>
<View style={styles.constitutionBody}>
<View style={styles.constitutionType}>
<Icon name="heart-pulse" size={32} color="#10B981" />
<Text style={styles.constitutionName}>
{constitutionNames[result.primaryType]}
</Text>
</View>
<Text style={styles.constitutionDesc} numberOfLines={2}>
{constitutionDescriptions[result.primaryType].description}
</Text>
</View>
</>
) : (
<TouchableOpacity
style={styles.noConstitution}
onPress={() => navigation.navigate('ConstitutionTab')}
>
<Icon name="clipboard-text-outline" size={48} color="#9CA3AF" />
<Text style={styles.noConstitutionText}>还未进行体质测试</Text>
<Text style={styles.noConstitutionHint}>点击开始测试,了解您的体质类型</Text>
</TouchableOpacity>
)}
</Card.Content>
</Card>
{/* 快捷入口 */}
<View style={styles.quickActions}>
{quickActions.map((action, index) => (
<TouchableOpacity
key={index}
style={styles.quickAction}
onPress={action.onPress}
>
<View style={[styles.quickActionIcon, { backgroundColor: action.color + '20' }]}>
<Icon name={action.icon} size={24} color={action.color} />
</View>
<Text style={styles.quickActionLabel}>{action.label}</Text>
</TouchableOpacity>
))}
</View>
{/* 健康提示 */}
<Card style={styles.tipCard}>
<Card.Content style={styles.tipContent}>
<Icon name="lightbulb-outline" size={24} color="#F59E0B" />
<View style={styles.tipTextContainer}>
<Text style={styles.tipTitle}>今日健康提示</Text>
<Text style={styles.tipText}>
{result
? constitutionDescriptions[result.primaryType].suggestions[0]
: '保持良好的作息习惯,每天喝足8杯水,适当运动有益身心健康。'
}
</Text>
</View>
</Card.Content>
</Card>
{/* 推荐产品 */}
<View style={styles.productsSection}>
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>
{result ? '适合您的调养产品' : '热门保健品'}
</Text>
<TouchableOpacity onPress={() => Linking.openURL('https://mall.example.com')}>
<Text style={styles.viewMore}>查看更多 →</Text>
</TouchableOpacity>
</View>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{recommendedProducts.map((product) => (
<TouchableOpacity
key={product.id}
style={styles.productCard}
onPress={() => Linking.openURL(product.mallUrl)}
>
<View style={styles.productImage}>
<Icon name="pill" size={32} color="#10B981" />
</View>
<Text style={styles.productName} numberOfLines={1}>{product.name}</Text>
<Text style={styles.productPrice}>¥{product.price}</Text>
</TouchableOpacity>
))}
</ScrollView>
</View>
</ScrollView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F3F4F6',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
backgroundColor: '#10B981',
},
greeting: {},
greetingText: {
fontSize: 20,
fontWeight: '600',
color: '#fff',
},
greetingSubtext: {
fontSize: 14,
color: 'rgba(255,255,255,0.8)',
marginTop: 4,
},
avatar: {
backgroundColor: 'rgba(255,255,255,0.2)',
},
constitutionCard: {
margin: 16,
marginTop: -20,
borderRadius: 16,
elevation: 4,
},
constitutionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
constitutionLabel: {
fontSize: 14,
color: '#6B7280',
},
viewMore: {
fontSize: 14,
color: '#10B981',
},
constitutionBody: {},
constitutionType: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
constitutionName: {
fontSize: 24,
fontWeight: 'bold',
color: '#1F2937',
marginLeft: 8,
},
constitutionDesc: {
fontSize: 14,
color: '#6B7280',
lineHeight: 20,
},
noConstitution: {
alignItems: 'center',
padding: 20,
},
noConstitutionText: {
fontSize: 16,
color: '#6B7280',
marginTop: 12,
},
noConstitutionHint: {
fontSize: 14,
color: '#9CA3AF',
marginTop: 4,
},
quickActions: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingHorizontal: 16,
marginBottom: 16,
},
quickAction: {
alignItems: 'center',
},
quickActionIcon: {
width: 56,
height: 56,
borderRadius: 16,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
},
quickActionLabel: {
fontSize: 12,
color: '#4B5563',
},
tipCard: {
marginHorizontal: 16,
marginBottom: 16,
borderRadius: 12,
backgroundColor: '#FFFBEB',
},
tipContent: {
flexDirection: 'row',
alignItems: 'flex-start',
},
tipTextContainer: {
flex: 1,
marginLeft: 12,
},
tipTitle: {
fontSize: 14,
fontWeight: '600',
color: '#92400E',
marginBottom: 4,
},
tipText: {
fontSize: 13,
color: '#B45309',
lineHeight: 18,
},
productsSection: {
paddingHorizontal: 16,
marginBottom: 24,
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
sectionTitle: {
fontSize: 16,
fontWeight: '600',
color: '#1F2937',
},
productCard: {
width: 120,
backgroundColor: '#fff',
borderRadius: 12,
padding: 12,
marginRight: 12,
alignItems: 'center',
},
productImage: {
width: 64,
height: 64,
backgroundColor: '#ECFDF5',
borderRadius: 32,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
},
productName: {
fontSize: 13,
color: '#1F2937',
marginBottom: 4,
},
productPrice: {
fontSize: 14,
fontWeight: '600',
color: '#EF4444',
},
})
export default HomeScreen
验收标准
- 首页 UI 正常显示
- 用户问候语显示正确
- 体质卡片显示(有/无体质结果两种状态)
- 快捷入口点击跳转正常
- 推荐产品显示正常
预计耗时
30-40 分钟
下一步
完成后进入 02-APP原型开发/05-体质辨识页面.md