feat(profile): 添加个人中心页面及资料编辑功能
This commit is contained in:
333
pages/profile.vue
Normal file
333
pages/profile.vue
Normal file
@@ -0,0 +1,333 @@
|
||||
<script lang="ts" setup>
|
||||
import { number, object, string, ref as yref, type InferType } from 'yup'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const toast = useToast()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const loginState = useLoginState()
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
slot: 'info',
|
||||
label: '基本资料',
|
||||
icon: 'tabler:user-square-rounded',
|
||||
},
|
||||
{
|
||||
slot: 'security',
|
||||
label: '账号安全',
|
||||
icon: 'tabler:shield-half-filled',
|
||||
},
|
||||
]
|
||||
|
||||
const currentTab = computed({
|
||||
get() {
|
||||
const index = tabs.findIndex((item) => item.slot === route.query.tab)
|
||||
if (index === -1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return index
|
||||
},
|
||||
set(value) {
|
||||
// Hash is specified here to prevent the page from scrolling to the top
|
||||
router.replace({
|
||||
query: { tab: tabs[value].slot },
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
const editProfileSchema = object({
|
||||
nickname: string(),
|
||||
email: string().notRequired().email('请输入正确的邮箱地址'),
|
||||
sex: number().required('请选择性别'),
|
||||
company: string().required('请输入公司名称'),
|
||||
})
|
||||
|
||||
type EditProfileSchema = InferType<typeof editProfileSchema>
|
||||
|
||||
const editProfileState = reactive({
|
||||
nickname: loginState.user?.nickname || '',
|
||||
email: loginState.user?.email || '',
|
||||
sex: loginState.user.sex || 0,
|
||||
company: loginState.user?.company || '',
|
||||
})
|
||||
|
||||
const isEditProfileModified = ref(false)
|
||||
|
||||
const onEditProfileSubmit = (event: FormSubmitEvent<EditProfileSchema>) => {
|
||||
useFetchWrapped<Partial<UserSchema> & AuthedRequest, BaseResponse<{}>>(
|
||||
'App.User_User.EditProfile',
|
||||
{
|
||||
token: loginState.token!,
|
||||
user_id: loginState.user.id,
|
||||
...(event.data as UserSchema),
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.ret === 200) {
|
||||
toast.add({
|
||||
title: '个人资料已更新',
|
||||
description: '您的个人资料已更新成功',
|
||||
color: 'green',
|
||||
})
|
||||
loginState.updateProfile()
|
||||
isEditProfileModified.value = false
|
||||
} else {
|
||||
toast.add({
|
||||
title: '更新失败',
|
||||
description: res.msg || '未知错误',
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.add({
|
||||
title: '更新失败',
|
||||
description: err.message || '未知错误',
|
||||
color: 'red',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const changePasswordState = reactive({
|
||||
old_password: '',
|
||||
new_password: '',
|
||||
confirm_password: '',
|
||||
})
|
||||
|
||||
const changePasswordSchema = object({
|
||||
old_password: string().required('请输入旧密码'),
|
||||
new_password: string().required('请输入新密码').min(6, '密码长度至少为6位'),
|
||||
confirm_password: string()
|
||||
.required('请再次输入新密码')
|
||||
.oneOf([yref('new_password')], '两次输入的密码不一致'),
|
||||
})
|
||||
|
||||
type ChangePasswordSchema = InferType<typeof changePasswordSchema>
|
||||
|
||||
const onChangePasswordSubmit = (
|
||||
event: FormSubmitEvent<ChangePasswordSchema>
|
||||
) => {
|
||||
useFetchWrapped<req.user.ChangePassword & AuthedRequest, BaseResponse<{}>>(
|
||||
'App.User_User.ChangePassword',
|
||||
{
|
||||
token: loginState.token!,
|
||||
user_id: loginState.user.id,
|
||||
username: loginState.user.username,
|
||||
old_password: event.data.old_password,
|
||||
new_password: event.data.new_password,
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.ret === 200) {
|
||||
toast.add({
|
||||
title: '密码已修改',
|
||||
description: '请重新登录',
|
||||
color: 'green',
|
||||
})
|
||||
setTimeout(() => {
|
||||
loginState.logout()
|
||||
window.location.reload()
|
||||
}, 2000)
|
||||
} else {
|
||||
toast.add({
|
||||
title: '修改密码失败',
|
||||
description: res.msg || '未知错误',
|
||||
color: 'red',
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.add({
|
||||
title: '修改密码失败',
|
||||
description: err.message || '未知错误',
|
||||
color: 'red',
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<LoginNeededContent
|
||||
content-class="w-full h-full bg-white dark:bg-neutral-900 p-4 sm:p-0"
|
||||
>
|
||||
<div class="container max-w-[1280px] mx-auto pt-12 flex flex-col gap-12">
|
||||
<h1 class="text-2xl font-medium inline-flex items-center gap-2">
|
||||
<UIcon
|
||||
name="line-md:person"
|
||||
class="text-3xl"
|
||||
/>
|
||||
<span>个人中心</span>
|
||||
</h1>
|
||||
<div class="flex flex-col gap-12">
|
||||
<div class="flex items-center gap-4 px-1">
|
||||
<UAvatar
|
||||
size="xl"
|
||||
icon="tabler:user"
|
||||
:src="loginState.user?.avatar"
|
||||
:alt="loginState.user?.nickname || loginState.user?.username"
|
||||
:ui="{
|
||||
rounded: 'rounded-xl',
|
||||
size: {
|
||||
huge: 'w-48 h-48 text-4xl',
|
||||
},
|
||||
}"
|
||||
/>
|
||||
<div>
|
||||
<h2 class="text-xl font-medium">
|
||||
{{ loginState.user?.username }}
|
||||
<span
|
||||
v-if="loginState.user?.nickname"
|
||||
class="text-neutral-500 dark:text-neutral-400"
|
||||
>
|
||||
({{ loginState.user?.nickname }})
|
||||
</span>
|
||||
</h2>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
{{ loginState.user?.company || '未填写公司' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<UTabs
|
||||
v-model="currentTab"
|
||||
:items="tabs"
|
||||
orientation="vertical"
|
||||
:ui="{
|
||||
wrapper:
|
||||
'w-full flex flex-col sm:flex-row items-start gap-4 sm:gap-16',
|
||||
list: {
|
||||
width: 'w-full sm:w-48 h-fit',
|
||||
background: 'bg-transparent',
|
||||
tab: { active: 'bg-neutral-100 dark:bg-neutral-700' },
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template #info>
|
||||
<div class="tab-content space-y-4">
|
||||
<UForm
|
||||
class="max-w-96 space-y-6"
|
||||
:state="editProfileState"
|
||||
:schema="editProfileSchema"
|
||||
@submit="onEditProfileSubmit"
|
||||
@change="isEditProfileModified = true"
|
||||
>
|
||||
<UFormGroup
|
||||
name="username"
|
||||
label="账户名"
|
||||
>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
{{ loginState.user?.username }}
|
||||
</p>
|
||||
<!-- <UInput v-model="editProfileState.username" /> -->
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
name="mobile"
|
||||
label="手机号码"
|
||||
>
|
||||
<p class="text-sm text-neutral-500 dark:text-neutral-400">
|
||||
{{ loginState.user?.mobile }}
|
||||
</p>
|
||||
<!-- <UInput v-model="editProfileState.mobile" /> -->
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
name="nickname"
|
||||
label="姓名"
|
||||
help="您的真实姓名,便于系统管理员联系您"
|
||||
>
|
||||
<UInput v-model="editProfileState.nickname" />
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
name="sex"
|
||||
label="性别"
|
||||
>
|
||||
<USelect
|
||||
v-model="editProfileState.sex"
|
||||
:options="[
|
||||
{ label: '男', value: 1 },
|
||||
{ label: '女', value: 2 },
|
||||
{ label: '保密', value: 0 },
|
||||
]"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
name="email"
|
||||
label="电子邮箱"
|
||||
>
|
||||
<UInput v-model="editProfileState.email" />
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
name="company"
|
||||
label="公司/学校/组织名称"
|
||||
help="您所在的公司或组织名称"
|
||||
>
|
||||
<UInput v-model="editProfileState.company" />
|
||||
</UFormGroup>
|
||||
|
||||
<div>
|
||||
<UButton
|
||||
type="submit"
|
||||
:disabled="!isEditProfileModified"
|
||||
>
|
||||
保存更改
|
||||
</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
</div>
|
||||
</template>
|
||||
<template #security>
|
||||
<div class="tab-content space-y-4">
|
||||
<UForm
|
||||
class="max-w-96 space-y-6"
|
||||
:schema="changePasswordSchema"
|
||||
:state="changePasswordState"
|
||||
@submit="onChangePasswordSubmit"
|
||||
>
|
||||
<UFormGroup
|
||||
name="old_password"
|
||||
label="旧密码"
|
||||
>
|
||||
<UInput
|
||||
type="password"
|
||||
v-model="changePasswordState.old_password"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
name="new_password"
|
||||
label="新密码"
|
||||
>
|
||||
<UInput
|
||||
type="password"
|
||||
v-model="changePasswordState.new_password"
|
||||
/>
|
||||
</UFormGroup>
|
||||
<UFormGroup
|
||||
name="confirm_password"
|
||||
label="确认新密码"
|
||||
>
|
||||
<UInput
|
||||
type="password"
|
||||
v-model="changePasswordState.confirm_password"
|
||||
/>
|
||||
</UFormGroup>
|
||||
|
||||
<div>
|
||||
<UButton type="submit">修改密码</UButton>
|
||||
</div>
|
||||
</UForm>
|
||||
<!-- <UDivider /> -->
|
||||
</div>
|
||||
</template>
|
||||
</UTabs>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LoginNeededContent>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tab-content {
|
||||
@apply bg-neutral-50 dark:bg-neutral-800 rounded-lg p-6;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user