feat: Add account upload functionality with backend validation, concurrent ID fetching, and a new frontend page.

This commit is contained in:
2026-01-30 12:37:18 +08:00
parent 77ed8d9e11
commit cbf65ba74f
2 changed files with 244 additions and 7 deletions

View File

@@ -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>