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:
103
frontend/src/components/records/RecordList.tsx
Normal file
103
frontend/src/components/records/RecordList.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
76
frontend/src/components/records/RecordStats.tsx
Normal file
76
frontend/src/components/records/RecordStats.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
2
frontend/src/components/records/index.ts
Normal file
2
frontend/src/components/records/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as RecordList } from './RecordList'
|
||||
export { default as RecordStats } from './RecordStats'
|
||||
Reference in New Issue
Block a user