Initial commit: 网络验证平台
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import Footer from '@/components/marketing-layout/the-footer.vue'
|
||||
import Header from '@/components/marketing-layout/the-header.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="min-h-screen flex flex-col">
|
||||
<Header />
|
||||
|
||||
<main class="flex-1">
|
||||
<div class="relative bg-background">
|
||||
<div class="py-8 lg:py-12">
|
||||
<div class="mx-auto max-w-6xl px-4 sm:px-6 lg:px-8">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,231 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { Boxes, CreditCard, Gauge, Key, Users } from 'lucide-vue-next'
|
||||
import { computed, onMounted, reactive } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import NavTeam from '@/components/app-sidebar/nav-team.vue'
|
||||
import TeamSwitcher from '@/components/app-sidebar/team-switcher.vue'
|
||||
import ThemePopover from '@/components/custom-theme/theme-popover.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const user = reactive({
|
||||
name: '代理商',
|
||||
email: 'agent@example.com',
|
||||
avatar: '/avatars/agent.jpg',
|
||||
role: 'agent',
|
||||
})
|
||||
|
||||
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: Users,
|
||||
plan: 'Agent',
|
||||
},
|
||||
]
|
||||
|
||||
const navMain = [
|
||||
{
|
||||
title: '主要功能',
|
||||
items: [
|
||||
{
|
||||
title: '控制台',
|
||||
url: '/agent',
|
||||
icon: Gauge,
|
||||
},
|
||||
{
|
||||
title: '应用管理',
|
||||
url: '/agent/apps',
|
||||
icon: Boxes,
|
||||
},
|
||||
{
|
||||
title: '卡密管理',
|
||||
url: '/agent/cards',
|
||||
icon: Key,
|
||||
},
|
||||
{
|
||||
title: '用户管理',
|
||||
url: '/agent/users',
|
||||
icon: Users,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '财务',
|
||||
items: [
|
||||
{
|
||||
title: '财务管理',
|
||||
url: '/agent/finance',
|
||||
icon: CreditCard,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
const path = router.currentRoute.value.path
|
||||
const parts = path.split('/').filter(Boolean)
|
||||
const crumbs = [{ title: '控制台', path: '/agent' }]
|
||||
|
||||
const titleMap: Record<string, string> = {
|
||||
apps: '应用管理',
|
||||
cards: '卡密管理',
|
||||
users: '用户管理',
|
||||
finance: '财务管理',
|
||||
profile: '个人中心',
|
||||
create: '创建',
|
||||
}
|
||||
|
||||
let currentPath = '/agent'
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
currentPath += `/${part}`
|
||||
|
||||
if (titleMap[part]) {
|
||||
crumbs.push({
|
||||
title: titleMap[part],
|
||||
path: currentPath,
|
||||
})
|
||||
}
|
||||
else if (!isNaN(Number(part))) {
|
||||
crumbs.push({
|
||||
title: '详情',
|
||||
path: currentPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return crumbs
|
||||
})
|
||||
|
||||
function handleLogout() {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
router.push('/login')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiSidebarProvider>
|
||||
<UiSidebar collapsible="icon" class="z-50">
|
||||
<UiSidebarHeader>
|
||||
<TeamSwitcher :teams="teams" />
|
||||
</UiSidebarHeader>
|
||||
|
||||
<UiSidebarContent>
|
||||
<NavTeam :nav-main="navMain" />
|
||||
</UiSidebarContent>
|
||||
|
||||
<UiSidebarFooter>
|
||||
<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="user.avatar" :alt="user.name" />
|
||||
<UiAvatarFallback class="rounded-lg">
|
||||
{{ user.name?.charAt(0)?.toUpperCase() }}
|
||||
</UiAvatarFallback>
|
||||
</UiAvatar>
|
||||
<div class="grid flex-1 text-sm leading-tight text-left">
|
||||
<span class="font-semibold truncate">{{ user.name }}</span>
|
||||
<span class="text-xs truncate">{{ user.email }}</span>
|
||||
</div>
|
||||
</UiSidebarMenuButton>
|
||||
</UiDropdownMenuTrigger>
|
||||
<UiDropdownMenuContent
|
||||
class="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
||||
side="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="user.avatar" :alt="user.name" />
|
||||
<UiAvatarFallback class="rounded-lg">
|
||||
{{ user.name?.charAt(0)?.toUpperCase() }}
|
||||
</UiAvatarFallback>
|
||||
</UiAvatar>
|
||||
<div class="grid flex-1 text-sm leading-tight text-left">
|
||||
<span class="font-semibold truncate">{{ user.name }}</span>
|
||||
<span class="text-xs truncate">{{ user.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</UiDropdownMenuLabel>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
<UiDropdownMenuGroup>
|
||||
<UiDropdownMenuItem @click="router.push('/agent/profile')">
|
||||
<Icon icon="lucide:user" class="mr-2 h-4 w-4" />
|
||||
个人中心
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuGroup>
|
||||
|
||||
<UiDropdownMenuSeparator />
|
||||
<UiDropdownMenuItem class="text-red-500" @click="handleLogout">
|
||||
<Icon icon="lucide:log-out" class="mr-2 h-4 w-4" />
|
||||
退出登录
|
||||
</UiDropdownMenuItem>
|
||||
</UiDropdownMenuContent>
|
||||
</UiDropdownMenu>
|
||||
</UiSidebarMenuItem>
|
||||
</UiSidebarMenu>
|
||||
</UiSidebarFooter>
|
||||
|
||||
<UiSidebarRail />
|
||||
</UiSidebar>
|
||||
|
||||
<UiSidebarInset>
|
||||
<header class="flex h-14 shrink-0 items-center gap-2 border-b px-4">
|
||||
<UiSidebarTrigger class="-ml-1" />
|
||||
<UiSeparator orientation="vertical" class="mr-2 h-4" />
|
||||
<UiBreadcrumb>
|
||||
<UiBreadcrumbList>
|
||||
<template v-for="(crumb, index) in breadcrumbs" :key="crumb.path">
|
||||
<UiBreadcrumbItem v-if="index < breadcrumbs.length - 1">
|
||||
<UiBreadcrumbLink as-child>
|
||||
<router-link :to="crumb.path">
|
||||
{{ crumb.title }}
|
||||
</router-link>
|
||||
</UiBreadcrumbLink>
|
||||
</UiBreadcrumbItem>
|
||||
<UiBreadcrumbItem v-else>
|
||||
<UiBreadcrumbPage>{{ crumb.title }}</UiBreadcrumbPage>
|
||||
</UiBreadcrumbItem>
|
||||
<UiBreadcrumbSeparator v-if="index < breadcrumbs.length - 1" />
|
||||
</template>
|
||||
</UiBreadcrumbList>
|
||||
</UiBreadcrumb>
|
||||
<div class="ml-auto">
|
||||
<ThemePopover />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 overflow-auto p-4 md:p-6">
|
||||
<router-view />
|
||||
</main>
|
||||
</UiSidebarInset>
|
||||
</UiSidebarProvider>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
@@ -0,0 +1,46 @@
|
||||
<script setup lang="ts">
|
||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import AppSidebar from '@/components/app-sidebar/index.vue'
|
||||
import CommandMenuPanel from '@/components/command-menu-panel/index.vue'
|
||||
import ThemePopover from '@/components/custom-theme/theme-popover.vue'
|
||||
import LanguageChange from '@/components/language-change.vue'
|
||||
import ToggleTheme from '@/components/toggle-theme.vue'
|
||||
import { SIDEBAR_COOKIE_NAME } from '@/components/ui/sidebar/utils'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
|
||||
const defaultOpen = useCookies([SIDEBAR_COOKIE_NAME])
|
||||
const themeStore = useThemeStore()
|
||||
const { contentLayout } = storeToRefs(themeStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiSidebarProvider :default-open="defaultOpen.get(SIDEBAR_COOKIE_NAME)">
|
||||
<AppSidebar />
|
||||
<UiSidebarInset class="w-full max-w-full peer-data-[state=collapsed]:w-[calc(100%-var(--sidebar-width-icon)-1rem)] peer-data-[state=expanded]:w-[calc(100%-var(--sidebar-width))]">
|
||||
<header
|
||||
class="flex items-center gap-3 sm:gap-4 h-16 p-4 shrink-0 transition-[width,height] ease-linear"
|
||||
>
|
||||
<UiSidebarTrigger class="-ml-1" />
|
||||
<UiSeparator orientation="vertical" class="h-6" />
|
||||
<CommandMenuPanel />
|
||||
<div class="flex-1" />
|
||||
<div class="ml-auto flex items-center space-x-4">
|
||||
<LanguageChange />
|
||||
<ToggleTheme />
|
||||
<ThemePopover />
|
||||
</div>
|
||||
</header>
|
||||
<div
|
||||
:class="cn(
|
||||
'p-4 grow',
|
||||
contentLayout === 'centered' ? 'container mx-auto ' : '',
|
||||
)"
|
||||
>
|
||||
<router-view />
|
||||
</div>
|
||||
</UiSidebarInset>
|
||||
</UiSidebarProvider>
|
||||
</template>
|
||||
@@ -0,0 +1,103 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import DeveloperSidebar from '@/components/developer-sidebar/index.vue'
|
||||
import ThemePopover from '@/components/custom-theme/theme-popover.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const breadcrumbs = computed(() => {
|
||||
const path = router.currentRoute.value.path
|
||||
const parts = path.split('/').filter(Boolean)
|
||||
const crumbs = [{ title: '控制台', path: '/developer' }]
|
||||
|
||||
const titleMap: Record<string, string> = {
|
||||
applications: '应用管理',
|
||||
cards: '卡密管理',
|
||||
users: '用户管理',
|
||||
devices: '设备管理',
|
||||
sessions: '在线实例',
|
||||
announcements: '公告管理',
|
||||
versions: '版本管理',
|
||||
'card-types': '卡类管理',
|
||||
'agent-apps': '代理授权',
|
||||
agents: '代理管理',
|
||||
finance: '财务管理',
|
||||
logs: '日志记录',
|
||||
tickets: '工单系统',
|
||||
'cloud-constants': '云端常量',
|
||||
'cloud-variables': '云端变量',
|
||||
'cloud-function': '云端函数',
|
||||
'risk-control': '风控管理',
|
||||
extension: '扩展配置',
|
||||
profile: '个人中心',
|
||||
settings: '基本设置',
|
||||
security: '安全设置',
|
||||
email: '邮箱设置',
|
||||
create: '创建',
|
||||
edit: '编辑',
|
||||
recharge: '充值',
|
||||
request: '申请授权',
|
||||
invite: '邀请授权',
|
||||
}
|
||||
|
||||
let currentPath = '/developer'
|
||||
for (let i = 1; i < parts.length; i++) {
|
||||
const part = parts[i]
|
||||
currentPath += `/${part}`
|
||||
|
||||
if (titleMap[part]) {
|
||||
crumbs.push({
|
||||
title: titleMap[part],
|
||||
path: currentPath,
|
||||
})
|
||||
}
|
||||
else if (!isNaN(Number(part)) && i === parts.length - 1) {
|
||||
crumbs.push({
|
||||
title: '详情',
|
||||
path: currentPath,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return crumbs
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UiSidebarProvider>
|
||||
<DeveloperSidebar />
|
||||
<UiSidebarInset>
|
||||
<header class="flex h-14 shrink-0 items-center gap-2 border-b px-4">
|
||||
<UiSidebarTrigger class="-ml-1" />
|
||||
<UiSeparator orientation="vertical" class="mr-2 h-4" />
|
||||
<UiBreadcrumb>
|
||||
<UiBreadcrumbList>
|
||||
<template v-for="(crumb, index) in breadcrumbs" :key="crumb.path">
|
||||
<UiBreadcrumbItem v-if="index < breadcrumbs.length - 1">
|
||||
<UiBreadcrumbLink as-child>
|
||||
<router-link :to="crumb.path">
|
||||
{{ crumb.title }}
|
||||
</router-link>
|
||||
</UiBreadcrumbLink>
|
||||
</UiBreadcrumbItem>
|
||||
<UiBreadcrumbItem v-else>
|
||||
<UiBreadcrumbPage>{{ crumb.title }}</UiBreadcrumbPage>
|
||||
</UiBreadcrumbItem>
|
||||
<UiBreadcrumbSeparator v-if="index < breadcrumbs.length - 1" />
|
||||
</template>
|
||||
</UiBreadcrumbList>
|
||||
</UiBreadcrumb>
|
||||
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<ThemePopover />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="flex-1 overflow-auto p-4 md:p-6">
|
||||
<router-view />
|
||||
</main>
|
||||
</UiSidebarInset>
|
||||
</UiSidebarProvider>
|
||||
</template>
|
||||
Reference in New Issue
Block a user