Files
VLoop/frontend/src/views/SettingsView.vue
2025-12-25 22:40:56 +08:00

169 lines
4.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, nextTick, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import AppShell from '../components/AppShell.vue'
import UserAvatar from '../components/UserAvatar.vue'
import { ApiError } from '../api/client'
import * as accountApi from '../api/account'
import { useAuthStore } from '../stores/auth'
import { useToastStore } from '../stores/toast'
const router = useRouter()
const auth = useAuthStore()
const toast = useToastStore()
const busy = ref(false)
const me = computed(() => ({
id: auth.claims?.account_id ?? 0,
username: auth.claims?.username ?? '',
}))
const rename = reactive({
open: false,
newUsername: '',
})
async function openRename() {
if (!auth.isLoggedIn) return
rename.open = true
rename.newUsername = me.value.username
await nextTick()
}
async function submitRename() {
if (!auth.isLoggedIn) return
if (busy.value) return
const newUsername = rename.newUsername.trim()
if (!newUsername) {
toast.error('请输入新用户名')
return
}
busy.value = true
try {
const res = await accountApi.rename(newUsername)
auth.setToken(res.token)
rename.open = false
toast.success('改名成功(已刷新 token')
} catch (e) {
const msg = e instanceof ApiError ? e.message : String(e)
toast.error(msg)
} finally {
busy.value = false
}
}
async function goLogin() {
await router.push('/account')
}
async function goChangePassword() {
await router.push('/account/change-password')
}
async function onLogout() {
if (!auth.isLoggedIn) return
if (busy.value) return
if (!window.confirm('确认退出登录?')) return
busy.value = true
try {
await accountApi.logout()
} catch (e) {
const msg = e instanceof ApiError ? e.message : String(e)
toast.error(`登出失败:${msg}`)
} finally {
auth.clearToken()
rename.open = false
toast.info('已退出登录')
busy.value = false
await router.push('/')
}
}
</script>
<template>
<AppShell>
<div v-if="!auth.isLoggedIn" class="grid two">
<div class="card">
<p class="title">设置</p>
<p class="subtle">需要先登录后才能进行改名/退出等操作</p>
<div class="row" style="margin-top: 12px; justify-content: flex-end">
<button class="primary" type="button" @click="goLogin">去登录</button>
</div>
</div>
<div class="card">
<p class="title">提示</p>
<p class="muted">登录入口在账号</p>
</div>
</div>
<div v-else class="grid two">
<div class="card">
<div class="row" style="justify-content: space-between; align-items: flex-start">
<div class="row" style="gap: 12px; align-items: center">
<UserAvatar :username="me.username" :id="me.id" :size="56" />
<div>
<div class="title" style="margin: 0">@{{ me.username }}</div>
<div class="subtle mono">#{{ me.id }}</div>
</div>
</div>
</div>
<div class="card" style="margin-top: 14px">
<div class="row" style="justify-content: space-between; align-items: center">
<p class="title" style="margin: 0">账号设置</p>
<button class="ghost" type="button" :disabled="busy" @click="openRename">改名</button>
</div>
<div v-if="rename.open" class="grid" style="margin-top: 12px">
<div>
<label>new_username</label>
<input v-model.trim="rename.newUsername" @keydown.enter="submitRename" />
</div>
<div class="row" style="justify-content: flex-end">
<button type="button" :disabled="busy" @click="rename.open = false">取消</button>
<button class="primary" type="button" :disabled="busy" @click="submitRename">提交</button>
</div>
</div>
</div>
<div class="card" style="margin-top: 14px">
<p class="title">账号安全</p>
<div class="row">
<button class="ghost" type="button" :disabled="busy" @click="goChangePassword">修改密码</button>
<button class="danger" type="button" :disabled="busy" @click="onLogout">退出登录</button>
</div>
</div>
</div>
<div class="card">
<p class="title">说明</p>
<div class="grid" style="margin-top: 10px">
<div class="pill ok">改名后会返回新 token token 立即失效</div>
<div class="pill ok">退出登录会清空本地 token</div>
<div class="pill">修改密码无需登录但成功后会让旧 token 失效</div>
</div>
</div>
</div>
</AppShell>
</template>
<style scoped>
.ghost {
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(0, 0, 0, 0.18);
color: rgba(255, 255, 255, 0.86);
border-radius: 12px;
padding: 10px 12px;
cursor: pointer;
}
.ghost:hover {
background: rgba(255, 255, 255, 0.1);
}
</style>