Initial commit: 网络验证平台

This commit is contained in:
Admin
2026-04-27 17:22:56 +08:00
commit afe67d704e
780 changed files with 88960 additions and 0 deletions
+22
View File
@@ -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>
+231
View File
@@ -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>
+3
View File
@@ -0,0 +1,3 @@
<template>
<router-view />
</template>
+46
View File
@@ -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>
+103
View File
@@ -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>