97 lines
3.8 KiB
Vue
97 lines
3.8 KiB
Vue
<script setup lang="ts">
|
||
import { onMounted, ref } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { changePassword, getCurrentUser, updateMyProfile } from '../services/auth'
|
||
import type { SessionUser } from '../types/auth'
|
||
|
||
const router = useRouter()
|
||
const user = ref<SessionUser>()
|
||
const profile = ref({ nickname: '', email: '' })
|
||
const password = ref({ current_password: '', new_password: '', confirm_password: '' })
|
||
const loading = ref(true)
|
||
const savingProfile = ref(false)
|
||
const savingPassword = ref(false)
|
||
const error = ref('')
|
||
const success = ref('')
|
||
|
||
onMounted(async () => {
|
||
try {
|
||
user.value = await getCurrentUser()
|
||
profile.value = { nickname: user.value.nickname ?? '', email: user.value.email ?? '' }
|
||
} catch (cause) {
|
||
error.value = cause instanceof Error ? cause.message : '无法读取账户资料'
|
||
} finally {
|
||
loading.value = false
|
||
}
|
||
})
|
||
|
||
async function saveProfile() {
|
||
error.value = ''
|
||
success.value = ''
|
||
if (profile.value.email && !/^\S+@\S+\.\S+$/.test(profile.value.email)) {
|
||
error.value = '请输入有效邮箱地址。'
|
||
return
|
||
}
|
||
savingProfile.value = true
|
||
try {
|
||
const updated = await updateMyProfile({ nickname: profile.value.nickname || null, email: profile.value.email || null })
|
||
if (user.value) user.value = { ...user.value, ...updated }
|
||
success.value = '基本资料已保存。'
|
||
} catch (cause) {
|
||
error.value = cause instanceof Error ? cause.message : '保存资料失败'
|
||
} finally {
|
||
savingProfile.value = false
|
||
}
|
||
}
|
||
|
||
async function savePassword() {
|
||
error.value = ''
|
||
success.value = ''
|
||
if (password.value.new_password.length < 15) {
|
||
error.value = '新密码至少需要 15 个字符。'
|
||
return
|
||
}
|
||
if (password.value.new_password !== password.value.confirm_password) {
|
||
error.value = '两次输入的新密码不一致。'
|
||
return
|
||
}
|
||
savingPassword.value = true
|
||
try {
|
||
await changePassword({ current_password: password.value.current_password, new_password: password.value.new_password })
|
||
password.value = { current_password: '', new_password: '', confirm_password: '' }
|
||
router.replace('/login?changed=1')
|
||
} catch (cause) {
|
||
error.value = cause instanceof Error ? cause.message : '修改密码失败'
|
||
} finally {
|
||
savingPassword.value = false
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<main>
|
||
<RouterLink to="/profile">返回个人中心</RouterLink>
|
||
<h1>账户设置</h1>
|
||
<p v-if="loading">正在读取账户资料…</p>
|
||
<template v-else>
|
||
<p v-if="error" role="alert">{{ error }}</p>
|
||
<p v-if="success" role="status">{{ success }}</p>
|
||
<form @submit.prevent="saveProfile">
|
||
<h2>基本资料</h2>
|
||
<label>用户名<input :value="user?.username" disabled /></label>
|
||
<label>昵称<input v-model.trim="profile.nickname" maxlength="80" autocomplete="nickname" /></label>
|
||
<label>邮箱<input v-model.trim="profile.email" type="email" maxlength="320" autocomplete="email" /></label>
|
||
<button :disabled="savingProfile">{{ savingProfile ? '保存中…' : '保存资料' }}</button>
|
||
</form>
|
||
<form @submit.prevent="savePassword">
|
||
<h2>修改密码</h2>
|
||
<label>当前密码<input v-model="password.current_password" type="password" required autocomplete="current-password" /></label>
|
||
<label>新密码<input v-model="password.new_password" type="password" required minlength="15" autocomplete="new-password" /></label>
|
||
<label>确认新密码<input v-model="password.confirm_password" type="password" required minlength="15" autocomplete="new-password" /></label>
|
||
<p>修改密码后会退出当前会话,请使用新密码重新登录。</p>
|
||
<button :disabled="savingPassword">{{ savingPassword ? '提交中…' : '修改密码' }}</button>
|
||
</form>
|
||
</template>
|
||
</main>
|
||
</template>
|