mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-10 05:00:19 +08:00
refactor(frontend): improve mobile overflow and proxy node table
This commit is contained in:
@@ -158,18 +158,18 @@
|
||||
|
||||
<!-- Mobile Dropdown Menu -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-300 ease-out overflow-hidden"
|
||||
enter-from-class="opacity-0 max-h-0"
|
||||
enter-to-class="opacity-100 max-h-[calc(100dvh-5rem)]"
|
||||
leave-active-class="transition-all duration-200 ease-in overflow-hidden"
|
||||
leave-from-class="opacity-100 max-h-[calc(100dvh-5rem)]"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-if="mobileMenuOpen"
|
||||
class="max-h-[calc(100dvh-5rem)] overflow-y-auto overscroll-contain border-t border-[var(--shell-border)] bg-[var(--shell-glass)] backdrop-blur-xl"
|
||||
class="fixed inset-x-0 bottom-0 top-[73px] overflow-y-scroll overscroll-contain border-t border-[var(--shell-border)] bg-[var(--shell-glass)] backdrop-blur-xl [-webkit-overflow-scrolling:touch] touch-pan-y"
|
||||
>
|
||||
<div class="mx-auto max-w-7xl px-6 py-4">
|
||||
<div class="mx-auto max-w-7xl px-6 py-4 pb-28">
|
||||
<!-- Navigation Groups -->
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
|
||||
@@ -53,21 +53,177 @@
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<ProxyNodeTableRow
|
||||
<template
|
||||
v-for="node in nodes"
|
||||
:key="node.id"
|
||||
:node="node"
|
||||
:expanded="expandedNodeIds.has(node.id)"
|
||||
:testing="testingNodeIds.has(node.id)"
|
||||
:detail-state="nodeDetails[node.id]"
|
||||
@toggle-details="$emit('toggle-details', node)"
|
||||
@refresh-details="$emit('refresh-details', node)"
|
||||
@test="$emit('test', node)"
|
||||
@edit="$emit('edit', node)"
|
||||
@config="$emit('config', node)"
|
||||
@view-events="$emit('view-events', node)"
|
||||
@delete="$emit('delete', node)"
|
||||
/>
|
||||
>
|
||||
<TableRow
|
||||
class="border-b border-border/40 hover:bg-muted/30 transition-colors"
|
||||
:class="expandedNodeIds.has(node.id) ? 'bg-muted/20' : ''"
|
||||
>
|
||||
<TableCell class="w-[28px] min-w-[28px] max-w-[28px] p-0 pl-2 text-center">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-5 w-5 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
|
||||
:title="expandedNodeIds.has(node.id) ? legacyT('收起数据') : legacyT('展开数据')"
|
||||
@click="$emit('toggle-details', node)"
|
||||
>
|
||||
<ChevronDown
|
||||
v-if="expandedNodeIds.has(node.id)"
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
<ChevronRight
|
||||
v-else
|
||||
class="h-3.5 w-3.5"
|
||||
/>
|
||||
</button>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 min-w-0">
|
||||
<div class="flex items-center gap-1.5 min-w-0">
|
||||
<span
|
||||
class="min-w-0 truncate text-sm font-semibold"
|
||||
:title="node.name"
|
||||
>{{ node.name }}</span>
|
||||
<Badge
|
||||
v-if="node.is_manual"
|
||||
variant="outline"
|
||||
class="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
{{ legacyT('手动') }}
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="node.tunnel_mode"
|
||||
variant="outline"
|
||||
class="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
Tunnel
|
||||
</Badge>
|
||||
<Badge
|
||||
v-if="proxyNodeSchedulingBadge(node)"
|
||||
:variant="proxyNodeSchedulingBadge(node)?.variant"
|
||||
class="text-[10px] px-1.5 py-0"
|
||||
>
|
||||
{{ legacyT(proxyNodeSchedulingBadge(node)?.label || '') }}
|
||||
</Badge>
|
||||
<HardwareTooltip :node="node" />
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 min-w-0">
|
||||
<code
|
||||
class="block min-w-0 truncate text-xs text-muted-foreground"
|
||||
:title="proxyNodeAddress(node)"
|
||||
>{{ proxyNodeAddress(node) }}</code>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 min-w-0">
|
||||
<span
|
||||
class="block min-w-0 truncate text-sm text-muted-foreground"
|
||||
:title="legacyT(formatProxyNodeRegion(node.region))"
|
||||
>{{ legacyT(formatProxyNodeRegion(node.region)) }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
<Badge
|
||||
:variant="proxyNodeStatusVariant(node.status)"
|
||||
:title="legacyT(proxyNodeStatusTitle(node))"
|
||||
class="font-medium px-2.5 py-0.5 text-xs"
|
||||
>
|
||||
{{ legacyT(proxyNodeStatusLabel(node)) }}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
<span class="text-sm tabular-nums">{{ formatProxyNodeNumber(node.total_requests) }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
<span
|
||||
class="text-sm tabular-nums"
|
||||
:class="proxyNodeFailureRate(node) > 5 ? 'text-destructive font-medium' : ''"
|
||||
>{{ formatProxyNodeFailureRate(node) }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
<span class="text-sm tabular-nums">{{ node.avg_latency_ms != null ? `${node.avg_latency_ms.toFixed(0)}ms` : '-' }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
<span class="text-sm tabular-nums">{{ node.is_manual ? '-' : proxyNodeVersion(node) }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 min-w-0">
|
||||
<span class="block min-w-0 truncate text-xs text-muted-foreground">{{ formatProxyNodeTime(node.last_heartbeat_at, locale) }}</span>
|
||||
</TableCell>
|
||||
<TableCell class="py-4 text-center">
|
||||
<div class="flex items-center justify-center gap-0.5">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
:title="testingNodeIds.has(node.id) ? legacyT('测试中...') : legacyT('测试连通性')"
|
||||
:disabled="testingNodeIds.has(node.id)"
|
||||
@click="$emit('test', node)"
|
||||
>
|
||||
<Loader2
|
||||
v-if="testingNodeIds.has(node.id)"
|
||||
class="h-4 w-4 animate-spin"
|
||||
/>
|
||||
<Activity
|
||||
v-else
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</Button>
|
||||
<Button
|
||||
v-if="node.is_manual"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
:title="legacyT('编辑')"
|
||||
@click="$emit('edit', node)"
|
||||
>
|
||||
<SquarePen class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!node.is_manual"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
:title="legacyT('远程配置')"
|
||||
@click="$emit('config', node)"
|
||||
>
|
||||
<Settings class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
v-if="!node.is_manual"
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
:title="legacyT('连接事件')"
|
||||
@click="$emit('view-events', node)"
|
||||
>
|
||||
<History class="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class="h-8 w-8"
|
||||
:title="legacyT('删除')"
|
||||
@click="$emit('delete', node)"
|
||||
>
|
||||
<Trash2 class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow
|
||||
v-if="expandedNodeIds.has(node.id)"
|
||||
class="border-b border-border/40 hover:bg-transparent"
|
||||
>
|
||||
<TableCell
|
||||
colspan="11"
|
||||
class="p-0"
|
||||
>
|
||||
<ProxyNodeDataPanel
|
||||
:node="node"
|
||||
:state="nodeDetails[node.id]"
|
||||
@refresh="$emit('refresh-details', node)"
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</template>
|
||||
<TableRow v-if="nodes.length === 0">
|
||||
<TableCell
|
||||
colspan="11"
|
||||
@@ -114,11 +270,26 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Pagination, SortableTableHead, Table, TableBody, TableCell, TableFilterMenu, TableHead, TableHeader, TableRow } from '@/components/ui'
|
||||
import { Activity, ChevronDown, ChevronRight, History, Loader2, Settings, SquarePen, Trash2 } from 'lucide-vue-next'
|
||||
import { Badge, Button, Pagination, SortableTableHead, Table, TableBody, TableCell, TableFilterMenu, TableHead, TableHeader, TableRow } from '@/components/ui'
|
||||
import type { ProxyNode } from '@/api/proxy-nodes'
|
||||
import { useI18n } from '@/i18n'
|
||||
import HardwareTooltip from './HardwareTooltip.vue'
|
||||
import ProxyNodeMobileCard from './ProxyNodeMobileCard.vue'
|
||||
import ProxyNodeTableRow from './ProxyNodeTableRow.vue'
|
||||
import ProxyNodeDataPanel from './ProxyNodeDataPanel.vue'
|
||||
import {
|
||||
formatProxyNodeFailureRate,
|
||||
formatProxyNodeNumber,
|
||||
formatProxyNodeRegion,
|
||||
formatProxyNodeTime,
|
||||
proxyNodeAddress,
|
||||
proxyNodeFailureRate,
|
||||
proxyNodeSchedulingBadge,
|
||||
proxyNodeStatusLabel,
|
||||
proxyNodeStatusTitle,
|
||||
proxyNodeStatusVariant,
|
||||
proxyNodeVersion,
|
||||
} from './proxy-node-display'
|
||||
import type { ProxyNodeDetailState, ProxyNodeStatusFilterOption } from './proxy-node-types'
|
||||
|
||||
defineProps<{
|
||||
@@ -147,5 +318,5 @@ defineEmits<{
|
||||
delete: [node: ProxyNode]
|
||||
}>()
|
||||
|
||||
const { legacyT } = useI18n()
|
||||
const { legacyT, locale } = useI18n()
|
||||
</script>
|
||||
|
||||
@@ -158,18 +158,18 @@
|
||||
|
||||
<!-- Mobile Dropdown Menu -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-300 ease-out overflow-hidden"
|
||||
enter-from-class="opacity-0 max-h-0"
|
||||
enter-to-class="opacity-100 max-h-[calc(100dvh-5rem)]"
|
||||
leave-active-class="transition-all duration-200 ease-in overflow-hidden"
|
||||
leave-from-class="opacity-100 max-h-[calc(100dvh-5rem)]"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-if="mobileMenuOpen"
|
||||
class="max-h-[calc(100dvh-5rem)] overflow-y-auto overscroll-contain border-t border-[var(--shell-border)] bg-[var(--shell-glass)] backdrop-blur-xl"
|
||||
class="fixed inset-x-0 bottom-0 top-[73px] overflow-y-scroll overscroll-contain border-t border-[var(--shell-border)] bg-[var(--shell-glass)] backdrop-blur-xl [-webkit-overflow-scrolling:touch] touch-pan-y"
|
||||
>
|
||||
<div class="mx-auto max-w-7xl px-6 py-4">
|
||||
<div class="mx-auto max-w-7xl px-6 py-4 pb-28">
|
||||
<div class="space-y-4">
|
||||
<div
|
||||
v-for="group in navigation"
|
||||
|
||||
Reference in New Issue
Block a user