@@ -765,63 +810,113 @@ export default function DouyuTasksPage() {
{/* Right: operations + logs */}
-
+ {selectedText}}
+ styles={{ body: { padding: 8 } }}
+ >
-
- {selectedIds.length > 0 ? `已选 ${selectedIds.length} 个账号` : '点击/右键选择账号'}
- setConcurrency(v || 1)} style={{ width: 70 }} addonBefore="并发" />
-
-
- {QUICK_ACTIONS.map((item) => (
-
- ))}
-
-
-
+
+
+ setConcurrency(v || 1)}
+ style={{ width: '100%' }}
+ />
+
+
+
+
+
+ 绑定与角色
+
+
+ {BIND_ACTION_KEYS.map((key) => renderActionButton(key))}
+
+
+
+
+
+
+ 查询
+
+
+ {QUERY_ACTION_KEYS.map((key) => renderActionButton(key))}
+
+
+
+
+
+
+ 兑换
+
+
+
+
+
+
+ 充值与送礼
+
+
+
+ setGoldAmount(v || 1)}
+ style={{ width: '100%' }}
+ />
}
- onClick={() => startTask('exchange_goods')}
- disabled={batchBusy || selectedIds.length === 0 || !selectedGoodsId}
+ icon={}
+ onClick={() => startTask('create_gold_qr')}
+ disabled={actionDisabled('create_gold_qr')}
>
- 兑换商品
-
-
-
-
-
-
-
- setGoldAmount(v || 1)} style={{ width: 80 }} />
- } onClick={() => startTask('create_gold_qr')} disabled={batchBusy || selectedIds.length === 0}>
充值鱼翅
-
-
- setGiftCount(v || 1)} style={{ width: 80 }} />
- } onClick={() => startTask('donate_elite_gift')} disabled={batchBusy || selectedIds.length === 0}>
+
+
+ setGiftCount(v || 1)}
+ style={{ width: '100%' }}
+ />
+ }
+ onClick={() => startTask('donate_elite_gift')}
+ disabled={actionDisabled('donate_elite_gift')}
+ >
赠送精英令
-
+
-
+
+
+ 勾选账号后可批量执行;右键账号行可快速执行单账号动作。
+
diff --git a/web/frontend/src/pages/HuyaTasksPage.tsx b/web/frontend/src/pages/HuyaTasksPage.tsx
index 7b4c252..8dc366f 100644
--- a/web/frontend/src/pages/HuyaTasksPage.tsx
+++ b/web/frontend/src/pages/HuyaTasksPage.tsx
@@ -50,6 +50,12 @@ const QUICK_ACTIONS = [
{ key: 'create_recharge_order', icon: },
];
+const BATCH_COMPATIBLE_TASK_TYPES = new Set(['confirm_bind', 'query_game_name']);
+
+function canRunDuringBatch(taskType: string): boolean {
+ return BATCH_COMPATIBLE_TASK_TYPES.has(taskType);
+}
+
const STATUS_COLORS: Record = {
planned: 'default',
pending: 'default',
@@ -106,6 +112,29 @@ const ACCOUNT_STATUS_COLORS: Record = {
recharge_order_created: 'processing',
};
+const HUYA_WORKBENCH_ACCOUNT_IDS_KEY = 'huya_task_workbench_account_ids';
+
+function readWorkbenchAccountIds(): number[] {
+ try {
+ const raw = localStorage.getItem(HUYA_WORKBENCH_ACCOUNT_IDS_KEY);
+ if (!raw) return [];
+ const values = JSON.parse(raw);
+ if (!Array.isArray(values)) return [];
+ return Array.from(new Set(values.map((value) => Number(value)).filter((value) => Number.isInteger(value) && value > 0)));
+ } catch {
+ return [];
+ }
+}
+
+function saveWorkbenchAccountIds(ids: number[]) {
+ const normalized = Array.from(new Set(ids.filter((id) => Number.isInteger(id) && id > 0)));
+ if (normalized.length === 0) {
+ localStorage.removeItem(HUYA_WORKBENCH_ACCOUNT_IDS_KEY);
+ return;
+ }
+ localStorage.setItem(HUYA_WORKBENCH_ACCOUNT_IDS_KEY, JSON.stringify(normalized));
+}
+
function accountLabel(account: HuyaAccountItem): string {
const name = account.nickname || account.username || account.uid || `#${account.id}`;
const tag = account.tag ? ` [${account.tag}]` : '';
@@ -155,29 +184,6 @@ function taskProductText(task: HuyaTaskItem | null | undefined): string {
);
}
-function isInteractiveTarget(target: EventTarget | null): boolean {
- const element = target instanceof HTMLElement ? target : null;
- if (!element) return false;
- return Boolean(element.closest([
- 'a',
- 'button',
- 'input',
- 'textarea',
- 'select',
- '[role="button"]',
- '[role="menuitem"]',
- '.ant-btn',
- '.ant-checkbox',
- '.ant-checkbox-wrapper',
- '.ant-dropdown',
- '.ant-dropdown-menu',
- '.ant-input',
- '.ant-picker',
- '.ant-select',
- '.ant-table-column-sorter',
- ].join(',')));
-}
-
function contextMenuPosition(x: number, y: number) {
if (typeof window === 'undefined') return { left: x, top: y };
return {
@@ -405,11 +411,15 @@ export default function HuyaTasksPage() {
const nextPool = accountResult.value;
const nextPoolById = new Map(nextPool.map((account) => [account.id, account]));
setAccountPool(nextPool);
- setAccounts((prev) => (
- prev
- .map((account) => nextPoolById.get(account.id))
- .filter((account): account is HuyaAccountItem => Boolean(account))
- ));
+ setAccounts((prev) => {
+ const persistedIds = readWorkbenchAccountIds();
+ const sourceIds = prev.length > 0 ? prev.map((account) => account.id) : persistedIds;
+ const nextAccounts = sourceIds
+ .map((id) => nextPoolById.get(id))
+ .filter((account): account is HuyaAccountItem => Boolean(account));
+ saveWorkbenchAccountIds(nextAccounts.map((account) => account.id));
+ return nextAccounts;
+ });
setSelectedIds((prev) => prev.filter((id) => nextPoolById.has(id)));
}
if (taskResult.status === 'fulfilled') {
@@ -529,14 +539,6 @@ export default function HuyaTasksPage() {
};
}, [accountContextMenu]);
- const toggleAccountSelection = useCallback((accountId: number) => {
- setSelectedIds((prev) => (
- prev.includes(accountId)
- ? prev.filter((id) => id !== accountId)
- : [...prev, accountId]
- ));
- }, []);
-
const tagColorMap = useMemo(() => {
const map: Record = {};
tags.forEach((tag, index) => {
@@ -644,6 +646,11 @@ export default function HuyaTasksPage() {
}, [tasks]);
const activeBatchId = runningTaskBatchId || (wsConnected ? batchId : null);
const batchBusy = Boolean(activeBatchId);
+ const isTaskActionDisabled = useCallback((taskType: string, accountIds = selectedIds) => (
+ !canTask
+ || accountIds.length === 0
+ || ((batchBusy || wsConnected) && !canRunDuringBatch(taskType))
+ ), [batchBusy, canTask, selectedIds, wsConnected]);
const sortedGoods = useMemo(() => {
return [...goods].sort((a, b) => (
@@ -755,7 +762,7 @@ export default function HuyaTasksPage() {
message.warning('请先选择虎牙 CK');
return;
}
- if (activeBatchId) {
+ if ((activeBatchId || wsConnected) && !canRunDuringBatch(taskType)) {
message.warning('当前已有虎牙批次在运行,请先停止或等待结束');
return;
}
@@ -860,6 +867,7 @@ export default function HuyaTasksPage() {
if (account) nextAccounts.push(account);
}
});
+ saveWorkbenchAccountIds(nextAccounts.map((account) => account.id));
return nextAccounts;
});
setSelectedIds(uniqueIds);
@@ -874,12 +882,17 @@ export default function HuyaTasksPage() {
return;
}
const selected = new Set(selectedIds);
- setAccounts((prev) => prev.filter((account) => !selected.has(account.id)));
+ setAccounts((prev) => {
+ const nextAccounts = prev.filter((account) => !selected.has(account.id));
+ saveWorkbenchAccountIds(nextAccounts.map((account) => account.id));
+ return nextAccounts;
+ });
setSelectedIds([]);
setAccountContextMenu(null);
};
const clearWorkbenchAccounts = () => {
+ saveWorkbenchAccountIds([]);
setAccounts([]);
setSelectedIds([]);
setAccountContextMenu(null);
@@ -981,20 +994,12 @@ export default function HuyaTasksPage() {
: '';
const confirmQrBind = () => {
if (!qrTask || !qrBindReady) return;
- if (batchBusy) {
- message.warning('当前已有虎牙批次在运行,请先停止或等待结束');
- return;
- }
const accountId = qrTask.account_id;
setQrTask(null);
void startTask('confirm_bind', [accountId]);
};
const queryQrRole = () => {
if (!qrTask) return;
- if (batchBusy) {
- message.warning('当前已有虎牙批次在运行,请先停止或等待结束');
- return;
- }
const accountId = qrTask.account_id;
void startTask('query_game_name', [accountId]);
};
@@ -1134,14 +1139,7 @@ export default function HuyaTasksPage() {
return value ? {JSON.stringify(value)} : -;
};
- const handleAccountRowClick = (record: HuyaAccountItem, event: ReactMouseEvent) => {
- if (isInteractiveTarget(event.target)) return;
- setAccountContextMenu(null);
- toggleAccountSelection(record.id);
- };
-
const handleAccountRowContextMenu = (record: HuyaAccountItem, event: ReactMouseEvent) => {
- if (isInteractiveTarget(event.target)) return;
event.preventDefault();
event.stopPropagation();
const accountIds = selectedIds.includes(record.id) && selectedIds.length > 0 ? selectedIds : [record.id];
@@ -1282,7 +1280,7 @@ export default function HuyaTasksPage() {
}}
trigger={['click']}
>
- } disabled={!canTask || batchBusy || wsConnected} />
+ } disabled={!canTask} />
);
},
@@ -1491,7 +1489,7 @@ export default function HuyaTasksPage() {
size="small"
block
icon={item.icon}
- disabled={!canTask || batchBusy || wsConnected}
+ disabled={isTaskActionDisabled(item.key, accountContextMenu.accountIds)}
onClick={() => runContextAccountAction(item.key)}
style={{ justifyContent: 'flex-start' }}
>
@@ -1585,10 +1583,9 @@ export default function HuyaTasksPage() {
}}
rowClassName={(record) => selectedIds.includes(record.id) ? 'ant-table-row-selected' : ''}
onRow={(record) => ({
- onClick: (event) => handleAccountRowClick(record, event),
onContextMenu: (event) => handleAccountRowContextMenu(record, event),
- style: { cursor: 'pointer' },
- title: '点击选中,右键打开账号动作',
+ style: { cursor: 'context-menu' },
+ title: '右键打开账号动作',
})}
/>
@@ -1651,7 +1648,7 @@ export default function HuyaTasksPage() {
block
icon={
}
loading={starting}
- disabled={!canTask || selectedIds.length === 0 || batchBusy || wsConnected}
+ disabled={isTaskActionDisabled(selectedTaskType)}
onClick={() => startTask()}
>
创建任务
@@ -1664,7 +1661,7 @@ export default function HuyaTasksPage() {
@@ -1904,17 +1901,6 @@ export default function HuyaTasksPage() {
scroll={{ x: 720, y: 360 }}
locale={{ emptyText: '没有可导入账号' }}
rowClassName={(record) => importSelectedIds.includes(record.id) ? 'ant-table-row-selected' : ''}
- onRow={(record) => ({
- onClick: (event) => {
- if (isInteractiveTarget(event.target)) return;
- setImportSelectedIds((prev) => (
- prev.includes(record.id)
- ? prev.filter((id) => id !== record.id)
- : [...prev, record.id]
- ));
- },
- style: { cursor: 'pointer' },
- })}
/>