Files
sports-empower-hub/app/citizen-frontend/src/pages/self-assessment.vue
T
skytek_xinliang 6da57e1775 chore: lint
2026-06-22 17:35:23 +08:00

506 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<Layout>
<template #main>
<div class="mb-4 text-center">
<h1 class="text-h5 font-weight-black text-primary">線上健康自評</h1>
<p class="text-caption text-grey">只需 3 分鐘評估您的運動風險與健康狀態</p>
</div>
<!-- 進度指示器 -->
<v-row class="mb-6 justify-center">
<v-col v-for="(stepName, idx) in stepNames" :key="idx" class="text-center" cols="3">
<v-avatar
class="text-white font-weight-bold mb-1 elevation-1"
:color="
currentStep > idx ? 'primary' : currentStep === idx ? 'secondary' : 'grey-lighten-2'
"
size="28"
>
<v-icon v-if="currentStep > idx" icon="mdi-check" size="14" />
<span v-else class="text-caption">{{ idx + 1 }}</span>
</v-avatar>
<div
class="text-caption font-weight-bold"
:class="
currentStep === idx
? 'text-secondary'
: currentStep > idx
? 'text-primary'
: 'text-grey'
"
>
{{ stepName }}
</div>
</v-col>
</v-row>
<!-- 步驟內容區 -->
<v-card class="rounded-xl elevation-3 border-accent-light mb-6" color="surface">
<v-card-text class="pa-5">
<!-- 步驟 1基本資料 -->
<div v-if="currentStep === 0">
<h2 class="text-subtitle-1 font-weight-bold text-primary mb-4 d-flex align-center">
<v-icon class="mr-2" color="secondary" icon="mdi-account-circle" />
基本資料確認
</h2>
<v-row>
<v-col cols="12" sm="6">
<v-text-field
v-model="profileForm.name"
class="mb-3"
color="primary"
density="comfortable"
hide-details="auto"
label="姓名"
variant="outlined"
/>
</v-col>
<v-col cols="12" sm="6">
<v-select
v-model="profileForm.gender"
class="mb-3"
color="primary"
density="comfortable"
hide-details="auto"
:items="['男', '女']"
label="性別"
variant="outlined"
/>
</v-col>
<v-col cols="12" sm="6">
<v-text-field
v-model="profileForm.birthday"
class="mb-3"
color="primary"
density="comfortable"
hide-details="auto"
label="生日"
type="date"
variant="outlined"
/>
</v-col>
<v-col cols="6">
<v-text-field
v-model.number="profileForm.height"
class="mb-3"
color="primary"
density="comfortable"
hide-details="auto"
label="身高 (公分)"
type="number"
variant="outlined"
/>
</v-col>
<v-col cols="6">
<v-text-field
v-model.number="profileForm.weight"
class="mb-3"
color="primary"
density="comfortable"
hide-details="auto"
label="體重 (公斤)"
type="number"
variant="outlined"
/>
</v-col>
</v-row>
<v-checkbox
v-model="profileForm.consent"
class="mt-2 text-caption"
color="primary"
density="compact"
hide-details
label="我已閱讀並同意個資授權條款"
/>
</div>
<!-- 步驟 2活動量與自評 -->
<div v-if="currentStep === 1">
<h2 class="text-subtitle-1 font-weight-bold text-primary mb-4 d-flex align-center">
<v-icon class="mr-2" color="secondary" icon="mdi-walk" />
活動習慣與想要改善的能力
</h2>
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
1. 最近 7 您每週進行大運動幾次
</p>
<v-radio-group v-model="assessmentForm.exerciseFrequency" class="mb-4" color="primary">
<v-card
class="mb-2 rounded-lg pa-1 cursor-pointer transition-card"
:color="assessmentForm.exerciseFrequency === '0' ? 'secondary' : 'grey-lighten-1'"
variant="outlined"
@click="assessmentForm.exerciseFrequency = '0'"
>
<v-radio label="0 次,幾乎沒有固定運動" value="0" />
</v-card>
<v-card
class="mb-2 rounded-lg pa-1 cursor-pointer transition-card"
:color="assessmentForm.exerciseFrequency === '1-2' ? 'secondary' : 'grey-lighten-1'"
variant="outlined"
@click="assessmentForm.exerciseFrequency = '1-2'"
>
<v-radio label="1-2 次,偶爾散步或伸展" value="1-2" />
</v-card>
<v-card
class="rounded-lg pa-1 cursor-pointer transition-card"
:color="assessmentForm.exerciseFrequency === '3+' ? 'secondary' : 'grey-lighten-1'"
variant="outlined"
@click="assessmentForm.exerciseFrequency = '3+'"
>
<v-radio label="3 次以上,有固定運動習慣" value="3+" />
</v-card>
</v-radio-group>
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
2. 您目前最想改善的身體功能 (可複選)
</p>
<v-row>
<v-col v-for="imp in improvementsOptions" :key="imp" cols="6">
<v-card
class="rounded-lg py-2 px-1 text-center cursor-pointer transition-card"
:class="{ 'bg-teal-lighten-5': assessmentForm.wantedImprovements.includes(imp) }"
:color="
assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey-lighten-1'
"
variant="outlined"
@click="toggleImprovement(imp)"
>
<v-icon
class="mb-1"
:color="assessmentForm.wantedImprovements.includes(imp) ? 'primary' : 'grey'"
:icon="getImprovementIcon(imp)"
/>
<div class="text-caption font-weight-bold">{{ imp }}</div>
</v-card>
</v-col>
</v-row>
</div>
<!-- 步驟 3身體狀況自評 -->
<div v-if="currentStep === 2">
<h2 class="text-subtitle-1 font-weight-bold text-primary mb-4 d-flex align-center">
<v-icon class="mr-2" color="secondary" icon="mdi-alert-decagram" />
身體限制與慢性病史
</h2>
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
1. 過去一年是否有過跌倒史
</p>
<v-radio-group v-model="assessmentForm.hasFalls" class="mb-4" color="primary" inline>
<v-radio label="無跌倒" :value="false" />
<v-radio label="有跌倒過" :value="true" />
</v-radio-group>
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
2. 目前身體關節疼痛狀況
</p>
<v-select
v-model="assessmentForm.painLevel"
class="mb-4"
color="primary"
density="comfortable"
:items="['無', '輕微', '中度', '重度']"
variant="outlined"
/>
<p class="text-body-2 font-weight-bold text-grey-darken-2 mb-2">
3. 您有哪些慢性疾病 (可複選)
</p>
<v-row class="mb-4">
<v-col v-for="disease in diseaseOptions" :key="disease" cols="6">
<v-checkbox
v-model="assessmentForm.chronicDiseases"
color="primary"
density="compact"
hide-details
:label="disease"
:value="disease"
/>
</v-col>
</v-row>
<v-text-field
v-model="medicationInput"
append-inner-icon="mdi-plus"
color="primary"
density="comfortable"
label="用藥提醒設定 (例如:每日晨間降血壓藥)"
placeholder="請輸入常用藥物或提醒"
variant="outlined"
@click:append-inner="addMedication"
@keyup.enter="addMedication"
/>
<div
v-if="assessmentForm.medicationReminders.length > 0"
class="d-flex flex-wrap gap-2 mt-2"
>
<v-chip
v-for="(med, idx) in assessmentForm.medicationReminders"
:key="idx"
closable
color="secondary"
size="small"
@click:close="removeMedication(idx)"
>
{{ med }}
</v-chip>
</div>
</div>
<!-- 步驟 4自評結果 -->
<div v-if="currentStep === 3" class="text-center py-4">
<v-avatar class="elevation-2 mb-4" :color="getResultColor(resultLevel)" size="72">
<v-icon color="white" :icon="getResultIcon(resultLevel)" size="40" />
</v-avatar>
<h2
class="text-h5 font-weight-bold mb-1"
:class="`text-${getResultColor(resultLevel)}`"
>
自評分級{{ resultLevel }}
</h2>
<p class="text-subtitle-2 text-grey-darken-2 mb-6">您的健康自評報告已產生</p>
<v-card class="rounded-xl pa-4 text-left mb-6" color="grey-lighten-4" variant="flat">
<div class="font-weight-bold mb-2 text-primary d-flex align-center">
<v-icon class="mr-1" icon="mdi-text-box-search" />
系統評估建議
</div>
<p v-if="resultLevel === '低風險'" class="text-body-2 text-grey-darken-3 mb-2">
您的身體功能與日常活動習慣良好建議您每週繼續保持 3
次以上的運動並可以預約普測活動以獲得精準的五維健康指標與運動處方
</p>
<p v-else-if="resultLevel === '需追蹤'" class="text-body-2 text-grey-darken-3 mb-2">
由於您運動頻率偏低或是過去有跌倒史建議您可以前往運動中心預約專業普測由現場教練為您提供適合的肌力與平衡回測建議
</p>
<p v-else class="text-body-2 text-grey-darken-3 mb-2">
檢測到您有多項慢性病或中重度疼痛運動時請特別留意建議您先向您的家庭醫師或社區據點諮詢並在運動普測現場合對您的健康狀態以確保運動安全
</p>
<div class="font-weight-bold text-caption text-secondary mt-3">
* 禁忌提醒若收縮壓 >
160mmHg有關節強烈疼痛或暈眩時請立即暫停所有體能檢測與訓練
</div>
</v-card>
</div>
</v-card-text>
<!-- 表單操作按鈕 -->
<v-divider />
<v-card-actions class="px-5 py-4 d-flex justify-between">
<v-btn
v-if="currentStep > 0 && currentStep < 3"
class="rounded-lg px-4"
color="primary"
variant="outlined"
@click="currentStep--"
>
上一步
</v-btn>
<v-spacer />
<v-btn
v-if="currentStep < 2"
class="rounded-lg px-6"
color="primary"
:disabled="currentStep === 0 && !profileForm.consent"
variant="flat"
@click="currentStep++"
>
下一步
</v-btn>
<v-btn
v-else-if="currentStep === 2"
class="rounded-lg px-6 text-white"
color="secondary"
variant="flat"
@click="handleSubmit"
>
看我的健康力分級
</v-btn>
<div v-else class="w-100 d-flex gap-2">
<v-btn
block
class="rounded-lg py-2"
color="primary"
to="/appointment"
variant="flat"
>
立即預約普測
</v-btn>
</div>
</v-card-actions>
</v-card>
<!-- 返回按鈕 (結果頁專用) -->
<v-btn
v-if="currentStep === 3"
block
class="rounded-lg py-2"
color="primary"
to="/"
variant="outlined"
>
回首頁健康儀表板
</v-btn>
</template>
</Layout>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import Layout from '@/components/layout/DefaultLayout.vue'
import { useAppStore } from '@/stores/app'
const store = useAppStore()
const currentStep = ref(0)
const stepNames = ['基本資料', '功能狀態', '健康限制', '自評結果']
const profileForm = reactive({
name: store.userProfile.name,
birthday: store.userProfile.birthday,
gender: store.userProfile.gender,
height: store.userProfile.height,
weight: store.userProfile.weight,
consent: store.userProfile.consent,
})
const assessmentForm = reactive({
exerciseFrequency: store.selfAssessment.exerciseFrequency,
wantedImprovements: [...store.selfAssessment.wantedImprovements],
hasFalls: store.selfAssessment.hasFalls,
painLevel: store.selfAssessment.painLevel,
chronicDiseases: [...store.selfAssessment.chronicDiseases],
medicationReminders: [...store.selfAssessment.medicationReminders],
})
const medicationInput = ref('')
const resultLevel = ref(store.selfAssessment.scoreLevel)
const improvementsOptions = ['肌力', '平衡', '心肺', '柔軟度', '敏捷', '體脂/BMI']
const diseaseOptions = ['高血壓', '糖尿病', '高血脂', '心臟病', '關節退化', '骨質疏鬆']
function getImprovementIcon (imp: string) {
switch (imp) {
case '肌力': {
return 'mdi-arm-flex'
}
case '平衡': {
return 'mdi-scale-balance'
}
case '心肺': {
return 'mdi-heart-pulse'
}
case '柔軟度': {
return 'mdi-human-stretching'
}
case '敏捷': {
return 'mdi-run-fast'
}
case '體脂/BMI': {
return 'mdi-calculator'
}
default: {
return 'mdi-help-circle'
}
}
}
function toggleImprovement (imp: string) {
const index = assessmentForm.wantedImprovements.indexOf(imp)
if (index === -1) {
assessmentForm.wantedImprovements.push(imp)
} else {
assessmentForm.wantedImprovements.splice(index, 1)
}
}
function addMedication () {
if (medicationInput.value.trim()) {
assessmentForm.medicationReminders.push(medicationInput.value.trim())
medicationInput.value = ''
}
}
function removeMedication (index: number) {
assessmentForm.medicationReminders.splice(index, 1)
}
function handleSubmit () {
// 儲存至 store
store.updateProfile({
name: profileForm.name,
birthday: profileForm.birthday,
gender: profileForm.gender,
height: profileForm.height,
weight: profileForm.weight,
consent: profileForm.consent,
})
store.submitSelfAssessment({
exerciseFrequency: assessmentForm.exerciseFrequency,
wantedImprovements: assessmentForm.wantedImprovements,
hasFalls: assessmentForm.hasFalls,
painLevel: assessmentForm.painLevel,
chronicDiseases: assessmentForm.chronicDiseases,
medicationReminders: assessmentForm.medicationReminders,
})
// 取得計算後的結果
resultLevel.value = store.selfAssessment.scoreLevel
currentStep.value = 3
}
function getResultColor (lvl: string) {
if (lvl === '低風險') return 'success'
if (lvl === '需追蹤') return 'warning'
return 'error'
}
function getResultIcon (lvl: string) {
if (lvl === '低風險') return 'mdi-check-decagram'
if (lvl === '需追蹤') return 'mdi-alert-decagram'
return 'mdi-alert-octagon'
}
</script>
<style scoped>
.transition-card {
transition: all 0.25s ease;
border-width: 1.5px;
}
.transition-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.05);
}
.gap-2 {
gap: 8px;
}
</style>