Files
xsh-assistant-next/pages/profile.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>