103 lines
5.3 KiB
Vue
103 lines
5.3 KiB
Vue
<script setup lang="ts">
|
|
import { Bell, Check, ChevronRight, CircleUserRound, Cloud, LogOut, Pencil, ShieldCheck, X } from "@lucide/vue";
|
|
import { ref } from "vue";
|
|
import { useRouter } from "vue-router";
|
|
import QuickEntryHost from "../components/QuickEntryHost.vue";
|
|
import { useAuthStore } from "../stores/auth";
|
|
import { useLedgerStore } from "../stores/ledgers";
|
|
|
|
const auth = useAuthStore();
|
|
const ledgerStore = useLedgerStore();
|
|
const router = useRouter();
|
|
const editing = ref(false);
|
|
const name = ref("");
|
|
const saving = ref(false);
|
|
const errorMessage = ref("");
|
|
const loggingOut = ref(false);
|
|
|
|
function startEditing() {
|
|
name.value = auth.user?.name ?? "";
|
|
errorMessage.value = "";
|
|
editing.value = true;
|
|
}
|
|
|
|
async function saveName() {
|
|
if (!name.value.trim() || saving.value) return;
|
|
saving.value = true;
|
|
errorMessage.value = "";
|
|
try {
|
|
await auth.updateName(name.value.trim());
|
|
await ledgerStore.loadCurrentMembers();
|
|
editing.value = false;
|
|
} catch {
|
|
errorMessage.value = "姓名保存失败";
|
|
} finally {
|
|
saving.value = false;
|
|
}
|
|
}
|
|
|
|
async function logout() {
|
|
if (loggingOut.value) return;
|
|
loggingOut.value = true;
|
|
try {
|
|
await auth.logout();
|
|
await router.replace({ name: "login" });
|
|
} finally {
|
|
loggingOut.value = false;
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<main class="app-shell me-shell">
|
|
<header class="me-header"><strong>我的</strong></header>
|
|
<div class="me-scroll">
|
|
<section class="profile-band">
|
|
<span><CircleUserRound :size="32" /></span>
|
|
<form v-if="editing" @submit.prevent="saveName">
|
|
<input v-model="name" maxlength="40" aria-label="姓名" autocomplete="name" autofocus />
|
|
<button type="button" aria-label="取消修改" title="取消" @click="editing = false"><X :size="18" /></button>
|
|
<button type="submit" aria-label="保存姓名" title="保存" :disabled="saving || !name.trim()"><Check :size="18" /></button>
|
|
<small v-if="errorMessage">{{ errorMessage }}</small>
|
|
</form>
|
|
<div v-else><strong>{{ auth.user?.name }}</strong><small>成员 ID · {{ auth.user?.id.slice(0, 8) }}</small></div>
|
|
<button v-if="!editing" class="edit-name" type="button" aria-label="修改姓名" title="修改姓名" @click="startEditing"><Pencil :size="18" /></button>
|
|
</section>
|
|
<section class="me-settings">
|
|
<button type="button"><Bell :size="19" /><span>通知与提醒</span><ChevronRight :size="18" /></button>
|
|
<button type="button"><Cloud :size="19" /><span>同步状态</span><ChevronRight :size="18" /></button>
|
|
<button type="button"><ShieldCheck :size="19" /><span>隐私与安全</span><ChevronRight :size="18" /></button>
|
|
</section>
|
|
<button class="logout-action" type="button" :disabled="loggingOut" @click="logout">
|
|
<LogOut :size="19" />{{ loggingOut ? "正在退出" : "退出登录" }}
|
|
</button>
|
|
</div>
|
|
<QuickEntryHost />
|
|
</main>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.me-shell { background:#f5f8f7; }
|
|
.me-header { height:70px; display:flex; align-items:end; border-bottom:1px solid #dce7e4; padding:14px 18px 12px; background:#fff; }
|
|
.me-header strong { font-size:20px; }
|
|
.me-scroll { height:calc(100% - 70px); overflow-y:auto; padding:14px 16px 104px; }
|
|
.profile-band { position:relative; min-height:94px; display:grid; grid-template-columns:58px minmax(0,1fr) 40px; align-items:center; gap:12px; border-top:1px solid #dce7e4; border-bottom:1px solid #dce7e4; padding:12px 14px; background:#fff; }
|
|
.profile-band > span { width:54px; height:54px; display:grid; place-items:center; border-radius:50%; background:#e8f7f3; color:#087f72; }
|
|
.profile-band > div { min-width:0; display:grid; gap:2px; }
|
|
.profile-band strong { overflow:hidden; font-size:16px; text-overflow:ellipsis; white-space:nowrap; }
|
|
.profile-band small { color:#7b8884; font-size:11px; }
|
|
.edit-name,.profile-band form button { width:38px; height:38px; display:grid; place-items:center; border:0; border-radius:50%; background:transparent; color:#536963; }
|
|
.profile-band form { display:grid; grid-template-columns:minmax(0,1fr) 38px 38px; align-items:center; gap:2px; }
|
|
.profile-band form input { min-width:0; height:42px; border:0; border-bottom:2px solid #087f72; outline:0; color:#26342f; font-size:16px; }
|
|
.profile-band form button:last-of-type { background:#e7f6f2; color:#087f72; }
|
|
.profile-band form button:disabled { opacity:.45; }
|
|
.profile-band form small { grid-column:1/-1; color:#cf513d; }
|
|
.me-settings { margin-top:12px; border-top:1px solid #dce7e4; border-bottom:1px solid #dce7e4; padding:0 14px; background:#fff; }
|
|
.me-settings button { width:100%; min-height:58px; display:grid; grid-template-columns:28px 1fr 20px; align-items:center; gap:8px; border:0; border-bottom:1px solid #e5edeb; padding:0; background:#fff; color:#31504a; text-align:left; }
|
|
.me-settings button:last-child { border-bottom:0; }
|
|
.me-settings span { color:#26342f; font-size:14px; }
|
|
.logout-action { width:100%; height:48px; display:flex; align-items:center; justify-content:center; gap:7px; margin-top:18px; border:1px solid #e2c4be; border-radius:8px; background:#fff; color:#bf503e; font-weight:700; }
|
|
.logout-action:disabled { opacity:.55; }
|
|
@media (max-width:360px) { .me-scroll { padding-right:12px; padding-left:12px; } .profile-band { grid-template-columns:52px minmax(0,1fr) 38px; gap:8px; padding-right:10px; padding-left:10px; } }
|
|
</style>
|