update
All checks were successful
Build Multi-Platform Binaries / build (push) Successful in 11m54s

This commit is contained in:
Flik
2025-12-26 17:14:54 +08:00
parent 4623a7f031
commit 549f9aaf26
63 changed files with 10266 additions and 740 deletions

View File

@@ -1,12 +1,42 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { RouterView } from 'vue-router'
import { getServerStatus } from './api'
import { ref, onMounted, computed, h } from 'vue'
import { RouterView, useRouter, useRoute } from 'vue-router'
import { NLayout, NLayoutHeader, NLayoutContent, NMenu, NButton, NSpace, NTag, NIcon, NConfigProvider, NMessageProvider, NDialogProvider } from 'naive-ui'
import { HomeOutline, ExtensionPuzzleOutline, LogOutOutline } from '@vicons/ionicons5'
import type { MenuOption } from 'naive-ui'
import { getServerStatus, removeToken } from './api'
const router = useRouter()
const route = useRoute()
const serverInfo = ref({ bind_addr: '', bind_port: 0 })
const clientCount = ref(0)
const isLoginPage = computed(() => route.path === '/login')
const menuOptions: MenuOption[] = [
{
label: '客户端',
key: '/',
icon: () => h(NIcon, null, { default: () => h(HomeOutline) })
},
{
label: '插件',
key: '/plugins',
icon: () => h(NIcon, null, { default: () => h(ExtensionPuzzleOutline) })
}
]
const activeKey = computed(() => {
if (route.path.startsWith('/client/')) return '/'
return route.path
})
const handleMenuUpdate = (key: string) => {
router.push(key)
}
onMounted(async () => {
if (isLoginPage.value) return
try {
const { data } = await getServerStatus()
serverInfo.value = data.server
@@ -15,41 +45,50 @@ onMounted(async () => {
console.error('Failed to get server status', e)
}
})
const logout = () => {
removeToken()
router.push('/login')
}
</script>
<template>
<div class="app">
<header class="header">
<h1>GoTunnel 控制台</h1>
<div class="server-info">
<span>{{ serverInfo.bind_addr }}:{{ serverInfo.bind_port }}</span>
<span class="badge">{{ clientCount }} 客户端</span>
</div>
</header>
<main class="main">
<RouterView />
</main>
</div>
<n-config-provider>
<n-dialog-provider>
<n-message-provider>
<n-layout v-if="!isLoginPage" style="min-height: 100vh;">
<n-layout-header bordered style="height: 64px; padding: 0 24px; display: flex; align-items: center; justify-content: space-between;">
<div style="display: flex; align-items: center; gap: 32px;">
<div style="font-size: 20px; font-weight: 600; color: #18a058; cursor: pointer;" @click="router.push('/')">
GoTunnel
</div>
<n-menu
mode="horizontal"
:options="menuOptions"
:value="activeKey"
@update:value="handleMenuUpdate"
/>
</div>
<n-space align="center" :size="16">
<n-tag type="info" round>
{{ serverInfo.bind_addr }}:{{ serverInfo.bind_port }}
</n-tag>
<n-tag type="success" round>
{{ clientCount }} 客户端
</n-tag>
<n-button quaternary circle @click="logout">
<template #icon>
<n-icon><LogOutOutline /></n-icon>
</template>
</n-button>
</n-space>
</n-layout-header>
<n-layout-content content-style="padding: 24px; max-width: 1200px; margin: 0 auto; width: 100%;">
<RouterView />
</n-layout-content>
</n-layout>
<RouterView v-else />
</n-message-provider>
</n-dialog-provider>
</n-config-provider>
</template>
<style scoped>
.app { min-height: 100vh; background: #f5f7fa; }
.header {
background: #fff;
padding: 16px 24px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.header h1 { font-size: 20px; color: #2c3e50; }
.server-info { display: flex; align-items: center; gap: 12px; color: #666; }
.badge {
background: #3498db;
color: #fff;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
}
.main { padding: 24px; max-width: 1200px; margin: 0 auto; }
</style>