334 lines
9.9 KiB
Vue
334 lines
9.9 KiB
Vue
<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>
|