feat: 添加用户设置功能,支持修改昵称和邮箱,重构相关路由和组件

This commit is contained in:
2025-12-21 01:11:53 +07:00
parent 2e42bbc278
commit a4034b6b78
22 changed files with 620 additions and 225 deletions

View File

@@ -1,191 +0,0 @@
<script lang='ts' setup>
import type { UpdateUserProfileBody, UserProfileData } from "@/api/types";
import { alertController, toastController } from "@ionic/vue";
import { arrowBackOutline } from "ionicons/icons";
import { client } from "@/api";
import { GenderEnum } from "@/api/enum";
import { authClient } from "@/auth";
const router = useRouter();
const userProfile = ref<UserProfileData | null>(null);
const birthday = computed(() => useDateFormat(userProfile?.value?.birthday || "", "YYYY/MM/DD"));
async function getUserProfile() {
const { data } = await client.api.user.profile.get();
if (data) {
userProfile.value = data.userProfile;
}
}
async function updateProfile(updates: UpdateUserProfileBody) {
const { data } = await client.api.user.profile.put(updates);
if (data) {
userProfile.value = data;
const toast = await toastController.create({
message: "Profile updated successfully",
duration: 2000,
position: "bottom",
color: "success",
});
await toast.present();
}
}
function handleBack() {
router.back();
}
async function handleEditField(field: keyof UpdateUserProfileBody, label: string) {
if (!userProfile.value)
return;
const currentValue = userProfile.value[field as keyof UserProfileData];
const alert = await alertController.create({
header: `Edit ${label}`,
inputs: [
{
name: "value",
type: "text",
placeholder: `Enter your ${label.toLowerCase()}`,
value: currentValue?.toString() || "",
},
],
buttons: [
{
text: "Cancel",
role: "cancel",
},
{
text: "Save",
handler: async (data) => {
if (data.value !== undefined) {
await updateProfile({ [field]: data.value } as UpdateUserProfileBody);
}
},
},
],
});
await alert.present();
}
async function onUpdateSelect(value: UpdateUserProfileBody["gender"]) {
await updateProfile({ gender: value } as UpdateUserProfileBody);
}
async function onChangeDateTime(event: CustomEvent) {
const selectedDate = useDateFormat(event.detail.value, "YYYY-MM-DD");
await updateProfile({ birthday: selectedDate.value } as UpdateUserProfileBody);
}
async function handleSignOut() {
const alert = await alertController.create({
header: "Sign Out",
message: "Are you sure you want to sign out?",
buttons: [
{
text: "Cancel",
role: "cancel",
},
{
text: "Sign Out",
role: "destructive",
handler: async () => {
authClient.signOut();
router.replace("/layout/riwa");
},
},
],
});
await alert.present();
}
onMounted(() => {
getUserProfile();
});
</script>
<template>
<ion-page>
<ion-header>
<ion-toolbar class="ui-toolbar">
<ion-buttons slot="start">
<ion-button @click="handleBack">
<ion-icon slot="icon-only" :icon="arrowBackOutline" />
</ion-button>
</ion-buttons>
<ion-title>Settings</ion-title>
</ion-toolbar>
</ion-header>
<ion-content :fullscreen="true">
<div class="flex flex-col items-center justify-center py-10">
<div class="relative">
<ui-avatar class="size-25" />
</div>
<div class="mt-4 text-lg font-semibold">
{{ userProfile?.nickname || 'User Name' }}
</div>
</div>
<!-- User Info List -->
<ion-list class="mt-5">
<ion-item button @click="handleEditField('nickname', 'Nickname')">
<ion-label>
<p class="text-xs text-text-400">
Full Name
</p>
<h2 class="mt-1">
{{ userProfile?.nickname || 'Not set' }}
</h2>
</ion-label>
</ion-item>
<ion-item button>
<ion-select interface="action-sheet" toggle-icon="" label-placement="floating" :model-value="userProfile?.gender" label="Gender" placeholder="Select Gender" @update:model-value="onUpdateSelect">
<ion-select-option v-for="item in GenderEnum" :key="item" :value="item">
{{ item }}
</ion-select-option>
</ion-select>
</ion-item>
<ion-item button>
<ion-datetime-button datetime="datetime" color="primary">
<div slot="date-target">
{{ birthday }}
</div>
</ion-datetime-button>
<ion-modal :keep-contents-mounted="true">
<ion-datetime
id="datetime"
class="ui-datetime"
done-text="Done"
presentation="date"
:value="userProfile?.birthday"
:show-default-buttons="true"
@ion-change="onChangeDateTime"
/>
</ion-modal>
</ion-item>
</ion-list>
<!-- Action Buttons -->
<div class="px-5 mt-10">
<ion-button expand="block" color="tertiary" @click="handleSignOut">
Sign Out
</ion-button>
</div>
</ion-content>
</ion-page>
</template>
<style lang='css' scoped>
ion-avatar {
--border-radius: 50%;
}
ion-item {
--min-height: 60px;
}
</style>