调整整体页面布局

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

View File

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

View File

@@ -3,7 +3,7 @@
import { ReactNode, useState } from 'react' import { ReactNode, useState } from 'react'
import { useRouter, usePathname } from 'next/navigation' import { useRouter, usePathname } from 'next/navigation'
import { logout } from '@/actions/auth' import { logout } from '@/actions/auth'
import { LogOut } from 'lucide-react' import { LogOutIcon } from 'lucide-react'
import Link from 'next/link' import Link from 'next/link'
export default function DashboardLayout({ export default function DashboardLayout({
@@ -37,47 +37,45 @@ export default function DashboardLayout({
} }
return ( 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="px-4 sm:px-6">
<div className="flex justify-between h-16 items-center"> <div className="flex justify-between h-16 items-center">
<div className="flex items-center"> <div className="flex items-center">
<h1 className="text-xl font-bold text-gray-900"></h1> <h1 className="text-xl font-bold text-gray-900"></h1>
</div> </div>
{/* 简化的退出按钮 */}
<button <button
onClick={handleLogout} onClick={handleLogout}
disabled={isLoading} 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" 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> <span>{isLoading ? '退出中...' : '退出登录'}</span>
</button> </button>
</div> </div>
</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-none basis-64 p-4 space-y-2 border-r flex flex-col">
<nav className="flex-1 p-4 space-y-2"> <NavbarItem href="/gatewayinfo" active={isActive('/gatewayinfo')}></NavbarItem>
<NavbarItem href="/gatewayinfo" active={isActive('/gatewayinfo')}></NavbarItem> <NavbarItem href="/gatewayConfig" active={isActive('/gatewayConfig')}></NavbarItem>
<NavbarItem href="/gatewayConfig" active={isActive('/gatewayConfig')}></NavbarItem> <NavbarItem href="/gatewayMonitor" active={isActive('/gatewayMonitor')}></NavbarItem>
<NavbarItem href="/gatewayMonitor" active={isActive('/gatewayMonitor')}></NavbarItem> <NavbarItem href="/cityNodeStats" active={isActive('/cityNodeStats')}></NavbarItem>
<NavbarItem href="/cityNodeStats" active={isActive('/cityNodeStats')}></NavbarItem> <NavbarItem href="/allocationStatus" active={isActive('/allocationStatus')}></NavbarItem>
<NavbarItem href="/allocationStatus" active={isActive('/allocationStatus')}></NavbarItem> <NavbarItem href="/edge" active={isActive('/edge')}></NavbarItem>
<NavbarItem href="/edge" active={isActive('/edge')}></NavbarItem> <NavbarItem href="/settings" active={isActive('/settings')}></NavbarItem>
<NavbarItem href="/settings" active={isActive('/settings')}></NavbarItem> </nav>
</nav>
</div>
{/* 内容区域 */} {/* 内容区域 */}
<div className="flex flex-3 w-full overflow-hidden"> <main className="flex-auto overflow-hidden">
{children} {children}
</div> </main>
</div> </div>
</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 ( return (
<div <div
data-slot="table-container" data-slot="table-container"
className="relative w-full overflow-x-auto" className="rounded-md border overflow-auto"
> >
<table <table
data-slot="table" data-slot="table"
className={cn('w-full caption-bottom text-sm ', className)} className={cn('w-full caption-bottom text-sm', className)}
{...props} {...props}
/> />
</div> </div>
@@ -23,7 +23,7 @@ function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return ( return (
<thead <thead
data-slot="table-header" data-slot="table-header"
className={cn('[&_tr]:border-b sticky top-0', className)} className={cn('sticky top-0 bg-gray-50', className)}
{...props} {...props}
/> />
) )
@@ -57,7 +57,7 @@ function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
<tr <tr
data-slot="table-row" data-slot="table-row"
className={cn( 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, className,
)} )}
{...props} {...props}
@@ -71,6 +71,7 @@ function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
data-slot="table-head" data-slot="table-head"
className={cn( 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-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, className,
)} )}
{...props} {...props}