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.
 
 
 
 
 
 

8.5 KiB

04-首页(原型)

目标

实现 Web 首页原型,展示用户体质信息、快捷入口和健康提示。


前置要求

  • 路由和布局配置完成
  • 模拟数据服务已创建
  • 登录页面完成

实施步骤

创建体质状态 Store

创建 src/stores/constitution.ts

import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { ConstitutionResult } from '@/types'

export const useConstitutionStore = defineStore('constitution', () => {
  const result = ref<ConstitutionResult | null>(null)

  function setResult(data: ConstitutionResult) {
    result.value = data
    localStorage.setItem('constitution_result', JSON.stringify(data))
  }

  function clearResult() {
    result.value = null
    localStorage.removeItem('constitution_result')
  }

  // 初始化时从 localStorage 恢复
  function init() {
    const saved = localStorage.getItem('constitution_result')
    if (saved) {
      result.value = JSON.parse(saved)
    }
  }

  init()

  return { result, setResult, clearResult }
})

创建首页

创建 src/views/home/HomeView.vue

<template>
  <div class="home-page">
    <!-- 体质卡片 -->
    <el-card class="constitution-card" :body-style="{ padding: '24px' }">
      <template v-if="constitutionStore.result">
        <div class="constitution-header">
          <span class="label">我的体质</span>
          <el-link type="primary" @click="router.push('/constitution/result')">
            查看详情 
          </el-link>
        </div>
        <div class="constitution-body">
          <el-icon size="40" color="#10B981"><TrendCharts /></el-icon>
          <div class="constitution-info">
            <h2>{{ constitutionNames[constitutionStore.result.primaryType] }}</h2>
            <p>{{ constitutionDescriptions[constitutionStore.result.primaryType].description }}</p>
          </div>
        </div>
      </template>
      <template v-else>
        <div class="no-constitution" @click="router.push('/constitution')">
          <el-icon size="48" color="#9CA3AF"><Document /></el-icon>
          <p>还未进行体质测试</p>
          <span>点击开始测试了解您的体质类型</span>
        </div>
      </template>
    </el-card>

    <!-- 快捷入口 -->
    <div class="quick-actions">
      <el-card
        v-for="action in quickActions"
        :key="action.label"
        class="action-card"
        shadow="hover"
        @click="action.onClick"
      >
        <div class="action-icon" :style="{ backgroundColor: action.bgColor }">
          <el-icon :size="24" :color="action.color">
            <component :is="action.icon" />
          </el-icon>
        </div>
        <div class="action-text">
          <h4>{{ action.label }}</h4>
          <p>{{ action.desc }}</p>
        </div>
      </el-card>
    </div>

    <!-- 健康提示 -->
    <el-card class="tip-card">
      <div class="tip-content">
        <el-icon size="24" color="#F59E0B"><Sunrise /></el-icon>
        <div class="tip-text">
          <h4>今日健康提示</h4>
          <p>{{ healthTip }}</p>
        </div>
      </div>
    </el-card>

    <!-- 推荐产品 -->
    <el-card class="products-card">
      <template #header>
        <div class="products-header">
          <span>{{ constitutionStore.result ? '适合您的调养产品' : '热门保健品' }}</span>
          <el-link type="primary" @click="openMall">查看更多 →</el-link>
        </div>
      </template>
      <div class="products-list">
        <div
          v-for="product in recommendedProducts"
          :key="product.id"
          class="product-item"
          @click="openMall(product.mallUrl)"
        >
          <div class="product-image">
            <el-icon size="32" color="#10B981"><FirstAidKit /></el-icon>
          </div>
          <p class="product-name">{{ product.name }}</p>
          <p class="product-price">¥{{ product.price }}</p>
        </div>
      </div>
    </el-card>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { ChatDotRound, TrendCharts, Document, Sunrise, FirstAidKit, Shop } from '@element-plus/icons-vue'
import { useConstitutionStore } from '@/stores/constitution'
import { constitutionNames, constitutionDescriptions } from '@/mock/constitution'
import { getProductsByConstitution, mockProducts } from '@/mock/products'

const router = useRouter()
const constitutionStore = useConstitutionStore()

const quickActions = [
  {
    icon: ChatDotRound,
    label: 'AI问诊',
    desc: '24小时智能健康问答',
    color: '#3B82F6',
    bgColor: '#DBEAFE',
    onClick: () => router.push('/chat')
  },
  {
    icon: TrendCharts,
    label: '体质测试',
    desc: '科学分析您的体质类型',
    color: '#10B981',
    bgColor: '#ECFDF5',
    onClick: () => router.push('/constitution')
  },
  {
    icon: Document,
    label: '健康档案',
    desc: '查看个人健康记录',
    color: '#8B5CF6',
    bgColor: '#EDE9FE',
    onClick: () => router.push('/profile/health-record')
  },
  {
    icon: Shop,
    label: '健康商城',
    desc: '选购调养保健品',
    color: '#F59E0B',
    bgColor: '#FEF3C7',
    onClick: () => window.open('https://mall.example.com')
  }
]

const healthTip = computed(() => {
  if (constitutionStore.result) {
    return constitutionDescriptions[constitutionStore.result.primaryType].suggestions[0]
  }
  return '保持良好的作息习惯,每天喝足8杯水,适当运动有益身心健康。'
})

const recommendedProducts = computed(() => {
  if (constitutionStore.result) {
    return getProductsByConstitution(constitutionStore.result.primaryType)
  }
  return mockProducts.slice(0, 4)
})

const openMall = (url?: string) => {
  window.open(url || 'https://mall.example.com')
}
</script>

<style scoped lang="scss">
.home-page {
  display: grid;
  gap: 20px;
}

.constitution-card {
  .constitution-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 16px;
    
    .label {
      color: #6B7280;
    }
  }
  
  .constitution-body {
    display: flex;
    align-items: center;
    gap: 16px;
    
    h2 {
      font-size: 24px;
      color: #1F2937;
      margin-bottom: 4px;
    }
    
    p {
      color: #6B7280;
      font-size: 14px;
    }
  }
  
  .no-constitution {
    text-align: center;
    padding: 32px;
    cursor: pointer;
    
    &:hover {
      background: #F9FAFB;
      border-radius: 8px;
    }
    
    p {
      margin: 12px 0 4px;
      color: #6B7280;
    }
    
    span {
      color: #9CA3AF;
      font-size: 14px;
    }
  }
}

.quick-actions {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 16px;
  
  .action-card {
    cursor: pointer;
    text-align: center;
    padding: 8px;
    
    .action-icon {
      width: 56px;
      height: 56px;
      border-radius: 16px;
      display: flex;
      align-items: center;
      justify-content: center;
      margin: 0 auto 12px;
    }
    
    h4 {
      font-size: 15px;
      color: #1F2937;
      margin-bottom: 4px;
    }
    
    p {
      font-size: 12px;
      color: #9CA3AF;
    }
  }
}

.tip-card {
  background: #FFFBEB;
  
  .tip-content {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    
    h4 {
      color: #92400E;
      margin-bottom: 4px;
    }
    
    p {
      color: #B45309;
      font-size: 14px;
      line-height: 1.5;
    }
  }
}

.products-card {
  .products-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  
  .products-list {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 16px;
    
    .product-item {
      text-align: center;
      padding: 16px;
      border-radius: 12px;
      cursor: pointer;
      
      &:hover {
        background: #F9FAFB;
      }
      
      .product-image {
        width: 64px;
        height: 64px;
        background: #ECFDF5;
        border-radius: 32px;
        display: flex;
        align-items: center;
        justify-content: center;
        margin: 0 auto 8px;
      }
      
      .product-name {
        font-size: 13px;
        color: #1F2937;
        margin-bottom: 4px;
      }
      
      .product-price {
        color: #EF4444;
        font-weight: 600;
      }
    }
  }
}
</style>

验收标准

  • 首页 UI 正常显示
  • 体质卡片显示正确
  • 快捷入口点击跳转正常
  • 健康提示显示正常
  • 推荐产品显示正常

预计耗时

30-35 分钟


下一步

完成后进入 03-Web原型开发/05-体质辨识页面.md