159 lines
4.0 KiB
Vue
159 lines
4.0 KiB
Vue
<template>
|
|
<div class="admin-layout">
|
|
<aside class="admin-layout__sidebar">
|
|
<div class="admin-layout__brand">
|
|
<div class="brand-title">控制台</div>
|
|
<div class="brand-sub">Admin Console</div>
|
|
</div>
|
|
<nav class="admin-layout__nav">
|
|
<NuxtLink
|
|
v-for="item in nav"
|
|
:key="item.to"
|
|
:to="item.to"
|
|
class="nav-item"
|
|
:class="{ active: isActive(item.to) }"
|
|
>
|
|
{{ item.label }}
|
|
</NuxtLink>
|
|
</nav>
|
|
</aside>
|
|
|
|
<main class="admin-layout__main">
|
|
<header class="admin-layout__topbar">
|
|
<div>
|
|
<div class="topbar-title">{{ pageTitle }}</div>
|
|
<div class="topbar-sub">{{ pageSub }}</div>
|
|
</div>
|
|
<NuxtLink to="/" class="home-link">返回前台</NuxtLink>
|
|
</header>
|
|
<div class="admin-layout__content">
|
|
<slot />
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
import { useRoute } from 'vue-router'
|
|
|
|
const route = useRoute()
|
|
|
|
const nav = [
|
|
{ label: '仪表盘', to: '/admin' },
|
|
{ label: '用户管理', to: '/admin/users' },
|
|
{ label: '角色管理', to: '/admin/roles' },
|
|
{ label: '文章管理', to: '/admin/articles' },
|
|
{ label: '菜单标签', to: '/admin/menu-tags' },
|
|
{ label: '首页推送', to: '/admin/home-featured' },
|
|
]
|
|
|
|
const currentTitle = computed(() => {
|
|
const hit = nav.find((item) => route.path.startsWith(item.to))
|
|
return hit?.label ?? '后台管理'
|
|
})
|
|
|
|
const isActive = (path: string): boolean => {
|
|
if (path === '/admin') return route.path === '/admin'
|
|
return route.path.startsWith(path)
|
|
}
|
|
|
|
const pageTitle = computed(() => {
|
|
const metaTitle = (route.meta as any)?.adminTitle as string | undefined
|
|
return metaTitle || currentTitle.value
|
|
})
|
|
|
|
const pageSub = computed(() => {
|
|
const metaSub = (route.meta as any)?.adminSub as string | undefined
|
|
return metaSub || '站点后台 · 管理用户、角色与文章'
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.admin-layout {
|
|
min-height: 100vh;
|
|
display: grid;
|
|
grid-template-columns: 240px 1fr;
|
|
background: #f5f7fb;
|
|
}
|
|
|
|
.admin-layout__sidebar {
|
|
background: #0f172a;
|
|
color: #e5e7eb;
|
|
padding: 20px 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.08);
|
|
}
|
|
|
|
.admin-layout__brand { margin-bottom: 6px; }
|
|
.brand-title { font-size: 18px; font-weight: 800; }
|
|
.brand-sub { font-size: 12px; color: #cbd5e1; margin-top: 2px; }
|
|
|
|
.admin-layout__nav { display: flex; flex-direction: column; gap: 10px; }
|
|
.nav-item {
|
|
display: block;
|
|
padding: 10px 12px;
|
|
border-radius: 12px;
|
|
color: #e5e7eb;
|
|
text-decoration: none;
|
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
transition: all 0.2s ease;
|
|
}
|
|
.nav-item:hover { background: rgba(255, 255, 255, 0.12); }
|
|
.nav-item.active {
|
|
background: linear-gradient(120deg, #22d3ee, #6366f1);
|
|
color: #0b1224;
|
|
border-color: transparent;
|
|
font-weight: 700;
|
|
box-shadow: 0 8px 18px rgba(99, 102, 241, 0.35);
|
|
}
|
|
|
|
.admin-layout__main {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.admin-layout__topbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 16px 20px;
|
|
background: #ffffff;
|
|
border-bottom: 1px solid #e5e7eb;
|
|
}
|
|
.topbar-title { font-size: 20px; font-weight: 800; }
|
|
.topbar-sub { color: #6b7280; margin-top: 2px; font-size: 13px; }
|
|
.home-link {
|
|
padding: 8px 12px;
|
|
border-radius: 10px;
|
|
border: 1px solid #e5e7eb;
|
|
text-decoration: none;
|
|
color: #111827;
|
|
background: #fff;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.home-link:hover { border-color: #111827; background: #f9fafb; }
|
|
|
|
.admin-layout__content {
|
|
padding: 18px clamp(16px, 5vw, 36px);
|
|
flex: 1 1 auto;
|
|
min-height: 0;
|
|
}
|
|
|
|
@media (max-width: 1024px) {
|
|
.admin-layout { grid-template-columns: 1fr; }
|
|
.admin-layout__sidebar {
|
|
flex-direction: row;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
.admin-layout__nav { flex-direction: row; flex-wrap: wrap; gap: 8px; }
|
|
.nav-item { flex: 1 1 140px; text-align: center; }
|
|
}
|
|
</style>
|