825 lines
25 KiB
Vue
825 lines
25 KiB
Vue
<template>
|
||
<Layout>
|
||
<template #main>
|
||
<v-card class="mb-6" variant="flat">
|
||
<v-card-title class="text-primary">線上健康自評</v-card-title>
|
||
|
||
<v-card-subtitle class="text-grey-darken-3">
|
||
只需 3 分鐘,評估您的運動風險與健康狀態
|
||
</v-card-subtitle>
|
||
</v-card>
|
||
|
||
<!-- 進度指示器 -->
|
||
<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-title-small">{{ idx + 1 }}</span>
|
||
</v-avatar>
|
||
|
||
<div
|
||
class="text-title-small 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 mb-6" color="surface">
|
||
<v-card-text class="py-2">
|
||
<v-alert
|
||
v-if="errorMessage"
|
||
class="mb-4"
|
||
density="compact"
|
||
type="error"
|
||
variant="tonal"
|
||
>
|
||
{{ errorMessage }}
|
||
</v-alert>
|
||
|
||
<!-- 步驟 1:基本資料 -->
|
||
<div v-if="currentStep === 0">
|
||
<h2
|
||
class="text-title-medium 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-text-field
|
||
v-model="profileForm.phone"
|
||
class="mb-3"
|
||
color="primary"
|
||
density="comfortable"
|
||
hide-details="auto"
|
||
label="手機號碼"
|
||
type="tel"
|
||
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="genderOptions"
|
||
label="性別"
|
||
variant="outlined"
|
||
/>
|
||
</v-col>
|
||
|
||
<v-col cols="12" sm="6">
|
||
<v-date-input
|
||
v-model="birthdayDate"
|
||
class="mb-3"
|
||
color="primary"
|
||
density="comfortable"
|
||
hide-details="auto"
|
||
label="生日"
|
||
prepend-icon=""
|
||
prepend-inner-icon="mdi-calendar"
|
||
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-col cols="12" sm="6">
|
||
<v-text-field
|
||
v-model="profileForm.email"
|
||
class="mb-3"
|
||
color="primary"
|
||
density="comfortable"
|
||
hide-details="auto"
|
||
label="Email(選填)"
|
||
type="email"
|
||
variant="outlined"
|
||
/>
|
||
</v-col>
|
||
|
||
<v-col cols="12" sm="6">
|
||
<v-text-field
|
||
v-model="profileForm.emergencyContact"
|
||
class="mb-3"
|
||
color="primary"
|
||
density="comfortable"
|
||
hide-details="auto"
|
||
label="緊急聯絡人(選填)"
|
||
variant="outlined"
|
||
/>
|
||
</v-col>
|
||
|
||
<v-col cols="12" sm="6">
|
||
<v-text-field
|
||
v-model="profileForm.emergencyPhone"
|
||
class="mb-3"
|
||
color="primary"
|
||
density="comfortable"
|
||
hide-details="auto"
|
||
label="緊急聯絡電話(選填)"
|
||
type="tel"
|
||
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-title-medium 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-medium 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 === 'none'
|
||
? 'secondary'
|
||
: 'grey-lighten-1'
|
||
"
|
||
variant="outlined"
|
||
@click="assessmentForm.exerciseFrequency = 'none'"
|
||
>
|
||
<v-radio label="0 次,幾乎沒有固定運動" value="none" />
|
||
</v-card>
|
||
|
||
<v-card
|
||
class="mb-2 rounded-lg pa-1 cursor-pointer transition-card"
|
||
:color="
|
||
assessmentForm.exerciseFrequency === 'oneToTwo'
|
||
? 'secondary'
|
||
: 'grey-lighten-1'
|
||
"
|
||
variant="outlined"
|
||
@click="assessmentForm.exerciseFrequency = 'oneToTwo'"
|
||
>
|
||
<v-radio label="1-2 次,偶爾散步或伸展" value="oneToTwo" />
|
||
</v-card>
|
||
|
||
<v-card
|
||
class="rounded-lg pa-1 cursor-pointer transition-card"
|
||
:color="
|
||
assessmentForm.exerciseFrequency === 'threeOrMore'
|
||
? 'secondary'
|
||
: 'grey-lighten-1'
|
||
"
|
||
variant="outlined"
|
||
@click="assessmentForm.exerciseFrequency = 'threeOrMore'"
|
||
>
|
||
<v-radio label="3 次以上,有固定運動習慣" value="threeOrMore" />
|
||
</v-card>
|
||
</v-radio-group>
|
||
|
||
<p
|
||
class="text-body-medium font-weight-bold text-grey-darken-2 mb-2"
|
||
>
|
||
2. 您目前最想改善的身體功能? (可複選)
|
||
</p>
|
||
|
||
<v-row>
|
||
<v-col
|
||
v-for="improvement in improvementsOptions"
|
||
:key="improvement.value"
|
||
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(
|
||
improvement.value,
|
||
),
|
||
}"
|
||
:color="
|
||
assessmentForm.wantedImprovements.includes(
|
||
improvement.value,
|
||
)
|
||
? 'primary'
|
||
: 'grey-lighten-1'
|
||
"
|
||
variant="outlined"
|
||
@click="toggleImprovement(improvement.value)"
|
||
>
|
||
<v-icon
|
||
class="mb-1"
|
||
:color="
|
||
assessmentForm.wantedImprovements.includes(
|
||
improvement.value,
|
||
)
|
||
? 'primary'
|
||
: 'grey'
|
||
"
|
||
:icon="improvement.icon"
|
||
/>
|
||
|
||
<div class="text-title-large font-weight-bold">
|
||
{{ improvement.title }}
|
||
</div>
|
||
</v-card>
|
||
</v-col>
|
||
</v-row>
|
||
</div>
|
||
|
||
<!-- 步驟 3:身體狀況自評 -->
|
||
<div v-if="currentStep === 2">
|
||
<h2
|
||
class="text-title-medium 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-medium 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-medium 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="painOptions"
|
||
variant="outlined"
|
||
/>
|
||
|
||
<p
|
||
class="text-body-medium 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.value"
|
||
cols="6"
|
||
>
|
||
<v-checkbox
|
||
v-model="assessmentForm.chronicDiseases"
|
||
color="primary"
|
||
density="compact"
|
||
hide-details
|
||
:label="disease.title"
|
||
:value="disease.value"
|
||
/>
|
||
</v-col>
|
||
</v-row>
|
||
</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-title-large font-weight-bold mb-1"
|
||
:class="`text-${getResultColor(resultLevel)}`"
|
||
>
|
||
暫存自評分級:{{ getRiskLabel(resultLevel) }}
|
||
</h2>
|
||
|
||
<p class="text-title-small 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 === 'low'"
|
||
class="text-body-medium text-grey-darken-3 mb-2"
|
||
>
|
||
您的身體功能與日常活動習慣良好。建議您每週繼續保持 3
|
||
次以上的運動,並可以預約普測活動以獲得精準的五維健康指標與運動處方。
|
||
</p>
|
||
|
||
<p
|
||
v-else-if="resultLevel === 'followUp'"
|
||
class="text-body-medium text-grey-darken-3 mb-2"
|
||
>
|
||
由於您運動頻率偏低,或是過去有跌倒史,建議您可以前往運動中心預約專業普測,由現場教練為您提供適合的肌力與平衡回測建議。
|
||
</p>
|
||
|
||
<p v-else class="text-body-medium text-grey-darken-3 mb-2">
|
||
檢測到您有多項慢性病或中重度疼痛,運動時請特別留意。建議您先向您的家庭醫師或社區據點諮詢,並在運動普測現場合對您的健康狀態,以確保運動安全。
|
||
</p>
|
||
|
||
<div
|
||
class="font-weight-bold text-body-medium 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="goToPreviousStep"
|
||
>
|
||
上一步
|
||
</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="goToNextStep"
|
||
>
|
||
下一步
|
||
</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"
|
||
:loading="appointmentLoading"
|
||
variant="flat"
|
||
@click="handleAppointment"
|
||
>
|
||
立即預約
|
||
</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 { computed, onMounted, reactive, ref } from "vue";
|
||
import { useRouter } from "vue-router";
|
||
import type {
|
||
ChronicDisease,
|
||
ExerciseFrequency,
|
||
Gender,
|
||
PainLevel,
|
||
RiskLevel,
|
||
SelfAssessmentRequest,
|
||
WantedImprovement,
|
||
} from "@/api/types";
|
||
import { CONSENT_VERSION } from "@/api/types";
|
||
import Layout from "@/components/layout/DefaultLayout.vue";
|
||
import {
|
||
readGuestSelfAssessmentDraft,
|
||
saveGuestSelfAssessmentDraft,
|
||
type GuestSelfAssessmentDraft,
|
||
} from "@/utils/guest-self-assessment";
|
||
import { calculateProvisionalRiskLevel } from "@/utils/self-assessment";
|
||
import { useAppStore } from "@/stores/app";
|
||
|
||
const store = useAppStore();
|
||
const router = useRouter();
|
||
const currentStep = ref(0);
|
||
const appointmentLoading = ref(false);
|
||
const errorMessage = ref("");
|
||
const stepNames = ["基本資料", "功能狀態", "健康限制", "自評結果"];
|
||
|
||
const profileForm = reactive<{
|
||
name: string;
|
||
phone: string;
|
||
birthday: string;
|
||
gender: Gender | "";
|
||
height: number;
|
||
weight: number;
|
||
email: string;
|
||
emergencyContact: string;
|
||
emergencyPhone: string;
|
||
consent: boolean;
|
||
}>({
|
||
name: store.userProfile.name,
|
||
phone: store.userProfile.phone,
|
||
birthday: store.userProfile.birthday,
|
||
gender:
|
||
store.userProfile.gender === "男"
|
||
? "male"
|
||
: store.userProfile.gender === "女"
|
||
? "female"
|
||
: "",
|
||
height: store.userProfile.height,
|
||
weight: store.userProfile.weight,
|
||
email: store.userProfile.email,
|
||
emergencyContact: store.userProfile.emergencyContact,
|
||
emergencyPhone: store.userProfile.emergencyPhone,
|
||
consent: store.userProfile.consent,
|
||
});
|
||
|
||
const assessmentForm = reactive<{
|
||
exerciseFrequency: ExerciseFrequency | "";
|
||
wantedImprovements: WantedImprovement[];
|
||
hasFalls: boolean;
|
||
painLevel: PainLevel | "";
|
||
chronicDiseases: ChronicDisease[];
|
||
}>({
|
||
exerciseFrequency: store.selfAssessment.exerciseFrequency,
|
||
wantedImprovements: [...store.selfAssessment.wantedImprovements],
|
||
hasFalls: store.selfAssessment.hasFalls,
|
||
painLevel: store.selfAssessment.painLevel,
|
||
chronicDiseases: [...store.selfAssessment.chronicDiseases],
|
||
});
|
||
|
||
const resultLevel = ref<RiskLevel | "">(store.selfAssessment.scoreLevel);
|
||
|
||
const genderOptions = [
|
||
{ title: "男", value: "male" as const },
|
||
{ title: "女", value: "female" as const },
|
||
];
|
||
const painOptions = [
|
||
{ title: "無", value: "none" as const },
|
||
{ title: "輕微", value: "mild" as const },
|
||
{ title: "中度", value: "moderate" as const },
|
||
{ title: "重度", value: "severe" as const },
|
||
];
|
||
const improvementsOptions = [
|
||
{ title: "肌力", value: "strength" as const, icon: "mdi-arm-flex" },
|
||
{ title: "平衡", value: "balance" as const, icon: "mdi-scale-balance" },
|
||
{ title: "心肺", value: "cardio" as const, icon: "mdi-heart-pulse" },
|
||
{
|
||
title: "柔軟度",
|
||
value: "flexibility" as const,
|
||
icon: "mdi-human-handsup",
|
||
},
|
||
{ title: "敏捷", value: "agility" as const, icon: "mdi-run-fast" },
|
||
{
|
||
title: "體脂/BMI",
|
||
value: "bodyComposition" as const,
|
||
icon: "mdi-calculator",
|
||
},
|
||
];
|
||
const diseaseOptions = [
|
||
{ title: "高血壓", value: "hypertension" as const },
|
||
{ title: "糖尿病", value: "diabetes" as const },
|
||
{ title: "高血脂", value: "hyperlipidemia" as const },
|
||
{ title: "心臟病", value: "heartDisease" as const },
|
||
{ title: "關節退化", value: "osteoarthritis" as const },
|
||
{ title: "骨質疏鬆", value: "osteoporosis" as const },
|
||
];
|
||
|
||
const birthdayDate = computed<Date | null>({
|
||
get: () => parseDateString(profileForm.birthday),
|
||
set: (value) => {
|
||
profileForm.birthday = formatDateString(value);
|
||
},
|
||
});
|
||
|
||
onMounted(() => {
|
||
const draft = readGuestSelfAssessmentDraft();
|
||
if (!draft) {
|
||
return;
|
||
}
|
||
|
||
profileForm.name = draft.profile.name;
|
||
profileForm.phone = draft.profile.phone;
|
||
profileForm.birthday = draft.profile.birthday;
|
||
profileForm.gender = draft.profile.gender;
|
||
profileForm.height = draft.profile.heightCm;
|
||
profileForm.weight = draft.profile.weightKg;
|
||
profileForm.email = draft.profile.email ?? "";
|
||
profileForm.emergencyContact = draft.profile.emergencyContact ?? "";
|
||
profileForm.emergencyPhone = draft.profile.emergencyPhone ?? "";
|
||
profileForm.consent = draft.profile.consent;
|
||
assessmentForm.exerciseFrequency = draft.assessment.exerciseFrequency;
|
||
assessmentForm.wantedImprovements = [...draft.assessment.wantedImprovements];
|
||
assessmentForm.hasFalls = draft.assessment.hasFalls;
|
||
assessmentForm.painLevel = draft.assessment.painLevel;
|
||
assessmentForm.chronicDiseases = [...draft.assessment.chronicDiseases];
|
||
resultLevel.value = draft.provisionalRiskLevel;
|
||
currentStep.value = 3;
|
||
});
|
||
|
||
function parseDateString(value: string) {
|
||
const [year, month, day] = value.split("-").map(Number);
|
||
if (!year || !month || !day) {
|
||
return null;
|
||
}
|
||
|
||
return new Date(year, month - 1, day);
|
||
}
|
||
|
||
function formatDateString(value: Date | null) {
|
||
if (!value) {
|
||
return "";
|
||
}
|
||
|
||
const year = value.getFullYear();
|
||
const month = String(value.getMonth() + 1).padStart(2, "0");
|
||
const day = String(value.getDate()).padStart(2, "0");
|
||
|
||
return `${year}-${month}-${day}`;
|
||
}
|
||
|
||
function toggleImprovement(improvement: WantedImprovement) {
|
||
const index = assessmentForm.wantedImprovements.indexOf(improvement);
|
||
if (index === -1) {
|
||
assessmentForm.wantedImprovements.push(improvement);
|
||
} else {
|
||
assessmentForm.wantedImprovements.splice(index, 1);
|
||
}
|
||
}
|
||
|
||
function scrollToPageTop() {
|
||
window.scrollTo({ top: 0 });
|
||
}
|
||
|
||
function goToPreviousStep() {
|
||
currentStep.value -= 1;
|
||
scrollToPageTop();
|
||
}
|
||
|
||
function goToNextStep() {
|
||
currentStep.value += 1;
|
||
scrollToPageTop();
|
||
}
|
||
|
||
function buildAssessmentRequest(): SelfAssessmentRequest | null {
|
||
if (
|
||
!profileForm.name.trim() ||
|
||
!/^09\d{8}$/.test(profileForm.phone.replace(/\D/g, "")) ||
|
||
!profileForm.birthday ||
|
||
!profileForm.gender ||
|
||
profileForm.height < 50 ||
|
||
profileForm.height > 250 ||
|
||
profileForm.weight < 20 ||
|
||
profileForm.weight > 300 ||
|
||
!profileForm.consent
|
||
) {
|
||
errorMessage.value = "請完整填寫基本資料,並同意個資授權條款。";
|
||
currentStep.value = 0;
|
||
return null;
|
||
}
|
||
|
||
if (!assessmentForm.exerciseFrequency || !assessmentForm.painLevel) {
|
||
errorMessage.value = "請完成所有自評題目後再送出。";
|
||
return null;
|
||
}
|
||
|
||
return {
|
||
exerciseFrequency: assessmentForm.exerciseFrequency,
|
||
wantedImprovements: [...assessmentForm.wantedImprovements],
|
||
hasFalls: assessmentForm.hasFalls,
|
||
painLevel: assessmentForm.painLevel,
|
||
chronicDiseases: [...assessmentForm.chronicDiseases],
|
||
};
|
||
}
|
||
|
||
function handleSubmit() {
|
||
errorMessage.value = "";
|
||
const assessment = buildAssessmentRequest();
|
||
if (!assessment || !profileForm.gender) {
|
||
return;
|
||
}
|
||
|
||
const normalizedPhone = profileForm.phone.replace(/\D/g, "");
|
||
const provisionalRiskLevel = calculateProvisionalRiskLevel(assessment);
|
||
const draft: GuestSelfAssessmentDraft = {
|
||
profile: {
|
||
name: profileForm.name.trim(),
|
||
phone: normalizedPhone,
|
||
birthday: profileForm.birthday,
|
||
gender: profileForm.gender,
|
||
heightCm: profileForm.height,
|
||
weightKg: profileForm.weight,
|
||
email: profileForm.email.trim() || null,
|
||
emergencyContact: profileForm.emergencyContact.trim() || null,
|
||
emergencyPhone: profileForm.emergencyPhone.replace(/\D/g, "") || null,
|
||
consent: profileForm.consent,
|
||
consentVersion: CONSENT_VERSION,
|
||
},
|
||
assessment,
|
||
provisionalRiskLevel,
|
||
};
|
||
|
||
saveGuestSelfAssessmentDraft(draft);
|
||
store.setProvisionalSelfAssessment(assessment);
|
||
resultLevel.value = provisionalRiskLevel;
|
||
currentStep.value = 3;
|
||
scrollToPageTop();
|
||
}
|
||
|
||
async function handleAppointment() {
|
||
errorMessage.value = "";
|
||
|
||
if (!store.isLoggedIn) {
|
||
await router.push({
|
||
name: "register",
|
||
query: { redirect: "/appointment" },
|
||
});
|
||
return;
|
||
}
|
||
|
||
appointmentLoading.value = true;
|
||
try {
|
||
await store.persistGuestSelfAssessment();
|
||
await router.push({ name: "appointment" });
|
||
} catch (error) {
|
||
errorMessage.value =
|
||
error instanceof Error
|
||
? error.message
|
||
: "資料保存失敗,請稍後重試。草稿仍會保留。";
|
||
} finally {
|
||
appointmentLoading.value = false;
|
||
}
|
||
}
|
||
|
||
function getRiskLabel(level: RiskLevel | "") {
|
||
if (level === "low") return "低風險";
|
||
if (level === "followUp") return "需追蹤";
|
||
if (level === "consultation") return "建議諮詢";
|
||
return "尚未完成";
|
||
}
|
||
|
||
function getResultColor(level: RiskLevel | "") {
|
||
if (level === "low") return "success";
|
||
if (level === "followUp") return "warning";
|
||
return "error";
|
||
}
|
||
|
||
function getResultIcon(level: RiskLevel | "") {
|
||
if (level === "low") return "mdi-check-decagram";
|
||
if (level === "followUp") 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>
|