优化任务列表性能和本地登录代理

This commit is contained in:
yml2213
2026-08-05 10:03:18 +08:00
parent dff0d7db9e
commit 2fda47c631
13 changed files with 252 additions and 29 deletions
+2
View File
@@ -31,6 +31,7 @@ import type {
MessageResponse,
PageParams,
PaginatedResponse,
TaskSummary,
} from './types';
export const huyaApi = {
@@ -108,6 +109,7 @@ export const huyaApi = {
// 列表轮询默认不带 base64 小程序码,避免每次 1MB+ 流量。
params: batchId ? { batch_id: batchId, include_images: false } : { include_images: false },
}),
tasksSummary: () => api.get<TaskSummary, TaskSummary>('/huya/tasks/summary'),
getTask: (taskId: number) =>
api.get<HuyaTaskItem, HuyaTaskItem>(`/huya/tasks/${taskId}`),
};
+2 -1
View File
@@ -1,5 +1,5 @@
import api from './client';
import type { BatchLoginResult, LoginTaskItem, MessageDeletedResponse, MessageResponse } from './types';
import type { BatchLoginResult, LoginTaskItem, MessageDeletedResponse, MessageResponse, TaskSummary } from './types';
interface CreateBatchParams {
account_ids: number[];
@@ -15,6 +15,7 @@ export const loginApi = {
api.post<BatchLoginResult, BatchLoginResult>('/login/batch', params),
listTasks: (batch_id?: string) =>
api.get<LoginTaskItem[], LoginTaskItem[]>('/login/tasks', { params: batch_id ? { batch_id } : {} }),
tasksSummary: () => api.get<TaskSummary, TaskSummary>('/login/tasks/summary'),
stop: (batch_id: string) => api.post<MessageResponse, MessageResponse>(`/login/stop/${batch_id}`),
deleteTask: (id: number) => api.delete<MessageResponse, MessageResponse>(`/login/tasks/${id}`),
deleteTasks: (ids: number[]) => api.delete<MessageDeletedResponse, MessageDeletedResponse>(`/login/tasks`, { params: { task_ids: ids.join(',') } }),
+7
View File
@@ -50,6 +50,13 @@ export interface BasicSummary {
tag_count?: number;
}
export interface TaskSummary {
total: number;
success: number;
failed: number;
status_counts: Record<string, number>;
}
// ==================== Auth ====================
export interface LoginResult {
+10 -16
View File
@@ -5,8 +5,6 @@ import {
cookieApi,
huyaApi,
loginApi,
type HuyaTaskItem,
type LoginTaskItem,
} from '../api/modules';
import { usePermissions } from '../hooks/usePermissions';
@@ -47,10 +45,6 @@ const EMPTY_STATS: DashboardStats = {
},
};
function countFailed<T extends { status: string }>(items: T[]) {
return items.filter((item) => ['failed', 'error'].includes(item.status)).length;
}
function StatCard({
title,
value,
@@ -98,10 +92,10 @@ export default function DashboardPage() {
huyaRechargeGoodsResult,
] = await Promise.allSettled([
canViewDouyuAccounts ? accountApi.summary() : Promise.resolve(null),
canViewDouyuTasks ? loginApi.listTasks() : Promise.resolve([]),
canViewDouyuTasks ? loginApi.tasksSummary() : Promise.resolve(null),
canViewCookies ? cookieApi.summary() : Promise.resolve(null),
canViewHuyaAccounts ? huyaApi.accountsSummary() : Promise.resolve(null),
canViewHuyaTasks ? huyaApi.listTasks() : Promise.resolve([]),
canViewHuyaTasks ? huyaApi.tasksSummary() : Promise.resolve(null),
canViewHuyaTasks ? huyaApi.listGoods() : Promise.resolve([]),
canViewHuyaTasks ? huyaApi.listRechargeGoods() : Promise.resolve([]),
]);
@@ -109,26 +103,26 @@ export default function DashboardPage() {
if (ignore) return;
const douyuAccounts = douyuAccountsResult.status === 'fulfilled' ? douyuAccountsResult.value : null;
const douyuTasks = douyuTasksResult.status === 'fulfilled' ? douyuTasksResult.value : [];
const douyuTasks = douyuTasksResult.status === 'fulfilled' ? douyuTasksResult.value : null;
const cookies = cookiesResult.status === 'fulfilled' ? cookiesResult.value : null;
const huyaAccounts = huyaAccountsResult.status === 'fulfilled' ? huyaAccountsResult.value : null;
const huyaTasks = huyaTasksResult.status === 'fulfilled' ? huyaTasksResult.value : [];
const huyaTasks = huyaTasksResult.status === 'fulfilled' ? huyaTasksResult.value : null;
const huyaGoods = huyaGoodsResult.status === 'fulfilled' ? huyaGoodsResult.value : [];
const huyaRechargeGoods = huyaRechargeGoodsResult.status === 'fulfilled' ? huyaRechargeGoodsResult.value : [];
setStats({
douyu: {
accounts: douyuAccounts?.total || 0,
tasks: douyuTasks.length,
success: douyuTasks.filter((task: LoginTaskItem) => task.status === 'success').length,
failed: countFailed(douyuTasks),
tasks: douyuTasks?.total || 0,
success: douyuTasks?.success || 0,
failed: douyuTasks?.failed || 0,
cookies: cookies?.total || 0,
},
huya: {
accounts: huyaAccounts?.total || 0,
tasks: huyaTasks.length,
success: huyaTasks.filter((task: HuyaTaskItem) => task.status === 'success').length,
failed: countFailed(huyaTasks),
tasks: huyaTasks?.total || 0,
success: huyaTasks?.success || 0,
failed: huyaTasks?.failed || 0,
goods: huyaGoods.length,
rechargeGoods: huyaRechargeGoods.length,
},
@@ -302,6 +302,7 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
const v = Number(localStorage.getItem('douyu_task_account_page_size'));
return [10, 20, 50, 100].includes(v) ? v : 20;
});
const tasksLoadingRef = useRef(false);
const [importOpen, setImportOpen] = useState(false);
const [importPool, setImportPool] = useState<DouyuTaskAccountItem[]>([]);
@@ -507,6 +508,8 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
}, [canConfig, isEsportsHandbook, workbenchIds]);
const loadTasks = useCallback(async () => {
if (tasksLoadingRef.current) return;
tasksLoadingRef.current = true;
try {
const data = await douyuApi.listTasks();
setTasks(data);
@@ -516,6 +519,8 @@ export default function DouyuTasksPage({ handbook }: { handbook: HandbookKind })
}
} catch {
// 轮询失败不打扰操作
} finally {
tasksLoadingRef.current = false;
}
}, []);
+5
View File
@@ -316,6 +316,7 @@ export default function HuyaTasksPage() {
const notifiedPaidTaskIds = useRef<Set<number>>(new Set());
const accountTableAreaRef = useRef<HTMLDivElement | null>(null);
const [accountTableAreaHeight, setAccountTableAreaHeight] = useState(460);
const tasksLoadingRef = useRef(false);
const { logs, connected: wsConnected, connect: connectLogs } = useWebSocketLogs();
const { can } = usePermissions();
@@ -455,6 +456,8 @@ export default function HuyaTasksPage() {
}, [canConfig, form, rememberExistingPaymentQrcodes, rememberExistingQrcodes]);
const loadTasks = useCallback(async () => {
if (tasksLoadingRef.current) return;
tasksLoadingRef.current = true;
try {
const data = await huyaApi.listTasks();
const nextTasks = mergeTaskImageCache(data, qrImageCacheRef.current);
@@ -466,6 +469,8 @@ export default function HuyaTasksPage() {
}
} catch {
// 轮询失败不打扰操作,下一轮继续刷新。
} finally {
tasksLoadingRef.current = false;
}
}, [rememberExistingPaymentQrcodes, rememberExistingQrcodes]);
+14 -3
View File
@@ -1,4 +1,4 @@
import { useEffect, useState, useMemo, useCallback } from 'react';
import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
import {
Table, Button, Select, Tag, Space, InputNumber, Tooltip, Popconfirm, theme, Modal, Form,
} from 'antd';
@@ -71,6 +71,7 @@ export default function LoginTasksPage() {
return localStorage.getItem('login_api_strategy') || 'wgapi';
});
const [settingsOpen, setSettingsOpen] = useState(false);
const tasksLoadingRef = useRef(false);
// 值变化时自动持久化
useEffect(() => { localStorage.setItem('login_concurrency', String(concurrency)); }, [concurrency]);
@@ -145,22 +146,32 @@ export default function LoginTasksPage() {
}, [selectedTags, tagAccountMap, accounts]);
const loadTasks = useCallback(async () => {
if (tasksLoadingRef.current) return;
tasksLoadingRef.current = true;
try {
const data = await loginApi.listTasks(batchId || undefined);
setTasks(data);
} catch {
// 忽略轮询失败,下一次定时刷新会继续尝试。
} finally {
tasksLoadingRef.current = false;
}
}, [batchId]);
const hasActiveTasks = useMemo(
() => tasks.some((task) => ['pending', 'running'].includes(task.status)),
[tasks],
);
useEffect(() => {
Promise.all([loadAccounts(), loadTasks()]);
}, [loadAccounts, loadTasks]);
useEffect(() => {
const timer = setInterval(loadTasks, 3000);
const intervalMs = hasActiveTasks || wsConnected || starting || batchId ? 3000 : 15000;
const timer = setInterval(loadTasks, intervalMs);
return () => clearInterval(timer);
}, [loadTasks]);
}, [batchId, hasActiveTasks, loadTasks, starting, wsConnected]);
// 共享的批量任务启动逻辑
const startBatch = async (accountIds: number[], mode: BatchMode = 'login') => {