feat: 分數改為分組
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user