336 lines
9.2 KiB
Vue
336 lines
9.2 KiB
Vue
<template>
|
||
<Layout>
|
||
<template #main>
|
||
<template v-if="store.isLoggedIn">
|
||
<!-- 頂部:我的健康力護照卡片 (Taipei Masters Hub) -->
|
||
<v-card class="border-primary border-xl rounded-xl" variant="flat">
|
||
<v-row class="mt-4" no-gutters>
|
||
<v-col cols="8">
|
||
<v-card-title> {{ store.userProfile.name }} 先生 </v-card-title>
|
||
<v-card-subtitle> 資料更新於 {{ todayTimeString }} </v-card-subtitle>
|
||
</v-col>
|
||
|
||
<v-col class="d-flex justify-center" cols="4">
|
||
<!-- 健康力分數大圓盤 -->
|
||
<div class="score-disk d-flex flex-column justify-center align-center">
|
||
<span class="score-val">{{ displayedHealthScore }}</span>
|
||
<span class="score-unit">分</span>
|
||
</div>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- 四項核心能力數值簡覽 -->
|
||
<v-row class="mt-4" no-gutters>
|
||
<v-col class="text-center border pa-2" cols="3">
|
||
<div class="">肌力</div>
|
||
|
||
<div class="font-weight-black">
|
||
{{ store.radarData.strength }}
|
||
</div>
|
||
</v-col>
|
||
|
||
<v-col class="text-center border pa-2" cols="3">
|
||
<div class="">平衡</div>
|
||
|
||
<div class="font-weight-black">
|
||
{{ store.radarData.balance }}
|
||
</div>
|
||
</v-col>
|
||
|
||
<v-col class="text-center border pa-2" cols="3">
|
||
<div class="">敏捷</div>
|
||
|
||
<div class="font-weight-black">
|
||
{{ store.radarData.agility }}
|
||
</div>
|
||
</v-col>
|
||
|
||
<v-col class="text-center border pa-2" cols="3">
|
||
<div class="">心肺</div>
|
||
|
||
<div class="font-weight-black">
|
||
{{ store.radarData.cardio }}
|
||
</div>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- 課程建議入口 -->
|
||
|
||
<v-list-item append-icon="mdi-chevron-right" class="bg-primary py-4" to="/course">
|
||
<v-list-item-title class="font-weight-bold text-accent-light">
|
||
<v-icon icon="mdi-notebook-check-outline" size="16" />
|
||
課程建議
|
||
</v-list-item-title>
|
||
|
||
<v-list-item-subtitle class="text-white mt-2">
|
||
依你的體適能組別,查看一週的每日課表
|
||
</v-list-item-subtitle>
|
||
</v-list-item>
|
||
</v-card>
|
||
|
||
<!-- 中間:功能指標與五維雷達圖 -->
|
||
<v-card class="border-xl border-primary rounded-xl mt-4" variant="flat">
|
||
<v-card-title> 個人五維健康力指標 </v-card-title>
|
||
|
||
<v-card-subtitle>
|
||
綜合 11 項現場普測數值計算得出,<br />藍色為同齡正常參考值區間。
|
||
</v-card-subtitle>
|
||
|
||
<HealthRadarChart :radar-data="store.radarData" />
|
||
</v-card>
|
||
|
||
<ModuleRouterCard
|
||
description="了解公益普測計畫、研究依據與平台流程。"
|
||
icon="mdi-information-outline"
|
||
icon-color="primary"
|
||
title="簡介"
|
||
to="/introduction"
|
||
/>
|
||
|
||
<ModuleRouterCard
|
||
description="完成線上自評,取得初步風險分級。"
|
||
icon="mdi-clipboard-text-search"
|
||
icon-color="accent"
|
||
title="自評"
|
||
to="/self-assessment"
|
||
/>
|
||
|
||
<ModuleRouterCard
|
||
description="完成數值紀錄,建立個人功能基線。"
|
||
icon="mdi-run-fast"
|
||
icon-color="accent"
|
||
title="量測"
|
||
to="/checkin"
|
||
/>
|
||
|
||
<ModuleRouterCard
|
||
description="預訂首次量測或回測,追蹤健康狀態。"
|
||
icon="mdi-calendar-clock"
|
||
icon-color="accent"
|
||
title="預約"
|
||
to="/appointment"
|
||
/>
|
||
</template>
|
||
|
||
<template v-else>
|
||
<div class="text-center mb-6">
|
||
<BrandAvatar class="elevation-2" :icon-size="42" :size="72" />
|
||
</div>
|
||
|
||
<v-card class="rounded-xl elevation-2 mb-4 py-4" color="surface">
|
||
<v-card-text>
|
||
<div class="d-flex align-center mb-4">
|
||
<v-avatar class="mr-4 text-white" color="accent" size="36">
|
||
<v-icon icon="mdi-clipboard-text-outline" />
|
||
</v-avatar>
|
||
|
||
<div class="text-left">
|
||
<div class="text-title-medium font-weight-bold text-primary">
|
||
無須登入,先做線上健康自評
|
||
</div>
|
||
|
||
<div class="text-title-small text-grey mt-2">
|
||
約 3 分鐘完成,取得初步健康風險分級。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<v-btn
|
||
block
|
||
class="rounded-lg font-weight-bold elevation-1 mb-3"
|
||
color="secondary"
|
||
prepend-icon="mdi-clipboard-text-search"
|
||
to="/self-assessment"
|
||
variant="flat"
|
||
>
|
||
開始自評
|
||
</v-btn>
|
||
|
||
<v-btn
|
||
block
|
||
class="rounded-lg font-weight-bold"
|
||
color="primary"
|
||
prepend-icon="mdi-login"
|
||
to="/login"
|
||
variant="outlined"
|
||
>
|
||
登入平台
|
||
</v-btn>
|
||
|
||
<div class="d-flex align-center mt-4">
|
||
<v-icon class="mr-3" color="primary" icon="mdi-lock-outline" size="28" />
|
||
|
||
<div>
|
||
<div class="font-weight-bold text-grey-darken-4">登入後可使用完整功能</div>
|
||
|
||
<div class="text-title-small text-grey">
|
||
普測預約、現場報到、量測紀錄與運動處方。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</v-card-text>
|
||
</v-card>
|
||
|
||
<ModuleRouterCard
|
||
description="了解公益普測計畫、研究依據與平台流程。"
|
||
icon="mdi-information-outline"
|
||
icon-color="primary"
|
||
title="簡介"
|
||
to="/introduction"
|
||
/>
|
||
</template>
|
||
</template>
|
||
</Layout>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||
import BrandAvatar from '@/components/BrandAvatar.vue'
|
||
import HealthRadarChart from '@/components/HealthRadarChart.vue'
|
||
import Layout from '@/components/layout/DefaultLayout.vue'
|
||
import ModuleRouterCard from '@/components/ModuleRouterCard.vue'
|
||
import { useAppStore } from '@/stores/app'
|
||
|
||
const store = useAppStore()
|
||
const displayedHealthScore = ref(1)
|
||
let healthScoreTimer: number | undefined
|
||
|
||
function animateHealthScore (targetScore: number) {
|
||
if (healthScoreTimer !== undefined) {
|
||
window.clearInterval(healthScoreTimer)
|
||
}
|
||
|
||
if (targetScore <= 1) {
|
||
displayedHealthScore.value = targetScore
|
||
return
|
||
}
|
||
|
||
displayedHealthScore.value = 1
|
||
|
||
const duration = 900
|
||
const stepDuration = Math.max(12, Math.floor(duration / targetScore))
|
||
|
||
healthScoreTimer = window.setInterval(() => {
|
||
if (displayedHealthScore.value >= targetScore) {
|
||
window.clearInterval(healthScoreTimer)
|
||
healthScoreTimer = undefined
|
||
return
|
||
}
|
||
|
||
displayedHealthScore.value += 1
|
||
}, stepDuration)
|
||
}
|
||
|
||
onMounted(() => {
|
||
animateHealthScore(store.healthScore)
|
||
})
|
||
|
||
onBeforeUnmount(() => {
|
||
if (healthScoreTimer !== undefined) {
|
||
window.clearInterval(healthScoreTimer)
|
||
}
|
||
})
|
||
|
||
watch(
|
||
() => store.healthScore,
|
||
score => {
|
||
animateHealthScore(score)
|
||
},
|
||
)
|
||
|
||
const todayTimeString = computed(() => {
|
||
const now = new Date()
|
||
const date = [
|
||
String(now.getMonth() + 1).padStart(2, '0'),
|
||
String(now.getDate()).padStart(2, '0'),
|
||
].join('/')
|
||
const time = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
|
||
|
||
return `${date} ${time}`
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.passport-card {
|
||
background: linear-gradient(135deg, #1d7063 0%, #145248 100%);
|
||
border: 1px solid rgba(71, 194, 166, 0.3);
|
||
box-shadow: 0 8px 16px rgba(20, 82, 72, 0.25) !important;
|
||
}
|
||
|
||
.bg-circle-1 {
|
||
position: absolute;
|
||
width: 150px;
|
||
height: 150px;
|
||
border-radius: 50%;
|
||
background: rgba(71, 194, 166, 0.15);
|
||
top: -50px;
|
||
right: -50px;
|
||
z-index: 0;
|
||
}
|
||
|
||
.bg-circle-2 {
|
||
position: absolute;
|
||
width: 100px;
|
||
height: 100px;
|
||
border-radius: 50%;
|
||
background: rgba(230, 105, 38, 0.1);
|
||
bottom: -30px;
|
||
left: -20px;
|
||
z-index: 0;
|
||
}
|
||
|
||
.z-index-1 {
|
||
z-index: 1;
|
||
}
|
||
|
||
.bg-white-20 {
|
||
background-color: rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.bg-white-10 {
|
||
background-color: rgba(255, 255, 255, 0.08);
|
||
border: 1px solid rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
.text-white-70 {
|
||
color: rgba(255, 255, 255, 0.7) !important;
|
||
}
|
||
|
||
.text-accent-light {
|
||
color: #8cebd2 !important;
|
||
}
|
||
|
||
.score-disk {
|
||
background: radial-gradient(circle, #e66926 0%, #cc561b 100%);
|
||
border: 3px solid #ffffff;
|
||
width: 72px;
|
||
height: 72px;
|
||
border-radius: 50%;
|
||
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.score-val {
|
||
font-size: 1.75rem;
|
||
font-weight: 900;
|
||
line-height: 1;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.score-unit {
|
||
font-size: 0.65rem;
|
||
font-weight: bold;
|
||
color: rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.transition-card {
|
||
transition: all 0.25s ease;
|
||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||
}
|
||
|
||
.transition-card:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.06);
|
||
}
|
||
</style>
|