import { useEffect, useMemo, useState } from 'react' import type { ReactNode } from 'react' import { App as AntApp, Button, Col, Divider, Form, Input, InputNumber, Row, Select, Space, Statistic, Switch, Table, Tabs, Tag, Tooltip, Typography, } from 'antd' import type { ColumnsType } from 'antd/es/table' import { ClipboardList, ListChecks, Play, RefreshCw, Search, ShoppingCart, Square, Users, } from 'lucide-react' import { DefaultConfig, Exchange, GetLogs, GetRecords, IsRunning, ListPrizes, PreviewAccounts, QueryScores, StartMonitor, StopMonitor, } from '../wailsjs/go/gui/App' import { EventsOn } from '../wailsjs/runtime/runtime' const storageKey = 'huya-go.gui.config' type UiConfig = { transport: string wssHost: string sid: number pid: number name: string cookieText: string loops: number delayMs: number concurrency: number stopAfterSuccess: boolean timeoutSec: number startAt: string } type AccountPreview = { line: number uid: number valid: boolean error?: string } type LogEntry = { time: string level: string message: string } type PrizeInfo = { id: number name: string score?: number new_score?: number num?: number left_num?: number status?: number percent?: string } 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 action?: string prize?: PrizeInfo exchange?: ExchangeResp 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 statusTag(status?: number, error?: string): ReactNode { if (error) { return 失败 } if (status === 200) { return 成功 } if (status) { return {status} } return 等待 } function pickConfig(values: Partial): UiConfig { return { transport: values.transport || 'http', wssHost: values.wssHost || '', sid: Number(values.sid || 0), pid: Number(values.pid || 0), name: values.name || '', cookieText: values.cookieText || '', loops: Number(values.loops || 1), delayMs: Number(values.delayMs || 150), concurrency: Number(values.concurrency || 1), stopAfterSuccess: Boolean(values.stopAfterSuccess), timeoutSec: Number(values.timeoutSec || 15), startAt: values.startAt || '', } } function readSavedConfig(): Partial { try { return JSON.parse(localStorage.getItem(storageKey) || '{}') as Partial } catch { return {} } } export default function Dashboard() { const { message } = AntApp.useApp() const [form] = Form.useForm() const [loading, setLoading] = useState('') const [running, setRunning] = useState(false) const [accounts, setAccounts] = useState([]) const [prizes, setPrizes] = useState([]) const [scores, setScores] = useState([]) const [records, setRecords] = useState([]) const [results, setResults] = useState([]) const [logs, setLogs] = useState([]) useEffect(() => { let mounted = true Promise.all([DefaultConfig(), GetLogs(), IsRunning()]) .then(([defaults, logItems, isRunning]) => { if (!mounted) return form.setFieldsValue({ ...(defaults as UiConfig), ...readSavedConfig(), cookieText: '', }) setLogs((logItems || []) as LogEntry[]) setRunning(Boolean(isRunning)) }) .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) } }, ) return () => { mounted = false offLog() offResult() offState() } }, [form, message]) const config = () => pickConfig(form.getFieldsValue(true)) const previewAccounts = async () => { const items = (await PreviewAccounts(form.getFieldValue('cookieText') || '')) as AccountPreview[] setAccounts(items || []) } 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 saveSettings = () => { const values = config() const saved: SavedConfig = { transport: values.transport, wssHost: values.wssHost, sid: values.sid, pid: values.pid, name: values.name, loops: values.loops, delayMs: values.delayMs, concurrency: values.concurrency, stopAfterSuccess: values.stopAfterSuccess, timeoutSec: values.timeoutSec, startAt: values.startAt, } localStorage.setItem(storageKey, JSON.stringify(saved)) message.success('已保存') } const prizeColumns: ColumnsType = useMemo( () => [ { title: 'PID', dataIndex: 'id', width: 92 }, { title: '名称', dataIndex: 'name', minWidth: 220, ellipsis: true }, { title: '积分', width: 90, render: (_, row) => row.new_score || row.score || 0, }, { title: '总量', dataIndex: 'num', width: 84 }, { title: '剩余', dataIndex: 'left_num', width: 84 }, { title: '状态', dataIndex: 'status', width: 82 }, { title: '比例', dataIndex: 'percent', width: 88 }, ], [], ) 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: 140 }, { title: '动作', dataIndex: 'action', width: 120 }, { title: '奖品', minWidth: 220, ellipsis: true, render: (_, row) => row.prize?.name || getExchangeInfo(row.exchange).desc || '', }, { title: '状态', width: 90, render: (_, row) => statusTag(row.exchange?.status, row.error), }, { title: '消息', render: (_, row) => row.error || row.exchange?.msg || getPreCondition(row.exchange).tip || row.exchange?.order_id || row.exchange?.orderId || '', }, ], [], ) return (
item.valid).length} /> row.id} columns={prizeColumns} dataSource={prizes} pagination={{ pageSize: 12, showSizeChanger: false }} scroll={{ x: 760 }} /> ), }, { key: 'scores', label: '积分', children: ( row.uid} columns={scoreColumns} dataSource={scores} pagination={{ pageSize: 12, showSizeChanger: false }} scroll={{ x: 680 }} /> ), }, { key: 'records', label: '记录', children: (
row.key} columns={recordColumns} dataSource={recordRows} pagination={{ pageSize: 12, showSizeChanger: false }} scroll={{ x: 740 }} /> ), }, { key: 'results', label: '结果', children: (
row.key || `${row.uid}-${row.loop}`} columns={resultColumns} dataSource={results} pagination={{ pageSize: 12, showSizeChanger: false }} scroll={{ x: 820 }} /> ), }, { key: 'logs', label: '日志', children: , }, ]} /> ) } function LogView({ logs }: { logs: LogEntry[] }) { return (
{logs.map((item, index) => (
{item.time} {item.level} {item.message}
))}
) } function normalizeExchangeResult(row: ExchangeResult, index: number): MonitorResult { return { key: `exchange-${row.uid}-${index}-${Date.now()}`, loop: 0, uid: row.uid, action: 'exchange', exchange: row.resp, error: row.error, } } function normalizeMonitorResult(row: MonitorResult): MonitorResult { return { ...row, key: `monitor-${row.loop || 0}-${row.uid}-${Date.now()}-${Math.random()}`, } }