healthapp
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

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