feat: Implement batch team owner processing with dedicated upload, configuration, and monitoring pages and backend services.

This commit is contained in:
2026-01-30 18:59:03 +08:00
parent 165c6d69b9
commit 6f18740215
7 changed files with 348 additions and 46 deletions

View File

@@ -89,29 +89,6 @@ export default function Monitor() {
return json
}
// 获取号池状态 - 使用 S2A 的 dashboard/stats 接口
const fetchPoolStatus = useCallback(async () => {
try {
const data = await requestS2A('/dashboard/stats')
if (data) {
// 从 dashboard/stats 构建 pool status
setPoolStatus({
current: data.normal_accounts || 0,
target: targetInput,
deficit: Math.max(0, targetInput - (data.normal_accounts || 0)),
auto_add: autoAdd,
min_interval: minInterval,
polling_enabled: pollingEnabled,
polling_interval: pollingInterval,
})
// 同时更新统计数据
setStats(data)
}
} catch (e) {
console.error('获取号池状态失败:', e)
}
}, [targetInput, autoAdd, minInterval, pollingEnabled, pollingInterval])
// 刷新 S2A 统计 - 使用 dashboard/stats
const refreshStats = useCallback(async () => {
setRefreshing(true)
@@ -277,27 +254,59 @@ export default function Monitor() {
const json = await res.json()
if (json.code === 0 && json.data) {
const s = json.data
setTargetInput(s.target || 50)
setAutoAdd(s.auto_add || false)
setMinInterval(s.min_interval || 300)
setPollingEnabled(s.polling_enabled || false)
const target = s.target || 50
const autoAddVal = s.auto_add || false
const minIntervalVal = s.min_interval || 300
const pollingEnabledVal = s.polling_enabled || false
const interval = s.polling_interval || 60
setTargetInput(target)
setAutoAdd(autoAddVal)
setMinInterval(minIntervalVal)
setPollingEnabled(pollingEnabledVal)
setPollingInterval(interval)
savedPollingIntervalRef.current = interval // 同步更新 ref
savedPollingIntervalRef.current = interval
setCountdown(interval)
// 返回加载的配置用于后续刷新
return { target, autoAdd: autoAddVal, minInterval: minIntervalVal, pollingEnabled: pollingEnabledVal, pollingInterval: interval }
}
}
} catch (e) {
console.error('加载监控设置失败:', e)
}
return null
}
// 初始化 - 只在组件挂载时执行一次
useEffect(() => {
loadMonitorSettings()
fetchPoolStatus()
refreshStats()
fetchAutoAddLogs()
const init = async () => {
// 先加载设置
const settings = await loadMonitorSettings()
// 然后刷新状态(使用加载的设置值)
try {
const data = await requestS2A('/dashboard/stats')
if (data) {
const target = settings?.target || 50
setStats(data)
setPoolStatus({
current: data.normal_accounts || 0,
target: target,
deficit: Math.max(0, target - (data.normal_accounts || 0)),
auto_add: settings?.autoAdd || false,
min_interval: settings?.minInterval || 300,
polling_enabled: settings?.pollingEnabled || false,
polling_interval: settings?.pollingInterval || 60,
})
}
} catch (e) {
console.error('获取号池状态失败:', e)
}
fetchAutoAddLogs()
}
init()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

View File

@@ -72,6 +72,7 @@ export default function Upload() {
const [browserType, setBrowserType] = useState<'chromedp' | 'rod'>('chromedp')
const [proxy, setProxy] = useState('')
const [includeOwner, setIncludeOwner] = useState(false) // 母号也入库
const [processCount, setProcessCount] = useState(0) // 处理数量0表示全部
const hasConfig = config.s2a.apiBase && config.s2a.adminKey
@@ -177,6 +178,7 @@ export default function Upload() {
headless: true, // 始终使用无头模式
proxy,
include_owner: includeOwner, // 母号也入库
process_count: processCount, // 处理数量0表示全部
}),
})
@@ -192,7 +194,7 @@ export default function Upload() {
alert('启动失败')
}
setLoading(false)
}, [stats, membersPerTeam, concurrentTeams, browserType, proxy, includeOwner, fetchStatus])
}, [stats, membersPerTeam, concurrentTeams, browserType, proxy, includeOwner, processCount, fetchStatus])
// 停止处理
const handleStop = useCallback(async () => {
@@ -371,6 +373,37 @@ export default function Upload() {
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{/* 处理数量设置 */}
<div>
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
</label>
<div className="flex gap-2">
<Input
type="number"
min={1}
max={stats?.valid || 100}
value={processCount || ''}
onChange={(e) => setProcessCount(Number(e.target.value) || 0)}
disabled={isRunning}
placeholder="输入数量"
className="flex-1"
/>
<Button
variant={processCount === 0 ? 'primary' : 'outline'}
size="sm"
onClick={() => setProcessCount(0)}
disabled={isRunning}
className="whitespace-nowrap"
>
({stats?.valid || 0})
</Button>
</div>
<p className="text-xs text-slate-500 mt-1">
{processCount > 0 ? `将处理 ${Math.min(processCount, stats?.valid || 0)} 个母号` : `将处理全部 ${stats?.valid || 0} 个母号`}
</p>
</div>
<div className="grid grid-cols-2 gap-3">
<Input
label="每个 Team 成员数"