Files
bianchengshequ/frontend/src/views/admin/AdminLayout.vue

105 lines
5.8 KiB
Vue

<template>
<div class="h-screen bg-gray-950 text-gray-100 flex overflow-hidden">
<!-- 左侧管理侧边栏 -->
<aside class="w-[180px] bg-gray-900 border-r border-gray-800 flex flex-col shrink-0">
<!-- 顶部 -->
<div class="px-4 py-4 border-b border-gray-800">
<div class="flex items-center justify-between">
<span class="text-sm font-bold text-white">管理后台</span>
<router-link to="/" class="p-1 text-gray-500 hover:text-indigo-400 rounded transition-colors" title="返回前台">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
</router-link>
</div>
</div>
<!-- 导航菜单 -->
<nav class="flex-1 py-3 px-2 space-y-0.5">
<router-link
v-for="item in menuItems"
:key="item.path"
:to="item.path"
class="flex items-center gap-2.5 px-3 py-2 rounded-lg text-sm transition-colors"
:class="isActive(item.path, item.exact) ? 'bg-indigo-600/20 text-indigo-400' : 'text-gray-400 hover:text-gray-200 hover:bg-gray-800/60'"
>
<div class="w-4 h-4 shrink-0" v-html="item.icon"></div>
<span>{{ item.label }}</span>
</router-link>
</nav>
<!-- 底部管理员信息 -->
<div class="border-t border-gray-800 px-4 py-3">
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded-full bg-indigo-600/30 flex items-center justify-center text-[10px] font-bold text-indigo-400">
{{ userStore.user?.username?.charAt(0)?.toUpperCase() || 'A' }}
</div>
<div class="min-w-0">
<div class="text-xs text-gray-300 truncate">{{ userStore.user?.username }}</div>
<div class="text-[10px] text-gray-600">管理员</div>
</div>
</div>
</div>
</aside>
<!-- 主内容 -->
<main class="flex-1 overflow-hidden">
<router-view />
</main>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
import { useUserStore } from '../../stores/user'
const route = useRoute()
const userStore = useUserStore()
const menuItems = [
{
path: '/admin', label: '数据概览', exact: true,
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>',
},
{
path: '/admin/users', label: '用户管理',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"/></svg>',
},
{
path: '/admin/posts', label: '内容管理',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>',
},
{
path: '/admin/categories', label: '分类管理',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"/></svg>',
},
{
path: '/admin/models', label: '模型管理',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"/></svg>',
},
{
path: '/admin/storage', label: '对象存储',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"/></svg>',
},
{
path: '/admin/nav', label: '导航管理',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/></svg>',
},
{
path: '/admin/projects', label: '项目管理',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/></svg>',
},
{
path: '/admin/api-hub', label: 'API管理',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>',
},
{
path: '/admin/kb', label: '知识库',
icon: '<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/></svg>',
},
]
function isActive(path, exact = false) {
if (exact) return route.path === path
return route.path.startsWith(path)
}
</script>