feat: add account settings and router dependency

This commit is contained in:
2026-10-16 19:42:41 +08:00
parent 3ef97a472e
commit 483cb4ec8c
5 changed files with 142 additions and 6 deletions
+1
View File
@@ -9,6 +9,7 @@ const router=createRouter({history:createWebHistory(),routes:[
{path:'/login',component:()=>import('./views/LoginView.vue'),meta:{public:true}},
{path:'/register',component:()=>import('./views/RegisterView.vue'),meta:{public:true}},
{path:'/profile',component:()=>import('./views/ProfileView.vue')},
{path:'/settings',component:()=>import('./views/AccountSettingsView.vue')},
{path:'/admin',component:()=>import('./views/AdminView.vue'),meta:{role:'admin'}},
]})
router.beforeEach(async to=>{if(to.meta.public)return true;try{role=(await getCurrentUser()).role}catch{return {path:'/login',query:{redirect:to.fullPath}}}if(to.meta.role&&role!==to.meta.role)return {path:'/profile',query:{forbidden:'1'}};return true})
@@ -0,0 +1,96 @@
<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 < 8) {
error.value = '新密码至少需要 8 个字符。'
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="8" autocomplete="new-password" /></label>
<label>确认新密码<input v-model="password.confirm_password" type="password" required minlength="8" autocomplete="new-password" /></label>
<p>修改密码后会退出当前会话,请使用新密码重新登录。</p>
<button :disabled="savingPassword">{{ savingPassword ? '提交中…' : '修改密码' }}</button>
</form>
</template>
</main>
</template>
+1 -1
View File
@@ -1 +1 @@
<script setup lang="ts">import { onMounted,ref } from 'vue';import { getCurrentUser,logout } from '../services/auth';import { useRouter } from 'vue-router';const user=ref<any>(),error=ref(''),router=useRouter();onMounted(async()=>{try{user.value=await getCurrentUser()}catch(e){error.value=e instanceof Error?e.message:'无法读取资料'}});async function signOut(){await logout();router.replace('/login')}</script><template><main><h1>个人中心</h1><p v-if="error">{{error}}</p><dl v-else-if="user"><dt>用户名</dt><dd>{{user.username}}</dd><dt>昵称</dt><dd>{{user.nickname||'未设置'}}</dd><dt>邮箱</dt><dd>{{user.email||'未设置'}}</dd><dt>角色</dt><dd>{{user.role}}</dd></dl><button @click="signOut">退出登录</button></main></template>
<script setup lang="ts">import { onMounted,ref } from 'vue';import { getCurrentUser,logout } from '../services/auth';import { useRouter } from 'vue-router';import type { SessionUser } from '../types/auth';const user=ref<SessionUser>(),error=ref(''),router=useRouter();onMounted(async()=>{try{user.value=await getCurrentUser()}catch(e){error.value=e instanceof Error?e.message:'无法读取资料'}});async function signOut(){try{await logout()}finally{router.replace('/login')}}</script><template><main><h1>个人中心</h1><p v-if="error">{{error}}</p><dl v-else-if="user"><dt>用户名</dt><dd>{{user.username}}</dd><dt>昵称</dt><dd>{{user.nickname||'未设置'}}</dd><dt>邮箱</dt><dd>{{user.email||'未设置'}}</dd><dt>角色</dt><dd>{{user.role}}</dd><dt>注册时间</dt><dd>{{new Date(user.created_at).toLocaleString()}}</dd></dl><div><RouterLink to="/settings">账户设置</RouterLink><button @click="signOut">退出登录</button></div></main></template>