style: index page
This commit is contained in:
@@ -3,341 +3,105 @@
|
||||
<template #main>
|
||||
<template v-if="store.isLoggedIn">
|
||||
<!-- 頂部:我的健康力護照卡片 (Taipei Masters Hub) -->
|
||||
<v-card
|
||||
class="passport-card rounded-xl text-white pa-5 elevation-8 mb-6 position-relative overflow-hidden"
|
||||
>
|
||||
<!-- 背景裝飾圓圈 -->
|
||||
<div class="bg-circle-1" />
|
||||
<div class="bg-circle-2" />
|
||||
<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>
|
||||
|
||||
<div class="d-flex justify-space-between align-center mb-6 z-index-1 position-relative">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 border-white" color="accent" size="44">
|
||||
<span class="text-white font-weight-black text-subtitle-1">{{
|
||||
store.userProfile.name.substring(1)
|
||||
}}</span>
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<h2 class="text-subtitle-1 font-weight-black mb-0">
|
||||
{{ store.userProfile.name }} 先生
|
||||
</h2>
|
||||
|
||||
<span class="text-caption text-white-70">更新於今日 {{ todayTimeString }}</span>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<!-- 健康力分數大圓盤 -->
|
||||
<div class="score-disk d-flex flex-column justify-center align-center">
|
||||
<span class="score-val">{{ store.healthScore }}</span>
|
||||
<span class="score-unit">分</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 功能儲備與運動處方簡介 -->
|
||||
<div class="z-index-1 position-relative mb-4">
|
||||
<div class="d-inline-flex align-center bg-white-20 px-3 py-1 rounded-pill mb-3">
|
||||
<v-icon class="mr-1 text-accent-light" icon="mdi-heart-pulse" size="16" />
|
||||
<span class="text-caption font-weight-bold">功能儲備:{{ functionalReserve }}</span>
|
||||
</div>
|
||||
|
||||
<div class="text-body-2 font-weight-medium bg-white-10 rounded-lg pa-3">
|
||||
<div class="font-weight-black mb-1 text-accent-light d-flex align-center">
|
||||
<v-icon class="mr-1" icon="mdi-notebook-check-outline" size="16" />
|
||||
本週處方建議:
|
||||
</div>
|
||||
{{ currentWeeklyPrescription }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider class="bg-white-20 my-3 z-index-1 position-relative" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 四項核心能力數值簡覽 -->
|
||||
<v-row class="z-index-1 position-relative text-center" dense>
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">肌力</div>
|
||||
<v-row class="mt-4" no-gutters>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">肌力</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.strength }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">平衡</div>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">平衡</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.balance }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">敏捷</div>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">敏捷</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.agility }}
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="3">
|
||||
<div class="text-caption text-white-70">心肺</div>
|
||||
<v-col class="text-center border pa-2" cols="3">
|
||||
<div class="">心肺</div>
|
||||
|
||||
<div class="text-subtitle-2 font-weight-black">
|
||||
<div class="font-weight-black">
|
||||
{{ store.radarData.cardio }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- 功能儲備與運動處方簡介 -->
|
||||
|
||||
<v-card-text class="bg-primary">
|
||||
<div class="font-weight-bold text-accent-light">
|
||||
<v-icon icon="mdi-notebook-check-outline" size="16" />
|
||||
運動處方建議:
|
||||
</div>
|
||||
|
||||
<p class="text-white mt-2">
|
||||
{{ currentWeeklyPrescription }}
|
||||
</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 中間:功能指標與五維雷達圖 -->
|
||||
<v-card class="rounded-xl elevation-3 border-accent-light mb-6 pa-4" color="surface">
|
||||
<h3 class="text-subtitle-1 font-weight-black text-primary mb-2 d-flex align-center">
|
||||
<v-icon class="mr-2" color="secondary" icon="mdi-radar" />
|
||||
個人五維健康力指標
|
||||
</h3>
|
||||
<v-card class="border-xl border-primary rounded-xl mt-4" variant="flat">
|
||||
<v-card-title> 個人五維健康力指標 </v-card-title>
|
||||
|
||||
<p class="text-caption text-grey mb-4">
|
||||
綜合 11 項現場普測數值計算得出,藍色為同齡正常參考值區間。
|
||||
</p>
|
||||
<v-card-subtitle>
|
||||
綜合 11 項現場普測數值計算得出,<br />藍色為同齡正常參考值區間。
|
||||
</v-card-subtitle>
|
||||
|
||||
<!-- 純 SVG 雷達圖,不依賴任何外部圖表庫,保證完美渲染 -->
|
||||
<div class="d-flex justify-center my-2 position-relative">
|
||||
<svg class="radar-svg" height="260" viewBox="0 0 120 120" width="260">
|
||||
<!-- 5 個同心五邊形網格 (背景線) -->
|
||||
<polygon
|
||||
v-for="grid in [20, 40, 60, 80, 100]"
|
||||
:key="grid"
|
||||
fill="none"
|
||||
:points="getPolygonPoints(grid)"
|
||||
stroke="#E0E0E0"
|
||||
stroke-width="0.3"
|
||||
/>
|
||||
|
||||
<!-- 五個維度軸線 -->
|
||||
<line
|
||||
v-for="i in 5"
|
||||
:key="`axis-${i}`"
|
||||
stroke="#E0E0E0"
|
||||
stroke-width="0.3"
|
||||
x1="60"
|
||||
:x2="getAxisEndPoint(i - 1).x"
|
||||
y1="60"
|
||||
:y2="getAxisEndPoint(i - 1).y"
|
||||
/>
|
||||
|
||||
<!-- 同齡參考區間 (同齡常模基準:設為 65 分) -->
|
||||
<polygon
|
||||
fill="rgba(33, 150, 243, 0.12)"
|
||||
:points="getPolygonPoints(65)"
|
||||
stroke="#2196F3"
|
||||
stroke-dasharray="1 1"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
|
||||
<!-- 使用者真實數據填充面 -->
|
||||
<polygon
|
||||
fill="rgba(71, 194, 166, 0.35)"
|
||||
:points="userDataPoints"
|
||||
stroke="#1D7063"
|
||||
stroke-width="1.2"
|
||||
/>
|
||||
|
||||
<!-- 使用者數據頂點標記圓圈 -->
|
||||
<circle
|
||||
v-for="(pt, idx) in userDataCoordArray"
|
||||
:key="`dot-${idx}`"
|
||||
:cx="pt.x"
|
||||
:cy="pt.y"
|
||||
fill="#E66926"
|
||||
r="1.5"
|
||||
stroke="#FFFFFF"
|
||||
stroke-width="0.5"
|
||||
/>
|
||||
|
||||
<!-- 頂點文字標記 (Cardio, Strength, Balance, Flexibility, Agility) -->
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="middle"
|
||||
x="60"
|
||||
y="8"
|
||||
>
|
||||
心肺 ({{ store.radarData.cardio }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="start"
|
||||
x="110"
|
||||
y="44"
|
||||
>
|
||||
肌力 ({{ store.radarData.strength }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="start"
|
||||
x="94"
|
||||
y="104"
|
||||
>
|
||||
平衡 ({{ store.radarData.balance }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="end"
|
||||
x="26"
|
||||
y="104"
|
||||
>
|
||||
柔軟 ({{ store.radarData.flexibility }})
|
||||
</text>
|
||||
|
||||
<text
|
||||
fill="#1D7063"
|
||||
font-size="4"
|
||||
font-weight="bold"
|
||||
text-anchor="end"
|
||||
x="10"
|
||||
y="44"
|
||||
>
|
||||
敏捷 ({{ store.radarData.agility }})
|
||||
</text>
|
||||
</svg>
|
||||
|
||||
<!-- 圖例標示 -->
|
||||
<div class="radar-legend d-flex flex-column">
|
||||
<div class="d-flex align-center text-caption mb-1">
|
||||
<span class="legend-color bg-primary mr-1" />
|
||||
<span>我的指標</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-center text-caption">
|
||||
<span class="legend-color bg-blue mr-1 border-dashed" />
|
||||
<span>同齡參考</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<HealthRadarChart :radar-data="store.radarData" />
|
||||
</v-card>
|
||||
|
||||
<!-- 下一步行動引導 (根據量測進度動態顯示) -->
|
||||
<h3 class="text-subtitle-2 font-weight-black text-primary mb-3">下一步行動推薦</h3>
|
||||
<ModuleRouterCard
|
||||
description="完成線上自評,取得初步風險分級。"
|
||||
icon="mdi-clipboard-text-search"
|
||||
icon-color="accent"
|
||||
title="自評"
|
||||
to="/self-assessment"
|
||||
/>
|
||||
|
||||
<!-- 情況 1:普測量測正在進行中 -->
|
||||
<v-card
|
||||
v-if="isIncompleteMeasurement"
|
||||
class="rounded-xl elevation-3 mb-4 transition-card text-left"
|
||||
color="surface"
|
||||
<ModuleRouterCard
|
||||
description="完成數值紀錄,建立個人功能基線。"
|
||||
icon="mdi-run-fast"
|
||||
icon-color="accent"
|
||||
title="量測"
|
||||
to="/checkin"
|
||||
>
|
||||
<v-card-text class="pa-4 d-flex align-center justify-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 text-white elevation-1" color="secondary" size="48">
|
||||
<v-icon icon="mdi-run-fast" />
|
||||
</v-avatar>
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-subtitle-2 text-grey-darken-4">
|
||||
繼續現場體能普測
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-grey">
|
||||
目前已完成 {{ completedCount }} / 11 站,繼續完成登錄。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-icon color="primary" icon="mdi-chevron-right" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 情況 2:量測全部完成,提醒可以看運動處方任務 -->
|
||||
<v-card
|
||||
v-else
|
||||
class="rounded-xl elevation-3 mb-4 transition-card text-left"
|
||||
color="surface"
|
||||
@click="prescriptionDialog = true"
|
||||
>
|
||||
<v-card-text class="pa-4 d-flex align-center justify-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 text-white elevation-1" color="secondary" size="48">
|
||||
<v-icon icon="mdi-calendar-multiselect" />
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-subtitle-2 text-grey-darken-4">
|
||||
執行本週運動處方課程
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-grey">
|
||||
依普測結果產生,肌力 2 次、心肺 2 次、平衡 3 次。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-icon color="primary" icon="mdi-chevron-right" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 普測預約快捷卡片 -->
|
||||
<v-card
|
||||
class="rounded-xl elevation-3 mb-6 transition-card text-left"
|
||||
color="surface"
|
||||
<ModuleRouterCard
|
||||
description="預訂首次量測或回測,追蹤健康狀態。"
|
||||
icon="mdi-calendar-clock"
|
||||
icon-color="accent"
|
||||
title="預約"
|
||||
to="/appointment"
|
||||
>
|
||||
<v-card-text class="pa-4 d-flex align-center justify-between">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar class="mr-3 text-white elevation-1" color="primary" size="48">
|
||||
<v-icon icon="mdi-calendar-clock" />
|
||||
</v-avatar>
|
||||
|
||||
<div>
|
||||
<div class="font-weight-bold text-subtitle-2 text-grey-darken-4">
|
||||
預約 30 秒坐站與平衡回測
|
||||
</div>
|
||||
|
||||
<div class="text-caption text-grey">4 週 / 12 週定期回測,動態追蹤體能進步狀態。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-icon color="primary" icon="mdi-chevron-right" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- 最近一次量測結果摘要 -->
|
||||
<v-card class="rounded-xl elevation-3 mb-8 pa-4" color="surface">
|
||||
<div class="d-flex justify-space-between align-center mb-3">
|
||||
<h3 class="text-subtitle-1 font-weight-black text-primary d-flex align-center">
|
||||
<v-icon class="mr-2" color="secondary" icon="mdi-file-eye-outline" />
|
||||
最近量測數值摘要
|
||||
</h3>
|
||||
|
||||
<v-btn
|
||||
class="font-weight-bold px-0"
|
||||
color="primary"
|
||||
size="small"
|
||||
to="/checkin"
|
||||
variant="text"
|
||||
>查看全部</v-btn>
|
||||
</div>
|
||||
|
||||
<v-row dense>
|
||||
<v-col v-for="item in recentItems" :key="item.name" class="mb-2" cols="6">
|
||||
<div class="bg-grey-lighten-4 rounded-lg pa-3 h-100">
|
||||
<div class="text-caption text-grey-darken-1 mb-1">{{ item.name }}</div>
|
||||
|
||||
<div class="text-body-1 font-weight-black text-grey-darken-4">
|
||||
{{ item.value }}
|
||||
</div>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card>
|
||||
/>
|
||||
|
||||
<!-- 運動處方對話框 -->
|
||||
<v-dialog v-model="prescriptionDialog" max-width="500">
|
||||
@@ -417,17 +181,15 @@
|
||||
<template v-else>
|
||||
<div class="text-center mb-6">
|
||||
<v-avatar class="elevation-3 mb-4 border-accent-light" color="primary" size="72">
|
||||
<v-icon color="accent" icon="mdi-heart-pulse" size="42" />
|
||||
<v-icon color="red-accent-1" icon="mdi-heart-pulse" size="42" />
|
||||
</v-avatar>
|
||||
|
||||
<h1 class="text-h5 font-weight-black text-primary mb-2">運動玩轉健康力</h1>
|
||||
|
||||
<p class="text-body-2 text-grey-darken-1 mb-0">
|
||||
不登入也可以先完成線上健康自評,登入後可預約普測與查看量測紀錄。
|
||||
<p class="text-body-medium text-grey-darken-1 mb-0">
|
||||
不登入也可以先完成線上健康自評,<br />登入後可預約普測與查看量測紀錄。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<v-card class="rounded-xl elevation-3 border-accent-light mb-4" color="surface">
|
||||
<v-card class="rounded-xl elevation-2 border-accent-light mb-4" color="surface">
|
||||
<v-card-text class="pa-5">
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-avatar class="mr-3 text-white" color="secondary" size="48">
|
||||
@@ -442,13 +204,13 @@
|
||||
|
||||
<v-btn
|
||||
block
|
||||
class="rounded-lg font-weight-bold mb-3"
|
||||
color="primary"
|
||||
prepend-icon="mdi-arrow-right-circle"
|
||||
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
|
||||
@@ -459,7 +221,7 @@
|
||||
to="/login"
|
||||
variant="outlined"
|
||||
>
|
||||
登入健康平台
|
||||
登入平台
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
@@ -483,116 +245,81 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from '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 prescriptionDialog = ref(false)
|
||||
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()
|
||||
return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
|
||||
})
|
||||
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')}`
|
||||
|
||||
// 功能儲備評估
|
||||
const functionalReserve = computed(() => {
|
||||
const score = store.healthScore
|
||||
if (score >= 80) return '良好 (無衰弱風險)'
|
||||
if (score >= 65) return '正常 (需持續運動維持)'
|
||||
return '待加強 (有衰弱與跌倒風險)'
|
||||
return `${date} ${time}`
|
||||
})
|
||||
|
||||
// 每週處方文字
|
||||
const currentWeeklyPrescription = computed(() => {
|
||||
if (store.healthScore >= 80) {
|
||||
return '本週處方:1次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
return '1次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
} else if (store.healthScore >= 65) {
|
||||
return '本週處方:2次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
return '2次肌力循環 + 1次平衡課 + 每日20分鐘快走'
|
||||
} else {
|
||||
return '本週處方:3次防跌平衡引導 + 每日15分鐘扶椅坐站與慢走'
|
||||
return '3次防跌平衡引導 + 每日15分鐘扶椅坐站與慢走'
|
||||
}
|
||||
})
|
||||
|
||||
// 檢測是否還有未完成的測量站
|
||||
const isIncompleteMeasurement = computed(() => {
|
||||
return store.completedStationsCount < store.totalStationsCount
|
||||
})
|
||||
|
||||
const completedCount = computed(() => store.completedStationsCount)
|
||||
|
||||
// 最近一次量測結果摘要列表
|
||||
const recentItems = computed(() => {
|
||||
const m = store.measurements
|
||||
const items = []
|
||||
|
||||
if (m.BloodPressure.completed) {
|
||||
items.push({
|
||||
name: '血壓量測',
|
||||
value: `${m.BloodPressure.values.systolic}/${m.BloodPressure.values.diastolic} mmHg`,
|
||||
})
|
||||
}
|
||||
if (m.BodyFat.completed) {
|
||||
items.push({ name: '體組成 BMI', value: `${m.BodyFat.values.bmi}` })
|
||||
}
|
||||
if (m.GripStrength.completed) {
|
||||
items.push({ name: '右手最大握力', value: `${m.GripStrength.values.rightHand} kg` })
|
||||
} else {
|
||||
items.push({ name: '左右握力', value: '未測量' })
|
||||
}
|
||||
if (m.OneLegTest.completed) {
|
||||
items.push({
|
||||
name: '單腳站立平衡',
|
||||
value: `${Math.max(m.OneLegTest.values.leftSeconds, m.OneLegTest.values.rightSeconds)} 秒`,
|
||||
})
|
||||
} else {
|
||||
items.push({ name: '開眼單足立', value: '未測量' })
|
||||
}
|
||||
|
||||
return items
|
||||
})
|
||||
|
||||
// --- SVG 雷達圖點計算邏輯 ---
|
||||
const cx = 60
|
||||
const cy = 60
|
||||
const r = 45
|
||||
|
||||
// 計算 5 角多邊形的頂點
|
||||
// 頂點順序: i=0(心肺), i=1(肌力), i=2(平衡), i=3(柔軟), i=4(敏捷)
|
||||
function getAxisEndPoint (i: number, radius = r) {
|
||||
const angle = -Math.PI / 2 + (i * 2 * Math.PI) / 5
|
||||
return {
|
||||
x: cx + radius * Math.cos(angle),
|
||||
y: cy + radius * Math.sin(angle),
|
||||
}
|
||||
}
|
||||
|
||||
// 根據百分比半徑繪製背景網格五邊形
|
||||
function getPolygonPoints (percent: number) {
|
||||
const radius = (percent / 100) * r
|
||||
const pts = []
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const pt = getAxisEndPoint(i, radius)
|
||||
pts.push(`${pt.x},${pt.y}`)
|
||||
}
|
||||
return pts.join(' ')
|
||||
}
|
||||
|
||||
// 使用者五維數據對應的雷達圖頂點座標
|
||||
const userDataCoordArray = computed(() => {
|
||||
const data = store.radarData
|
||||
// 順序與頂點對應:
|
||||
// 0: cardio (心肺), 1: strength (肌力), 2: balance (平衡), 3: flexibility (柔軟), 4: agility (敏捷)
|
||||
const values = [data.cardio, data.strength, data.balance, data.flexibility, data.agility]
|
||||
return values.map((val, i) => {
|
||||
const radius = (Math.max(5, Math.min(100, val)) / 100) * r
|
||||
return getAxisEndPoint(i, radius)
|
||||
})
|
||||
})
|
||||
|
||||
const userDataPoints = computed(() => {
|
||||
return userDataCoordArray.value.map(pt => `${pt.x},${pt.y}`).join(' ')
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -681,31 +408,4 @@ const userDataPoints = computed(() => {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 雷達圖圖例 */
|
||||
.radar-legend {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid rgba(0, 0, 0, 0.05);
|
||||
padding: 6px 8px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.border-dashed {
|
||||
border: 1px dashed #2196f3;
|
||||
background-color: rgba(33, 150, 243, 0.12) !important;
|
||||
}
|
||||
|
||||
.radar-svg text {
|
||||
font-family: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user