- 新增每页数量localStorage缓存,刷新后保持用户选择 - 增加表格显示高度从600px到800px,显示更多数据 - 支持点击表格行进行选择,提升操作便利性 - 格式化创建时间显示为详细时间(年/月/日 时:分) - 优化复选框和操作按钮的点击事件冲突 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
238 lines
9.8 KiB
TypeScript
238 lines
9.8 KiB
TypeScript
"use client";
|
|
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
|
import { ChevronLeft, ChevronRight, Eye, Edit, Trash2 } from 'lucide-react';
|
|
import { Account } from '@/lib/types';
|
|
|
|
interface AccountTableProps {
|
|
accounts: Account[];
|
|
loading: boolean;
|
|
selectedIds: number[];
|
|
pagination: {
|
|
page: number;
|
|
pageSize: number;
|
|
total: number;
|
|
totalPages: number;
|
|
};
|
|
onSelectAll: (checked: boolean) => void;
|
|
onSelectOne: (id: number, checked: boolean) => void;
|
|
onPageChange: (page: number) => void;
|
|
onPageSizeChange: (pageSize: number) => void;
|
|
onView?: (account: Account) => void;
|
|
onEdit?: (account: Account) => void;
|
|
onDelete?: (account: Account) => void;
|
|
}
|
|
|
|
export function AccountTable({
|
|
accounts,
|
|
loading,
|
|
selectedIds,
|
|
pagination,
|
|
onSelectAll,
|
|
onSelectOne,
|
|
onPageChange,
|
|
onPageSizeChange,
|
|
onView,
|
|
onEdit,
|
|
onDelete
|
|
}: AccountTableProps) {
|
|
const getStatusBadge = (status: string) => {
|
|
switch (status) {
|
|
case 'available':
|
|
return <Badge variant="default" className="bg-green-500">可用</Badge>;
|
|
case 'locked':
|
|
return <Badge variant="secondary">已锁定</Badge>;
|
|
case 'banned':
|
|
return <Badge variant="destructive">已封禁</Badge>;
|
|
case 'exported':
|
|
return <Badge variant="outline" className="bg-blue-50 text-blue-700 border-blue-200">已导出</Badge>;
|
|
default:
|
|
return <Badge variant="outline">{status}</Badge>;
|
|
}
|
|
};
|
|
|
|
const renderPagination = () => {
|
|
return (
|
|
<div className="flex items-center justify-between mb-4">
|
|
<div className="flex items-center space-x-4">
|
|
<div className="text-sm text-muted-foreground">
|
|
显示 {(pagination.page - 1) * pagination.pageSize + 1} - {Math.min(pagination.page * pagination.pageSize, pagination.total)} 共 {pagination.total} 条
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
<span className="text-sm text-muted-foreground">每页显示</span>
|
|
<Select value={pagination.pageSize.toString()} onValueChange={(value) => onPageSizeChange(Number(value))}>
|
|
<SelectTrigger className="w-26">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="20">20</SelectItem>
|
|
<SelectItem value="100">100</SelectItem>
|
|
<SelectItem value="1000">1000</SelectItem>
|
|
<SelectItem value="10000">10000</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
<span className="text-sm text-muted-foreground">条</span>
|
|
</div>
|
|
</div>
|
|
{pagination.totalPages > 1 && (
|
|
<div className="flex items-center space-x-2">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => onPageChange(pagination.page - 1)}
|
|
disabled={pagination.page <= 1}
|
|
>
|
|
<ChevronLeft className="h-4 w-4" />
|
|
上一页
|
|
</Button>
|
|
<div className="flex items-center space-x-1">
|
|
{Array.from({ length: Math.min(5, pagination.totalPages) }, (_, i) => {
|
|
let pageNum;
|
|
if (pagination.totalPages <= 5) {
|
|
pageNum = i + 1;
|
|
} else if (pagination.page <= 3) {
|
|
pageNum = i + 1;
|
|
} else if (pagination.page >= pagination.totalPages - 2) {
|
|
pageNum = pagination.totalPages - 4 + i;
|
|
} else {
|
|
pageNum = pagination.page - 2 + i;
|
|
}
|
|
|
|
return (
|
|
<Button
|
|
key={pageNum}
|
|
variant={pageNum === pagination.page ? "default" : "outline"}
|
|
size="sm"
|
|
onClick={() => onPageChange(pageNum)}
|
|
>
|
|
{pageNum}
|
|
</Button>
|
|
);
|
|
})}
|
|
</div>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={() => onPageChange(pagination.page + 1)}
|
|
disabled={pagination.page >= pagination.totalPages}
|
|
>
|
|
下一页
|
|
<ChevronRight className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col">
|
|
{/* 分页器 - 移到顶部 */}
|
|
{renderPagination()}
|
|
|
|
{/* 表格区域 - 增加高度以显示更多数据 */}
|
|
<div className="border rounded-lg max-h-[800px] overflow-auto">
|
|
<table className="w-full caption-bottom text-sm">
|
|
<thead className="sticky top-0 bg-background z-20 border-b shadow-sm">
|
|
<tr className="hover:bg-muted/50 border-b transition-colors">
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap w-12">
|
|
<Checkbox
|
|
checked={selectedIds.length === accounts.length && accounts.length > 0}
|
|
onCheckedChange={onSelectAll}
|
|
/>
|
|
</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap">ID</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap">平台</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap">自定义ID</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap">所有者</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap">状态</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap">备注</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap">创建时间</th>
|
|
<th className="text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap w-[120px]">操作</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{loading ? (
|
|
<tr className="hover:bg-muted/50 border-b transition-colors">
|
|
<td colSpan={9} className="p-2 align-middle text-center py-8">
|
|
加载中...
|
|
</td>
|
|
</tr>
|
|
) : accounts.length === 0 ? (
|
|
<tr className="hover:bg-muted/50 border-b transition-colors">
|
|
<td colSpan={9} className="p-2 align-middle text-center py-8">
|
|
暂无数据
|
|
</td>
|
|
</tr>
|
|
) : (
|
|
accounts.map((account) => (
|
|
<tr
|
|
key={account.id}
|
|
className="hover:bg-muted/50 border-b transition-colors cursor-pointer"
|
|
onClick={() => onSelectOne(account.id, !selectedIds.includes(account.id))}
|
|
>
|
|
<td className="p-2 align-middle w-12" onClick={(e) => e.stopPropagation()}>
|
|
<Checkbox
|
|
checked={selectedIds.includes(account.id)}
|
|
onCheckedChange={(checked) => onSelectOne(account.id, checked as boolean)}
|
|
/>
|
|
</td>
|
|
<td className="p-2 align-middle font-mono">{account.id}</td>
|
|
<td className="p-2 align-middle">{account.platform}</td>
|
|
<td className="p-2 align-middle font-mono max-w-[150px] truncate">{account.customId}</td>
|
|
<td className="p-2 align-middle">{account.ownerId}</td>
|
|
<td className="p-2 align-middle">{getStatusBadge(account.status)}</td>
|
|
<td className="p-2 align-middle max-w-[200px] truncate">{account.notes || '-'}</td>
|
|
<td className="p-2 align-middle text-sm text-muted-foreground">
|
|
{new Date(account.createdAt).toLocaleString('zh-CN', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</td>
|
|
<td className="p-2 align-middle w-[120px]" onClick={(e) => e.stopPropagation()}>
|
|
<div className="flex items-center space-x-1">
|
|
{onView && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onView(account)}
|
|
>
|
|
<Eye className="h-3 w-3" />
|
|
</Button>
|
|
)}
|
|
{onEdit && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onEdit(account)}
|
|
>
|
|
<Edit className="h-3 w-3" />
|
|
</Button>
|
|
)}
|
|
{onDelete && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() => onDelete(account)}
|
|
>
|
|
<Trash2 className="h-3 w-3" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
))
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |