基本样式优化 & 数据表组件使用 tanstack-table 库实现
This commit is contained in:
@@ -39,7 +39,6 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool
|
||||
...item,
|
||||
overage: Math.max(0, Number(item.assigned) - Number(item.count)),
|
||||
}))
|
||||
console.log(newData, 'newData')
|
||||
|
||||
const fetchData = useCallback(async () => {
|
||||
try {
|
||||
@@ -116,7 +115,6 @@ export default function AllocationStatus({ detailed = false }: { detailed?: bool
|
||||
{
|
||||
label: '城市',
|
||||
props: 'city',
|
||||
sortable: true,
|
||||
},
|
||||
{
|
||||
label: '可用IP量',
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useRouter, usePathname } from 'next/navigation'
|
||||
import { logout } from '@/actions/auth'
|
||||
import { LogOutIcon } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export default function DashboardLayout({
|
||||
children,
|
||||
@@ -62,7 +63,7 @@ export default function DashboardLayout({
|
||||
{/* 主要内容区域 */}
|
||||
<div className="flex-auto overflow-hidden flex">
|
||||
{/* 侧边栏 */}
|
||||
<nav className="flex-none basis-64 p-4 space-y-2 border-r flex flex-col">
|
||||
<nav className="flex-none basis-64 p-3 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>
|
||||
@@ -89,11 +90,13 @@ function NavbarItem(props: {
|
||||
return (
|
||||
<Link
|
||||
href={props.href}
|
||||
className={`block px-3 py-2 rounded-md text-sm font-medium transition-colors text-center items-center justify-center ${
|
||||
className={cn(
|
||||
'transition-colors duration-150 ease-in-out',
|
||||
'p-2 rounded-md text-sm flex items-center',
|
||||
props.active
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:bg-gray-200'
|
||||
}`}
|
||||
? 'text-primary bg-primary/10'
|
||||
: 'hover:bg-muted',
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</Link>
|
||||
|
||||
@@ -51,8 +51,10 @@
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.205 0 0);
|
||||
|
||||
--primary: oklch(0.65 0.175 255);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
'use client'
|
||||
import * as React from 'react'
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from './ui/table'
|
||||
import { ReactNode, useState } from 'react'
|
||||
import { ArrowUpDownIcon, ArrowUpIcon, ArrowDownIcon, Columns } from 'lucide-react'
|
||||
import { ArrowUpDownIcon, ArrowUpIcon, ArrowDownIcon } from 'lucide-react'
|
||||
import { ColumnDef, flexRender, getCoreRowModel, getSortedRowModel, useReactTable } from '@tanstack/react-table'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
type Data = {
|
||||
[key: string]: unknown
|
||||
}
|
||||
type Data = Record<string, unknown>
|
||||
|
||||
type Column = {
|
||||
label: string
|
||||
@@ -14,73 +15,68 @@ type Column = {
|
||||
sortable?: boolean
|
||||
}
|
||||
|
||||
export function DataTable(props: { data: Data[], columns: Column[] }) {
|
||||
const [sortKey, setSortKey] = useState<string>('')
|
||||
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc')
|
||||
|
||||
const sortedData = [...props.data].sort((a, b) => {
|
||||
let aValue = a[sortKey]
|
||||
let bValue = b[sortKey]
|
||||
|
||||
if (aValue === undefined || aValue === null) aValue = ''
|
||||
if (bValue === undefined || bValue === null) bValue = ''
|
||||
|
||||
const aStr = String(aValue)
|
||||
const bStr = String(bValue)
|
||||
|
||||
const aNum = Number(aValue)
|
||||
const bNum = Number(bValue)
|
||||
|
||||
if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||
return sortDirection === 'asc' ? aNum - bNum : bNum - aNum
|
||||
}
|
||||
else {
|
||||
return sortDirection === 'asc'
|
||||
? aStr.localeCompare(bStr)
|
||||
: bStr.localeCompare(aStr)
|
||||
}
|
||||
export function DataTable<T extends Data>(props: {
|
||||
data: T[]
|
||||
columns: Column[]
|
||||
}) {
|
||||
const table = useReactTable({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
data: props.data,
|
||||
columns: props.columns.map(col => ({
|
||||
meta: col,
|
||||
header: col.label,
|
||||
accessorKey: col.props,
|
||||
cell: info => col.render?.(info.row.original) || String(info.getValue()),
|
||||
enableSorting: col.sortable,
|
||||
})) as ColumnDef<T>[],
|
||||
})
|
||||
|
||||
const handleSort = (key: string) => {
|
||||
if (!key) return
|
||||
if (sortKey === key) {
|
||||
setSortDirection(direction => direction === 'asc' ? 'desc' : 'asc')
|
||||
}
|
||||
else {
|
||||
setSortKey(key)
|
||||
setSortDirection('desc')
|
||||
}
|
||||
}
|
||||
|
||||
const renderSortIcon = (key: string) => {
|
||||
if (sortKey !== key) {
|
||||
return <ArrowUpDownIcon className="h-4 w-4 text-gray-400" />
|
||||
}
|
||||
return sortDirection === 'asc'
|
||||
? <ArrowUpIcon className="h-4 w-4 text-blue-600" />
|
||||
: <ArrowDownIcon className="h-4 w-4 text-blue-600" />
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{props.columns.map((item, index) => (
|
||||
<TableHead key={index} onClick={() => item.sortable && handleSort(item.props || item.label)} className={item.sortable ? 'cursor-pointer hover:bg-gray-50' : ''}>
|
||||
<div className="flex items-center gap-2">
|
||||
<span> {item.label}</span>
|
||||
{item.sortable && item.props && renderSortIcon(item.props)}
|
||||
</div>
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
|
||||
{/* 表头行 */}
|
||||
{table.getHeaderGroups().map(group => (
|
||||
<TableRow key={group.id}>
|
||||
|
||||
{/* 表头 */}
|
||||
{group.headers.map(header => (
|
||||
<TableHead
|
||||
key={header.id}
|
||||
className={cn(
|
||||
header.column.columnDef.enableSorting && 'hover:bg-gray-200 transition-colors duration-150 ease-in-out cursor-pointer',
|
||||
header.column.getIsSorted() && 'text-primary',
|
||||
)}
|
||||
onClick={header.column.getToggleSortingHandler()}>
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
{flexRender(header.column.columnDef.header, header.getContext())}
|
||||
{header.column.columnDef.enableSorting && (
|
||||
header.column.getIsSorted() == 'asc' ? (
|
||||
<ArrowUpIcon className="size-4" />
|
||||
) : header.column.getIsSorted() == 'desc' ? (
|
||||
<ArrowDownIcon className="size-4" />
|
||||
) : (
|
||||
<ArrowUpDownIcon className="size-4" />
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</TableHead>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableHeader>
|
||||
|
||||
<TableBody>
|
||||
{sortedData.map((row, index) => (
|
||||
<TableRow key={index}>
|
||||
{props.columns.map((colume, index) => (
|
||||
<TableCell key={index}>
|
||||
{ colume.props ? String(row[colume.props]) : colume.render ? colume.render(row) : undefined }
|
||||
|
||||
{/* 表格行 */}
|
||||
{table.getRowModel().rows.map(row => (
|
||||
<TableRow key={row.id}>
|
||||
|
||||
{/* 表格 */}
|
||||
{row.getVisibleCells().map(cell => (
|
||||
<TableCell key={cell.id}>
|
||||
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
|
||||
@@ -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 border-border/50 transition-colors',
|
||||
'hover:data-[state=selected]:bg-muted border-b border-border/50 transition-colors',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
Reference in New Issue
Block a user