1
This commit is contained in:
+44
-29
@@ -1,8 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Button as VanButton, showConfirmDialog, showToast } from 'vant'
|
||||
import AppTabBar from '../components/AppTabBar.vue'
|
||||
import { usePageRequest } from '../composables/usePageRequest'
|
||||
import { ageFromBirthday } from '../services/formValidation'
|
||||
import { clearTokens } from '../services/auth'
|
||||
import { healthApi, type Dashboard, type HealthAssessmentOutput } from '../services/health'
|
||||
|
||||
@@ -10,34 +12,42 @@ const router = useRouter()
|
||||
const loading = ref(true)
|
||||
const dashboard = ref<Dashboard>()
|
||||
const assessment = ref<HealthAssessmentOutput>()
|
||||
const loadError = ref('')
|
||||
const pageRequest = usePageRequest()
|
||||
const logoutBusy = ref(false)
|
||||
let active = true
|
||||
|
||||
async function loadData() {
|
||||
const requestId = pageRequest.begin()
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
try {
|
||||
const [dashRes, assessRes] = await Promise.all([
|
||||
healthApi.dashboard(),
|
||||
healthApi.getAssessment().catch(() => undefined)
|
||||
healthApi.getAssessment()
|
||||
])
|
||||
if (!pageRequest.isCurrent(requestId)) return
|
||||
if (!dashRes || !assessRes) throw new Error('健康档案暂时无法读取,请重试')
|
||||
dashboard.value = dashRes
|
||||
assessment.value = assessRes
|
||||
} catch (err) {
|
||||
showToast(err instanceof Error ? err.message : '加载失败')
|
||||
if (pageRequest.isCurrent(requestId)) loadError.value = err instanceof Error ? err.message : '加载失败,请重试'
|
||||
} finally {
|
||||
loading.value = false
|
||||
if (pageRequest.isCurrent(requestId)) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const bmiDisplay = computed(() => {
|
||||
if (dashboard.value?.bmi) {
|
||||
if (typeof dashboard.value?.bmi === 'number' && Number.isFinite(dashboard.value.bmi) && dashboard.value.bmi > 0) {
|
||||
return {
|
||||
val: dashboard.value.bmi,
|
||||
status: dashboard.value.bmiStatus || '正常'
|
||||
status: dashboard.value.bmiStatus || '未提供分类'
|
||||
}
|
||||
}
|
||||
if (assessment.value?.bmi) {
|
||||
if (typeof assessment.value?.bmi === 'number' && Number.isFinite(assessment.value.bmi) && assessment.value.bmi > 0) {
|
||||
return {
|
||||
val: assessment.value.bmi,
|
||||
status: assessment.value.bmiStatus || '正常'
|
||||
status: assessment.value.bmiStatus || '未提供分类'
|
||||
}
|
||||
}
|
||||
return null
|
||||
@@ -45,12 +55,7 @@ const bmiDisplay = computed(() => {
|
||||
|
||||
const ageDisplay = computed(() => {
|
||||
const raw = dashboard.value?.profile?.birthday || assessment.value?.basicInfo.birthday
|
||||
if (!raw) return undefined
|
||||
const birthday = new Date(raw)
|
||||
const today = new Date()
|
||||
let age = today.getFullYear() - birthday.getFullYear()
|
||||
if (today < new Date(today.getFullYear(), birthday.getMonth(), birthday.getDate())) age--
|
||||
return age
|
||||
return ageFromBirthday(raw)
|
||||
})
|
||||
|
||||
// 健康评估只展示生活习惯画像;体质由中医四诊独立判定。
|
||||
@@ -64,7 +69,7 @@ const profileTags = computed(() => {
|
||||
list.push({ label: '夜间偏热感', desc: '自述夜间较容易感到闷热', type: 'blue' })
|
||||
} else if (ans.temperaturePreference === 'cold_fear') {
|
||||
list.push({ label: '夜间偏冷感', desc: '自述睡眠时较容易感到寒冷', type: 'gold' })
|
||||
} else {
|
||||
} else if (ans.temperaturePreference === 'normal') {
|
||||
list.push({ label: '冷热感受适中', desc: '自述夜间温度感受较平稳', type: 'green' })
|
||||
}
|
||||
|
||||
@@ -86,19 +91,23 @@ const profileTags = computed(() => {
|
||||
})
|
||||
|
||||
function handleLogout() {
|
||||
if (!active || logoutBusy.value) return
|
||||
logoutBusy.value = true
|
||||
showConfirmDialog({
|
||||
title: '确认退出',
|
||||
message: '确定要退出当前账号登录吗?'
|
||||
}).then(() => {
|
||||
if (!active) return
|
||||
clearTokens()
|
||||
showToast('已安全退出')
|
||||
router.replace('/auth')
|
||||
}).catch(() => {})
|
||||
}).catch(() => {}).finally(() => { if (active) logoutBusy.value = false })
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
onUnmounted(() => { active = false })
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -110,7 +119,7 @@ onMounted(() => {
|
||||
<span>{{ (dashboard?.user.displayName || '客').slice(0, 1) }}</span>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<h2>{{ dashboard?.user.displayName || '尊贵会员' }}</h2>
|
||||
<h2>{{ dashboard?.user.displayName || '我的健康档案' }}</h2>
|
||||
<p class="phone-text">{{ dashboard?.user.phone || '健康管理账号' }}</p>
|
||||
<span class="user-badge">常青树 · 智能健康睡眠会员</span>
|
||||
</div>
|
||||
@@ -118,17 +127,22 @@ onMounted(() => {
|
||||
</header>
|
||||
|
||||
<main class="profile-content">
|
||||
<section v-if="loading" class="profile-card page-load-state" role="status"><span class="page-loading-mark" aria-hidden="true"></span><p>正在读取您的健康档案…</p></section>
|
||||
<section v-else-if="loadError" class="profile-card page-error-state" role="alert">
|
||||
<h3>暂时无法读取健康档案</h3><p>{{ loadError }}</p><button class="retry-button" type="button" @click="loadData">重新加载</button>
|
||||
</section>
|
||||
<template v-else-if="dashboard">
|
||||
<!-- 身体指标基线档案 -->
|
||||
<section class="profile-card">
|
||||
<div class="card-head">
|
||||
<h3>身体基线档案</h3>
|
||||
<button class="edit-btn" @click="router.replace('/assessment')">编辑指标</button>
|
||||
<button class="edit-btn" type="button" @click="router.replace('/assessment')">编辑指标</button>
|
||||
</div>
|
||||
|
||||
<div class="metrics-grid">
|
||||
<div class="metric-item">
|
||||
<span class="m-label">性别</span>
|
||||
<strong class="m-val">{{ dashboard?.profile?.gender || assessment?.basicInfo.gender || '已保密' }}</strong>
|
||||
<strong class="m-val">{{ dashboard?.profile?.gender || assessment?.basicInfo.gender || '未填写' }}</strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<span class="m-label">身高</span>
|
||||
@@ -142,7 +156,7 @@ onMounted(() => {
|
||||
<span class="m-label">体重</span>
|
||||
<strong class="m-val">{{ dashboard?.profile?.weightKg || assessment?.basicInfo.weightKg || '--' }} <small>kg</small></strong>
|
||||
</div>
|
||||
<div class="metric-item">
|
||||
<div class="metric-item bmi-metric">
|
||||
<span class="m-label">BMI 指数</span>
|
||||
<strong class="m-val" :class="{ highlight: bmiDisplay }">
|
||||
{{ bmiDisplay?.val || '--' }}
|
||||
@@ -155,7 +169,7 @@ onMounted(() => {
|
||||
<!-- 专属评估画像标签 -->
|
||||
<section class="profile-card">
|
||||
<div class="card-head">
|
||||
<h3>健康评估画像与专属策略</h3>
|
||||
<h3>生活习惯画像</h3>
|
||||
<span class="status-indicator" :class="{ done: assessment?.hasCompletedAssessment }">
|
||||
{{ assessment?.hasCompletedAssessment ? '已完成评估' : '未评估' }}
|
||||
</span>
|
||||
@@ -208,8 +222,8 @@ onMounted(() => {
|
||||
<!-- 当前健康方案 -->
|
||||
<section class="profile-card" v-if="dashboard?.currentPlan">
|
||||
<div class="card-head">
|
||||
<h3>当前执行方案</h3>
|
||||
<span class="version-tag">版本 v{{ dashboard.currentPlan.version }}.0</span>
|
||||
<h3>日常健康建议</h3>
|
||||
<span class="version-tag">版本 v{{ dashboard.currentPlan.version }}</span>
|
||||
</div>
|
||||
<p class="plan-summary-text">{{ dashboard.currentPlan.summary }}</p>
|
||||
|
||||
@@ -231,7 +245,7 @@ onMounted(() => {
|
||||
|
||||
<!-- 快捷功能入口与操作菜单 -->
|
||||
<section class="action-card-menu">
|
||||
<div class="menu-item" @click="router.replace('/assessment')">
|
||||
<button class="menu-item" type="button" @click="router.replace('/assessment')">
|
||||
<div class="menu-left">
|
||||
<div class="menu-icon icon-assess">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -247,9 +261,9 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<span class="arrow">></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div class="menu-item" @click="router.push('/device-control')">
|
||||
<button class="menu-item" type="button" @click="router.push('/device-control')">
|
||||
<div class="menu-left">
|
||||
<div class="menu-icon icon-device">
|
||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2">
|
||||
@@ -263,16 +277,17 @@ onMounted(() => {
|
||||
</div>
|
||||
<div class="menu-text">
|
||||
<strong>设备详情与参数调节</strong>
|
||||
<small>查看7台设备状态、独立开关与精细参数设置</small>
|
||||
<small>查看设备状态、快捷开关与完整参数</small>
|
||||
</div>
|
||||
</div>
|
||||
<span class="arrow">></span>
|
||||
</div>
|
||||
</button>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<!-- 退出登录按钮 -->
|
||||
<div class="logout-wrap">
|
||||
<button class="logout-btn" @click="handleLogout">
|
||||
<button class="logout-btn" type="button" :disabled="logoutBusy" @click="handleLogout">
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user