From db089ce26784e06ee2de12a15c610da68f2b5673 Mon Sep 17 00:00:00 2001 From: skytek_xinliang Date: Fri, 28 Aug 2026 15:18:11 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9F=BA=E6=9C=AC=E8=B3=87=E6=96=99?= =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/layout/DefaultLayout.vue | 59 +-- app/citizen-frontend/src/pages/login.vue | 1 + app/citizen-frontend/src/pages/profile.vue | 363 ++++++++++++++++++ app/citizen-frontend/src/router/index.ts | 7 + app/citizen-frontend/src/stores/app.ts | 38 +- 5 files changed, 416 insertions(+), 52 deletions(-) create mode 100644 app/citizen-frontend/src/pages/profile.vue diff --git a/app/citizen-frontend/src/components/layout/DefaultLayout.vue b/app/citizen-frontend/src/components/layout/DefaultLayout.vue index 720cebd..4493df3 100644 --- a/app/citizen-frontend/src/components/layout/DefaultLayout.vue +++ b/app/citizen-frontend/src/components/layout/DefaultLayout.vue @@ -100,6 +100,15 @@ to="/" /> + + - - @@ -270,48 +272,6 @@ - - - - - - - 緊急聯絡資訊 - - - - - - - - - - - 儲存並關閉 - - - - @@ -327,7 +287,6 @@ const route = useRoute(); const drawer = ref(false); const activeTab = ref("home"); const showPrivacy = ref(false); -const showEmergency = ref(false); // 無障礙狀態 (可持久化儲存於 localStorage) const isLargeText = ref(localStorage.getItem("mode-large-text") === "true"); diff --git a/app/citizen-frontend/src/pages/login.vue b/app/citizen-frontend/src/pages/login.vue index 367809d..3a01c19 100644 --- a/app/citizen-frontend/src/pages/login.vue +++ b/app/citizen-frontend/src/pages/login.vue @@ -50,6 +50,7 @@ color="primary" hide-details="auto" label="登入密碼" + placeholder="例如: 00000000" prepend-inner-icon="mdi-lock-outline" required rounded="lg" diff --git a/app/citizen-frontend/src/pages/profile.vue b/app/citizen-frontend/src/pages/profile.vue new file mode 100644 index 0000000..d3f4907 --- /dev/null +++ b/app/citizen-frontend/src/pages/profile.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/app/citizen-frontend/src/router/index.ts b/app/citizen-frontend/src/router/index.ts index b837345..d041a34 100644 --- a/app/citizen-frontend/src/router/index.ts +++ b/app/citizen-frontend/src/router/index.ts @@ -6,6 +6,7 @@ import Course from "@/pages/course.vue"; import Index from "@/pages/index.vue"; import Introduction from "@/pages/introduction.vue"; import Login from "@/pages/login.vue"; +import Profile from "@/pages/profile.vue"; import Register from "@/pages/register.vue"; import SelfAssessment from "@/pages/self-assessment.vue"; import { useAppStore } from "@/stores/app"; @@ -43,6 +44,12 @@ const router = createRouter({ name: "self-assessment", component: SelfAssessment, }, + { + path: "/profile", + name: "profile", + component: Profile, + meta: { requiresAuth: true, title: "個人資料修改|運動玩轉健康力" }, + }, { path: "/appointment", name: "appointment", diff --git a/app/citizen-frontend/src/stores/app.ts b/app/citizen-frontend/src/stores/app.ts index 3ea0987..9eeded0 100644 --- a/app/citizen-frontend/src/stores/app.ts +++ b/app/citizen-frontend/src/stores/app.ts @@ -54,6 +54,8 @@ export interface UserProfile { consentVersion: ConsentVersion; } +type EditableProfile = Omit; + export interface SelfAssessment { exerciseFrequency: ExerciseFrequency | ""; painLevel: PainLevel | ""; @@ -168,6 +170,25 @@ function toProfileRequest( }; } +function toProfileRequestFromStore(profile: UserProfile): ProfileRequest { + if (profile.gender !== "男" && profile.gender !== "女") { + throw new Error("請先完成性別資料"); + } + + return { + name: profile.name, + birthday: profile.birthday, + gender: profile.gender === "男" ? "male" : "female", + heightCm: profile.height, + weightKg: profile.weight, + email: profile.email || null, + emergencyContact: profile.emergencyContact || null, + emergencyPhone: profile.emergencyPhone || null, + consent: profile.consent, + consentVersion: profile.consentVersion, + }; +} + function createDefaultAppointments(): Appointment[] { const measurementItems = [ "血壓", @@ -417,8 +438,21 @@ export const useAppStore = defineStore("app", () => { } } - function updateProfile(profile: Partial) { - userProfile.value = { ...userProfile.value, ...profile }; + async function updateProfile( + profile: Partial, + ): Promise { + const updatedProfile = { ...userProfile.value, ...profile }; + + if (USE_MOCK) { + userProfile.value = updatedProfile; + return userProfile.value; + } + + const response = await apiUpdateProfile( + toProfileRequestFromStore(updatedProfile), + ); + userProfile.value = applyProfileToStore(response); + return userProfile.value; } function setSelfAssessment(response: SelfAssessmentResponse) {