Add gallery search and account menu

This commit is contained in:
2026-05-23 17:36:50 +08:00
parent 3af5d52fb5
commit 00695a3053
2 changed files with 241 additions and 21 deletions
+135 -20
View File
@@ -1,19 +1,26 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { NButton, NSpace } from 'naive-ui'
import { NIcon, NSpace } from 'naive-ui'
import { Logout, Settings, Shield, User } from '@vicons/tabler'
import { useAuthStore } from '@/stores/auth'
import { RouterLink, useRoute } from 'vue-router'
import { RouterLink, useRoute, useRouter } from 'vue-router'
const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()
const isMapRoute = computed(() => route.name === 'map')
const headerHidden = ref(false)
const headerPinnedOpen = ref(false)
const accountCardOpen = ref(false)
const activeNavClass = 'bg-teal-100 text-teal-800 ring-1 ring-teal-200'
const inactiveNavClass = 'text-slate-600 hover:bg-teal-50 hover:text-teal-800'
const displayUsername = computed(() => authStore.profile?.username || authStore.user?.email || 'OpenCloud 用户')
const userEmail = computed(() => authStore.user?.email || '未绑定邮箱')
let lastScrollY = 0
let accountCloseTimer: number | null = null
const HIDE_HEADER_EVENT = 'opencloud:hide-header'
function showHeader(pin = false) {
@@ -69,11 +76,16 @@ onMounted(() => {
onUnmounted(() => {
window.removeEventListener('scroll', handleScroll)
window.removeEventListener(HIDE_HEADER_EVENT, forceHideHeader as EventListener)
if (accountCloseTimer !== null) {
window.clearTimeout(accountCloseTimer)
accountCloseTimer = null
}
})
function syncHeaderForRoute() {
headerPinnedOpen.value = false
headerHidden.value = false
accountCardOpen.value = false
}
watch(() => route.fullPath, () => {
@@ -87,6 +99,32 @@ watch(isMapRoute, () => {
onMounted(() => {
syncHeaderForRoute()
})
function openAccountCard() {
if (accountCloseTimer !== null) {
window.clearTimeout(accountCloseTimer)
accountCloseTimer = null
}
accountCardOpen.value = true
}
function closeAccountCard() {
if (accountCloseTimer !== null) {
window.clearTimeout(accountCloseTimer)
}
accountCloseTimer = window.setTimeout(() => {
accountCardOpen.value = false
accountCloseTimer = null
}, 100)
}
async function handleLogout() {
await authStore.logout()
closeAccountCard()
if (route.meta.requiresAuth) {
router.push({ name: 'map' })
}
}
</script>
<template>
@@ -155,25 +193,92 @@ onMounted(() => {
</RouterLink>
<template v-if="authStore.isLoggedIn">
<RouterLink
to="/profile"
class="no-underline"
<div
class="relative"
@mouseenter="openAccountCard"
@mouseleave="closeAccountCard"
@focusin="openAccountCard"
@focusout="closeAccountCard"
>
<span
class="inline-flex h-8 max-w-[7.5rem] items-center border border-teal-100 bg-white/80 px-3 text-sm font-medium text-teal-800 shadow-[3px_3px_0_0_rgba(20,184,166,0.08)] transition-colors hover:border-teal-200 hover:bg-teal-50 md:h-10 md:max-w-none"
<button
type="button"
class="inline-flex h-8 max-w-[8.5rem] items-center gap-2 border border-teal-100 bg-white/80 px-3 text-sm font-medium text-teal-800 shadow-[3px_3px_0_0_rgba(20,184,166,0.08)] transition-colors hover:border-teal-200 hover:bg-teal-50 md:h-10 md:max-w-none"
:class="route.name === 'profile' || route.name === 'profile-settings' ? 'bg-teal-50 ring-1 ring-teal-200' : ''"
aria-haspopup="menu"
:aria-expanded="accountCardOpen"
>
<span class="truncate">@{{ authStore.profile?.username }}</span>
</span>
</RouterLink>
<NButton
quaternary
size="small"
class="md:!h-10 md:!px-4 md:!text-sm"
@click="authStore.logout()"
>
登出
</NButton>
<NIcon size="16" class="shrink-0">
<User />
</NIcon>
<span class="truncate">@{{ displayUsername }}</span>
</button>
<div
v-if="accountCardOpen"
class="absolute right-0 top-full z-50 w-72 pt-3"
role="menu"
>
<div class="border border-slate-200 bg-white shadow-[8px_8px_0_0_rgba(15,23,42,0.08)]">
<div class="border-b border-slate-200 bg-[linear-gradient(180deg,#f0fdfa_0%,#ffffff_100%)] px-4 py-4">
<div class="flex items-center gap-3">
<div class="flex h-11 w-11 shrink-0 items-center justify-center border border-teal-200 bg-[linear-gradient(135deg,#ecfeff_0%,#ccfbf1_100%)] text-lg font-bold text-teal-800">
{{ displayUsername.slice(0, 1).toUpperCase() }}
</div>
<div class="min-w-0">
<p class="truncate text-sm font-semibold text-slate-950">@{{ displayUsername }}</p>
<p class="mt-1 truncate text-xs text-slate-500">{{ userEmail }}</p>
</div>
</div>
<div class="mt-3 flex items-center gap-2 text-xs text-slate-500">
<span class="border border-teal-100 bg-teal-50 px-2 py-1 text-teal-700">
{{ authStore.isAdmin ? '管理员' : '观测者' }}
</span>
<span>OpenCloud 账号</span>
</div>
</div>
<div class="py-2">
<RouterLink
to="/profile"
class="flex items-center gap-3 px-4 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-teal-50 hover:text-teal-800"
role="menuitem"
>
<NIcon size="18"><User /></NIcon>
<span>个人中心</span>
</RouterLink>
<RouterLink
to="/profile/settings"
class="flex items-center gap-3 px-4 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-teal-50 hover:text-teal-800"
role="menuitem"
>
<NIcon size="18"><Settings /></NIcon>
<span>账号设置</span>
</RouterLink>
<RouterLink
v-if="authStore.isAdmin"
to="/admin"
class="flex items-center gap-3 px-4 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-amber-50 hover:text-amber-700"
role="menuitem"
>
<NIcon size="18"><Shield /></NIcon>
<span>管理后台</span>
</RouterLink>
</div>
<div class="border-t border-slate-200 p-2">
<button
type="button"
class="flex w-full items-center gap-3 px-2 py-2.5 text-left text-sm font-medium text-rose-600 transition-colors hover:bg-rose-50 hover:text-rose-700"
role="menuitem"
@click="handleLogout"
>
<NIcon size="18"><Logout /></NIcon>
<span>登出</span>
</button>
</div>
</div>
</div>
</div>
</template>
<template v-else>
@@ -181,13 +286,23 @@ onMounted(() => {
to="/login"
class="no-underline"
>
<NButton quaternary size="small" class="md:!h-10 md:!px-4 md:!text-sm">登录</NButton>
<span
class="inline-flex h-8 items-center border border-slate-200 bg-white/80 px-3 text-sm font-medium text-slate-700 shadow-[3px_3px_0_0_rgba(15,23,42,0.06)] transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-800 md:h-10 md:px-4"
:class="route.name === 'login' ? 'bg-teal-50 ring-1 ring-teal-200 text-teal-800' : ''"
>
登录
</span>
</RouterLink>
<RouterLink
to="/register"
class="no-underline"
>
<NButton type="primary" strong size="small" class="md:!h-10 md:!px-4 md:!text-sm">注册</NButton>
<span
class="inline-flex h-8 items-center border border-sky-200 bg-sky-100 px-3 text-sm font-medium text-sky-800 shadow-[4px_4px_0_0_rgba(14,165,233,0.14)] transition-colors hover:bg-sky-50 hover:text-sky-900 md:h-10 md:px-4"
:class="route.name === 'register' ? 'ring-1 ring-sky-300' : ''"
>
注册
</span>
</RouterLink>
</template>
</NSpace>