import { useState, useEffect, useCallback } from 'react' import { Users, Play, Square, RefreshCw, Settings, CheckCircle, Clock, Upload, Loader2, AlertTriangle, } from 'lucide-react' import { Card, CardHeader, CardTitle, CardContent, Button, Input } from '../components/common' import { useConfig } from '../hooks/useConfig' interface Owner { email: string password: string token: string } interface TeamResult { team_index: number owner_email: string team_id: string registered: number added_to_s2a: number member_emails: string[] errors: string[] duration_ms: number } interface ProcessStatus { running: boolean started_at: string total_teams: number completed: number results: TeamResult[] elapsed_ms: number } export default function TeamProcess() { const { config } = useConfig() const [owners, setOwners] = useState([]) const [status, setStatus] = useState(null) const [loading, setLoading] = useState(false) const [polling, setPolling] = useState(false) // 配置 const [membersPerTeam, setMembersPerTeam] = useState(4) const [concurrentTeams, setConcurrentTeams] = useState(2) const [browserType, setBrowserType] = useState<'chromedp' | 'rod'>('chromedp') const [headless, setHeadless] = useState(true) const [proxy, setProxy] = useState('') const backendUrl = config.s2a.apiBase.replace(/\/api.*$/, '').replace(/:8080/, ':8088') // 获取状态 const fetchStatus = useCallback(async () => { try { const res = await fetch(`${backendUrl}/api/team/status`) if (res.ok) { const data = await res.json() if (data.code === 0) { setStatus(data.data) if (!data.data.running) { setPolling(false) } } } } catch (e) { console.error('获取状态失败:', e) } }, [backendUrl]) // 轮询状态 useEffect(() => { if (polling) { const interval = setInterval(fetchStatus, 2000) return () => clearInterval(interval) } }, [polling, fetchStatus]) // 初始化 useEffect(() => { fetchStatus() }, [fetchStatus]) // 上传账号文件 const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return try { const text = await file.text() const data = JSON.parse(text) const parsed = Array.isArray(data) ? data : [data] const validOwners = parsed.filter((a: Record) => (a.email || a.account) && a.password && (a.token || a.access_token) ).map((a: Record) => ({ email: (a.email || a.account) as string, password: a.password as string, token: (a.token || a.access_token) as string, })) setOwners(validOwners) setConcurrentTeams(Math.min(validOwners.length, 2)) } catch (err) { alert('文件解析失败,请确保是有效的 JSON 格式') } } // 启动处理 const handleStart = async () => { if (owners.length === 0) { alert('请先上传账号文件') return } setLoading(true) try { const res = await fetch(`${backendUrl}/api/team/process`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ owners: owners.slice(0, concurrentTeams), members_per_team: membersPerTeam, concurrent_teams: concurrentTeams, browser_type: browserType, headless, proxy, }), }) if (res.ok) { setPolling(true) fetchStatus() } else { const data = await res.json() alert(data.message || '启动失败') } } catch (e) { console.error('启动失败:', e) alert('启动失败') } setLoading(false) } // 停止处理 const handleStop = async () => { try { await fetch(`${backendUrl}/api/team/stop`, { method: 'POST' }) setPolling(false) fetchStatus() } catch (e) { console.error('停止失败:', e) } } const isRunning = status?.running // 计算统计 const totalRegistered = status?.results.reduce((sum, r) => sum + r.registered, 0) || 0 const totalS2A = status?.results.reduce((sum, r) => sum + r.added_to_s2a, 0) || 0 const expectedTotal = (status?.total_teams || 0) * membersPerTeam return (
{/* Header */}

Team 批量处理

多 Team 并发注册成员并入库 S2A

{/* 状态概览 */}

运行状态

{isRunning ? '运行中' : '空闲'}

{isRunning ? ( ) : ( )}

进度

{status?.completed || 0} / {status?.total_teams || '-'}

已注册

{totalRegistered} / {expectedTotal || '-'}

已入库

{totalS2A}

{/* 配置面板 */} 处理配置 {/* 账号文件上传 */}
setMembersPerTeam(Number(e.target.value))} disabled={isRunning} /> setConcurrentTeams(Number(e.target.value))} disabled={isRunning} hint={`最多 ${owners.length} 个`} />
setHeadless(e.target.checked)} disabled={isRunning} className="h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500" />
setProxy(e.target.value)} disabled={isRunning} />
{isRunning ? ( ) : ( )}
{/* 结果列表 */} 处理结果 {status && status.elapsed_ms > 0 && ( 耗时: {(status.elapsed_ms / 1000).toFixed(1)}s )} {status?.results && status.results.length > 0 ? (
{status.results.map((result) => (
Team {result.team_index} {result.owner_email}
注册: {result.registered} 入库: {result.added_to_s2a}
{result.member_emails.length > 0 && (

成员邮箱:

{result.member_emails.map((email, idx) => ( {email} ))}
)} {result.errors.length > 0 && (

错误:

{result.errors.map((err, idx) => (

• {err}

))}
)}
耗时: {(result.duration_ms / 1000).toFixed(1)}s
))}
) : (

暂无处理结果

上传账号文件并点击开始处理

)}
) }