import { useEffect, useMemo, useRef, useState } from 'react' import type { Key, ReactNode } from 'react' import { App as AntApp, Button, Form, Input, InputNumber, Modal, Select, Space, Statistic, Switch, Table, Tabs, Tag, Tooltip, Typography, } from 'antd' import type { ColumnsType } from 'antd/es/table' import { ClipboardList, ClipboardPaste, ListChecks, Pencil, Play, Plus, RefreshCw, Save, ShoppingCart, Square, Settings, Trash2, } from 'lucide-react' import { DefaultConfig, Exchange, GetLogs, GetRecords, IsRunning, ListPrizes, PreviewAccounts, QueryScores, StartMonitor, StopMonitor, } from '../wailsjs/go/gui/App' import { ClipboardGetText, EventsOn } from '../wailsjs/runtime/runtime' const storageKey = 'huya-go.gui.config.v2' const cookieStorageKey = 'huya-go.gui.cookies.v1' const prizeCacheStorageKey = 'huya-go.gui.prizes.v1' type UiConfig = { transport: string wssHost: string sid: number pid: number name: string targetText: string cookieText: string selectedAccountLines: number[] loops: number delayMs: number concurrency: number stopAfterSuccess: boolean stopAccountAfterSuccess: boolean prewarm: boolean directExchange: boolean sharePrizeList: boolean autoSelect: boolean refreshAfterSuccess: boolean leadMs: number frequentBackoffMs: number errorBackoffMs: number timeoutSec: number startAt: string } type AccountPreview = { line: number uid: number valid: boolean error?: string } type CookieEntry = { id: string text: string uid: number valid: boolean error?: string updatedAt: number } type LogEntry = { time: string level: string message: string } type PrizeInfo = { id: number sid?: number name: string score?: number new_score?: number num?: number left_num?: number status?: number percent?: string img?: string } type PrizeCache = { prizes: PrizeInfo[] updatedAt: number sid?: number } type ScoreResult = { uid: number score: number resp?: { status?: number msg?: string } error?: string } type UserPrizeInfo = { id?: number name?: string score?: number new_score?: number status?: number create_time?: number } type RecordResult = { uid: number records?: UserPrizeInfo[] resp?: { msg?: string } error?: string } type ExchangeInfo = { pid?: number exchange_score?: number exchangeScore?: number desc?: string } type ActPreCondition = { tip?: string } type ExchangeResp = { status?: number msg?: string order_id?: string orderId?: string exchange_info?: ExchangeInfo exchangeInfo?: ExchangeInfo act_pre_condition?: ActPreCondition actPreCondition?: ActPreCondition } type ExchangeResult = { uid: number resp?: ExchangeResp error?: string } type MonitorResult = { key?: string loop?: number uid: number target_index?: number targetIndex?: number action?: string category?: string message?: string done?: boolean prize?: PrizeInfo exchange?: ExchangeResp score?: ScoreResult['resp'] records?: RecordResult['resp'] error?: string } type RecordRow = UserPrizeInfo & { key: string uid: number } type SavedConfig = Omit function getExchangeInfo(resp?: ExchangeResp): ExchangeInfo { return resp?.exchange_info || resp?.exchangeInfo || {} } function getPreCondition(resp?: ExchangeResp): ActPreCondition { return resp?.act_pre_condition || resp?.actPreCondition || {} } function priceOf(row?: PrizeInfo): number { if (!row) return 0 return row.new_score || row.score || 0 } function leftOf(row?: PrizeInfo): number { if (!row) return 0 return row.left_num || 0 } function statusTag(status?: number, error?: string): ReactNode { if (error) return 失败 if (status === 200) return 成功 if (status) return {status} return 待处理 } function categoryTag(category?: string): ReactNode { const map: Record = { ready: { color: 'blue', label: '预热' }, success: { color: 'success', label: '成功' }, not_found: { color: 'default', label: '未命中' }, not_open: { color: 'processing', label: '未开放' }, too_frequent: { color: 'warning', label: '频控' }, not_enough: { color: 'error', label: '不足' }, already_done: { color: 'purple', label: '已兑' }, off_shelf: { color: 'error', label: '下架' }, sold_out: { color: 'error', label: '抢完' }, failed: { color: 'warning', label: '失败' }, error: { color: 'error', label: '异常' }, } if (!category) return 待处理 const item = map[category] || { color: 'default', label: category } return {item.label} } function readSavedConfig(): Partial { try { return JSON.parse(localStorage.getItem(storageKey) || '{}') as Partial } catch { return {} } } function readSavedCookies(): CookieEntry[] { try { const items = JSON.parse(localStorage.getItem(cookieStorageKey) || '[]') as Partial[] return items .map((item, index) => ({ id: item.id || `ck-${Date.now()}-${index}`, text: String(item.text || '').trim(), uid: Number(item.uid || 0), valid: Boolean(item.valid), error: item.error, updatedAt: Number(item.updatedAt || Date.now()), })) .filter((item) => item.text) } catch { return [] } } function writeSavedCookies(items: CookieEntry[]) { localStorage.setItem(cookieStorageKey, JSON.stringify(items)) } function readSavedPrizeCache(): PrizeCache { try { const cache = JSON.parse(localStorage.getItem(prizeCacheStorageKey) || '{}') as Partial const prizes = Array.isArray(cache.prizes) ? cache.prizes.filter((item) => Number(item?.id || 0) > 0 && String(item?.name || '').trim()) : [] return { prizes: prizes as PrizeInfo[], updatedAt: Number(cache.updatedAt || 0), sid: Number(cache.sid || 0) || undefined, } } catch { return { prizes: [], updatedAt: 0 } } } function writeSavedPrizeCache(prizes: PrizeInfo[], sid: number) { localStorage.setItem( prizeCacheStorageKey, JSON.stringify({ prizes, sid, updatedAt: Date.now(), }), ) } function normalizeCookieText(text: string): string { const normalized = text .replace(/\r\n?/g, '\n') .replace(/\\r\\n|\\r|\\n/g, '\n') const withLineBreaks = normalized.includes('\n') || !/\/r\/n|\/r/.test(normalized) ? normalized : normalized.replace(/\/r\/n|\/r|\/n/g, '\n') return withLineBreaks .split('\n') .map((line) => line.trim()) .filter(Boolean) .join('\n') } function cookieTextFromEntries(items: CookieEntry[]): string { return items .map((item) => item.text.trim()) .filter(Boolean) .join('\n') } function newCookieID(): string { return `ck-${Date.now()}-${Math.random().toString(36).slice(2, 8)}` } function pickConfig(values: Partial): UiConfig { return { transport: values.transport || 'wss', wssHost: values.wssHost || '', sid: Number(values.sid || 0), pid: Number(values.pid || 0), name: values.name || '', targetText: values.targetText || '', cookieText: values.cookieText || '', selectedAccountLines: values.selectedAccountLines || [], loops: Number(values.loops || 1), delayMs: Number(values.delayMs || 150), concurrency: Number(values.concurrency || 1), stopAfterSuccess: Boolean(values.stopAfterSuccess), stopAccountAfterSuccess: values.stopAccountAfterSuccess !== false, prewarm: values.prewarm !== false, directExchange: values.directExchange !== false, sharePrizeList: values.sharePrizeList !== false, autoSelect: Boolean(values.autoSelect), refreshAfterSuccess: values.refreshAfterSuccess !== false, leadMs: Number(values.leadMs || 0), frequentBackoffMs: Number(values.frequentBackoffMs || 300), errorBackoffMs: Number(values.errorBackoffMs || 300), timeoutSec: Number(values.timeoutSec || 15), startAt: values.startAt || '', } } function normalizeExchangeResult(row: ExchangeResult, index: number): MonitorResult { const message = row.error || row.resp?.msg || getPreCondition(row.resp).tip || row.resp?.order_id || row.resp?.orderId || '' const category = row.error ? 'error' : row.resp?.status === 200 ? 'success' : message.includes('积分') && message.includes('不足') ? 'not_enough' : 'failed' return { key: `exchange-${row.uid}-${index}-${Date.now()}`, loop: 0, uid: row.uid, action: 'exchange', category, message, exchange: row.resp, error: row.error, } } function normalizeMonitorResult(row: MonitorResult): MonitorResult { return { ...row, key: `monitor-${row.loop || 0}-${row.uid}-${Date.now()}-${Math.random()}`, } } export default function Dashboard() { const { message, modal } = AntApp.useApp() const [form] = Form.useForm() const previewedCookieText = useRef('') const watchedTransport = Form.useWatch('transport', form) const watchedPID = Form.useWatch('pid', form) const watchedName = Form.useWatch('name', form) const watchedCookieText = Form.useWatch('cookieText', form) const [loading, setLoading] = useState('') const [running, setRunning] = useState(false) const [accounts, setAccounts] = useState([]) const [selectedAccountLines, setSelectedAccountLines] = useState([]) const [prizes, setPrizes] = useState([]) const [selectedPrizeKeys, setSelectedPrizeKeys] = useState([]) const [productSearch, setProductSearch] = useState('') const [scores, setScores] = useState([]) const [records, setRecords] = useState([]) const [results, setResults] = useState([]) const [logs, setLogs] = useState([]) const [now, setNow] = useState(new Date()) const [settingsOpen, setSettingsOpen] = useState(false) const [cookieManagerOpen, setCookieManagerOpen] = useState(false) const [cookieEditOpen, setCookieEditOpen] = useState(false) const [editingCookie, setEditingCookie] = useState(null) const [cookieDraft, setCookieDraft] = useState('') const [cookieEntries, setCookieEntries] = useState([]) const [prizeCacheUpdatedAt, setPrizeCacheUpdatedAt] = useState(0) const applyCookieEntries = async (entries: CookieEntry[], persist = true, refreshLatest = false) => { const normalizedEntries = entries .map((entry) => ({ ...entry, text: normalizeCookieText(entry.text).split('\n')[0] || '', })) .filter((entry) => entry.text) const cookieText = cookieTextFromEntries(normalizedEntries) previewedCookieText.current = cookieText form.setFieldsValue({ cookieText }) setSelectedPrizeKeys([]) if (!cookieText) { setCookieEntries([]) setAccounts([]) setSelectedAccountLines([]) if (persist) writeSavedCookies([]) return [] } const previews = (await PreviewAccounts(cookieText)) as AccountPreview[] const enriched = normalizedEntries.map((entry, index) => { const preview = previews.find((item) => item.line === index + 1) return { ...entry, uid: preview?.uid || 0, valid: Boolean(preview?.valid), error: preview?.error, } }) setCookieEntries(enriched) setAccounts(previews || []) setSelectedAccountLines((previews || []).filter((item) => item.valid).map((item) => item.line)) if (persist) writeSavedCookies(enriched) if (refreshLatest && enriched.some((item) => item.valid)) { window.setTimeout(() => queryPrizes(true), 0) } return enriched } useEffect(() => { let mounted = true Promise.all([DefaultConfig(), GetLogs(), IsRunning()]) .then(([defaults, logItems, isRunning]) => { if (!mounted) return const savedCookies = readSavedCookies() const savedPrizeCache = readSavedPrizeCache() form.setFieldsValue({ ...(defaults as UiConfig), ...readSavedConfig(), cookieText: cookieTextFromEntries(savedCookies), }) setCookieEntries(savedCookies) setPrizes(savedPrizeCache.prizes) setPrizeCacheUpdatedAt(savedPrizeCache.updatedAt) setLogs((logItems || []) as LogEntry[]) setRunning(Boolean(isRunning)) if (savedCookies.length > 0) { applyCookieEntries(savedCookies, true, true).catch((err: unknown) => message.error(String(err))) } }) .catch((err: unknown) => message.error(String(err))) const offLog = EventsOn('gui:log', (entry: LogEntry) => { setLogs((prev) => [...prev.slice(-499), entry]) }) const offResult = EventsOn('gui:monitor-result', (result: MonitorResult) => { setResults((prev) => [normalizeMonitorResult(result), ...prev].slice(0, 300)) }) const offState = EventsOn( 'gui:monitor-state', (state: { running?: boolean; message?: string }) => { setRunning(Boolean(state?.running)) if (state?.message) message.info(state.message) }, ) const tick = window.setInterval(() => setNow(new Date()), 250) return () => { mounted = false offLog() offResult() offState() window.clearInterval(tick) } }, [form, message]) useEffect(() => { if ((watchedCookieText || '') === previewedCookieText.current) return setAccounts([]) setSelectedAccountLines([]) setSelectedPrizeKeys([]) }, [watchedCookieText]) const validAccounts = useMemo(() => accounts.filter((item) => item.valid), [accounts]) const importedCookieCount = useMemo( () => normalizeCookieText(watchedCookieText || '').split('\n').filter(Boolean).length, [watchedCookieText], ) const selectedAccountCount = selectedAccountLines.length const selectedPrizeID = selectedPrizeKeys.length === 1 ? Number(selectedPrizeKeys[0]) || undefined : undefined const scoreMap = useMemo(() => { const map = new Map() scores.forEach((item) => map.set(item.uid, item)) return map }, [scores]) const latestResultByUID = useMemo(() => { const map = new Map() results.forEach((item) => { if (!map.has(item.uid)) map.set(item.uid, item) }) return map }, [results]) const successCount = useMemo( () => new Set(results.filter((item) => item.category === 'success').map((item) => item.uid)).size, [results], ) const errorCount = useMemo( () => results.filter((item) => ['error', 'failed', 'not_enough', 'off_shelf', 'sold_out'].includes(item.category || ''), ).length, [results], ) const pendingCount = Math.max(selectedAccountCount - successCount, 0) const currentPrize = useMemo(() => { const fromSelection = prizes.find((item) => item.id === selectedPrizeID) if (fromSelection) return fromSelection const pid = Number(watchedPID || 0) return prizes.find((item) => item.id === pid) }, [prizes, selectedPrizeID, watchedPID]) const filteredPrizes = useMemo(() => { const keyword = productSearch.trim().toLowerCase() if (!keyword) return prizes return prizes.filter( (item) => item.name.toLowerCase().includes(keyword) || String(item.id).includes(keyword) || String(priceOf(item)).includes(keyword), ) }, [prizes, productSearch]) const config = () => ({ ...pickConfig(form.getFieldsValue(true)), selectedAccountLines: selectedAccountLines.map(Number).filter((line) => line > 0), }) const runAction = async ( key: string, action: () => Promise, onDone?: (data: T) => void, ) => { setLoading(key) try { const data = await action() onDone?.(data) message.success('完成') } catch (err: unknown) { message.error(String(err)) } finally { setLoading('') } } const upsertCookieLines = async (lines: string[]) => { const normalizedText = normalizeCookieText(lines.join('\n')) const nextLines = normalizedText.split('\n').filter(Boolean) if (nextLines.length === 0) return { added: 0, updated: 0 } const previews = (await PreviewAccounts(nextLines.join('\n'))) as AccountPreview[] const nextEntries = [...cookieEntries] let added = 0 let updated = 0 nextLines.forEach((text, index) => { const preview = previews.find((item) => item.line === index + 1) const uid = preview?.uid || 0 const valid = Boolean(preview?.valid) const existingIndex = valid && uid > 0 ? nextEntries.findIndex((item) => item.valid && item.uid === uid) : nextEntries.findIndex((item) => item.text === text) const entry: CookieEntry = { id: existingIndex >= 0 ? nextEntries[existingIndex].id : newCookieID(), text, uid, valid, error: preview?.error, updatedAt: Date.now(), } if (existingIndex >= 0) { nextEntries[existingIndex] = entry updated += 1 } else { nextEntries.push(entry) added += 1 } }) await applyCookieEntries(nextEntries, true, true) return { added, updated } } const importCookiesFromClipboard = async () => { setLoading('clipboard') try { const clipboardText = await ClipboardGetText() const cookieText = normalizeCookieText(clipboardText || '') if (!cookieText) { message.warning('剪贴板没有可导入的 CK') return } const result = await upsertCookieLines(cookieText.split('\n')) message.success(`已导入 ${result.added} 条,更新 ${result.updated} 条 CK`) } catch (err: unknown) { message.error(`剪贴板导入失败: ${String(err)}`) } finally { setLoading('') } } const openNewCookie = () => { setEditingCookie(null) setCookieDraft('') setCookieEditOpen(true) } const openEditCookie = (entry: CookieEntry) => { setEditingCookie(entry) setCookieDraft(entry.text) setCookieEditOpen(true) } const saveCookieDraft = async () => { const normalizedText = normalizeCookieText(cookieDraft) if (!normalizedText) { message.warning('CK 不能为空') return } setLoading('cookie-save') try { if (editingCookie) { const text = normalizedText.split('\n')[0] const nextEntries = cookieEntries.map((entry) => entry.id === editingCookie.id ? { ...entry, text, updatedAt: Date.now() } : entry, ) await applyCookieEntries(nextEntries, true, true) message.success('CK 已更新') } else { const result = await upsertCookieLines(normalizedText.split('\n')) message.success(`已新增 ${result.added} 条,更新 ${result.updated} 条 CK`) } setCookieEditOpen(false) setCookieDraft('') setEditingCookie(null) } catch (err: unknown) { message.error(String(err)) } finally { setLoading('') } } const deleteCookie = async (entry: CookieEntry) => { await applyCookieEntries(cookieEntries.filter((item) => item.id !== entry.id)) message.success('CK 已删除') } const clearCookies = () => { modal.confirm({ title: '清空 CK', content: '确认删除所有已保存 CK?', okText: '清空', cancelText: '取消', okButtonProps: { danger: true }, onOk: async () => { await applyCookieEntries([]) message.success('CK 已清空') }, }) } const reloadSavedCookies = async () => { const savedCookies = readSavedCookies() await applyCookieEntries(savedCookies, true, true) message.success('已重新载入本地 CK') } function queryPrizes(quiet = false) { if (!String(form.getFieldValue('cookieText') || '').trim()) { if (!quiet) message.warning('Cookie 不能为空') return } setLoading('list') ListPrizes(config()) .then((data) => { const result = data as { prizes?: PrizeInfo[] } const nextPrizes = result?.prizes || [] if (nextPrizes.length === 0) { if (!quiet) message.warning('当前 CK 未返回可选商品') return } const updatedAt = Date.now() setPrizes(nextPrizes) setPrizeCacheUpdatedAt(updatedAt) writeSavedPrizeCache(nextPrizes, Number(form.getFieldValue('sid') || 0)) setSelectedPrizeKeys((prev) => prev.filter((key) => nextPrizes.some((item) => item.id === Number(key))), ) if (!quiet) message.success('商品已刷新') }) .catch((err: unknown) => { if (!quiet) message.error(String(err)) }) .finally(() => { setLoading('') }) } const productListUpdatedAt = prizeCacheUpdatedAt ? new Date(prizeCacheUpdatedAt).toLocaleString('zh-CN', { hour12: false }) : '' const clearPrizeCache = () => { modal.confirm({ title: '清空商品缓存', content: '确认清空本地缓存的商品列表?', okText: '清空', cancelText: '取消', okButtonProps: { danger: true }, onOk: () => { localStorage.removeItem(prizeCacheStorageKey) setPrizes([]) setSelectedPrizeKeys([]) setPrizeCacheUpdatedAt(0) message.success('商品缓存已清空') } }) } const saveSettings = () => { const values = config() const saved: SavedConfig = { transport: values.transport, wssHost: values.wssHost, sid: values.sid, pid: values.pid, name: values.name, targetText: values.targetText, loops: values.loops, delayMs: values.delayMs, concurrency: values.concurrency, stopAfterSuccess: values.stopAfterSuccess, stopAccountAfterSuccess: values.stopAccountAfterSuccess, prewarm: values.prewarm, directExchange: values.directExchange, sharePrizeList: values.sharePrizeList, autoSelect: values.autoSelect, refreshAfterSuccess: values.refreshAfterSuccess, leadMs: values.leadMs, frequentBackoffMs: values.frequentBackoffMs, errorBackoffMs: values.errorBackoffMs, timeoutSec: values.timeoutSec, startAt: values.startAt, } localStorage.setItem(storageKey, JSON.stringify(saved)) message.success('已保存') } const applyPrizeSelection = (keys: Key[]) => { const normalized = keys.map(Number).filter((item) => item > 0) setSelectedPrizeKeys(normalized) const selected = prizes.filter((item) => normalized.includes(item.id)) if (selected.length === 0) { form.setFieldsValue({ pid: 0, name: '', targetText: '' }) return } form.setFieldsValue({ pid: selected[0].id, name: selected[0].name, targetText: selected.map((item) => String(item.id)).join('\n'), autoSelect: false, }) } const startMonitor = (scheduled: boolean) => { const values = config() if (!scheduled) values.startAt = '' setResults([]) return runAction('monitor', () => StartMonitor(values), () => setRunning(true)) } const exchangeNow = () => { const values = config() if (values.pid <= 0) { message.warning('请选择商品或填写 PID') return } setResults([]) return runAction('exchange', () => Exchange(values), (data) => { const items = (data || []) as ExchangeResult[] setResults(items.map(normalizeExchangeResult)) }) } const selectAllAccounts = () => { setSelectedAccountLines(validAccounts.map((item) => item.line)) } const clearAccounts = () => { setSelectedAccountLines([]) } const accountColumns: ColumnsType = useMemo( () => [ { title: '账号', width: 150, render: (_, row) => (
{row.uid || `第${row.line}行`}
{row.valid ? '已加入任务' : row.error || '未选择'}
), }, { title: '凭据', width: 90, render: (_, row) => (row.valid ? 已加载 : 无效), }, { title: '商品', width: 150, ellipsis: true, render: () => currentPrize?.name || watchedName || '未选择', }, { title: '积分', width: 84, render: (_, row) => scoreMap.get(row.uid)?.score ?? '-', }, { title: '状态', width: 98, render: (_, row) => categoryTag(latestResultByUID.get(row.uid)?.category), }, { title: '最后结果', ellipsis: true, render: (_, row) => { const result = latestResultByUID.get(row.uid) return result?.error || result?.message || result?.exchange?.msg || '' }, }, ], [currentPrize?.name, latestResultByUID, scoreMap, watchedName], ) const cookieColumns: ColumnsType = useMemo( () => [ { title: '账号', width: 150, render: (_, row) => (
{row.uid || '-'}
{row.valid ? '有效 CK' : row.error || '待校验'}
), }, { title: '状态', width: 86, render: (_, row) => (row.valid ? 有效 : 无效), }, { title: 'CK', ellipsis: true, render: (_, row) => {row.text}, }, { title: '更新时间', width: 170, render: (_, row) => new Date(row.updatedAt).toLocaleString('zh-CN', { hour12: false }), }, { title: '操作', width: 150, render: (_, row) => ( ), }, ], [cookieEntries], ) const prizeColumns: ColumnsType = useMemo( () => [ { title: 'num', dataIndex: 'id', width: 92 }, { title: 'name', dataIndex: 'name', minWidth: 220, ellipsis: true }, { title: '剩余', width: 84, render: (_, row) => leftOf(row) || '-' }, { title: '总量', dataIndex: 'num', width: 84 }, { title: '价格', width: 84, render: (_, row) => priceOf(row) || '-' }, ], [], ) const scoreColumns: ColumnsType = useMemo( () => [ { title: 'UID', dataIndex: 'uid', width: 140 }, { title: '可用积分', dataIndex: 'score', width: 110 }, { title: '状态', width: 90, render: (_, row) => statusTag(row.resp?.status, row.error) }, { title: '消息', render: (_, row) => row.error || row.resp?.msg || '' }, ], [], ) const recordRows = useMemo(() => { const rows: RecordRow[] = [] records.forEach((item) => { if (!item.records?.length) { rows.push({ key: `${item.uid}-empty`, uid: item.uid, name: item.error || item.resp?.msg || '' }) return } item.records.forEach((record, index) => { rows.push({ key: `${item.uid}-${index}-${record.id || 'record'}`, uid: item.uid, ...record }) }) }) return rows }, [records]) const recordColumns: ColumnsType = useMemo( () => [ { title: 'UID', dataIndex: 'uid', width: 140 }, { title: '名称', dataIndex: 'name', minWidth: 220, ellipsis: true }, { title: '积分', width: 90, render: (_, row) => row.new_score || row.score || 0 }, { title: '状态', dataIndex: 'status', width: 84 }, { title: '时间', dataIndex: 'create_time', width: 130 }, ], [], ) const resultColumns: ColumnsType = useMemo( () => [ { title: '轮次', dataIndex: 'loop', width: 70 }, { title: 'UID', dataIndex: 'uid', width: 132 }, { title: '动作', dataIndex: 'action', width: 112 }, { title: '分类', width: 90, render: (_, row) => categoryTag(row.category) }, { title: '奖品', minWidth: 220, ellipsis: true, render: (_, row) => row.prize?.name || getExchangeInfo(row.exchange).desc || '', }, { title: '消息', render: (_, row) => row.error || row.message || row.exchange?.msg || getPreCondition(row.exchange).tip || row.exchange?.order_id || row.exchange?.orderId || '', }, ], [], ) const panelTabs = [ { key: 'logs', label: '日志', children: , }, { key: 'results', label: '结果', children: ( row.key || `${row.uid}-${row.loop}`} columns={resultColumns} dataSource={results} pagination={{ pageSize: 12, showSizeChanger: false }} scroll={{ x: 820, y: 520 }} /> ), }, { key: 'stats', label: '统计', children: (
), }, { key: 'records', label: '复盘', children: (
row.key} columns={recordColumns} dataSource={recordRows} pagination={{ pageSize: 12, showSizeChanger: false }} scroll={{ x: 740, y: 520 }} /> ), }, ] return (
{String(watchedTransport || 'wss').toUpperCase()} {running ? '运行中' : '空闲'}
服务器 {formatClock(now)} 账号 {selectedAccountCount}/{validAccounts.length} 成功 {successCount} 异常 {errorCount} 待处理 {pendingCount}
目标
账号作战表 {validAccounts.length} 个账号 已选 {selectedAccountCount}
row.line} rowSelection={{ selectedRowKeys: selectedAccountLines, onChange: setSelectedAccountLines, getCheckboxProps: (row) => ({ disabled: !row.valid }), }} columns={accountColumns} dataSource={accounts} pagination={false} scroll={{ x: 850, y: 560 }} />
setCookieManagerOpen(false)} footer={null} >
{cookieEntries.length} 条 CK {validAccounts.length} 个有效
row.id} columns={cookieColumns} dataSource={cookieEntries} pagination={{ pageSize: 8, showSizeChanger: false }} scroll={{ x: 900, y: 430 }} /> { setCookieEditOpen(false) setCookieDraft('') setEditingCookie(null) }} > setCookieDraft(event.target.value)} /> setSettingsOpen(false)} footer={[ , , ]} >
CK 来源 {importedCookieCount > 0 ? `已导入 ${validAccounts.length}/${importedCookieCount} 个有效账号` : '未导入 CK'}
商品缓存 {prizes.length > 0 ? `${prizes.length} 个 · ${productListUpdatedAt || '未知时间'}` : '未缓存'}
) } function CurrentProduct({ prize }: { prize?: PrizeInfo }) { const name = prize?.name || '未从 CK 列表选择' return (
当前商品 {name}
{prize ? ( <> {priceOf(prize)} 积分 库存 {leftOf(prize) || '-'} PID {prize.id} ) : ( CK 商品源 )}
) } function LogView({ logs }: { logs: LogEntry[] }) { return (
{logs.map((item, index) => (
{item.time} {item.level} {item.message}
))}
) } function formatClock(value: Date): string { const pad = (num: number, size = 2) => String(num).padStart(size, '0') return `${value.getFullYear()}-${pad(value.getMonth() + 1)}-${pad(value.getDate())} ${pad( value.getHours(), )}:${pad(value.getMinutes())}:${pad(value.getSeconds())}.${pad(value.getMilliseconds(), 3)}` }