fix: 自評去識別化
This commit is contained in:
@@ -209,10 +209,10 @@ const existingDraft = readGuestSelfAssessmentDraft();
|
||||
const routePhone = normalizePhone(String(route.query.phone ?? ""));
|
||||
|
||||
const form = reactive({
|
||||
phone: routePhone || existingDraft?.profile.phone || "",
|
||||
phone: routePhone,
|
||||
password: "",
|
||||
passwordConfirmation: "",
|
||||
consent: existingDraft?.profile.consent ?? false,
|
||||
consent: false,
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
v-for="(stepName, idx) in stepNames"
|
||||
:key="idx"
|
||||
class="text-center"
|
||||
cols="3"
|
||||
cols="4"
|
||||
>
|
||||
<v-avatar
|
||||
class="text-white font-weight-bold mb-1 elevation-1"
|
||||
@@ -54,149 +54,8 @@
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
|
||||
<!-- 步驟 1:基本資料 -->
|
||||
<!-- 步驟 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"
|
||||
>
|
||||
@@ -305,8 +164,8 @@
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- 步驟 3:身體狀況自評 -->
|
||||
<div v-if="currentStep === 2">
|
||||
<!-- 步驟 2:身體狀況自評 -->
|
||||
<div v-if="currentStep === 1">
|
||||
<h2
|
||||
class="text-title-medium font-weight-bold text-primary mb-4 d-flex align-center"
|
||||
>
|
||||
@@ -373,8 +232,8 @@
|
||||
</v-row>
|
||||
</div>
|
||||
|
||||
<!-- 步驟 4:自評結果 -->
|
||||
<div v-if="currentStep === 3" class="text-center py-4">
|
||||
<!-- 步驟 3:自評結果 -->
|
||||
<div v-if="currentStep === 2" class="text-center py-4">
|
||||
<v-avatar
|
||||
class="elevation-2 mb-4"
|
||||
:color="getResultColor(resultLevel)"
|
||||
@@ -444,7 +303,7 @@
|
||||
|
||||
<v-card-actions class="px-5 py-4 d-flex justify-between">
|
||||
<v-btn
|
||||
v-if="currentStep > 0 && currentStep < 3"
|
||||
v-if="currentStep > 0 && currentStep < 2"
|
||||
class="rounded-lg px-4"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
@@ -456,10 +315,9 @@
|
||||
<v-spacer />
|
||||
|
||||
<v-btn
|
||||
v-if="currentStep < 2"
|
||||
v-if="currentStep < 1"
|
||||
class="rounded-lg px-6"
|
||||
color="primary"
|
||||
:disabled="currentStep === 0 && !profileForm.consent"
|
||||
variant="flat"
|
||||
@click="goToNextStep"
|
||||
>
|
||||
@@ -467,7 +325,7 @@
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
v-else-if="currentStep === 2"
|
||||
v-else-if="currentStep === 1"
|
||||
class="rounded-lg px-6 text-white"
|
||||
color="secondary"
|
||||
variant="flat"
|
||||
@@ -493,7 +351,7 @@
|
||||
|
||||
<!-- 返回按鈕 (結果頁專用) -->
|
||||
<v-btn
|
||||
v-if="currentStep === 3"
|
||||
v-if="currentStep === 2"
|
||||
block
|
||||
class="rounded-lg py-2"
|
||||
color="primary"
|
||||
@@ -507,18 +365,16 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, reactive, ref, watch } from "vue";
|
||||
import { onMounted, reactive, ref, watch } 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 PageHeading from "@/components/layout/PageHeading.vue";
|
||||
import { APPOINTMENT_ENABLED } from "@/config/features";
|
||||
@@ -535,36 +391,7 @@ 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 stepNames = ["功能狀態", "健康限制", "自評結果"];
|
||||
|
||||
const assessmentForm = reactive<{
|
||||
exerciseFrequency: ExerciseFrequency | "";
|
||||
@@ -582,10 +409,6 @@ const assessmentForm = reactive<{
|
||||
|
||||
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 },
|
||||
@@ -617,59 +440,21 @@ const diseaseOptions = [
|
||||
{ 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;
|
||||
currentStep.value = 2;
|
||||
});
|
||||
|
||||
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) {
|
||||
@@ -699,26 +484,9 @@ function goToNextStep() {
|
||||
scrollToPageTop();
|
||||
}
|
||||
|
||||
watch(profileForm, clearErrorMessage);
|
||||
watch(assessmentForm, clearErrorMessage);
|
||||
|
||||
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;
|
||||
@@ -736,26 +504,12 @@ function buildAssessmentRequest(): SelfAssessmentRequest | null {
|
||||
function handleSubmit() {
|
||||
errorMessage.value = "";
|
||||
const assessment = buildAssessmentRequest();
|
||||
if (!assessment || !profileForm.gender) {
|
||||
if (!assessment) {
|
||||
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,
|
||||
};
|
||||
@@ -763,7 +517,7 @@ function handleSubmit() {
|
||||
saveGuestSelfAssessmentDraft(draft);
|
||||
store.setProvisionalSelfAssessment(assessment);
|
||||
resultLevel.value = provisionalRiskLevel;
|
||||
currentStep.value = 3;
|
||||
currentStep.value = 2;
|
||||
scrollToPageTop();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user