fix: 註冊頁去識別化

This commit is contained in:
skytek_xinliang
2026-09-29 09:25:55 +08:00
parent 0727a803c0
commit 7eb37a37fb
4 changed files with 22 additions and 99 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ export function login(
.json<AuthResponse>(); .json<AuthResponse>();
} }
/** 註冊帳號並建立完整個人資料。 */ /** 使用手機門號、密碼與個資同意狀態註冊帳號。 */
export function register(request: RegisterRequest): Promise<AuthResponse> { export function register(request: RegisterRequest): Promise<AuthResponse> {
return api.post("v1/auth/register", { json: request }).json<AuthResponse>(); return api.post("v1/auth/register", { json: request }).json<AuthResponse>();
} }
+4 -2
View File
@@ -113,10 +113,12 @@ export interface Profile extends ProfileRequest {
updatedAt: string; updatedAt: string;
} }
/** 註冊請求:建立帳號並同時建立完整個人資料。 */ /** 註冊請求:僅建立帳號,個人資料由後續流程另行保存。 */
export interface RegisterRequest extends ProfileRequest { export interface RegisterRequest {
phone: string; phone: string;
password: string; password: string;
consent: boolean;
consentVersion: ConsentVersion;
} }
/** 登入請求。 */ /** 登入請求。 */
+15 -81
View File
@@ -30,7 +30,6 @@
v-model="form.phone" v-model="form.phone"
class="mb-3" class="mb-3"
color="primary" color="primary"
hint="手機門號註冊後不可由個人資料修改"
label="手機號碼" label="手機號碼"
persistent-hint persistent-hint
type="tel" type="tel"
@@ -59,15 +58,15 @@
<v-divider class="my-5" /> <v-divider class="my-5" />
<v-text-field <!-- <v-text-field
v-model="form.name" v-model="form.name"
class="mb-3" class="mb-3"
color="primary" color="primary"
label="姓名" label="姓名"
variant="outlined" variant="outlined"
/> /> -->
<v-row> <!-- <v-row>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-select <v-select
v-model="form.gender" v-model="form.gender"
@@ -112,18 +111,18 @@
variant="outlined" variant="outlined"
/> />
</v-col> </v-col>
</v-row> </v-row> -->
<v-text-field <!-- <v-text-field
v-model="form.email" v-model="form.email"
class="mb-3" class="mb-3"
color="primary" color="primary"
label="Email(選填)" label="Email(選填)"
type="email" type="email"
variant="outlined" variant="outlined"
/> /> -->
<v-row> <!-- <v-row>
<v-col cols="12" sm="6"> <v-col cols="12" sm="6">
<v-text-field <v-text-field
v-model="form.emergencyContact" v-model="form.emergencyContact"
@@ -144,7 +143,7 @@
variant="outlined" variant="outlined"
/> />
</v-col> </v-col>
</v-row> </v-row> -->
<v-checkbox v-model="form.consent" class="mb-3" color="primary"> <v-checkbox v-model="form.consent" class="mb-3" color="primary">
<template #label> <template #label>
@@ -188,9 +187,9 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, ref } from "vue"; import { reactive, ref } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import type { Gender, RegisterRequest } from "@/api/types"; import type { RegisterRequest } from "@/api/types";
import { CONSENT_VERSION } from "@/api/types"; import { CONSENT_VERSION } from "@/api/types";
import BrandAvatar from "@/components/BrandAvatar.vue"; import BrandAvatar from "@/components/BrandAvatar.vue";
import PrivacyConsentDialog from "@/components/PrivacyConsentDialog.vue"; import PrivacyConsentDialog from "@/components/PrivacyConsentDialog.vue";
@@ -200,10 +199,7 @@ import {
getApiErrorMessage, getApiErrorMessage,
isUnregisteredPhoneError, isUnregisteredPhoneError,
} from "@/utils/api-error"; } from "@/utils/api-error";
import { import { readGuestSelfAssessmentDraft } from "@/utils/guest-self-assessment";
readGuestSelfAssessmentDraft,
saveGuestSelfAssessmentDraft,
} from "@/utils/guest-self-assessment";
const store = useAppStore(); const store = useAppStore();
const route = useRoute(); const route = useRoute();
@@ -211,54 +207,21 @@ const router = useRouter();
const showPrivacyConsent = ref(false); const showPrivacyConsent = ref(false);
const existingDraft = readGuestSelfAssessmentDraft(); const existingDraft = readGuestSelfAssessmentDraft();
const routePhone = normalizePhone(String(route.query.phone ?? "")); const routePhone = normalizePhone(String(route.query.phone ?? ""));
const initialProfile = existingDraft?.profile;
const form = reactive({ const form = reactive({
phone: routePhone || initialProfile?.phone || "", phone: routePhone || existingDraft?.profile.phone || "",
password: "", password: "",
passwordConfirmation: "", passwordConfirmation: "",
name: initialProfile?.name ?? "", consent: existingDraft?.profile.consent ?? false,
birthday: initialProfile?.birthday ?? "",
gender: (initialProfile?.gender ?? "") as Gender | "",
heightCm: initialProfile?.heightCm ?? 0,
weightKg: initialProfile?.weightKg ?? 0,
email: initialProfile?.email ?? "",
emergencyContact: initialProfile?.emergencyContact ?? "",
emergencyPhone: initialProfile?.emergencyPhone ?? "",
consent: initialProfile?.consent ?? false,
}); });
const loading = ref(false); const loading = ref(false);
const errorMessage = ref(""); const errorMessage = ref("");
const genderOptions = [
{ title: "男", value: "male" as const },
{ title: "女", value: "female" as const },
];
const birthdayDate = computed<Date | null>({
get: () => parseDateString(form.birthday),
set: (value) => {
form.birthday = formatDateString(value);
},
});
function normalizePhone(value: string) { function normalizePhone(value: string) {
return value.replace(/\D/g, ""); return value.replace(/\D/g, "");
} }
function parseDateString(value: string) {
const [year, month, day] = value.split("-").map(Number);
return year && month && day ? new Date(year, month - 1, day) : null;
}
function formatDateString(value: Date | null) {
if (!value) {
return "";
}
return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
}
function redirectTarget() { function redirectTarget() {
const target = String(route.query.redirect ?? ""); const target = String(route.query.redirect ?? "");
return target.startsWith("/") && !target.startsWith("//") return target.startsWith("/") && !target.startsWith("//")
@@ -278,17 +241,6 @@ function validateForm(phone: string) {
if (form.password !== form.passwordConfirmation) { if (form.password !== form.passwordConfirmation) {
return "兩次輸入的密碼不一致。"; return "兩次輸入的密碼不一致。";
} }
if (
!form.name.trim() ||
!form.birthday ||
!form.gender ||
form.heightCm < 50 ||
form.heightCm > 250 ||
form.weightKg < 20 ||
form.weightKg > 300
) {
return "請完整填寫姓名、生日、性別、身高與體重。";
}
if (!form.consent) { if (!form.consent) {
return "請同意個資授權條款後再註冊。"; return "請同意個資授權條款後再註冊。";
} }
@@ -304,32 +256,14 @@ async function handleRegister() {
return; return;
} }
const profile = { const request: RegisterRequest = {
name: form.name.trim(),
phone, phone,
birthday: form.birthday, password: form.password,
gender: form.gender as Gender,
heightCm: form.heightCm,
weightKg: form.weightKg,
email: form.email.trim() || null,
emergencyContact: form.emergencyContact.trim() || null,
emergencyPhone: normalizePhone(form.emergencyPhone) || null,
consent: form.consent, consent: form.consent,
consentVersion: CONSENT_VERSION, consentVersion: CONSENT_VERSION,
}; };
const currentDraft = readGuestSelfAssessmentDraft(); const currentDraft = readGuestSelfAssessmentDraft();
if (currentDraft) {
saveGuestSelfAssessmentDraft({
...currentDraft,
profile,
});
}
const request: RegisterRequest = {
...profile,
password: form.password,
};
loading.value = true; loading.value = true;
try { try {
+2 -15
View File
@@ -429,23 +429,10 @@ export const useAppStore = defineStore("app", () => {
const response: AuthResponse = USE_MOCK const response: AuthResponse = USE_MOCK
? { ? {
userID: "MOCK-USER", userID: "MOCK-USER",
userName: request.name, userName: "",
phone: request.phone, phone: request.phone,
group: "medium", group: "medium",
profile: { profile: null,
name: request.name,
phone: request.phone,
birthday: request.birthday,
gender: request.gender,
heightCm: request.heightCm,
weightKg: request.weightKg,
email: request.email,
emergencyContact: request.emergencyContact,
emergencyPhone: request.emergencyPhone,
consent: request.consent,
consentVersion: request.consentVersion,
updatedAt: new Date().toISOString(),
},
} }
: await apiRegister(request); : await apiRegister(request);