refactor: 移除developer目录,统一使用admin角色;重命名developer-sidebar为admin-sidebar;修复路由守卫和header中的developer引用

This commit is contained in:
2026-05-01 12:18:58 +08:00
parent 9563c8fad6
commit c0edb32614
136 changed files with 26 additions and 26221 deletions
@@ -0,0 +1,155 @@
<script lang="ts" setup>
import { Boxes, Code, DollarSign, FileLock, Gauge, GitBranch, Hash, Key, Megaphone, MessageSquare, Network, Plug, ScrollText, Shield, Users, Variable } from 'lucide-vue-next'
import { onMounted, reactive } from 'vue'
import NavTeam from '@/components/app-sidebar/nav-team.vue'
import TeamSwitcher from '@/components/app-sidebar/team-switcher.vue'
import NavFooter from '@/components/admin-sidebar/nav-footer.vue'
const user = reactive({
name: '管理员',
email: 'admin@example.com',
avatar: '/avatars/admin.jpg',
role: 'admin',
})
onMounted(() => {
const storedUser = localStorage.getItem('user')
if (storedUser) {
try {
const parsedUser = JSON.parse(storedUser)
user.name = parsedUser.username || user.name
user.email = parsedUser.email || user.email
user.avatar = parsedUser.avatar || user.avatar
user.role = parsedUser.role || user.role
}
catch (e) {
console.error('Failed to parse user from localStorage', e)
}
}
})
const teams = [
{
name: '管理后台',
logo: Code,
plan: 'Admin',
},
]
const navMain = [
{
title: '主要功能',
items: [
{
title: '控制台',
url: '/admin',
icon: Gauge,
},
{
title: '应用管理',
url: '/admin/applications',
icon: Boxes,
},
{
title: '卡类管理',
url: '/admin/card-types',
icon: Hash,
},
{
title: '卡密管理',
url: '/admin/cards',
icon: Key,
},
{
title: '公告管理',
url: '/admin/announcements',
icon: Megaphone,
},
{
title: '版本管理',
url: '/admin/versions',
icon: GitBranch,
},
{
title: '用户管理',
url: '/admin/users',
icon: Users,
},
{
title: '代理管理',
url: '/admin/agents',
icon: Network,
},
],
},
{
title: '业务管理',
items: [
{
title: '财务管理',
url: '/admin/finance',
icon: DollarSign,
},
{
title: '日志记录',
url: '/admin/logs',
icon: ScrollText,
},
{
title: '工单系统',
url: '/admin/tickets',
icon: MessageSquare,
},
],
},
{
title: '高级功能',
items: [
{
title: '云端常量',
url: '/admin/cloud-constants',
icon: FileLock,
},
{
title: '云端变量',
url: '/admin/cloud-variables',
icon: Variable,
},
{
title: '云端函数',
url: '/admin/cloud-function',
icon: Code,
},
{
title: '风控管理',
url: '/admin/risk-control',
icon: Shield,
},
{
title: '扩展配置',
url: '/admin/extension',
icon: Plug,
},
],
},
]
</script>
<template>
<UiSidebar collapsible="icon" class="z-50">
<UiSidebarHeader>
<TeamSwitcher :teams="teams" />
</UiSidebarHeader>
<UiSidebarContent>
<NavTeam :nav-main="navMain" />
</UiSidebarContent>
<UiSidebarFooter>
<NavFooter :user="user" />
</UiSidebarFooter>
<UiSidebarRail />
</UiSidebar>
</template>
@@ -0,0 +1,162 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ChevronsUpDown, Database, LogOut, UserRoundCog, Zap } from 'lucide-vue-next'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import type { User } from '@/components/app-sidebar/types'
import { useSidebar } from '@/components/ui/sidebar'
const router = useRouter()
const props = defineProps<{ user: User }>()
const { isMobile, open } = useSidebar()
const subscription = ref({
plan: '基础版',
status: '正常',
apiQuota: 10000,
apiUsed: 0,
storageQuota: 100 * 1024 * 1024,
storageUsed: 0,
})
function formatStorage(bytes: number) {
if (!bytes || bytes === 0)
return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return `${Math.round(bytes / k ** i * 100) / 100} ${sizes[i]}`
}
function formatNumber(num: number) {
return (num || 0).toLocaleString()
}
function handleLogout() {
localStorage.removeItem('token')
localStorage.removeItem('user')
router.push('/login')
}
</script>
<template>
<div class="space-y-2">
<UiCard v-if="open" class="border-0 bg-muted/50">
<UiCardContent class="p-3 space-y-3">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-2">
<Icon icon="lucide:crown" class="size-4 text-primary" />
<span class="text-xs font-medium">{{ subscription.plan }}</span>
</div>
<UiBadge
:variant="subscription.status === '正常' ? 'default' : 'secondary'"
:class="subscription.status === '正常' ? 'bg-green-500/10 text-green-500' : ''"
class="text-[10px]"
>
{{ subscription.status }}
</UiBadge>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between text-[10px]">
<span class="text-muted-foreground flex items-center space-x-1">
<Zap class="size-3" />
<span>API</span>
</span>
<span class="font-medium">{{ formatNumber(subscription.apiUsed) }} / {{ formatNumber(subscription.apiQuota) }}</span>
</div>
<div class="w-full bg-background rounded-full h-1">
<div
class="bg-primary h-1 rounded-full transition-all duration-300"
:style="{ width: `${(subscription.apiUsed / subscription.apiQuota) * 100}%` }"
/>
</div>
</div>
<div class="space-y-2">
<div class="flex items-center justify-between text-[10px]">
<span class="text-muted-foreground flex items-center space-x-1">
<Database class="size-3" />
<span>存储</span>
</span>
<span class="font-medium">{{ formatStorage(subscription.storageUsed) }} / {{ formatStorage(subscription.storageQuota) }}</span>
</div>
<div class="w-full bg-background rounded-full h-1">
<div
class="bg-primary h-1 rounded-full transition-all duration-300"
:style="{ width: `${Math.min((subscription.storageUsed / subscription.storageQuota) * 100, 100)}%` }"
/>
</div>
</div>
</UiCardContent>
</UiCard>
<UiSidebarMenu>
<UiSidebarMenuItem>
<UiDropdownMenu>
<UiDropdownMenuTrigger as-child>
<UiSidebarMenuButton
size="lg"
class="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<UiAvatar class="size-8 rounded-lg">
<UiAvatarImage :src="props.user.avatar" :alt="props.user.name" />
<UiAvatarFallback class="rounded-lg">
{{ props.user.name?.charAt(0)?.toUpperCase() }}
</UiAvatarFallback>
</UiAvatar>
<div class="grid flex-1 text-sm leading-tight text-left">
<span class="font-semibold truncate">{{ props.user.name }}</span>
<span class="text-xs truncate">{{ props.user.email }}</span>
</div>
<ChevronsUpDown class="ml-auto size-4" />
</UiSidebarMenuButton>
</UiDropdownMenuTrigger>
<UiDropdownMenuContent
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
:side="(isMobile || open) ? 'bottom' : 'right'"
align="start"
:side-offset="4"
>
<UiDropdownMenuLabel class="p-0 font-normal">
<div class="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<UiAvatar class="size-8 rounded-lg">
<UiAvatarImage :src="props.user.avatar" :alt="props.user.name" />
<UiAvatarFallback class="rounded-lg">
{{ props.user.name?.charAt(0)?.toUpperCase() }}
</UiAvatarFallback>
</UiAvatar>
<div class="grid flex-1 text-sm leading-tight text-left">
<span class="font-semibold truncate">{{ props.user.name }}</span>
<span class="text-xs truncate">{{ props.user.email }}</span>
</div>
</div>
</UiDropdownMenuLabel>
<UiDropdownMenuSeparator />
<UiDropdownMenuGroup>
<UiDropdownMenuItem @click="router.push('/admin/profile')">
<UserRoundCog class="mr-2 h-4 w-4" />
个人中心
</UiDropdownMenuItem>
<UiDropdownMenuItem @click="router.push('/admin/tickets')">
<Icon icon="lucide:ticket" class="mr-2 h-4 w-4" />
工单系统
</UiDropdownMenuItem>
</UiDropdownMenuGroup>
<UiDropdownMenuSeparator />
<UiDropdownMenuItem class="text-red-500" @click="handleLogout">
<LogOut class="mr-2 h-4 w-4" />
退出登录
</UiDropdownMenuItem>
</UiDropdownMenuContent>
</UiDropdownMenu>
</UiSidebarMenuItem>
</UiSidebarMenu>
</div>
</template>