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.
13 KiB
13 KiB
07-个人中心页面(原型)
目标
实现 APP 端个人中心和健康档案管理页面原型。
UI 设计参考
参考设计稿:
files/ui/我的.png
前置要求
- 导航配置完成
- 认证状态 Store 已创建
- 体质状态 Store 已创建
实施步骤
步骤 1:个人中心页面
创建 src/screens/profile/ProfileHomeScreen.tsx:
import React from 'react'
import { View, ScrollView, StyleSheet, Alert, Linking } from 'react-native'
import { Text, Avatar, Card, List, Button, Divider } 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 } from '../../mock/constitution'
const ProfileHomeScreen = () => {
const navigation = useNavigation<any>()
const { user, logout } = useAuthStore()
const { result } = useConstitutionStore()
const handleLogout = () => {
Alert.alert('提示', '确定要退出登录吗?', [
{ text: '取消', style: 'cancel' },
{ text: '确定', style: 'destructive', onPress: () => logout() },
])
}
return (
<ScrollView style={styles.container}>
{/* 用户信息卡片 */}
<View style={styles.headerCard}>
<View style={styles.userInfo}>
<Avatar.Text
size={64}
label={user?.nickname?.charAt(0) || 'U'}
style={styles.avatar}
/>
<View style={styles.userText}>
<Text style={styles.nickname}>{user?.nickname || '用户'}</Text>
<Text style={styles.phone}>{user?.phone}</Text>
{result && (
<View style={styles.constitutionTag}>
<Icon name="heart-pulse" size={14} color="#10B981" />
<Text style={styles.constitutionText}>
{constitutionNames[result.primaryType]}
</Text>
</View>
)}
</View>
</View>
</View>
{/* 健康管理 */}
<Card style={styles.menuCard}>
<Card.Title title="健康管理" titleStyle={styles.menuTitle} />
<List.Item
title="健康档案"
description="查看和管理您的健康信息"
left={(props) => (
<View style={[styles.iconBg, { backgroundColor: '#ECFDF5' }]}>
<Icon name="file-document" size={24} color="#10B981" />
</View>
)}
right={(props) => <List.Icon {...props} icon="chevron-right" />}
onPress={() => navigation.navigate('HealthRecord')}
style={styles.listItem}
/>
<Divider />
<List.Item
title="体质报告"
description={result ? `当前体质:${constitutionNames[result.primaryType]}` : '暂无测评记录'}
left={(props) => (
<View style={[styles.iconBg, { backgroundColor: '#EDE9FE' }]}>
<Icon name="chart-line" size={24} color="#8B5CF6" />
</View>
)}
right={(props) => <List.Icon {...props} icon="chevron-right" />}
onPress={() => navigation.navigate('ConstitutionTab', { screen: 'ConstitutionResult' })}
style={styles.listItem}
/>
<Divider />
<List.Item
title="对话历史"
description="查看AI咨询记录"
left={(props) => (
<View style={[styles.iconBg, { backgroundColor: '#DBEAFE' }]}>
<Icon name="chat-processing" size={24} color="#3B82F6" />
</View>
)}
right={(props) => <List.Icon {...props} icon="chevron-right" />}
onPress={() => navigation.navigate('ChatTab')}
style={styles.listItem}
/>
</Card>
{/* 其他设置 */}
<Card style={styles.menuCard}>
<Card.Title title="其他" titleStyle={styles.menuTitle} />
<List.Item
title="健康商城"
description="选购适合您的保健品"
left={(props) => (
<View style={[styles.iconBg, { backgroundColor: '#FEF3C7' }]}>
<Icon name="store" size={24} color="#F59E0B" />
</View>
)}
right={(props) => <List.Icon {...props} icon="chevron-right" />}
onPress={() => Linking.openURL('https://mall.example.com')}
style={styles.listItem}
/>
<Divider />
<List.Item
title="关于我们"
description="了解健康AI助手"
left={(props) => (
<View style={[styles.iconBg, { backgroundColor: '#E5E7EB' }]}>
<Icon name="information" size={24} color="#6B7280" />
</View>
)}
right={(props) => <List.Icon {...props} icon="chevron-right" />}
onPress={() => Alert.alert('关于我们', '健康AI助手 v1.0.0\n\n结合中医体质辨识理论,为您提供个性化健康建议。')}
style={styles.listItem}
/>
</Card>
{/* 退出登录 */}
<Button
mode="outlined"
onPress={handleLogout}
textColor="#EF4444"
style={styles.logoutButton}
>
退出登录
</Button>
<Text style={styles.version}>版本 1.0.0(原型版)</Text>
</ScrollView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F3F4F6' },
headerCard: { backgroundColor: '#10B981', padding: 20, paddingTop: 40 },
userInfo: { flexDirection: 'row', alignItems: 'center' },
avatar: { backgroundColor: 'rgba(255,255,255,0.2)' },
userText: { marginLeft: 16 },
nickname: { fontSize: 20, fontWeight: '600', color: '#fff' },
phone: { fontSize: 14, color: 'rgba(255,255,255,0.8)', marginTop: 4 },
constitutionTag: { flexDirection: 'row', alignItems: 'center', marginTop: 8, backgroundColor: 'rgba(255,255,255,0.2)', paddingHorizontal: 8, paddingVertical: 4, borderRadius: 12 },
constitutionText: { fontSize: 12, color: '#fff', marginLeft: 4 },
menuCard: { margin: 16, marginBottom: 0, borderRadius: 12 },
menuTitle: { fontSize: 14, color: '#6B7280' },
listItem: { paddingVertical: 4 },
iconBg: { width: 40, height: 40, borderRadius: 20, justifyContent: 'center', alignItems: 'center', marginLeft: 8 },
logoutButton: { margin: 16, borderColor: '#EF4444', borderRadius: 8 },
version: { textAlign: 'center', fontSize: 12, color: '#9CA3AF', marginBottom: 32 },
})
export default ProfileHomeScreen
步骤 2:健康档案页面
创建 src/screens/profile/HealthRecordScreen.tsx:
import React from 'react'
import { View, ScrollView, StyleSheet } from 'react-native'
import { Text, Card, Chip } from 'react-native-paper'
import Icon from 'react-native-vector-icons/MaterialCommunityIcons'
import { useAuthStore } from '../../stores/useAuthStore'
import { useConstitutionStore } from '../../stores/useConstitutionStore'
import { constitutionNames, constitutionDescriptions } from '../../mock/constitution'
const HealthRecordScreen = () => {
const { user } = useAuthStore()
const { result } = useConstitutionStore()
// 模拟健康档案数据
const mockProfile = {
basicInfo: {
name: user?.nickname || '用户',
gender: '男',
age: 45,
height: 170,
weight: 68,
bloodType: 'A型',
},
medicalHistory: ['高血压', '轻度脂肪肝'],
allergyRecords: ['青霉素'],
lifestyleInfo: {
sleepTime: '23:00',
wakeTime: '07:00',
exerciseFrequency: '每周2-3次',
},
}
const bmi = (mockProfile.basicInfo.weight / Math.pow(mockProfile.basicInfo.height / 100, 2)).toFixed(1)
return (
<ScrollView style={styles.container}>
{/* 基础信息 */}
<Card style={styles.card}>
<Card.Title
title="基础信息"
left={(props) => <Icon name="account" size={24} color="#10B981" />}
/>
<Card.Content>
<View style={styles.infoGrid}>
<InfoItem label="姓名" value={mockProfile.basicInfo.name} />
<InfoItem label="性别" value={mockProfile.basicInfo.gender} />
<InfoItem label="年龄" value={`${mockProfile.basicInfo.age}岁`} />
<InfoItem label="身高" value={`${mockProfile.basicInfo.height}cm`} />
<InfoItem label="体重" value={`${mockProfile.basicInfo.weight}kg`} />
<InfoItem label="BMI" value={bmi} />
<InfoItem label="血型" value={mockProfile.basicInfo.bloodType} />
</View>
</Card.Content>
</Card>
{/* 体质信息 */}
<Card style={styles.card}>
<Card.Title
title="体质信息"
left={(props) => <Icon name="heart-pulse" size={24} color="#10B981" />}
/>
<Card.Content>
{result ? (
<View style={styles.constitutionInfo}>
<Chip style={styles.constitutionChip} textStyle={styles.constitutionChipText}>
{constitutionNames[result.primaryType]}
</Chip>
<Text style={styles.constitutionDesc}>
{constitutionDescriptions[result.primaryType].description}
</Text>
<Text style={styles.assessedTime}>
测评时间:{new Date(result.assessedAt).toLocaleDateString()}
</Text>
</View>
) : (
<Text style={styles.emptyText}>暂无体质测评记录</Text>
)}
</Card.Content>
</Card>
{/* 既往病史 */}
<Card style={styles.card}>
<Card.Title
title="既往病史"
left={(props) => <Icon name="medical-bag" size={24} color="#10B981" />}
/>
<Card.Content>
{mockProfile.medicalHistory.length > 0 ? (
<View style={styles.tagList}>
{mockProfile.medicalHistory.map((item, index) => (
<Chip key={index} style={styles.tag}>{item}</Chip>
))}
</View>
) : (
<Text style={styles.emptyText}>暂无病史记录</Text>
)}
</Card.Content>
</Card>
{/* 过敏信息 */}
<Card style={styles.card}>
<Card.Title
title="过敏信息"
left={(props) => <Icon name="alert-circle" size={24} color="#EF4444" />}
/>
<Card.Content>
{mockProfile.allergyRecords.length > 0 ? (
<View style={styles.tagList}>
{mockProfile.allergyRecords.map((item, index) => (
<Chip key={index} style={styles.allergyTag}>{item}</Chip>
))}
</View>
) : (
<Text style={styles.emptyText}>暂无过敏信息</Text>
)}
</Card.Content>
</Card>
{/* 生活习惯 */}
<Card style={styles.card}>
<Card.Title
title="生活习惯"
left={(props) => <Icon name="calendar-clock" size={24} color="#10B981" />}
/>
<Card.Content>
<View style={styles.infoGrid}>
<InfoItem label="入睡时间" value={mockProfile.lifestyleInfo.sleepTime} />
<InfoItem label="起床时间" value={mockProfile.lifestyleInfo.wakeTime} />
<InfoItem label="运动频率" value={mockProfile.lifestyleInfo.exerciseFrequency} />
</View>
</Card.Content>
</Card>
<Text style={styles.note}>
以上为模拟数据,后续将支持编辑和同步
</Text>
</ScrollView>
)
}
const InfoItem = ({ label, value }: { label: string; value: string }) => (
<View style={styles.infoItem}>
<Text style={styles.infoLabel}>{label}</Text>
<Text style={styles.infoValue}>{value}</Text>
</View>
)
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#F3F4F6', padding: 16 },
card: { borderRadius: 12, marginBottom: 16 },
infoGrid: { flexDirection: 'row', flexWrap: 'wrap' },
infoItem: { width: '50%', marginBottom: 16 },
infoLabel: { fontSize: 12, color: '#9CA3AF', marginBottom: 4 },
infoValue: { fontSize: 15, color: '#1F2937' },
constitutionInfo: { alignItems: 'center' },
constitutionChip: { backgroundColor: '#10B981' },
constitutionChipText: { color: '#fff', fontSize: 16 },
constitutionDesc: { marginTop: 12, fontSize: 14, color: '#6B7280', textAlign: 'center', lineHeight: 22 },
assessedTime: { marginTop: 8, fontSize: 12, color: '#9CA3AF' },
tagList: { flexDirection: 'row', flexWrap: 'wrap', gap: 8 },
tag: { backgroundColor: '#ECFDF5' },
allergyTag: { backgroundColor: '#FEE2E2' },
emptyText: { color: '#9CA3AF', textAlign: 'center', paddingVertical: 16 },
note: { fontSize: 12, color: '#9CA3AF', textAlign: 'center', marginBottom: 24 },
})
export default HealthRecordScreen
验收标准
- 个人中心页面正常显示
- 用户信息显示正确
- 体质标签显示(如已测评)
- 菜单导航正常
- 健康档案数据显示
- 退出登录功能正常
预计耗时
25-30 分钟
完成
恭喜!APP 原型开发任务全部完成!
下一步
进入 03-Web原型开发/01-项目初始化和模拟数据.md