feat(admin): add contextual sidebar account menu
This commit is contained in:
+106
-12
@@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, watch, type Component } from 'vue'
|
||||
import { NAlert, NButton, NEmpty, NIcon, NSkeleton, NTag, useMessage } from 'naive-ui'
|
||||
import { NAlert, NButton, NEmpty, NIcon, NPopover, NSkeleton, NTag, useMessage } from 'naive-ui'
|
||||
import {
|
||||
Check,
|
||||
ChevronUp,
|
||||
ClipboardCheck,
|
||||
Dashboard,
|
||||
Eye,
|
||||
@@ -10,14 +11,17 @@ import {
|
||||
Home,
|
||||
LayoutSidebarLeftCollapse,
|
||||
LayoutSidebarLeftExpand,
|
||||
Logout,
|
||||
Photo,
|
||||
Refresh,
|
||||
Settings,
|
||||
Shield,
|
||||
Trash,
|
||||
UserCircle,
|
||||
Users,
|
||||
X,
|
||||
} from '@vicons/tabler'
|
||||
import { useRouter } from 'vue-router'
|
||||
import ImageDetailModal from '@/components/cloud/ImageDetailModal.vue'
|
||||
import MiniLocationMap from '@/components/cloud/MiniLocationMap.vue'
|
||||
import { apiRequest } from '@/lib/api'
|
||||
@@ -61,9 +65,11 @@ interface DashboardStats {
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const message = useMessage()
|
||||
const router = useRouter()
|
||||
|
||||
const activeTab = ref<AdminTab>('dashboard')
|
||||
const sidebarCollapsed = ref(false)
|
||||
const accountMenuOpen = ref(false)
|
||||
const loading = ref(true)
|
||||
const actionLoading = ref(false)
|
||||
const loadError = ref('')
|
||||
@@ -117,6 +123,8 @@ const filteredImages = computed(() => {
|
||||
return images.value.filter(item => item.status === imageFilter.value)
|
||||
})
|
||||
const selectedManagedCount = computed(() => selectedImageIds.value.size)
|
||||
const displayUsername = computed(() => authStore.profile?.username || authStore.user?.email || '管理员')
|
||||
const userEmail = computed(() => authStore.user?.email || '未绑定邮箱')
|
||||
|
||||
watch(imageFilter, () => {
|
||||
setSelectedImageIds([])
|
||||
@@ -463,6 +471,20 @@ async function toggleUserDisabled(user: Profile) {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
accountMenuOpen.value = false
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value
|
||||
}
|
||||
|
||||
async function handleLogout() {
|
||||
accountMenuOpen.value = false
|
||||
try {
|
||||
await authStore.logout()
|
||||
} finally {
|
||||
await router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadAdminData)
|
||||
</script>
|
||||
|
||||
@@ -498,7 +520,7 @@ onMounted(loadAdminData)
|
||||
:aria-label="sidebarCollapsed ? '展开侧边栏' : '折叠侧边栏'"
|
||||
:aria-expanded="!sidebarCollapsed"
|
||||
:title="sidebarCollapsed ? '展开侧边栏' : '折叠侧边栏'"
|
||||
@click="sidebarCollapsed = !sidebarCollapsed"
|
||||
@click="toggleSidebar"
|
||||
>
|
||||
<NIcon size="20">
|
||||
<LayoutSidebarLeftExpand v-if="sidebarCollapsed" />
|
||||
@@ -542,17 +564,89 @@ onMounted(loadAdminData)
|
||||
</nav>
|
||||
|
||||
<div class="mt-4 flex gap-2 border-t border-slate-100 pt-3 lg:mt-auto lg:block lg:space-y-2 lg:border-t-0 lg:pt-0">
|
||||
<div
|
||||
class="flex min-w-0 flex-1 items-center gap-3 border border-slate-200 bg-slate-50 px-3 py-2.5 text-slate-700"
|
||||
:class="sidebarCollapsed ? 'lg:justify-center lg:px-0' : ''"
|
||||
:title="sidebarCollapsed ? (authStore.profile?.username || '管理员') : undefined"
|
||||
<NPopover
|
||||
v-model:show="accountMenuOpen"
|
||||
trigger="click"
|
||||
placement="top-start"
|
||||
:show-arrow="false"
|
||||
raw
|
||||
>
|
||||
<NIcon size="22" class="shrink-0"><UserCircle /></NIcon>
|
||||
<span class="min-w-0" :class="sidebarCollapsed ? 'lg:hidden' : ''">
|
||||
<span class="block truncate text-sm font-semibold text-slate-900">{{ authStore.profile?.username || '管理员' }}</span>
|
||||
<span class="mt-0.5 block text-xs text-slate-500">当前管理员</span>
|
||||
</span>
|
||||
</div>
|
||||
<template #trigger>
|
||||
<button
|
||||
type="button"
|
||||
class="flex min-h-[46px] min-w-0 flex-1 items-center gap-3 border px-3 py-2 text-left transition-[background-color,border-color,color,box-shadow] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-teal-600 lg:w-full"
|
||||
:class="[
|
||||
accountMenuOpen
|
||||
? 'border-teal-200 bg-teal-50 text-teal-800 shadow-[3px_3px_0_0_rgba(20,184,166,0.12)]'
|
||||
: 'border-slate-200 bg-slate-50 text-slate-700 hover:border-teal-200 hover:bg-teal-50 hover:text-teal-800',
|
||||
sidebarCollapsed ? 'lg:justify-center lg:px-0' : '',
|
||||
]"
|
||||
aria-label="打开管理员账户菜单"
|
||||
aria-haspopup="menu"
|
||||
:aria-expanded="accountMenuOpen"
|
||||
:title="sidebarCollapsed ? displayUsername : undefined"
|
||||
>
|
||||
<NIcon size="22" class="shrink-0"><UserCircle /></NIcon>
|
||||
<span class="min-w-0 flex-1" :class="sidebarCollapsed ? 'lg:hidden' : ''">
|
||||
<span class="block truncate text-sm font-semibold text-slate-900">{{ displayUsername }}</span>
|
||||
<span class="mt-0.5 block text-xs text-slate-500">管理员账户</span>
|
||||
</span>
|
||||
<NIcon
|
||||
v-if="!sidebarCollapsed"
|
||||
size="16"
|
||||
class="shrink-0 transition-transform"
|
||||
:class="accountMenuOpen ? 'rotate-180' : ''"
|
||||
>
|
||||
<ChevronUp />
|
||||
</NIcon>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<div
|
||||
class="w-64 border border-slate-200 bg-white shadow-[8px_8px_0_0_rgba(15,23,42,0.08)]"
|
||||
role="menu"
|
||||
aria-label="管理员账户"
|
||||
>
|
||||
<div class="border-b border-slate-200 bg-[linear-gradient(180deg,#f0fdfa_0%,#ffffff_100%)] px-4 py-3">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-teal-700">Administrator</p>
|
||||
<p class="mt-2 truncate text-sm font-semibold text-slate-950">@{{ displayUsername }}</p>
|
||||
<p class="mt-1 truncate text-xs text-slate-500">{{ userEmail }}</p>
|
||||
</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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-teal-600"
|
||||
role="menuitem"
|
||||
@click="accountMenuOpen = false"
|
||||
>
|
||||
<NIcon size="18"><UserCircle /></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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-teal-600"
|
||||
role="menuitem"
|
||||
@click="accountMenuOpen = false"
|
||||
>
|
||||
<NIcon size="18"><Settings /></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 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-rose-500"
|
||||
role="menuitem"
|
||||
@click="handleLogout"
|
||||
>
|
||||
<NIcon size="18"><Logout /></NIcon>
|
||||
<span>登出</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</NPopover>
|
||||
<RouterLink
|
||||
to="/"
|
||||
class="flex h-[46px] shrink-0 items-center justify-center gap-3 border border-slate-200 bg-white px-3 text-sm font-semibold text-slate-700 shadow-[3px_3px_0_0_rgba(15,23,42,0.06)] transition-colors hover:border-sky-200 hover:bg-sky-50 hover:text-sky-800 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-600"
|
||||
|
||||
Reference in New Issue
Block a user