feat: Implement initial full-stack application structure including frontend pages, components, hooks, API integration, and backend services for account pooling and management.

This commit is contained in:
2026-01-30 07:40:35 +08:00
commit f4448bbef2
106 changed files with 19282 additions and 0 deletions

View File

@@ -0,0 +1,103 @@
import { CheckCircle, Trash2 } from 'lucide-react'
import type { AddRecord } from '../../types'
import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell, Button } from '../common'
import { formatDateTime } from '../../utils/format'
interface RecordListProps {
records: AddRecord[]
onDelete?: (id: string) => void
loading?: boolean
}
export default function RecordList({ records, onDelete, loading = false }: RecordListProps) {
if (loading) {
return (
<div className="space-y-3">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="h-16 bg-slate-100 dark:bg-slate-700 rounded-lg animate-pulse" />
))}
</div>
)
}
if (records.length === 0) {
return (
<div className="text-center py-12">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-slate-100 dark:bg-slate-700 mb-4">
<CheckCircle className="h-8 w-8 text-slate-400 dark:text-slate-500" />
</div>
<p className="text-slate-500 dark:text-slate-400"></p>
</div>
)
}
return (
<div className="border border-slate-200 dark:border-slate-700 rounded-lg overflow-hidden">
<Table>
<TableHeader>
<TableRow hoverable={false}>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead className="text-right"></TableHead>
<TableHead></TableHead>
{onDelete && <TableHead className="w-16"></TableHead>}
</TableRow>
</TableHeader>
<TableBody>
{records.map((record) => (
<TableRow key={record.id}>
<TableCell>
<span className="text-sm">{formatDateTime(record.timestamp)}</span>
</TableCell>
<TableCell>
<span
className={`inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${
record.source === 'manual'
? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
}`}
>
{record.source === 'manual' ? '手动上传' : '自动补号'}
</span>
</TableCell>
<TableCell className="text-right font-medium">{record.total}</TableCell>
<TableCell className="text-right">
<span className="text-green-600 dark:text-green-400 font-medium">
{record.success}
</span>
</TableCell>
<TableCell className="text-right">
{record.failed > 0 ? (
<span className="text-red-600 dark:text-red-400 font-medium">
{record.failed}
</span>
) : (
<span className="text-slate-400">0</span>
)}
</TableCell>
<TableCell>
<span className="text-sm text-slate-500 dark:text-slate-400 truncate max-w-[200px] block">
{record.details || '-'}
</span>
</TableCell>
{onDelete && (
<TableCell>
<Button
variant="ghost"
size="sm"
onClick={() => onDelete(record.id)}
className="text-slate-400 hover:text-red-500"
>
<Trash2 className="h-4 w-4" />
</Button>
</TableCell>
)}
</TableRow>
))}
</TableBody>
</Table>
</div>
)
}

View File

@@ -0,0 +1,76 @@
import { TrendingUp, Calendar, CheckCircle, XCircle } from 'lucide-react'
import { Card } from '../common'
interface RecordStatsProps {
stats: {
totalRecords: number
totalAdded: number
totalSuccess: number
totalFailed: number
todayAdded: number
weekAdded: number
}
}
export default function RecordStats({ stats }: RecordStatsProps) {
const successRate =
stats.totalAdded > 0 ? ((stats.totalSuccess / stats.totalAdded) * 100).toFixed(1) : '0'
return (
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<Card padding="sm">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
<TrendingUp className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<div>
<p className="text-xs text-slate-500 dark:text-slate-400"></p>
<p className="text-xl font-bold text-slate-900 dark:text-slate-100">
{stats.totalSuccess}
</p>
</div>
</div>
</Card>
<Card padding="sm">
<div className="flex items-center gap-3">
<div className="p-2 bg-green-50 dark:bg-green-900/20 rounded-lg">
<CheckCircle className="h-5 w-5 text-green-600 dark:text-green-400" />
</div>
<div>
<p className="text-xs text-slate-500 dark:text-slate-400"></p>
<p className="text-xl font-bold text-slate-900 dark:text-slate-100">{successRate}%</p>
</div>
</div>
</Card>
<Card padding="sm">
<div className="flex items-center gap-3">
<div className="p-2 bg-purple-50 dark:bg-purple-900/20 rounded-lg">
<Calendar className="h-5 w-5 text-purple-600 dark:text-purple-400" />
</div>
<div>
<p className="text-xs text-slate-500 dark:text-slate-400"></p>
<p className="text-xl font-bold text-slate-900 dark:text-slate-100">
{stats.todayAdded}
</p>
</div>
</div>
</Card>
<Card padding="sm">
<div className="flex items-center gap-3">
<div className="p-2 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<XCircle className="h-5 w-5 text-orange-600 dark:text-orange-400" />
</div>
<div>
<p className="text-xs text-slate-500 dark:text-slate-400"></p>
<p className="text-xl font-bold text-slate-900 dark:text-slate-100">
{stats.weekAdded}
</p>
</div>
</div>
</Card>
</div>
)
}

View File

@@ -0,0 +1,2 @@
export { default as RecordList } from './RecordList'
export { default as RecordStats } from './RecordStats'