feat: 分數改為分組

This commit is contained in:
skytek_xinliang
2026-08-31 10:04:09 +08:00
parent 28cda7be1c
commit 12519f7809
3 changed files with 101 additions and 65 deletions
+54 -55
View File
@@ -15,16 +15,47 @@
</v-col>
<v-col class="d-flex justify-center" cols="4">
<!-- 健康力分數大圓盤 -->
<!-- 體適能分組徽章 -->
<div
class="score-disk d-flex flex-column justify-center align-center"
:class="[
'score-disk',
'd-flex',
'flex-column',
'justify-center',
'align-center',
courseStore.groupLoaded
? `bg-${courseStore.groupMeta.color}`
: 'bg-primary',
]"
>
<span class="score-val">{{ displayedHealthScore }}</span>
<span class="score-unit">分</span>
<v-progress-circular
v-if="courseStore.groupLoading"
color="white"
indeterminate
size="28"
width="3"
/>
<span v-else-if="courseStore.groupLoaded" class="group-label">
{{ courseStore.groupMeta.label }}
</span>
<span v-else class="group-label">無資料</span>
</div>
</v-col>
</v-row>
<v-card-subtitle v-if="courseStore.groupError" class="text-error">
{{ courseStore.groupError }}
<v-btn
class="ml-2"
color="error"
size="small"
variant="text"
@click="loadFitnessGroup"
>
重試
</v-btn>
</v-card-subtitle>
<!-- 四項核心能力數值簡覽 -->
<v-row class="mt-4" no-gutters>
<v-col class="text-center border pa-2" cols="3">
@@ -201,59 +232,31 @@
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { computed, 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";
import { useCourseStore } from "@/stores/course";
const store = useAppStore();
const displayedHealthScore = ref(1);
let healthScoreTimer: number | undefined;
const courseStore = useCourseStore();
function animateHealthScore(targetScore: number) {
if (healthScoreTimer !== undefined) {
window.clearInterval(healthScoreTimer);
}
if (targetScore <= 1) {
displayedHealthScore.value = targetScore;
async function loadFitnessGroup() {
if (!store.isLoggedIn) {
courseStore.resetGroup();
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);
try {
await courseStore.loadGroup();
} catch {
// 錯誤狀態由 course store 提供給畫面顯示與重試。
}
}
onMounted(() => {
animateHealthScore(store.healthScore);
});
onBeforeUnmount(() => {
if (healthScoreTimer !== undefined) {
window.clearInterval(healthScoreTimer);
}
});
watch(
() => store.healthScore,
(score) => {
animateHealthScore(score);
},
);
watch(() => store.isLoggedIn, loadFitnessGroup, { immediate: true });
const todayTimeString = computed(() => {
const now = new Date();
@@ -314,26 +317,22 @@ const todayTimeString = computed(() => {
}
.score-disk {
background: radial-gradient(circle, #157ba9 0%, #13967f 100%);
border: 3px solid #ffffff;
width: 72px;
width: min(100%, 108px);
height: 72px;
border-radius: 50%;
padding: 0 10px;
border-radius: 22px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
box-sizing: border-box;
flex-shrink: 0;
}
.score-val {
font-size: 1.75rem;
.group-label {
font-size: clamp(0.95rem, 3vw, 1.1rem);
font-weight: 900;
line-height: 1;
color: #ffffff;
}
.score-unit {
font-size: 0.65rem;
font-weight: bold;
color: rgba(255, 255, 255, 0.8);
white-space: nowrap;
}
.transition-card {