From 555f179b411539684b1a7130c680f747a4ba9b9f Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sun, 2 Aug 2026 13:50:17 +0800 Subject: [PATCH] =?UTF-8?q?feat(douyu):=20=E7=B2=BE=E8=8B=B1=E5=AE=9D?= =?UTF-8?q?=E5=85=B8=E5=B7=A5=E4=BD=9C=E5=8F=B0=E6=94=AF=E6=8C=81=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B/=E5=B7=A6=E5=8F=B3=E5=B8=83=E5=B1=80=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E5=B9=B6=E8=AE=B0=E5=BF=86=E5=81=8F=E5=A5=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/frontend/src/pages/DouyuTasksPage.tsx | 500 ++++++++++++---------- 1 file changed, 274 insertions(+), 226 deletions(-) diff --git a/web/frontend/src/pages/DouyuTasksPage.tsx b/web/frontend/src/pages/DouyuTasksPage.tsx index c30cef3..c14a608 100644 --- a/web/frontend/src/pages/DouyuTasksPage.tsx +++ b/web/frontend/src/pages/DouyuTasksPage.tsx @@ -1,10 +1,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { - Button, Card, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tag, Typography, + Button, Card, Input, InputNumber, Modal, QRCode, Select, Space, Table, Tag, Tooltip, Typography, } from 'antd'; import type { TableProps } from 'antd'; import { - CheckCircleOutlined, CopyOutlined, CreditCardOutlined, ExclamationCircleOutlined, FieldTimeOutlined, GiftOutlined, + CheckCircleOutlined, ColumnWidthOutlined, CopyOutlined, CreditCardOutlined, ExclamationCircleOutlined, FieldTimeOutlined, GiftOutlined, ImportOutlined, QrcodeOutlined, ReloadOutlined, SearchOutlined, SettingOutlined, ShoppingOutlined, StopOutlined, } from '@ant-design/icons'; @@ -116,6 +116,7 @@ const ESPORTS_TASK_TYPES = new Set([ ]); const DOUYU_GOLD_AMOUNT_STORAGE_KEY = 'douyu_task_gold_amount'; const DOUYU_GIFT_COUNT_STORAGE_KEY = 'douyu_task_gift_count'; +const DOUYU_LAYOUT_MODE_STORAGE_KEY = 'douyu_task_layout_mode'; const CONFIRM_FAIL_TIP_SECONDS = 6; function resultText(result: Record | null | undefined, key: string): string { @@ -284,6 +285,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) const [giftCount, setGiftCount] = useState(() => savedPositiveInteger(DOUYU_GIFT_COUNT_STORAGE_KEY)); const [selectedGoodsId, setSelectedGoodsId] = useState(''); const [accountSearch, setAccountSearch] = useState(''); + const [layoutMode, setLayoutMode] = useState<'stack' | 'split'>(() => { + const v = localStorage.getItem(DOUYU_LAYOUT_MODE_STORAGE_KEY); + return v === 'split' ? 'split' : 'stack'; + }); const accountTableAreaRef = useRef(null); const [accountTableAreaHeight, setAccountTableAreaHeight] = useState(360); const [accountPageSize, setAccountPageSize] = useState(() => { @@ -459,6 +464,10 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) useEffect(() => { loadData(); }, [loadData]); + useEffect(() => { + localStorage.setItem(DOUYU_LAYOUT_MODE_STORAGE_KEY, layoutMode); + }, [layoutMode]); + // 监听账号表格区域高度变化,动态计算 scroll.y 实现表体内部滚动 useEffect(() => { const node = accountTableAreaRef.current; @@ -472,7 +481,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) const observer = new ResizeObserver(update); observer.observe(node); return () => observer.disconnect(); - }, []); + }, [layoutMode]); useEffect(() => { localStorage.setItem(DOUYU_GOLD_AMOUNT_STORAGE_KEY, String(goldAmount)); @@ -1236,6 +1245,222 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 8, }; + const operationExtra = ( + + {selectedText} + + + setConcurrency(v || 1)} + style={{ width: 70 }} + /> + + + ); + const operationBody = ( + <> + {isEsportsHandbook ? ( +
+
+
+ + 开通手册 +
+ {renderActionButton('create_esports_qr', 'primary')} +
+
+
+ + 皮肤兑换 +
+ + ({ value: item.commodity_id, label: goodsLabel(item) }))} + placeholder="选择兑换商品" + showSearch + optionFilterProp="label" + style={{ width: '100%' }} + /> +
+ {renderActionButton('refresh_goods')} + {renderActionButton('exchange_goods', 'primary')} +
+
+
+ +
+
+ + 开通、充值与送礼 +
+ + {renderActionButton('create_elite_qr', 'primary')} + + setGoldAmount(v || 1)} + style={{ width: '100%' }} + /> + + + + setGiftCount(v || 1)} + style={{ width: '100%' }} + /> + + + +
+
+ )} + + 勾选账号后可批量执行;账号行右键可执行绑定与查询动作。 + + + ); + const accountsBody = ( + <> + + + } + value={accountSearch} onChange={(e) => setAccountSearch(e.target.value)} + style={{ width: 200 }} allowClear + /> + + + + {selectedIds.length > 0 && ( + + )} + + +
+ setSelectedIds(keys.map(Number)), + }} + columns={accountColumns} + dataSource={filteredAccounts} + pagination={{ + pageSize: accountPageSize, + showSizeChanger: true, + pageSizeOptions: [10, 20, 50, 100], + size: 'small', + showLessItems: true, + showTotal: (total) => `共 ${total} 条`, + onChange: (_page, size) => { + if (size !== accountPageSize) { + setAccountPageSize(size); + localStorage.setItem('douyu_task_account_page_size', String(size)); + } + }, + }} + tableLayout="fixed" + scroll={{ x: 1180, y: Math.max(120, accountTableAreaHeight - 80) }} + onRow={(record) => ({ + onContextMenu: (e) => handleRowContextMenu(record, e), + style: { cursor: 'context-menu' }, + title: '右键打开账号动作', + })} + /> + + + ); // 任务记录表:表头固定、表体内部滚动、分页器固定显示在底部 const taskRecordTableStyle = ` .douyu-task-record-table { @@ -1304,237 +1529,60 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind }) {handbookDescription} + + {canConfig && } {(activeBatchIds.size > 0 || runningBatchIds.size > 0) && } - {/* 操作工具栏 */} - - {selectedText} - - - setConcurrency(v || 1)} - style={{ width: 70 }} - /> - - - } - style={{ flexShrink: 0, marginBottom: 12 }} - styles={{ body: { padding: 8 } }} - > - {isEsportsHandbook ? ( -
-
-
- - 开通手册 -
- {renderActionButton('create_esports_qr', 'primary')} -
-
-
- - 皮肤兑换 -
- - ({ value: item.commodity_id, label: goodsLabel(item) }))} - placeholder="选择兑换商品" - showSearch - optionFilterProp="label" - style={{ width: '100%' }} - /> -
- {renderActionButton('refresh_goods')} - {renderActionButton('exchange_goods', 'primary')} -
-
-
- -
-
- - 开通、充值与送礼 -
- - {renderActionButton('create_elite_qr', 'primary')} - - setGoldAmount(v || 1)} - style={{ width: '100%' }} - /> - - - - setGiftCount(v || 1)} - style={{ width: '100%' }} - /> - - - -
-
- )} - - 勾选账号后可批量执行;账号行右键可执行绑定与查询动作。 - -
- - {/* 账号 */} - 已选 {selectedIds.length}/{accounts.length}} - 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' } }} - > - - - } - value={accountSearch} onChange={(e) => setAccountSearch(e.target.value)} - style={{ width: 200 }} allowClear - /> - - - - {selectedIds.length > 0 && ( - - )} - - -
-
+ 已选 {selectedIds.length}/{accounts.length}} + 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} + + setSelectedIds(keys.map(Number)), - }} - columns={accountColumns} - dataSource={filteredAccounts} - pagination={{ - pageSize: accountPageSize, - showSizeChanger: true, - pageSizeOptions: [10, 20, 50, 100], - size: 'small', - showLessItems: true, - showTotal: (total) => `共 ${total} 条`, - onChange: (_page, size) => { - if (size !== accountPageSize) { - setAccountPageSize(size); - localStorage.setItem('douyu_task_account_page_size', String(size)); - } - }, - }} - tableLayout="fixed" - scroll={{ x: 1180, y: Math.max(120, accountTableAreaHeight - 80) }} - onRow={(record) => ({ - onContextMenu: (e) => handleRowContextMenu(record, e), - style: { cursor: 'context-menu' }, - title: '右键打开账号动作', - })} - /> + title={isEsportsHandbook ? '电竞手册操作' : '精英宝典操作'} + extra={operationExtra} + style={{ width: 340, flexShrink: 0, minHeight: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden' }} + styles={{ body: { padding: 8, flex: 1, minHeight: 0, overflowY: 'auto' } }} + > + {operationBody} + - + ) : ( + <> + + {operationBody} + + 已选 {selectedIds.length}/{accounts.length}} + 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} + + + )} {/* 右键菜单 */} {contextMenu && (