From 947539f01efb1b3fa9007a7196240a525064b230 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 1 Aug 2026 10:03:02 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=99=90=E5=88=B6=E5=85=91?= =?UTF-8?q?=E6=8D=A2=E6=98=BE=E7=A4=BA=E6=96=87=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/frontend/src/pages/DouyuTasksPage.tsx | 260 +++++++++++++--------- 1 file changed, 153 insertions(+), 107 deletions(-) diff --git a/web/frontend/src/pages/DouyuTasksPage.tsx b/web/frontend/src/pages/DouyuTasksPage.tsx index 8d5981e..18131ea 100644 --- a/web/frontend/src/pages/DouyuTasksPage.tsx +++ b/web/frontend/src/pages/DouyuTasksPage.tsx @@ -15,7 +15,6 @@ import { type DouyuTaskAccountItem, type DouyuTaskItem, } from '../api/modules'; -import RealtimeLogPanel from '../components/RealtimeLogPanel'; import { usePermissions } from '../hooks/usePermissions'; import { useWebSocketLogs } from '../hooks/useWebSocketLogs'; import { formatTime } from '../utils/time'; @@ -53,8 +52,6 @@ const ESPORTS_QUICK_ACTIONS = [ { key: 'donate_esports_firework_gift', icon: }, ]; -const ELITE_BIND_ACTION_KEYS = ['get_bind_qr', 'confirm_bind', 'query_game_name', 'query_change_bind_time']; -const ELITE_QUERY_ACTION_KEYS = ['query_points', 'query_limited_goods', 'query_gold_balance']; const ELITE_TASK_TYPES = new Set([ 'get_bind_qr', 'confirm_bind', @@ -98,26 +95,6 @@ const STATUS_LABELS: Record = { success: '成功', failed: '失败', error: '异常', stopped: '已停止', }; -const BIND_STATUS_LABELS: Record = { - bind_qr_generated: '待扫码', - game_queried: '已识别角色', - game_not_bound: '未绑定', - bind_confirmed: '已绑定', - esports_bind_qr_generated: '电竞待绑定', - esports_role_queried: '已查电竞角色', - esports_bind_ready: '电竞待确认', - esports_role_switching: '电竞换角色', - esports_bind_confirming: '电竞绑定中', - esports_bound: '电竞已绑定', - esports_qr_created: '电竞待开通', - esports_opened: '电竞已开通', - esports_points_queried: '已查电竞积分', - esports_gift_donated: '已赠电竞礼物', - esports_goods_refreshed: '已刷电竞皮肤', - esports_goods_exchanged: '已兑电竞皮肤', - change_time_queried: '已查换绑', -}; - function resultText(result: Record | null | undefined, key: string): string { const value = result?.[key]; return typeof value === 'string' ? value : ''; @@ -224,6 +201,26 @@ function resultNumber(result: Record | null | undefined, key: s return null; } +function limitedGoodsName(item: unknown): string { + if (!item || typeof item !== 'object') return ''; + const record = item as Record; + const value = record.commodityName ?? record.name; + return typeof value === 'string' ? value : ''; +} + +function limitedGoodsTaskText(task: DouyuTaskItem | null | undefined): string { + if (!task) return ''; + if (task.message?.trim()) return task.message.trim(); + const count = resultNumber(task.result, 'limited_count'); + if (count == null) return ''; + if (count <= 0) return '无限制商品'; + const goods = task.result?.limited_goods; + const names = Array.isArray(goods) + ? goods.map(limitedGoodsName).filter(Boolean).slice(0, 5) + : []; + return names.length ? `限兑 ${count} 个: ${names.join(', ')}` : `限兑 ${count} 个`; +} + function savedPositiveInteger(key: string, fallback = 1): number { const value = Number(localStorage.getItem(key)); return Number.isInteger(value) && value >= 1 ? value : fallback; @@ -322,6 +319,17 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) return map; }, [isEsportsHandbook, tasks]); + const latestLimitedGoodsTaskByAccount = useMemo(() => { + const map = new Map(); + for (const task of tasks) { + if (task.task_type !== 'query_limited_goods') continue; + if (!['success', 'failed', 'error'].includes(task.status)) continue; + const previous = map.get(task.account_id); + if (!previous || task.id > previous.id) map.set(task.account_id, task); + } + return map; + }, [tasks]); + const loadData = useCallback(async () => { setLoading(true); try { @@ -355,7 +363,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) } finally { setLoading(false); } - }, [canConfig]); + }, [canConfig, isEsportsHandbook]); const loadTasks = useCallback(async () => { try { @@ -779,6 +787,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) // Account table const accountColumns: TableProps['columns'] = [ + { + title: '#', width: 45, + render: (_, __, index) => {index + 1}, + }, { title: '账号', dataIndex: 'username', width: 150, render: (_, record) => ( @@ -822,6 +834,20 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) title: '鱼翅', dataIndex: 'gold_balance', width: 70, render: (v: number | null) => v ?? -, }, + { + title: '限制兑换', dataIndex: 'exchange_balance', width: 150, ellipsis: true, + render: (_, record) => { + const latestLimitedTask = latestLimitedGoodsTaskByAccount.get(record.id); + const text = limitedGoodsTaskText(latestLimitedTask); + if (!text) return 未查; + if (latestLimitedTask?.status !== 'success') { + return {text}; + } + const count = resultNumber(latestLimitedTask.result, 'limited_count'); + if (count === 0 || text === '无限制商品') return 无限制商品; + return {text}; + }, + }, { title: '换绑时间', dataIndex: 'change_role_wait_time', width: 130, render: (_, record) => { @@ -874,30 +900,16 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) return aw - bw; }, }, - { - title: '状态', dataIndex: 'bind_status', width: 100, - render: (_, record) => { - const value = isEsportsHandbook ? record.esports_bind_status : record.bind_status; - return value - ? {BIND_STATUS_LABELS[value] || value} - : -; - }, - }, ]; - // Task table + // 任务记录表 const taskColumns: TableProps['columns'] = [ { - title: '账号', dataIndex: 'account_username', width: 120, + title: '账号', dataIndex: 'account_username', width: 130, render: (_, r) => r.account_nickname || r.account_username || r.account_uid, }, - { title: '任务', dataIndex: 'task_type', width: 140, render: (v) => taskTypes[v] || v }, { - title: '状态', dataIndex: 'status', width: 80, - render: (v) => {STATUS_LABELS[v] || v}, - }, - { - title: '结果', dataIndex: 'message', ellipsis: true, + title: '结果', dataIndex: 'message', width: 300, ellipsis: true, render: (_, r) => { if (['prepare_esports_bind', 'get_esports_bind_qr'].includes(r.task_type)) { const roleName = resultText(r.result, 'role_name'); @@ -990,8 +1002,13 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) ); }, }, + { title: '任务', dataIndex: 'task_type', width: 170, render: (v) => taskTypes[v] || v }, { - title: '时间', dataIndex: 'finished_at', width: 150, + title: '状态', dataIndex: 'status', width: 80, + render: (v) => {STATUS_LABELS[v] || v}, + }, + { + title: '时间', dataIndex: 'finished_at', width: 170, render: (_, r) => formatTime(r.finished_at || r.created_at), }, ]; @@ -1059,10 +1076,73 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) gridTemplateColumns: '1fr 1fr', gap: 6, }; + const compactOperationGridStyle = { + display: 'grid', + gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', + gap: 8, + }; + // 任务记录表:表头固定、表体内部滚动、分页器固定显示在底部 + const taskRecordTableStyle = ` + .douyu-task-record-table { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + } + .douyu-task-record-table .ant-spin-nested-loading, + .douyu-task-record-table .ant-spin-container { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + } + .douyu-task-record-table .ant-table { + flex: 1; + min-height: 0; + font-size: 12px; + display: flex; + flex-direction: column; + } + .douyu-task-record-table .ant-table-container { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; + } + .douyu-task-record-table .ant-table-header { + flex-shrink: 0; + } + .douyu-task-record-table .ant-table-body { + flex: 1; + min-height: 0; + overflow: auto !important; + } + .douyu-task-record-table .ant-table-thead > tr > th, + .douyu-task-record-table .ant-table-tbody > tr > td { + padding: 4px 8px; + line-height: 20px; + } + .douyu-task-record-table .ant-typography { + font-size: 12px; + } + .douyu-task-record-table .ant-tag { + font-size: 11px; + line-height: 18px; + margin-inline-end: 0; + padding-inline: 5px; + } + .douyu-task-record-table .ant-pagination { + flex-shrink: 0; + margin: 0; + padding: 6px 4px 2px; + border-top: 1px solid #f0f0f0; + } + `; return (
- {/* Header */} + + {/* 头部 */}

{handbookTitle}

@@ -1075,19 +1155,19 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) - {/* Main grid */} + {/* 主布局 */}
- {/* Left: accounts + tasks */} + {/* 左侧账号 */}
已选 {selectedIds.length}/{accounts.length}} - style={{ flex: '1 1 0', minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} + style={{ flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} styles={{ body: { flex: 1, minHeight: 0, overflow: 'hidden', padding: 8, display: 'flex', flexDirection: 'column' } }} > @@ -1108,7 +1188,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) ({ onContextMenu: (e) => handleRowContextMenu(record, e), style: { cursor: 'context-menu' }, @@ -1125,7 +1204,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) })} /> + + {/* 右侧:任务记录(弹性高度) + 操作区(自适应) */} +
失败 {visibleTasks.filter((t) => ['failed', 'error'].includes(t.status)).length} } - style={{ flex: '0 0 300px', minHeight: 260, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} - styles={{ body: { flex: 1, minHeight: 0, padding: 8, overflow: 'hidden', display: 'flex', flexDirection: 'column' } }} + 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' } }} >
`共 ${total} 条`, + }} + tableLayout="fixed" + scroll={{ x: 850 }} /> - - {/* Right: operations + logs */} -
{selectedText}} - styles={{ body: { padding: 8 } }} + style={{ flexShrink: 0, display: 'flex', flexDirection: 'column' }} + styles={{ body: { padding: 8, maxHeight: 260, overflow: 'auto' } }} > @@ -1168,17 +1256,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) {isEsportsHandbook ? ( - <> -
-
- - 角色绑定 -
-
- {renderActionButton('prepare_esports_bind', 'primary')} - {renderActionButton('query_esports_game_name')} -
-
+
@@ -1186,16 +1264,6 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
{renderActionButton('create_esports_qr', 'primary')}
-
-
- - 查询 -
-
- {renderActionButton('query_esports_points')} - {renderActionButton('query_gold_balance')} -
-
@@ -1255,29 +1323,9 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
- +
) : ( - <> -
-
- - 绑定与角色 -
-
- {ELITE_BIND_ACTION_KEYS.map((key) => renderActionButton(key))} -
-
- -
-
- - 查询 -
-
- {ELITE_QUERY_ACTION_KEYS.map((key) => renderActionButton(key))} -
-
- +
@@ -1344,19 +1392,17 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
- +
)} - 勾选账号后可批量执行;右键账号行可快速执行单账号动作。 + 勾选账号后可批量执行;账号行右键可执行绑定与查询动作。 - -
- {/* Context menu */} + {/* 右键菜单 */} {contextMenu && (
e.stopPropagation()}>