feat: 添加用户设置页面及相关功能,更新用户头像组件
This commit is contained in:
@@ -15,3 +15,7 @@ export type WithdrawBody = Omit<Parameters<typeof client.api.asset.withdraw.post
|
||||
assetCode: AssetCodeEnum;
|
||||
withdrawMethod: WithdrawMethodEnum;
|
||||
};
|
||||
|
||||
export type UserProfileData = Treaty.Data<typeof client.api.user.profile.get>;
|
||||
|
||||
export type UpdateUserProfileBody = Parameters<typeof client.api.user.profile.put>[0];
|
||||
|
||||
21
src/components/ui/avatar/index.vue
Normal file
21
src/components/ui/avatar/index.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang='ts' setup></script>
|
||||
|
||||
<template>
|
||||
<ion-avatar v-bind="$attrs">
|
||||
<img
|
||||
src="https://api.iconify.design/material-icon-theme:bruno.svg"
|
||||
alt="Avatar"
|
||||
>
|
||||
</ion-avatar>
|
||||
</template>
|
||||
|
||||
<style lang='css' scoped>
|
||||
ion-avatar {
|
||||
background-color: #7e5cff;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
ion-avatar {
|
||||
background-color: #7e5cff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -48,6 +48,10 @@ const routes: Array<RouteRecordRaw> = [
|
||||
path: "/withdraw/index",
|
||||
component: () => import("@/views/withdraw/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/user/settings",
|
||||
component: () => import("@/views/user/settings.vue"),
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
.ui-toolbar {
|
||||
--background: var(--ion-color-primary-contrast);
|
||||
--min-height: 50px;
|
||||
}
|
||||
|
||||
@@ -19,9 +19,7 @@ const { user } = useAuth();
|
||||
<div class="container">
|
||||
<div class="user-info">
|
||||
<div class="avatar-wrapper">
|
||||
<ion-avatar class="avatar size-16">
|
||||
<img alt="User avatar" class="size-full" :src="user?.image || 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'">
|
||||
</ion-avatar>
|
||||
<ui-avatar class="size-18" />
|
||||
</div>
|
||||
<IonText class="user-email">
|
||||
{{ user?.email }}
|
||||
|
||||
@@ -7,9 +7,7 @@ const { user } = useAuth();
|
||||
<template>
|
||||
<div class="user-info-container">
|
||||
<div class="user-info">
|
||||
<ion-avatar>
|
||||
<img class="size-full" alt="User avatar" :src="user?.image || 'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg'">
|
||||
</ion-avatar>
|
||||
<ui-avatar class="size-18" />
|
||||
<div>
|
||||
<div class="user-name">
|
||||
{{ user?.email }}
|
||||
|
||||
@@ -19,7 +19,7 @@ import WalletCard from "./components/wallet-card.vue";
|
||||
<ion-button fill="clear">
|
||||
<ion-icon slot="icon-only" :icon="notificationsOutline" />
|
||||
</ion-button>
|
||||
<ion-button fill="clear">
|
||||
<ion-button fill="clear" router-link="/user/settings">
|
||||
<ion-icon slot="icon-only" :icon="settingsOutline" />
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
194
src/views/user/settings.vue
Normal file
194
src/views/user/settings.vue
Normal file
@@ -0,0 +1,194 @@
|
||||
<script lang='ts' setup>
|
||||
import type { UpdateUserProfileBody, UserProfileData } from "@/api/types";
|
||||
import { alertController, toastController } from "@ionic/vue";
|
||||
import { arrowBackOutline, cameraOutline, chevronForwardOutline } from "ionicons/icons";
|
||||
import { client } from "@/api";
|
||||
|
||||
const router = useRouter();
|
||||
const userProfile = ref<UserProfileData | null>(null);
|
||||
|
||||
async function getUserProfile() {
|
||||
const { data } = await client.api.user.profile.get();
|
||||
if (data) {
|
||||
userProfile.value = data;
|
||||
}
|
||||
}
|
||||
|
||||
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 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 () => {
|
||||
// TODO: 实现登出逻辑
|
||||
router.push("/");
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
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?.fullName || 'User Name' }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Info List -->
|
||||
<ion-list class="mt-5">
|
||||
<ion-item button @click="handleEditField('fullName', 'Full Name')">
|
||||
<ion-label>
|
||||
<p class="text-xs text-text-400">
|
||||
Full Name
|
||||
</p>
|
||||
<h2 class="mt-1">
|
||||
{{ userProfile?.fullName || 'Not set' }}
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button @click="handleEditField('gender', 'Gender')">
|
||||
<ion-label>
|
||||
<p class="text-xs text-text-400">
|
||||
Gender
|
||||
</p>
|
||||
<h2 class="mt-1">
|
||||
{{ userProfile?.gender || 'Not set' }}
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button @click="handleEditField('birthday', 'Birthday')">
|
||||
<ion-label>
|
||||
<p class="text-xs text-text-400">
|
||||
Birthday
|
||||
</p>
|
||||
<h2 class="mt-1">
|
||||
{{ userProfile?.birthday || 'Not set' }}
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button @click="handleEditField('country', 'Country')">
|
||||
<ion-label>
|
||||
<p class="text-xs text-text-400">
|
||||
Country
|
||||
</p>
|
||||
<h2 class="mt-1">
|
||||
{{ userProfile?.country || 'Not set' }}
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<ion-item button @click="handleEditField('city', 'City')">
|
||||
<ion-label>
|
||||
<p class="text-xs text-text-400">
|
||||
City
|
||||
</p>
|
||||
<h2 class="mt-1">
|
||||
{{ userProfile?.city || 'Not set' }}
|
||||
</h2>
|
||||
</ion-label>
|
||||
</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%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user