feat: Add account upload functionality with backend validation, concurrent ID fetching, and a new frontend page.
This commit is contained in:
@@ -58,6 +58,12 @@ export default function Upload() {
|
||||
const [status, setStatus] = useState<ProcessStatus | null>(null)
|
||||
const [polling, setPolling] = useState(false)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [importResult, setImportResult] = useState<{
|
||||
imported: number
|
||||
total: number
|
||||
account_id_ok: number
|
||||
account_id_fail: number
|
||||
} | null>(null)
|
||||
|
||||
// 配置
|
||||
const [membersPerTeam, setMembersPerTeam] = useState(4)
|
||||
@@ -117,6 +123,7 @@ export default function Upload() {
|
||||
async (file: File) => {
|
||||
setFileError(null)
|
||||
setValidating(true)
|
||||
setImportResult(null)
|
||||
|
||||
try {
|
||||
const text = await file.text()
|
||||
@@ -128,6 +135,12 @@ export default function Upload() {
|
||||
|
||||
const data = await res.json()
|
||||
if (data.code === 0) {
|
||||
setImportResult({
|
||||
imported: data.data.imported,
|
||||
total: data.data.total,
|
||||
account_id_ok: data.data.account_id_ok || 0,
|
||||
account_id_fail: data.data.account_id_fail || 0,
|
||||
})
|
||||
loadStats()
|
||||
} else {
|
||||
setFileError(data.message || '验证失败')
|
||||
@@ -300,9 +313,42 @@ export default function Upload() {
|
||||
error={fileError}
|
||||
/>
|
||||
{validating && (
|
||||
<div className="mt-3 flex items-center gap-2 text-blue-500 bg-blue-50 dark:bg-blue-900/20 p-2 rounded-lg text-sm">
|
||||
<div className="mt-3 flex items-center gap-2 text-blue-500 bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg text-sm">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
<span>正在验证账号...</span>
|
||||
<span>正在验证并获取 account_id (20并发)...</span>
|
||||
</div>
|
||||
)}
|
||||
{importResult && !validating && (
|
||||
<div className="mt-3 p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-lg">
|
||||
<div className="flex items-center gap-2 text-green-600 dark:text-green-400 font-medium mb-2">
|
||||
<CheckCircle className="h-4 w-4" />
|
||||
<span>导入完成</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="p-2 bg-white dark:bg-slate-800 rounded">
|
||||
<div className="text-slate-500 text-xs">成功导入</div>
|
||||
<div className="font-bold text-green-600">{importResult.imported} / {importResult.total}</div>
|
||||
</div>
|
||||
<div className="p-2 bg-white dark:bg-slate-800 rounded">
|
||||
<div className="text-slate-500 text-xs">Account ID</div>
|
||||
<div className="font-bold">
|
||||
<span className="text-green-600">{importResult.account_id_ok}</span>
|
||||
{importResult.account_id_fail > 0 && (
|
||||
<span className="text-red-500 ml-1">/ {importResult.account_id_fail} 失败</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{importResult.account_id_ok > 0 && (
|
||||
<div className="mt-2">
|
||||
<div className="h-2 bg-slate-200 dark:bg-slate-700 rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-green-500 rounded-full transition-all duration-500"
|
||||
style={{ width: `${(importResult.account_id_ok / importResult.total) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
Reference in New Issue
Block a user