<template>
<q-layout view="hHh lpr fFf" class="user-shell user-center-shell">
<q-header class="user-center-header">
<q-toolbar class="user-center-topbar">
<q-btn flat round dense icon="menu" @click="toggleDrawer">
<q-tooltip>导航</q-tooltip>
</q-btn>
<button class="user-center-brand" type="button" @click="goHome">
<span class="auth-logo">
<q-icon name="shield" size="25px" />
</span>
<span>
<strong>Dpz.Core 用户中心</strong>
<small>Identity Workspace</small>
</span>
</button>
<q-space />
<div class="user-route-pill">
<q-icon :name="route.meta.icon" />
<span>{{ route.meta.title }}</span>
</div>
<q-btn flat round icon="logout" @click="signOut">
<q-tooltip>退出登录</q-tooltip>
</q-btn>
<q-btn
round
flat
:icon="$q.dark.isActive ? 'light_mode' : 'dark_mode'"
@click="$q.dark.toggle()"
>
<q-tooltip>{{
$q.dark.isActive ? "浅色模式" : "深色模式"
}}</q-tooltip>
</q-btn>
</q-toolbar>
</q-header>
<q-drawer
v-model="drawer"
show-if-above
:width="drawerWidth"
:mini="miniState"
:mini-width="64"
class="user-center-drawer"
@click.capture="handleDrawerClick"
>
<div class="user-drawer-panel">
<div class="user-profile-tile">
<q-avatar size="54px">
<img v-if="currentUser.avatar" :src="currentUser.avatar" alt="" />
<q-icon v-else name="person" size="34px" />
</q-avatar>
<div>
<strong>{{
currentUser.name || currentUser.id || "Dpz.Core 用户"
}}</strong>
<small>{{ currentUser.id || "Identity verified" }}</small>
</div>
</div>
<div class="user-center-nav">
<section
v-for="group in menuGroups"
:key="group.key"
class="user-center-nav-group"
>
<div class="user-center-nav-group__title">
<span>{{ group.label }}</span>
<small>{{ group.items.length }}</small>
</div>
<q-list class="user-center-nav-list">
<q-item
v-for="item in group.items"
:key="item.key"
clickable
:active="activeNavKey === item.key"
class="user-center-nav__item"
:data-accent="item.accent"
@click="navigate(item.hash)"
>
<q-item-section avatar>
<q-icon :name="item.icon" />
</q-item-section>
<q-item-section>
<q-item-label>{{ item.label }}</q-item-label>
<q-item-label caption>{{ item.subtitle }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</section>
</div>
</div>
</q-drawer>
<q-page-container>
<q-page class="user-center-page-shell">
<div class="user-decor" aria-hidden="true">
<div class="user-grid" />
<div class="user-scan" />
<div class="user-beam user-beam--a" />
<div class="user-beam user-beam--b" />
</div>
<div class="user-center-container">
<router-view v-slot="{ Component, route: viewRoute }">
<component
:is="Component"
:key="viewRoute.matched[0]?.name"
v-bind="currentComponentProps"
@profile-updated="handleProfileUpdated"
/>
</router-view>
</div>
</q-page>
</q-page-container>
</q-layout>
</template>
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useQuasar } from "quasar";
import { useRoute, useRouter } from "vue-router";
import type { UserCenterPayload } from "../models/AuthModels";
import { PageContext } from "../services/PageContext";
import { userMenu } from "./user/userMenu";
const $q = useQuasar();
$q.dark.set("auto");
const context = PageContext.read<UserCenterPayload>();
const currentUser = ref({
...(context.user ?? { id: "", name: "", avatar: "" }),
});
const drawer = ref(false);
const miniState = ref(false);
let drawerToggleLocked = false;
const route = useRoute();
const router = useRouter();
const activeNavKey = computed(
() => String(route.meta.key ?? route.name),
);
const drawerWidth = computed(() => ($q.screen.gt.lg ? 276 : 248));
const menuGroups = computed(() => [
{
key: "account",
label: "身份与安全",
items: userMenu.filter((item) => item.group === "account"),
},
{
key: "content",
label: "内容资产",
items: userMenu.filter((item) => item.group === "content"),
},
]);
const currentComponentProps = computed(() =>
route.meta.key === "articles"
? { articleHost: context.payload?.webHost }
: {},
);
watch(
() => route.meta.title,
(title) => {
document.title = `${title ?? "工作台"} - Dpz.Core 用户中心`;
},
{ immediate: true },
);
onMounted(() => {
document.body.classList.add("user-center-body");
});
onBeforeUnmount(() => {
document.body.classList.remove("user-center-body");
});
function navigate(hash: string) {
void router.push(hash.replace(/^#/, ""));
if ($q.screen.lt.md) {
drawer.value = false;
}
}
function goHome() {
window.location.href = "/";
}
function signOut() {
$q.dialog({
title: "退出登录",
message: "确认退出当前认证中心会话?",
cancel: true,
persistent: true,
}).onOk(() => {
window.location.href = "/sign-out";
});
}
function handleProfileUpdated(profile: {
displayName: string;
avatar?: string;
}) {
currentUser.value = {
...currentUser.value,
name: profile.displayName,
avatar: profile.avatar ?? currentUser.value.avatar,
};
}
function toggleDrawer(event: Event) {
event.preventDefault();
event.stopPropagation();
if (drawerToggleLocked) {
return;
}
drawerToggleLocked = true;
if ($q.screen.gt.md) {
miniState.value = !miniState.value;
} else {
drawer.value = !drawer.value;
}
window.setTimeout(() => {
drawerToggleLocked = false;
}, 120);
}
function handleDrawerClick() {
if ($q.screen.lt.md || !miniState.value || drawerToggleLocked) {
return;
}
drawerToggleLocked = true;
miniState.value = false;
window.setTimeout(() => {
drawerToggleLocked = false;
}, 120);
}
</script>
AI 正在分析代码…
评论加载中...