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.
 
 
 
 
 
 

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