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.
 
 
 
 
 
 

12 KiB

07-个人中心页面(原型)

目标

实现 Web 端个人中心和健康档案管理页面原型。


前置要求

  • 路由配置完成
  • 认证状态 Store 已创建
  • 体质状态 Store 已创建

实施步骤

步骤 1:个人中心页面

创建 src/views/profile/ProfileView.vue

<template>
  <div class="profile-page">
    <!-- 用户信息卡片 -->
    <el-card class="user-card">
      <div class="user-info">
        <el-avatar :size="80">
          {{ authStore.user?.nickname?.charAt(0) || 'U' }}
        </el-avatar>
        <div class="user-text">
          <h2>{{ authStore.user?.nickname || '用户' }}</h2>
          <p>{{ authStore.user?.phone }}</p>
          <el-tag v-if="constitutionStore.result" type="success">
            {{ constitutionNames[constitutionStore.result.primaryType] }}
          </el-tag>
        </div>
        <el-button :icon="Edit" circle @click="handleEdit" />
      </div>
    </el-card>

    <!-- 健康管理 -->
    <el-card class="menu-card">
      <template #header>健康管理</template>
      <div class="menu-list">
        <div class="menu-item" @click="router.push('/profile/health-record')">
          <div class="menu-icon" style="background: #ECFDF5">
            <el-icon size="20" color="#10B981"><Document /></el-icon>
          </div>
          <div class="menu-text">
            <h4>健康档案</h4>
            <p>查看和管理您的健康信息</p>
          </div>
          <el-icon><ArrowRight /></el-icon>
        </div>
        <el-divider />
        <div class="menu-item" @click="router.push('/constitution/result')">
          <div class="menu-icon" style="background: #EDE9FE">
            <el-icon size="20" color="#8B5CF6"><TrendCharts /></el-icon>
          </div>
          <div class="menu-text">
            <h4>体质报告</h4>
            <p>{{ constitutionStore.result ? `当前体质:${constitutionNames[constitutionStore.result.primaryType]}` : '暂无测评记录' }}</p>
          </div>
          <el-icon><ArrowRight /></el-icon>
        </div>
        <el-divider />
        <div class="menu-item" @click="router.push('/chat')">
          <div class="menu-icon" style="background: #DBEAFE">
            <el-icon size="20" color="#3B82F6"><ChatDotRound /></el-icon>
          </div>
          <div class="menu-text">
            <h4>对话历史</h4>
            <p>查看AI咨询记录</p>
          </div>
          <el-icon><ArrowRight /></el-icon>
        </div>
      </div>
    </el-card>

    <!-- 其他设置 -->
    <el-card class="menu-card">
      <template #header>其他</template>
      <div class="menu-list">
        <div class="menu-item" @click="openMall">
          <div class="menu-icon" style="background: #FEF3C7">
            <el-icon size="20" color="#F59E0B"><Shop /></el-icon>
          </div>
          <div class="menu-text">
            <h4>健康商城</h4>
            <p>选购适合您的保健品</p>
          </div>
          <el-icon><ArrowRight /></el-icon>
        </div>
        <el-divider />
        <div class="menu-item" @click="showAbout">
          <div class="menu-icon" style="background: #F3F4F6">
            <el-icon size="20" color="#6B7280"><InfoFilled /></el-icon>
          </div>
          <div class="menu-text">
            <h4>关于我们</h4>
            <p>了解健康AI助手</p>
          </div>
          <el-icon><ArrowRight /></el-icon>
        </div>
      </div>
    </el-card>

    <!-- 退出登录 -->
    <el-button type="danger" plain class="logout-btn" @click="handleLogout">
      退出登录
    </el-button>

    <p class="version">版本 1.0.0(原型版)</p>
  </div>
</template>

<script setup lang="ts">
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Edit, Document, TrendCharts, ChatDotRound, Shop, InfoFilled, ArrowRight } from '@element-plus/icons-vue'
import { useAuthStore } from '@/stores/auth'
import { useConstitutionStore } from '@/stores/constitution'
import { constitutionNames } from '@/mock/constitution'

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

const handleEdit = () => {
  ElMessage.info('编辑功能将在后续版本中提供')
}

const openMall = () => {
  window.open('https://mall.example.com')
}

const showAbout = () => {
  ElMessageBox.alert(
    '健康AI助手 v1.0.0<br><br>结合中医体质辨识理论,为您提供个性化健康建议。',
    '关于我们',
    { dangerouslyUseHTMLString: true }
  )
}

const handleLogout = () => {
  ElMessageBox.confirm('确定要退出登录吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    authStore.logout()
    router.push('/login')
  })
}
</script>

<style scoped lang="scss">
.profile-page {
  max-width: 600px;
  margin: 0 auto;
}

.user-card {
  margin-bottom: 20px;
  
  .user-info {
    display: flex;
    align-items: center;
    gap: 20px;
    
    .el-avatar {
      background: #10B981;
      font-size: 28px;
    }
    
    .user-text {
      flex: 1;
      
      h2 {
        font-size: 20px;
        margin-bottom: 4px;
      }
      
      p {
        color: #6B7280;
        margin-bottom: 8px;
      }
    }
  }
}

.menu-card {
  margin-bottom: 20px;
  
  .menu-list {
    .menu-item {
      display: flex;
      align-items: center;
      gap: 16px;
      padding: 12px 0;
      cursor: pointer;
      
      &:hover {
        background: #F9FAFB;
        margin: 0 -20px;
        padding: 12px 20px;
      }
      
      .menu-icon {
        width: 44px;
        height: 44px;
        border-radius: 12px;
        display: flex;
        align-items: center;
        justify-content: center;
      }
      
      .menu-text {
        flex: 1;
        
        h4 {
          font-size: 15px;
          margin-bottom: 2px;
        }
        
        p {
          font-size: 13px;
          color: #9CA3AF;
        }
      }
    }
  }
}

.logout-btn {
  width: 100%;
  margin-bottom: 16px;
}

.version {
  text-align: center;
  font-size: 12px;
  color: #9CA3AF;
}
</style>

步骤 2:健康档案页面

创建 src/views/profile/HealthRecordView.vue

<template>
  <div class="health-record-page">
    <el-page-header @back="router.back()">
      <template #content>健康档案</template>
    </el-page-header>

    <!-- 基础信息 -->
    <el-card class="info-card">
      <template #header>
        <div class="card-header">
          <el-icon><User /></el-icon>
          <span>基础信息</span>
        </div>
      </template>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="姓名">{{ mockProfile.name }}</el-descriptions-item>
        <el-descriptions-item label="性别">{{ mockProfile.gender }}</el-descriptions-item>
        <el-descriptions-item label="年龄">{{ mockProfile.age }}</el-descriptions-item>
        <el-descriptions-item label="身高">{{ mockProfile.height }}cm</el-descriptions-item>
        <el-descriptions-item label="体重">{{ mockProfile.weight }}kg</el-descriptions-item>
        <el-descriptions-item label="BMI">{{ bmi }}</el-descriptions-item>
        <el-descriptions-item label="血型">{{ mockProfile.bloodType }}</el-descriptions-item>
      </el-descriptions>
    </el-card>

    <!-- 体质信息 -->
    <el-card class="info-card">
      <template #header>
        <div class="card-header">
          <el-icon><TrendCharts /></el-icon>
          <span>体质信息</span>
        </div>
      </template>
      <div v-if="constitutionStore.result" class="constitution-info">
        <el-tag type="success" size="large">
          {{ constitutionNames[constitutionStore.result.primaryType] }}
        </el-tag>
        <p class="constitution-desc">
          {{ constitutionDescriptions[constitutionStore.result.primaryType].description }}
        </p>
        <p class="assessed-time">
          测评时间:{{ formatTime(constitutionStore.result.assessedAt) }}
        </p>
      </div>
      <el-empty v-else description="暂无体质测评记录">
        <el-button type="primary" @click="router.push('/constitution')">
          开始测评
        </el-button>
      </el-empty>
    </el-card>

    <!-- 既往病史 -->
    <el-card class="info-card">
      <template #header>
        <div class="card-header">
          <el-icon><FirstAidKit /></el-icon>
          <span>既往病史</span>
        </div>
      </template>
      <div class="tag-list">
        <el-tag v-for="disease in mockProfile.medicalHistory" :key="disease">
          {{ disease }}
        </el-tag>
      </div>
    </el-card>

    <!-- 过敏信息 -->
    <el-card class="info-card">
      <template #header>
        <div class="card-header">
          <el-icon color="#EF4444"><Warning /></el-icon>
          <span>过敏信息</span>
        </div>
      </template>
      <div class="tag-list">
        <el-tag v-for="allergy in mockProfile.allergyRecords" :key="allergy" type="danger">
          {{ allergy }}
        </el-tag>
      </div>
    </el-card>

    <!-- 生活习惯 -->
    <el-card class="info-card">
      <template #header>
        <div class="card-header">
          <el-icon><Clock /></el-icon>
          <span>生活习惯</span>
        </div>
      </template>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="入睡时间">{{ mockProfile.sleepTime }}</el-descriptions-item>
        <el-descriptions-item label="起床时间">{{ mockProfile.wakeTime }}</el-descriptions-item>
        <el-descriptions-item label="运动频率">{{ mockProfile.exerciseFrequency }}</el-descriptions-item>
      </el-descriptions>
    </el-card>

    <el-alert type="info" :closable="false" class="note-alert">
      以上为模拟数据,后续将支持编辑和同步
    </el-alert>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { User, TrendCharts, FirstAidKit, Warning, Clock } from '@element-plus/icons-vue'
import dayjs from 'dayjs'
import { useAuthStore } from '@/stores/auth'
import { useConstitutionStore } from '@/stores/constitution'
import { constitutionNames, constitutionDescriptions } from '@/mock/constitution'

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

// 模拟数据
const mockProfile = {
  name: authStore.user?.nickname || '用户',
  gender: '男',
  age: 45,
  height: 170,
  weight: 68,
  bloodType: 'A型',
  medicalHistory: ['高血压', '轻度脂肪肝'],
  allergyRecords: ['青霉素'],
  sleepTime: '23:00',
  wakeTime: '07:00',
  exerciseFrequency: '每周2-3次'
}

const bmi = computed(() => {
  const h = mockProfile.height / 100
  return (mockProfile.weight / (h * h)).toFixed(1)
})

const formatTime = (time: string) => dayjs(time).format('YYYY-MM-DD HH:mm')
</script>

<style scoped lang="scss">
.health-record-page {
  max-width: 800px;
  margin: 0 auto;
}

.el-page-header {
  margin-bottom: 20px;
}

.info-card {
  margin-bottom: 20px;
  
  .card-header {
    display: flex;
    align-items: center;
    gap: 8px;
  }
  
  .constitution-info {
    text-align: center;
    padding: 20px;
    
    .constitution-desc {
      margin: 16px 0 8px;
      color: #4B5563;
    }
    
    .assessed-time {
      font-size: 12px;
      color: #9CA3AF;
    }
  }
  
  .tag-list {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
  }
}

.note-alert {
  margin-top: 20px;
}
</style>

验收标准

  • 个人中心页面正常显示
  • 菜单跳转正常
  • 退出登录正常
  • 健康档案页面正常显示
  • 体质信息正确展示

预计耗时

30-35 分钟


完成

至此,Web 原型开发所有页面文档创建完成!

可以开始第四阶段:后端开发。