Files
Wormhole/web/src/layouts/MainLayout.vue
T
rose_cat707 612d68f56f feat: Wormhole 内网穿透与反向代理网关
Go + Vue 管理台,SQLite 持久化;支持隧道/域名穿透、域名转发插件链、访客认证、
IP 规则、API Key、Docker 单镜像部署;配置通过 Web 系统设置管理,无需 YAML 文件。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 12:15:02 +08:00

144 lines
3.8 KiB
Vue

<template>
<div class="layout">
<aside class="sidebar custom-scrollbar">
<div class="sidebar-brand">
<div class="brand-icon">
<AppLogo :size="20" />
</div>
<span class="brand-name">{{ t('app.name') }}</span>
</div>
<template v-for="group in navGroups" :key="group.labelKey">
<template v-if="group.visible">
<div class="nav-group-label">{{ t(group.labelKey) }}</div>
<router-link
v-for="item in group.items"
:key="item.path"
:to="item.path"
class="nav-item"
:class="{ active: route.path === item.path }"
>
<el-icon :size="16"><component :is="item.icon" /></el-icon>
{{ t(item.labelKey) }}
</router-link>
</template>
</template>
</aside>
<div class="main-area">
<header class="topbar">
<span class="topbar-title">{{ pageTitle }}</span>
<div class="topbar-user">
<el-select
:model-value="locale"
class="toolbar-select"
size="small"
@change="onLocaleChange"
>
<el-option :label="t('common.zh')" value="zh-CN" />
<el-option :label="t('common.en')" value="en" />
</el-select>
<span>{{ user?.username }}</span>
<el-button text @click="logout">{{ t('common.logout') }}</el-button>
</div>
</header>
<main class="main-content custom-scrollbar">
<router-view />
</main>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
import {
Odometer,
Link,
Connection,
Share,
Monitor,
Lock,
User,
Setting,
} from '@element-plus/icons-vue'
import AppLogo from '@/components/AppLogo.vue'
import { setLocale } from '@/i18n'
const { t, locale } = useI18n()
const route = useRoute()
const router = useRouter()
const topbarKeys = {
'/dashboard': 'topbar.dashboard',
'/clients': 'topbar.clients',
'/tunnels': 'topbar.tunnels',
'/hosts': 'topbar.hosts',
'/domain-forwards': 'topbar.domainForwards',
'/security/ip-rules': 'topbar.ipRules',
'/security/request-ips': 'topbar.requestIPs',
'/users': 'topbar.users',
'/settings': 'topbar.settings',
}
const user = computed(() => {
try {
return JSON.parse(localStorage.getItem('user') || '{}')
} catch {
return {}
}
})
const isAdmin = computed(() => user.value?.role === 'admin')
const pageTitle = computed(() => t(topbarKeys[route.path] || 'topbar.fallback'))
const navGroups = computed(() => [
{
labelKey: 'nav.groups.overview',
visible: true,
items: [{ path: '/dashboard', labelKey: 'nav.dashboard', icon: Odometer }],
},
{
labelKey: 'nav.groups.forward',
visible: isAdmin.value,
items: [{ path: '/domain-forwards', labelKey: 'nav.domainForwards', icon: Link }],
},
{
labelKey: 'nav.groups.tunnel',
visible: true,
items: [
{ path: '/clients', labelKey: 'nav.clients', icon: Connection },
{ path: '/tunnels', labelKey: 'nav.tunnels', icon: Share },
{ path: '/hosts', labelKey: 'nav.hosts', icon: Monitor },
],
},
{
labelKey: 'nav.groups.security',
visible: true,
items: [
{ path: '/security/ip-rules', labelKey: 'nav.ipRules', icon: Lock },
{ path: '/security/request-ips', labelKey: 'nav.requestIPs', icon: Monitor },
],
},
{
labelKey: 'nav.groups.system',
visible: isAdmin.value,
items: [
{ path: '/users', labelKey: 'nav.users', icon: User },
{ path: '/settings', labelKey: 'nav.settings', icon: Setting },
],
},
])
function onLocaleChange(value) {
setLocale(value)
}
function logout() {
localStorage.removeItem('token')
localStorage.removeItem('user')
router.push('/login')
}
</script>