优化登录重试与配置清理

This commit is contained in:
yml2213
2026-06-24 10:20:33 +08:00
parent 72248eb3cc
commit 0d03b2c242
19 changed files with 136 additions and 273 deletions
+2 -1
View File
@@ -12,7 +12,8 @@ import ProxyPage from './pages/ProxyPage';
import UsersPage from './pages/UsersPage';
import CookiePage from './pages/CookiePage';
import { getUser } from './store/auth';
import { ThemeProvider, useTheme } from './store/theme';
import { ThemeProvider } from './store/theme';
import { useTheme } from './store/useTheme';
function AppContent() {
// 用 state 驱动重渲染,登录/登出时调 refreshAuth()
-2
View File
@@ -3,9 +3,7 @@ import type { BatchLoginResult, LoginTaskItem, MessageDeletedResponse, MessageRe
interface CreateBatchParams {
account_ids: number[];
max_geetest_retries?: number;
concurrency?: number;
max_proxy_retries?: number;
max_login_retries?: number;
max_total_time?: number;
}
-27
View File
@@ -121,33 +121,6 @@ export interface ProxyTestResult {
success: boolean;
}
// ==================== Log ====================
export interface HttpLogEntry {
timestamp: string;
ts: number;
category: string;
tag: string;
method: string;
url: string;
proxy: string | null;
request: { headers: Record<string, string>; body: string };
response: { status_code: number | null; headers: Record<string, string>; body: string };
duration_ms: number | null;
error: string | null;
level: string;
}
export interface HttpLogListResult {
items: HttpLogEntry[];
total: number;
}
export interface HttpLogClearResult {
success: boolean;
cleared: number;
}
// ==================== Permissions ====================
export interface PermissionsListResult {
+2 -1
View File
@@ -9,7 +9,8 @@ import {
import { useNavigate, useLocation, Outlet } from 'react-router-dom';
import { getUser, clearAuth, type AuthUser } from '../store/auth';
import { authApi } from '../api/modules';
import { useTheme, type ThemeMode } from '../store/theme';
import { type ThemeMode } from '../store/theme';
import { useTheme } from '../store/useTheme';
import { usePermissions } from '../hooks/usePermissions';
const { Sider, Content } = Layout;
+5 -1
View File
@@ -24,7 +24,11 @@ function copyToClipboard(text: string): Promise<void> {
try {
const ok = document.execCommand('copy');
document.body.removeChild(ta);
ok ? resolve() : reject(new Error('execCommand copy failed'));
if (ok) {
resolve();
} else {
reject(new Error('execCommand copy failed'));
}
} catch (e) {
document.body.removeChild(ta);
reject(e);
+1 -1
View File
@@ -5,7 +5,7 @@ import { useNavigate } from 'react-router-dom';
import { authApi } from '../api/modules';
import { setAuth, type AuthUser } from '../store/auth';
import { getErrorMessage } from '../utils/error';
import { useTheme } from '../store/theme';
import { useTheme } from '../store/useTheme';
const { Title } = Typography;
+1 -23
View File
@@ -2,7 +2,7 @@ import { useEffect, useState, useMemo, useCallback } from 'react';
import {
Table, Button, Select, message, Tag, Space, InputNumber, Tooltip, Popconfirm, theme,
} from 'antd';
import { PlayCircleOutlined, StopOutlined, FilterOutlined, ThunderboltOutlined, ReloadOutlined, DeleteOutlined, SwapOutlined } from '@ant-design/icons';
import { PlayCircleOutlined, StopOutlined, FilterOutlined, ThunderboltOutlined, ReloadOutlined, DeleteOutlined } from '@ant-design/icons';
import { accountApi, loginApi, type AccountItem, type LoginTaskItem } from '../api/modules';
import RealtimeLogPanel from '../components/RealtimeLogPanel';
import { usePermissions } from '../hooks/usePermissions';
@@ -42,14 +42,9 @@ export default function LoginTasksPage() {
const v = localStorage.getItem('login_concurrency');
return v ? Math.max(1, Math.min(10, Number(v) || 3)) : 3;
});
const [maxProxyRetries, setMaxProxyRetries] = useState(() => {
const v = localStorage.getItem('login_max_proxy_retries');
return v ? Math.max(0, Number(v) || 0) : 0;
});
// 值变化时自动持久化
useEffect(() => { localStorage.setItem('login_concurrency', String(concurrency)); }, [concurrency]);
useEffect(() => { localStorage.setItem('login_max_proxy_retries', String(maxProxyRetries)); }, [maxProxyRetries]);
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
const { logs, connected: wsConnected, connect: connectLogs } = useWebSocketLogs();
const { can } = usePermissions();
@@ -145,11 +140,7 @@ export default function LoginTasksPage() {
try {
const result = await loginApi.createBatch({
account_ids: accountIds,
max_geetest_retries: 5,
concurrency,
max_proxy_retries: maxProxyRetries,
max_login_retries: 3,
max_total_time: 300,
});
setBatchId(result.batch_id);
message.success(`已创建登录任务,共 ${result.count} 个账号`);
@@ -347,19 +338,6 @@ export default function LoginTasksPage() {
/>
</Space>
</Tooltip>
<Tooltip title="代理切换次数,0=无限切换直到成功">
<Space size={4}>
<SwapOutlined style={{ color: token.colorTextSecondary }} />
<InputNumber
min={0}
max={999}
value={maxProxyRetries}
onChange={(v) => setMaxProxyRetries(v ?? 10)}
style={{ width: 55 }}
size="small"
/>
</Space>
</Tooltip>
<Button
type="primary"
icon={<PlayCircleOutlined />}
+11 -26
View File
@@ -1,28 +1,17 @@
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from 'react';
import { useState, useEffect, useCallback, type ReactNode } from 'react';
import {
DEFAULT_THEME_MODE,
THEME_STORAGE_KEY,
ThemeContext,
type ThemeMode,
} from './themeContext';
export type ThemeMode = 'light' | 'dark' | 'system';
const STORAGE_KEY = 'theme_mode';
const DEFAULT_MODE: ThemeMode = 'system';
interface ThemeContextValue {
mode: ThemeMode;
isDark: boolean;
setMode: (mode: ThemeMode) => void;
toggle: () => void;
}
const ThemeContext = createContext<ThemeContextValue>({
mode: DEFAULT_MODE,
isDark: false,
setMode: () => {},
toggle: () => {},
});
export type { ThemeMode } from './themeContext';
export function ThemeProvider({ children }: { children: ReactNode }) {
const [mode, setModeState] = useState<ThemeMode>(() => {
const saved = localStorage.getItem(STORAGE_KEY);
return (saved as ThemeMode) || DEFAULT_MODE;
const saved = localStorage.getItem(THEME_STORAGE_KEY);
return (saved as ThemeMode) || DEFAULT_THEME_MODE;
});
// 监听系统主题变化
@@ -41,7 +30,7 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
const setMode = useCallback((newMode: ThemeMode) => {
setModeState(newMode);
localStorage.setItem(STORAGE_KEY, newMode);
localStorage.setItem(THEME_STORAGE_KEY, newMode);
}, []);
const toggle = useCallback(() => {
@@ -59,7 +48,3 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
+20
View File
@@ -0,0 +1,20 @@
import { createContext } from 'react';
export type ThemeMode = 'light' | 'dark' | 'system';
export const DEFAULT_THEME_MODE: ThemeMode = 'system';
export const THEME_STORAGE_KEY = 'theme_mode';
export interface ThemeContextValue {
mode: ThemeMode;
isDark: boolean;
setMode: (mode: ThemeMode) => void;
toggle: () => void;
}
export const ThemeContext = createContext<ThemeContextValue>({
mode: DEFAULT_THEME_MODE,
isDark: false,
setMode: () => {},
toggle: () => {},
});
+6
View File
@@ -0,0 +1,6 @@
import { useContext } from 'react';
import { ThemeContext } from './themeContext';
export function useTheme() {
return useContext(ThemeContext);
}