调整整体页面布局

This commit is contained in:
2025-10-14 17:55:32 +08:00
parent 6bbaf9f904
commit d798eab0a9
5 changed files with 104 additions and 125 deletions

View File

@@ -12,6 +12,7 @@ import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { Button } from '@/components/ui/button'
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'
import { Page } from '@/components/page'
const filterSchema = z.object({
timeFilter: z.string(),
@@ -139,9 +140,8 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool
if (error) return <ErrorCard title="节点分配状态" error={error} onRetry={fetchData} />
return (
<div className="flex flex-col w-full bg-white shadow p-6 overflow-hidden">
<h2 className="text-lg font-semibold mb-4"></h2>
<Page>
<h2 className="flex-none text-lg font-semibold mb-4"></h2>
<div className="mb-4 flex flex-wrap items-center gap-3">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex items-center gap-4">
@@ -173,69 +173,48 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool
</Form>
</div>
<div className="flex gap-6 overflow-hidden">
<div className="flex w-full">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
<TableHead
className="px-4 py-2 text-left cursor-pointer hover:bg-blue-50 transition-colors"
onClick={() => handleSort('city')}
>
<div className="flex items-center gap-2">
<span></span>
{renderSortIcon('city')}
</div>
</TableHead>
<TableHead
className="px-4 py-2 text-left cursor-pointer hover:bg-blue-50 transition-colors"
onClick={() => handleSort('count')}
>
<div className="flex items-center gap-2">
<span>IP量</span>
{renderSortIcon('count')}
</div>
</TableHead>
<TableHead
className="px-4 py-2 text-left cursor-pointer hover:bg-blue-50 transition-colors"
onClick={() => handleSort('assigned')}
>
<div className="flex items-center gap-2">
<span>IP量</span>
{renderSortIcon('assigned')}
</div>
</TableHead>
<TableHead
className="px-4 py-2 text-left cursor-pointer hover:bg-blue-50 transition-colors"
onClick={() => handleSort('overage')}
>
<div className="flex items-center gap-2">
<span></span>
{renderSortIcon('overage')}
</div>
</TableHead>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead onClick={() => handleSort('count')}>
<div className="flex items-center gap-2">
<span>IP量</span>
{renderSortIcon('count')}
</div>
</TableHead>
<TableHead onClick={() => handleSort('assigned')}>
<div className="flex items-center gap-2">
<span>IP量</span>
{renderSortIcon('assigned')}
</div>
</TableHead>
<TableHead onClick={() => handleSort('overage')}>
<div className="flex items-center gap-2">
<span></span>
{renderSortIcon('overage')}
</div>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedData.map((item, index) => {
const overage = calculateOverage(Number(item.assigned), Number(item.count))
return (
<TableRow key={index}>
<TableCell>{item.city}</TableCell>
<TableCell>{item.count}</TableCell>
<TableCell>{item.assigned}</TableCell>
<TableCell>
<span className={overage > 0 ? 'text-red-600 font-medium' : ''}>
{overage}
</span>
</TableCell>
</TableRow>
</TableHeader>
<TableBody>
{sortedData.map((item, index) => {
const overage = calculateOverage(Number(item.assigned), Number(item.count))
return (
<TableRow key={index} className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}>
<TableCell className="px-4 py-2">{item.city}</TableCell>
<TableCell className="px-4 py-2">{item.count}</TableCell>
<TableCell className="px-4 py-2">{item.assigned}</TableCell>
<TableCell className="px-4 py-2">
<span className={overage > 0 ? 'text-red-600 font-medium' : ''}>
{overage}
</span>
</TableCell>
</TableRow>
)
})}
</TableBody>
</Table>
</div>
</div>
</div>
)
})}
</TableBody>
</Table>
</Page>
)
}

View File

@@ -3,6 +3,7 @@
import { useEffect, useState } from 'react'
import { Table, TableHeader, TableBody, TableHead, TableRow, TableCell } from '@/components/ui/table'
import { getCityNodeCount, type CityNode } from '@/actions/stats'
import { Page } from '@/components/page'
export default function CityNodeStats() {
const [data, setData] = useState<CityNode[]>([])
@@ -38,7 +39,7 @@ export default function CityNodeStats() {
}
return (
<div className="flex flex-col w-full bg-white p-6 overflow-hidden">
<Page>
<div className="flex justify-between items-center mb-4">
<h2 className="text-lg font-semibold"></h2>
<span className="text-sm text-gray-500">
@@ -46,39 +47,28 @@ export default function CityNodeStats() {
</span>
</div>
<div className="flex overflow-hidden ">
<div className="flex w-full">
<Table>
<TableHeader>
<TableRow className="bg-gray-50">
<TableHead className="px-4 py-2 text-left font-medium text-gray-600"></TableHead>
<TableHead className="px-4 py-2 text-left font-medium text-gray-600"></TableHead>
<TableHead className="px-4 py-2 text-left font-medium text-gray-600">Hash</TableHead>
<TableHead className="px-4 py-2 text-left font-medium text-gray-600"></TableHead>
<TableHead className="px-4 py-2 text-left font-medium text-gray-600"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => (
<TableRow
key={index}
className={index % 2 === 0 ? 'bg-white' : 'bg-gray-50'}
>
<TableCell className="px-4 py-2">{item.city}</TableCell>
<TableCell className="px-4 py-2">{item.count}</TableCell>
<TableCell className="px-4 py-2">{item.hash}</TableCell>
<TableCell className="px-4 py-2">
<span className="bg-gray-100 px-2 py-1 rounded text-gray-700">
{item.label || '无标签'}
</span>
</TableCell>
<TableCell className="px-4 py-2">{item.offset}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead>Hash</TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{data.map((item, index) => (
<TableRow key={index}>
<TableCell>{item.city}</TableCell>
<TableCell>{item.count}</TableCell>
<TableCell>{item.hash}</TableCell>
<TableCell>{item.label}</TableCell>
<TableCell>{item.offset}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Page>
)
}

View File

@@ -3,7 +3,7 @@
import { ReactNode, useState } from 'react'
import { useRouter, usePathname } from 'next/navigation'
import { logout } from '@/actions/auth'
import { LogOut } from 'lucide-react'
import { LogOutIcon } from 'lucide-react'
import Link from 'next/link'
export default function DashboardLayout({
@@ -37,47 +37,45 @@ export default function DashboardLayout({
}
return (
<div className="bg-gray-100 w-screen h-screen flex flex-col overflow-hidden">
<div className="w-screen h-screen flex flex-col">
{/* 顶部导航栏 */}
<nav className="bg-white flex-none border-b h-16 shadow-sm">
<header className="flex-none basis-16 border-b">
<div className="px-4 sm:px-6">
<div className="flex justify-between h-16 items-center">
<div className="flex items-center">
<h1 className="text-xl font-bold text-gray-900"></h1>
</div>
{/* 简化的退出按钮 */}
<button
onClick={handleLogout}
disabled={isLoading}
className="flex items-center space-x-2 px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 disabled:opacity-50 transition-colors"
>
<LogOut className="h-4 w-4" />
<LogOutIcon className="h-4 w-4" />
<span>{isLoading ? '退出中...' : '退出登录'}</span>
</button>
</div>
</div>
</nav>
</header>
{/* 主要内容区域 */}
<div className="flex flex-1 overflow-hidden">
<div className="flex-auto overflow-hidden flex">
{/* 侧边栏 */}
<div className="w-64 bg-gray-100 border-r border-gray-200 flex flex-col">
<nav className="flex-1 p-4 space-y-2">
<NavbarItem href="/gatewayinfo" active={isActive('/gatewayinfo')}></NavbarItem>
<NavbarItem href="/gatewayConfig" active={isActive('/gatewayConfig')}></NavbarItem>
<NavbarItem href="/gatewayMonitor" active={isActive('/gatewayMonitor')}></NavbarItem>
<NavbarItem href="/cityNodeStats" active={isActive('/cityNodeStats')}></NavbarItem>
<NavbarItem href="/allocationStatus" active={isActive('/allocationStatus')}></NavbarItem>
<NavbarItem href="/edge" active={isActive('/edge')}></NavbarItem>
<NavbarItem href="/settings" active={isActive('/settings')}></NavbarItem>
</nav>
</div>
<nav className="flex-none basis-64 p-4 space-y-2 border-r flex flex-col">
<NavbarItem href="/gatewayinfo" active={isActive('/gatewayinfo')}></NavbarItem>
<NavbarItem href="/gatewayConfig" active={isActive('/gatewayConfig')}></NavbarItem>
<NavbarItem href="/gatewayMonitor" active={isActive('/gatewayMonitor')}></NavbarItem>
<NavbarItem href="/cityNodeStats" active={isActive('/cityNodeStats')}></NavbarItem>
<NavbarItem href="/allocationStatus" active={isActive('/allocationStatus')}></NavbarItem>
<NavbarItem href="/edge" active={isActive('/edge')}></NavbarItem>
<NavbarItem href="/settings" active={isActive('/settings')}></NavbarItem>
</nav>
{/* 内容区域 */}
<div className="flex flex-3 w-full overflow-hidden">
<main className="flex-auto overflow-hidden">
{children}
</div>
</main>
</div>
</div>
)

11
src/components/page.tsx Normal file
View File

@@ -0,0 +1,11 @@
import { ComponentProps, ReactNode } from 'react'
export function Page(props: {
children: ReactNode
} & ComponentProps<'div'>) {
return (
<div className="w-full h-full p-6 flex flex-col">
{props.children}
</div>
)
}

View File

@@ -8,11 +8,11 @@ function Table({ className, ...props }: React.ComponentProps<'table'>) {
return (
<div
data-slot="table-container"
className="relative w-full overflow-x-auto"
className="rounded-md border overflow-auto"
>
<table
data-slot="table"
className={cn('w-full caption-bottom text-sm ', className)}
className={cn('w-full caption-bottom text-sm', className)}
{...props}
/>
</div>
@@ -23,7 +23,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return (
<thead
data-slot="table-header"
className={cn('[&_tr]:border-b sticky top-0', className)}
className={cn('sticky top-0 bg-gray-50', className)}
{...props}
/>
)
@@ -57,7 +57,7 @@ function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
<tr
data-slot="table-row"
className={cn(
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors h-10',
'hover:bg-muted/50 data-[state=selected]:bg-muted border-b border-border/50 transition-colors',
className,
)}
{...props}
@@ -71,6 +71,7 @@ function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
data-slot="table-head"
className={cn(
'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
'text-sm text-gray-500',
className,
)}
{...props}