'use client';
import { useEffect, useState } from 'react';
import { Loader2, Unlock, Lock, Ban, Pencil, Trash2 } from 'lucide-react';
export interface ProxyItem {
id: number;
pool_name: string;
proto: string;
ip: string;
port: number;
username?: string | null;
password?: string | null;
next_use_time?: string | null;
status?: string | null;
created_at?: string;
updated_at?: string;
}
interface ProxyTableProps {
data: ProxyItem[];
loading: boolean;
selectedIds: number[];
onToggleSelect: (proxyId: number) => void;
onToggleSelectAll: () => void;
onEdit: (proxy: ProxyItem) => void;
onDelete: (proxy: ProxyItem) => void;
}
function parseUtcDateTime(value?: string | null) {
if (!value) return null;
const normalized = value.trim().replace(' ', 'T');
const hasTimezone = /[zZ]|[+-]\d{2}:\d{2}$/.test(normalized);
const utcValue = hasTimezone ? normalized : `${normalized}Z`;
const timestamp = new Date(utcValue).getTime();
return Number.isNaN(timestamp) ? null : timestamp;
}
export default function ProxyTable({ data, loading, selectedIds, onToggleSelect, onToggleSelectAll, onEdit, onDelete }: ProxyTableProps) {
const [now, setNow] = useState(() => Date.now());
useEffect(() => {
const timer = window.setInterval(() => {
setNow(Date.now());
}, 1000);
return () => window.clearInterval(timer);
}, []);
const getRemainingTime = (nextUseTime?: string | null): string | null => {
if (!nextUseTime) return null;
const untilMs = parseUtcDateTime(nextUseTime);
if (untilMs === null || untilMs <= now) return null;
const diffSeconds = Math.floor((untilMs - now) / 1000);
const days = Math.floor(diffSeconds / (3600 * 24));
const hours = Math.floor((diffSeconds % (3600 * 24)) / 3600);
const minutes = Math.floor((diffSeconds % 3600) / 60);
const seconds = diffSeconds % 60;
if (days > 0) return `${days}d ${hours}h`;
if (hours > 0) return `${hours}h ${minutes}m`;
if (minutes > 0) return `${minutes}m ${seconds}s`;
return `${seconds}s`;
};
const renderStatusBadge = (isDisabled: boolean, isLocked: boolean) => {
if (isDisabled) {
return (
| ID | 代理池 | 协议 | 地址 | 鉴权 | 状态 | 剩余时间 | 操作 | |
|---|---|---|---|---|---|---|---|---|
| onToggleSelect(item.id)} className="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500" aria-label={`选择代理 ${item.ip}:${item.port}`} /> | #{item.id} | {item.pool_name} | {item.proto} | {item.ip}:{item.port} | {item.username ? ( {item.username}{item.password ? ' / ******' : ''} ) : ( N/A )} | {renderStatusBadge(isDisabled, isLocked)} |
{isLocked ? (
|
|