虎牙精英宝典

This commit is contained in:
yml2213
2026-08-31 19:34:59 +08:00
parent d391e2ce3e
commit 955ba45488
16 changed files with 743 additions and 5 deletions
+2
View File
@@ -25,6 +25,7 @@ const HuyaCookiePage = lazy(() => import('./pages/HuyaCookiePage'));
const HuyaRegisterPage = lazy(() => import('./pages/HuyaRegisterPage'));
const HuyaDeviceBindingsPage = lazy(() => import('./pages/HuyaDeviceBindingsPage'));
const HuyaTasksPage = lazy(() => import('./pages/HuyaTasksPage'));
const HuyaElitePage = lazy(() => import('./pages/HuyaElitePage'));
const YybRechargePage = lazy(() => import('./pages/YybRechargePage'));
const AuditLogsPage = lazy(() => import('./pages/AuditLogsPage'));
@@ -94,6 +95,7 @@ function AppContent() {
<Route path="huya/assignments" element={lazyRoute(<HuyaAssignmentsPage />)} />
<Route path="huya/cookies" element={lazyRoute(<HuyaCookiePage />)} />
<Route path="huya/tasks" element={lazyRoute(<HuyaTasksPage />)} />
<Route path="huya/elite" element={lazyRoute(<HuyaElitePage />)} />
<Route path="yyb/recharge" element={lazyRoute(<YybRechargePage />)} />
<Route path="proxy" element={lazyRoute(<ProxyPage />)} />
<Route path="users" element={lazyRoute(<UsersPage />)} />
+14 -2
View File
@@ -118,12 +118,24 @@ export const huyaApi = {
updateConfig: (data: Partial<HuyaConfig>) => api.put<HuyaConfig, HuyaConfig>('/huya/config', data),
listGoods: () => api.get<HuyaGoodsItem[], HuyaGoodsItem[]>('/huya/goods'),
listRechargeGoods: () => api.get<HuyaRechargeGoodsItem[], HuyaRechargeGoodsItem[]>('/huya/recharge-goods'),
listWorkbenchAccounts: (scope: 'elite' = 'elite') =>
api.get<{ account_ids: number[]; configured: boolean }, { account_ids: number[]; configured: boolean }>(
'/huya/workbench-accounts', { params: { handbook_scope: scope } },
),
updateWorkbenchAccounts: (accountIds: number[], scope: 'elite' = 'elite') =>
api.put<{ account_ids: number[]; success: boolean }, { account_ids: number[]; success: boolean }>(
'/huya/workbench-accounts', { handbook_scope: scope, account_ids: accountIds },
),
createTasks: (data: HuyaTaskBatchRequest) =>
api.post<HuyaTaskBatchResult, HuyaTaskBatchResult>('/huya/tasks/batch', data),
listTasks: (batchId?: string) =>
listTasks: (batchId?: string, scope?: 'legacy' | 'elite') =>
api.get<HuyaTaskItem[], HuyaTaskItem[]>('/huya/tasks', {
// 列表轮询默认不带 base64 小程序码,避免每次 1MB+ 流量。
params: batchId ? { batch_id: batchId, include_images: false } : { include_images: false },
params: {
...(batchId ? { batch_id: batchId } : {}),
...(scope ? { handbook_scope: scope } : {}),
include_images: false,
},
}),
tasksSummary: () => api.get<TaskSummary, TaskSummary>('/huya/tasks/summary'),
getTask: (taskId: number) =>
+2
View File
@@ -682,6 +682,7 @@ export interface HuyaConfig {
export interface HuyaTaskBatchRequest {
account_ids: number[];
task_type: string;
handbook_scope?: 'legacy' | 'elite';
concurrency?: number;
payload?: Record<string, unknown>;
}
@@ -699,6 +700,7 @@ export interface HuyaTaskItem {
account_uid: string;
account_nickname: string;
task_type: string;
handbook_scope: 'legacy' | 'elite';
status: string;
message: string;
result: Record<string, unknown> | null;
+2 -1
View File
@@ -95,7 +95,8 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
huyaItems.push({ key: '/huya/cookies', label: 'Cookie 管理', icon: <KeyOutlined /> });
}
if (can('huya:task')) {
huyaItems.push({ key: '/huya/tasks', label: '任务操作台', icon: <ShoppingCartOutlined /> });
huyaItems.push({ key: '/huya/elite', label: '精英宝典', icon: <BookOutlined /> });
huyaItems.push({ key: '/huya/tasks', label: '旧版任务台', icon: <ShoppingCartOutlined /> });
}
if (canAny(['yyb:session', 'yyb:history'])) {
+209
View File
@@ -0,0 +1,209 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
Button, Card, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tag, Tooltip, Typography,
} from 'antd';
import type { TableProps } from 'antd';
import {
CheckCircleOutlined, ColumnWidthOutlined, GiftOutlined, ImportOutlined, QrcodeOutlined,
ReloadOutlined, SearchOutlined, SettingOutlined, ShoppingOutlined, StopOutlined,
} from '@ant-design/icons';
import { huyaApi, type HuyaAccountItem, type HuyaConfig, type HuyaGoodsItem, type HuyaTaskItem } from '../api/modules';
import RealtimeLogPanel from '../components/RealtimeLogPanel';
import { usePermissions } from '../hooks/usePermissions';
import { useWebSocketLogs } from '../hooks/useWebSocketLogs';
import { getErrorMessage } from '../utils/error';
import { message } from '../utils/antdMessage';
const { Text } = Typography;
const SCOPE = 'elite' as const;
const ACT_ID = 25135;
const DEFAULT_SID = '2203';
const LAYOUT_KEY = 'huya_elite_layout_mode';
const TASK_LABELS: Record<string, string> = {
query_act_tasks: '读取宝典任务', get_bind_qr: '获取绑定二维码', query_game_name: '查询游戏角色',
confirm_bind: '确认绑定角色', query_points: '查询积分', refresh_goods: '刷新商品',
exchange_goods: '兑换商品', query_exchange_records: '查询兑换记录', create_recharge_order: '生成开通支付码',
};
const TASK_COLORS: Record<string, string> = {
planned: 'default', pending: 'default', running: 'processing', success: 'success',
failed: 'error', error: 'error', stopped: 'warning',
};
const TASK_STATUS_LABELS: Record<string, string> = {
planned: '已计划', pending: '等待中', running: '执行中', success: '成功',
failed: '失败', error: '异常', stopped: '已停止',
};
function savedInterval(): number {
const value = Number(localStorage.getItem('huya_elite_refresh_interval'));
return Number.isInteger(value) && value >= 5 && value <= 60 ? value : 15;
}
function resultText(task: HuyaTaskItem | undefined, key: string): string {
const value = task?.result?.[key];
return value == null ? '' : String(value);
}
function latestTask(tasks: HuyaTaskItem[], accountId: number): HuyaTaskItem | undefined {
return tasks.filter((task) => task.account_id === accountId).sort((a, b) => b.id - a.id)[0];
}
export default function HuyaElitePage() {
const { can } = usePermissions();
const canConfig = can('huya:config');
const [pool, setPool] = useState<HuyaAccountItem[]>([]);
const [accounts, setAccounts] = useState<HuyaAccountItem[]>([]);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [tasks, setTasks] = useState<HuyaTaskItem[]>([]);
const [goods, setGoods] = useState<HuyaGoodsItem[]>([]);
const [selectedGoodsId, setSelectedGoodsId] = useState('');
const [search, setSearch] = useState('');
const [loading, setLoading] = useState(false);
const [starting, setStarting] = useState(false);
const [concurrency, setConcurrency] = useState(3);
const [refreshInterval, setRefreshInterval] = useState(savedInterval);
const [layoutMode, setLayoutMode] = useState<'split' | 'stack'>(() => localStorage.getItem(LAYOUT_KEY) === 'stack' ? 'stack' : 'split');
const [activeBatches, setActiveBatches] = useState<string[]>([]);
const [importOpen, setImportOpen] = useState(false);
const [importSearch, setImportSearch] = useState('');
const [importTag, setImportTag] = useState('');
const [importTags, setImportTags] = useState<string[]>([]);
const [importSelected, setImportSelected] = useState<number[]>([]);
const [qrTask, setQrTask] = useState<HuyaTaskItem | null>(null);
const [config, setConfig] = useState<HuyaConfig | null>(null);
const [configDraft, setConfigDraft] = useState<HuyaConfig | null>(null);
const [configOpen, setConfigOpen] = useState(false);
const [configLoading, setConfigLoading] = useState(false);
const [configSaving, setConfigSaving] = useState(false);
const tasksLoading = useRef(false);
const logs = useWebSocketLogs();
const load = useCallback(async () => {
setLoading(true);
try {
const [poolResult, workbenchResult, goodsResult, taskResult] = await Promise.all([
huyaApi.listAccounts({ include_cookie: false }), huyaApi.listWorkbenchAccounts(SCOPE),
huyaApi.listGoods(), huyaApi.listTasks(undefined, SCOPE),
]);
setPool(poolResult);
const byId = new Map(poolResult.map((item) => [item.id, item]));
let storedIds: number[] = workbenchResult.account_ids;
if (!workbenchResult.configured) {
try { storedIds = JSON.parse(localStorage.getItem('huya_elite_workbench_account_ids') || '[]') as number[]; } catch { storedIds = []; }
}
setAccounts(storedIds.map((id) => byId.get(Number(id))).filter((item): item is HuyaAccountItem => Boolean(item)));
setSelectedIds((prev) => prev.filter((id) => storedIds.includes(id)));
setGoods(goodsResult);
setTasks(taskResult);
} catch (error) { message.error(getErrorMessage(error)); } finally { setLoading(false); }
}, []);
const loadTasks = useCallback(async () => {
if (tasksLoading.current) return;
tasksLoading.current = true;
try { setTasks(await huyaApi.listTasks(undefined, SCOPE)); } catch { /* 下一轮刷新 */ } finally { tasksLoading.current = false; }
}, []);
useEffect(() => { void load(); }, [load]);
useEffect(() => { if (importOpen) void huyaApi.listTags().then(setImportTags).catch(() => {}); }, [importOpen]);
useEffect(() => {
const active = tasks.some((task) => ['planned', 'pending', 'running'].includes(task.status));
const timer = window.setInterval(() => { void loadTasks(); }, (active ? 3 : refreshInterval) * 1000);
return () => window.clearInterval(timer);
}, [loadTasks, refreshInterval, tasks]);
const saveAccounts = async (ids: number[]) => {
const saved = await huyaApi.updateWorkbenchAccounts(ids, SCOPE);
localStorage.setItem('huya_elite_workbench_account_ids', JSON.stringify(saved.account_ids));
const next = saved.account_ids.map((id) => pool.find((item) => item.id === id)).filter((item): item is HuyaAccountItem => Boolean(item));
setAccounts(next);
setSelectedIds((prev) => prev.filter((id) => saved.account_ids.includes(id)));
};
const startTask = async (taskType: string) => {
if (!selectedIds.length) { message.warning('请先勾选账号'); return; }
if (taskType === 'exchange_goods' && !selectedGoodsId) { message.warning('请先选择兑换商品'); return; }
setStarting(true);
try {
const payload = taskType === 'exchange_goods'
? { sid: Number(config?.sid || DEFAULT_SID), product_id: Number(selectedGoodsId), act_id: ACT_ID }
: taskType === 'query_act_tasks' ? { act_id: ACT_ID } : {};
const created = await huyaApi.createTasks({ account_ids: selectedIds, task_type: taskType, handbook_scope: SCOPE, concurrency, payload });
setActiveBatches((prev) => [...new Set([...prev, created.batch_id])]);
logs.connectBatch(created.batch_id, `/api/huya/ws/${created.batch_id}`, {
clear: false,
onTask: (raw) => setTasks((prev) => [raw as unknown as HuyaTaskItem, ...prev.filter((item) => item.id !== Number((raw as { id?: number }).id))].slice(0, 300)),
onResult: () => { setActiveBatches((prev) => prev.filter((id) => id !== created.batch_id)); void loadTasks(); },
});
message.success(`已创建 ${created.count} 个任务`);
window.setTimeout(() => { void loadTasks(); }, 400);
} catch (error) { message.error(getErrorMessage(error)); } finally { setStarting(false); }
};
const openSettings = async () => {
if (!canConfig) return;
setConfigOpen(true);
if (config) { setConfigDraft({ ...config }); return; }
setConfigLoading(true);
try { const current = await huyaApi.getConfig(); setConfig(current); setConfigDraft({ ...current }); }
catch (error) { setConfigOpen(false); message.error(getErrorMessage(error)); }
finally { setConfigLoading(false); }
};
const saveConfig = async () => {
if (!configDraft) return;
setConfigSaving(true);
try { const saved = await huyaApi.updateConfig(configDraft); setConfig(saved); setConfigDraft({ ...saved }); setConfigOpen(false); message.success('配置已保存'); }
catch (error) { message.error(getErrorMessage(error)); } finally { setConfigSaving(false); }
};
const stop = async () => {
try { await Promise.all(activeBatches.map((batchId) => huyaApi.stopBatch(batchId))); setActiveBatches([]); message.success('已停止当前批次'); }
catch (error) { message.error(getErrorMessage(error)); }
};
const openQrTask = async (task: HuyaTaskItem) => {
setQrTask(task);
try { setQrTask(await huyaApi.getTask(task.id)); } catch { /* 使用列表结果 */ }
};
const filteredAccounts = useMemo(() => {
const value = search.trim().toLowerCase();
return accounts.filter((item) => !value || [item.uid, item.nickname, item.username, item.tag, item.game_name].some((v) => String(v || '').toLowerCase().includes(value)));
}, [accounts, search]);
const filteredGoods = useMemo(() => goods.map((item) => ({ ...item, label: `${item.name} / ${item.price ?? '-'}分 / ${item.remain_text || '动态库存'}` })), [goods]);
const exchangeCount = tasks.filter((task) => task.task_type === 'exchange_goods').length;
const accountColumns: TableProps<HuyaAccountItem>['columns'] = [
{ title: '#', width: 45, render: (_value, _row, index) => <Text type="secondary">{index + 1}</Text> },
{ title: '账号', width: 190, render: (_value, row) => <Space direction="vertical" size={0}><Text strong ellipsis>{row.nickname || row.username || row.uid}</Text><Text type="secondary" style={{ fontSize: 12 }}>UID {row.uid || '-'}</Text></Space> },
{ title: '游戏名', width: 180, render: (_value, row) => row.game_name || row.game_channel || <Text type="secondary"></Text> },
{ title: '积分', dataIndex: 'points', width: 80, render: (value: number | null) => value ?? <Text type="secondary">-</Text> },
{ title: '最近操作', ellipsis: true, render: (_value, row) => { const task = latestTask(tasks, row.id); return task ? <Space direction="vertical" size={0}><Text>{TASK_LABELS[task.task_type] || task.task_type}</Text><Text type="secondary" ellipsis>{task.message || '-'}</Text></Space> : <Text type="secondary"></Text>; } },
{ title: '状态', width: 90, render: (_value, row) => { const task = latestTask(tasks, row.id); const status = task?.status || row.status || ''; return <Tag color={TASK_COLORS[status] || 'default'}>{TASK_STATUS_LABELS[status] || status || '待操作'}</Tag>; } },
];
const operationBody = (
<Space direction="vertical" size={10} style={{ width: '100%' }}>
<div className="huya-operation-section"><div className="huya-operation-title"><QrcodeOutlined /><span></span></div><div className="huya-action-grid">
<Button size="small" icon={<QrcodeOutlined />} onClick={() => void startTask('get_bind_qr')} disabled={starting || !selectedIds.length}></Button><Button size="small" icon={<SearchOutlined />} onClick={() => void startTask('query_game_name')} disabled={starting || !selectedIds.length}></Button><Button size="small" icon={<CheckCircleOutlined />} onClick={() => void startTask('confirm_bind')} disabled={starting || !selectedIds.length}></Button><Button size="small" icon={<SearchOutlined />} onClick={() => void startTask('query_points')} disabled={starting || !selectedIds.length}></Button><Button size="small" onClick={() => void startTask('query_act_tasks')} disabled={starting || !selectedIds.length}></Button><Button size="small" onClick={() => void startTask('query_exchange_records')} disabled={starting || !selectedIds.length}></Button>
</div></div>
<div className="huya-operation-section"><div className="huya-operation-title"><ShoppingOutlined /><span></span></div><Select size="small" showSearch optionFilterProp="label" value={selectedGoodsId || undefined} onChange={setSelectedGoodsId} options={filteredGoods.map((item) => ({ value: item.product_id, label: item.label }))} placeholder="选择兑换商品" style={{ width: '100%' }} /><div className="huya-action-grid"><Button size="small" icon={<ReloadOutlined />} onClick={() => void startTask('refresh_goods')} disabled={starting || !selectedIds.length}></Button><Button size="small" type="primary" icon={<ShoppingOutlined />} onClick={() => void startTask('exchange_goods')} disabled={starting || !selectedIds.length || !selectedGoodsId}></Button></div></div>
<div className="huya-operation-section"><div className="huya-operation-title"><GiftOutlined /><span></span></div><Button block size="small" icon={<QrcodeOutlined />} onClick={() => void startTask('create_recharge_order')} disabled={starting || !selectedIds.length}></Button></div>
<RealtimeLogPanel logs={logs.logs} connected={logs.connected} title="实时日志" emptyText="暂无任务日志" height={150} collapsible defaultVisible={false} />
</Space>
);
const accountsBody = <><Space style={{ marginBottom: 8, width: '100%', justifyContent: 'space-between' }} wrap><Input size="small" allowClear prefix={<SearchOutlined />} placeholder="搜索账号/昵称/游戏名" value={search} onChange={(event) => setSearch(event.target.value)} style={{ width: 240 }} /><Space><Button size="small" icon={<ImportOutlined />} onClick={() => setImportOpen(true)}></Button>{selectedIds.length > 0 && <Button size="small" danger onClick={() => void saveAccounts(accounts.map((item) => item.id).filter((id) => !selectedIds.includes(id)))}></Button>}</Space></Space><div className="huya-account-table"><Table rowKey="id" size="small" loading={loading} rowSelection={{ selectedRowKeys: selectedIds, onChange: (keys) => setSelectedIds(keys.map(Number)) }} columns={accountColumns} dataSource={filteredAccounts} pagination={{ pageSize: 20, showSizeChanger: true, showTotal: (total) => `${total}` }} scroll={{ x: 760, y: 'calc(100vh - 300px)' }} onRow={(row) => ({ onClick: () => { const task = latestTask(tasks, row.id); if (task?.task_type === 'get_bind_qr') void openQrTask(task); } })} /></div></>;
const configField = (key: Exclude<keyof HuyaConfig, 'updated_at'>, label: string) => configDraft && <Space key={key} style={{ width: '100%', justifyContent: 'space-between' }}><Text>{label}</Text><Input value={configDraft[key] || ''} onChange={(event) => setConfigDraft({ ...configDraft, [key]: event.target.value })} style={{ width: 300 }} /></Space>;
return <div style={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}><style>{`.huya-operation-section{border:1px solid rgba(128,128,128,.22);border-radius:6px;padding:8px}.huya-operation-title{display:flex;gap:6px;align-items:center;font-weight:600;margin-bottom:8px}.huya-action-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px}.huya-account-table{flex:1;min-height:0;overflow:hidden}.huya-account-table .ant-table-wrapper,.huya-account-table .ant-spin-nested-loading,.huya-account-table .ant-spin-container{height:100%}`}</style>
<Space style={{ justifyContent: 'space-between', width: '100%', marginBottom: 8, flexShrink: 0 }}><div><h2 style={{ margin: 0 }}></h2><Text type="secondary"></Text></div><Space><Tooltip title={layoutMode === 'split' ? '切换为上下布局' : '切换为左右布局'}><Button icon={<ColumnWidthOutlined />} onClick={() => { const next = layoutMode === 'split' ? 'stack' : 'split'; setLayoutMode(next); localStorage.setItem(LAYOUT_KEY, next); }} /></Tooltip><Button icon={<ReloadOutlined />} onClick={() => void load()} loading={loading}></Button>{canConfig && <Button icon={<SettingOutlined />} onClick={() => void openSettings()}></Button>}{!!activeBatches.length && <Button danger icon={<StopOutlined />} onClick={() => void stop()}></Button>}</Space></Space>
{layoutMode === 'split' ? <div style={{ flex: 1, minHeight: 0, display: 'flex', gap: 12, overflow: 'hidden' }}><Card size="small" title="账号" extra={<Tag color="blue"> {selectedIds.length}/{accounts.length}</Tag>} style={{ flex: 1, minWidth: 0, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} styles={{ body: { flex: 1, minHeight: 0, padding: '4px 6px', overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}>{accountsBody}</Card><Card size="small" title="精英宝典操作" extra={<Space size={4}><Text type="secondary"></Text><InputNumber size="small" min={1} max={10} value={concurrency} onChange={(value) => setConcurrency(value || 1)} style={{ width: 58 }} /></Space>} style={{ width: 360, flexShrink: 0, minHeight: 0, overflow: 'hidden' }} styles={{ body: { padding: 8, overflowY: 'auto', height: 'calc(100% - 38px)' } }}>{operationBody}</Card></div> : <><Card size="small" title="精英宝典操作" extra={<Space size={4}><Text type="secondary"></Text><InputNumber size="small" min={1} max={10} value={concurrency} onChange={(value) => setConcurrency(value || 1)} style={{ width: 58 }} /></Space>} style={{ flexShrink: 0, marginBottom: 12 }} styles={{ body: { padding: 8 } }}>{operationBody}</Card><Card size="small" title="账号" extra={<Tag color="blue"> {selectedIds.length}/{accounts.length}</Tag>} style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} styles={{ body: { flex: 1, minHeight: 0, padding: '4px 6px', overflow: 'hidden', display: 'flex', flexDirection: 'column' } }}>{accountsBody}</Card></>}
<Text type="secondary" style={{ fontSize: 12, marginTop: 6 }}> {exchangeCount} </Text>
<Modal open={qrTask !== null} title="绑定二维码" footer={null} onCancel={() => setQrTask(null)} centered>{qrTask && <div style={{ textAlign: 'center' }}>{resultText(qrTask, 'mini_qrcode_image') ? <img src={`data:image/png;base64,${resultText(qrTask, 'mini_qrcode_image')}`} alt="绑定二维码" style={{ width: 240, height: 240 }} /> : <QRCode value={resultText(qrTask, 'bind_redirect_url') || 'https://zt.huya.com/b02faae1/pc/index.html'} size={240} />}<p><Tag color={TASK_COLORS[qrTask.status] || 'default'}>{qrTask.message || qrTask.status}</Tag></p></div>}</Modal>
<Modal open={importOpen} title="导入精英宝典账号" onCancel={() => setImportOpen(false)} onOk={() => { void saveAccounts([...new Set([...accounts.map((item) => item.id), ...importSelected])]).then(() => { setImportSelected([]); setImportOpen(false); }).catch((error) => message.error(getErrorMessage(error))); }} okText="导入" cancelText="取消"><Input.Search allowClear placeholder="搜索账号" value={importSearch} onChange={(event) => setImportSearch(event.target.value)} style={{ marginBottom: 8 }} /><Select allowClear placeholder="标签" value={importTag || undefined} onChange={(value) => setImportTag(value || '')} options={importTags.map((tag) => ({ value: tag, label: tag }))} style={{ width: '100%', marginBottom: 8 }} /><Table rowKey="id" size="small" dataSource={pool.filter((item) => !accounts.some((current) => current.id === item.id) && (!importSearch || [item.uid, item.nickname, item.username].some((value) => String(value || '').includes(importSearch))) && (!importTag || item.tag === importTag))} columns={[{ title: '账号', render: (_value, row) => row.nickname || row.username || row.uid }, { title: '标签', dataIndex: 'tag' }]} pagination={{ pageSize: 8 }} rowSelection={{ selectedRowKeys: importSelected, onChange: (keys) => setImportSelected(keys.map(Number)) }} /></Modal>
<Modal open={configOpen} title="精英宝典设置" onCancel={() => setConfigOpen(false)} onOk={() => void saveConfig()} confirmLoading={configSaving} okText="保存" cancelText="取消" width={500}>{configLoading ? <div style={{ padding: 24, textAlign: 'center' }}>...</div> : configDraft && <Space direction="vertical" size={10} style={{ width: '100%' }}>{configField('sid', '活动 SID')}{configField('bind_act_id', '绑定活动 ID')}{configField('outer_act_id', '外部活动 ID')}{configField('room_pid', '直播间 PID')}{configField('pay_channel', '支付渠道')}<Space style={{ width: '100%', justifyContent: 'space-between' }}><Text></Text><InputNumber min={5} max={60} value={refreshInterval} onChange={(value) => { const next = Math.min(60, Math.max(5, value || 15)); setRefreshInterval(next); localStorage.setItem('huya_elite_refresh_interval', String(next)); }} style={{ width: 300 }} /></Space></Space>}</Modal>
</div>;
}