refactor: 消除前端52处any类型+删除冗余requirements.txt+调试print改日志

- 删除 requirements.txt,pyproject.toml 为唯一依赖源(含 requests[socks] extra)
- core/geetest 下 5 处 print() 替换为 loguru logger.debug()
- api/modules.ts 新增 13 个响应接口,消除所有 api.xxx<any, any>
- 新建 utils/error.ts 提供 getErrorMessage(e: unknown) 安全取消息
- 11 个页面组件 catch (e: any) 改为 catch (e: unknown)
- useState<any[]>、columns: any[]、render 参数全部类型化
- TypeScript 类型检查零错误通过
This commit is contained in:
yml2213
2026-06-23 07:14:08 +08:00
parent 453a637480
commit 12c31c2e09
16 changed files with 305 additions and 189 deletions
+167 -26
View File
@@ -1,5 +1,22 @@
import api from './index';
// ==================== 通用响应类型 ====================
interface MessageResponse {
message: string;
success: boolean;
}
interface MessageCountResponse extends MessageResponse {
count: number;
}
interface MessageDeletedResponse extends MessageResponse {
deleted: number;
}
// ==================== Auth ====================
export interface LoginResult {
access_token: string;
token_type: string;
@@ -8,6 +25,16 @@ export interface LoginResult {
permissions: string[];
}
export interface CurrentUser {
id: number;
username: string;
role: string;
role_label: string;
is_active: boolean;
remark: string | null;
permissions: string[];
}
export interface UserInfo {
id: number;
username: string;
@@ -18,13 +45,127 @@ export interface UserInfo {
custom_permissions?: string[] | null;
}
// ==================== Account ====================
export interface AccountItem {
id: number;
username: string;
password?: string | null;
email?: string | null;
email_password?: string | null;
tag: string;
assigned_to: number | null;
assigned_username: string | null;
remark: string;
created_at: string | null;
}
export interface AssignmentsSummary {
support_users: SupportUserItem[];
unassigned_count: number;
}
export interface SupportUserItem {
id: number;
username: string;
assigned_count: number;
}
// ==================== Login Task ====================
export interface LoginTaskItem {
id: number;
batch_id: string;
account_id: number;
account_username: string;
status: string;
cookie: string;
message: string;
created_by: number;
created_at: string | null;
finished_at: string | null;
}
export interface BatchLoginResult {
batch_id: string;
count: number;
success: boolean;
}
// ==================== Cookie ====================
export interface CookieItem {
id: number;
batch_id: string;
account_id: number;
account_username: string;
assigned_to: number | null;
assigned_username: string | null;
created_at: string | null;
cookie: string;
cookie_preview: string;
}
// ==================== Proxy ====================
export interface ProxyConfig {
enabled: boolean;
api_url: string;
http: string;
https: string;
whitelist_enabled: boolean;
whitelist_uid: string;
whitelist_ukey: string;
}
export interface ProxyTestResult {
test_id: string;
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 {
permissions: Record<string, string>;
role_permissions: Record<string, string[]>;
}
// ==================== API 定义 ====================
export const authApi = {
login: (username: string, password: string) =>
api.post<any, LoginResult>('/auth/login', { username, password }),
me: () => api.get<any, any>('/auth/me'),
me: () => api.get<any, CurrentUser>('/auth/me'),
logout: () => api.post<any, any>('/auth/logout'),
logout: () => api.post<any, MessageResponse>('/auth/logout'),
};
export const userApi = {
@@ -33,55 +174,55 @@ export const userApi = {
api.post<any, UserInfo>('/users', data),
update: (id: number, data: { password?: string; role?: string; is_active?: boolean; remark?: string; custom_permissions?: string[] | null }) =>
api.put<any, UserInfo>(`/users/${id}`, data),
delete: (id: number) => api.delete<any, any>(`/users/${id}`),
listPermissions: () => api.get<any, any>('/users/permissions/list'),
delete: (id: number) => api.delete<any, MessageResponse>(`/users/${id}`),
listPermissions: () => api.get<any, PermissionsListResult>('/users/permissions/list'),
};
export const accountApi = {
list: (params?: { assigned_only?: boolean; tag?: string; has_cookie?: boolean }) =>
api.get<any, any[]>('/accounts', { params }),
import: (text: string) => api.post<any, any>('/accounts/import', { text }),
api.get<any, AccountItem[]>('/accounts', { params }),
import: (text: string) => api.post<any, MessageCountResponse>('/accounts/import', { text }),
assign: (id: number, assigned_to: number | null) =>
api.put<any, any>(`/accounts/${id}/assign`, { assigned_to }),
api.put<any, MessageResponse>(`/accounts/${id}/assign`, { assigned_to }),
batchAssign: (account_ids: number[], assigned_to: number | null) =>
api.post<any, any>('/accounts/batch-assign', { account_ids, assigned_to }),
api.post<any, MessageCountResponse>('/accounts/batch-assign', { account_ids, assigned_to }),
assignmentsSummary: () =>
api.get<any, any>('/accounts/assignments/summary'),
api.get<any, AssignmentsSummary>('/accounts/assignments/summary'),
setTag: (id: number, tag: string) =>
api.put<any, any>(`/accounts/${id}/tag`, { tag }),
api.put<any, MessageResponse>(`/accounts/${id}/tag`, { tag }),
batchTag: (account_ids: number[], tag: string) =>
api.put<any, any>('/accounts/batch-tag', { account_ids, tag }),
api.put<any, MessageCountResponse>('/accounts/batch-tag', { account_ids, tag }),
listTags: () => api.get<any, string[]>('/accounts/tags/list'),
delete: (id: number) => api.delete<any, any>(`/accounts/${id}`),
delete: (id: number) => api.delete<any, MessageResponse>(`/accounts/${id}`),
batchDelete: (account_ids: number[]) =>
api.delete<any, any>('/accounts/batch/delete', { params: { account_ids: account_ids.join(',') } }),
api.delete<any, MessageDeletedResponse>('/accounts/batch/delete', { params: { account_ids: account_ids.join(',') } }),
};
export const loginApi = {
createBatch: (account_ids: number[], max_geetest_retries?: number, concurrency?: number, max_proxy_retries?: number) =>
api.post<any, any>('/login/batch', { account_ids, max_geetest_retries, concurrency, max_proxy_retries }),
api.post<any, BatchLoginResult>('/login/batch', { account_ids, max_geetest_retries, concurrency, max_proxy_retries }),
listTasks: (batch_id?: string) =>
api.get<any, any[]>('/login/tasks', { params: batch_id ? { batch_id } : {} }),
stop: (batch_id: string) => api.post<any, any>(`/login/stop/${batch_id}`),
deleteTask: (id: number) => api.delete<any, any>(`/login/tasks/${id}`),
deleteTasks: (ids: number[]) => api.delete<any, any>(`/login/tasks`, { params: { task_ids: ids.join(',') } }),
api.get<any, LoginTaskItem[]>('/login/tasks', { params: batch_id ? { batch_id } : {} }),
stop: (batch_id: string) => api.post<any, MessageResponse>(`/login/stop/${batch_id}`),
deleteTask: (id: number) => api.delete<any, MessageResponse>(`/login/tasks/${id}`),
deleteTasks: (ids: number[]) => api.delete<any, MessageDeletedResponse>(`/login/tasks`, { params: { task_ids: ids.join(',') } }),
};
export const cookieApi = {
list: () => api.get<any, any[]>('/cookies'),
list: () => api.get<any, CookieItem[]>('/cookies'),
exportCsv: (format?: string) => api.get('/cookies/export', { responseType: 'blob', params: format ? { format } : {} }),
delete: (id: number) => api.delete<any, any>(`/cookies/${id}`),
delete: (id: number) => api.delete<any, MessageResponse>(`/cookies/${id}`),
};
export const proxyApi = {
get: () => api.get<any, any>('/proxy'),
update: (data: any) => api.put<any, any>('/proxy', data),
test: () => api.post<any, any>('/proxy/test'),
testWhitelist: () => api.post<any, any>('/proxy/whitelist/test'),
get: () => api.get<any, ProxyConfig>('/proxy'),
update: (data: ProxyConfig) => api.put<any, ProxyConfig>('/proxy', data),
test: () => api.post<any, ProxyTestResult>('/proxy/test'),
testWhitelist: () => api.post<any, ProxyTestResult>('/proxy/whitelist/test'),
};
export const logApi = {
listHttp: (params?: { limit?: number; offset?: number; category?: string; level?: string; keyword?: string }) =>
api.get<any, { items: any[]; total: number }>('/logs/http', { params }),
clearHttp: () => api.delete<any, { success: boolean; cleared: number }>('/logs/http'),
api.get<any, HttpLogListResult>('/logs/http', { params }),
clearHttp: () => api.delete<any, HttpLogClearResult>('/logs/http'),
};
+1 -1
View File
@@ -39,7 +39,7 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) {
if (!user) return null;
const menuItems: any[] = [];
const menuItems: { key: string; label: string; icon: React.ReactNode }[] = [];
// Dashboard - 所有人可见
menuItems.push({ key: '/', label: '概览', icon: <DashboardOutlined /> });
+25 -24
View File
@@ -4,8 +4,9 @@ import {
Row, Col, Card, Statistic,
} from 'antd';
import { ImportOutlined, DeleteOutlined, TagOutlined, FilterOutlined } from '@ant-design/icons';
import { accountApi, userApi } from '../api/modules';
import { accountApi, userApi, type AccountItem, type UserInfo } from '../api/modules';
import { getUser, hasPerm } from '../store/auth';
import { getErrorMessage } from '../utils/error';
const { TextArea } = Input;
const { Text } = Typography;
@@ -15,8 +16,8 @@ const TAG_COLORS = [
];
export default function AccountsPage() {
const [accounts, setAccounts] = useState<any[]>([]);
const [users, setUsers] = useState<any[]>([]);
const [accounts, setAccounts] = useState<AccountItem[]>([]);
const [users, setUsers] = useState<UserInfo[]>([]);
const [tags, setTags] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const [importOpen, setImportOpen] = useState(false);
@@ -36,12 +37,12 @@ export default function AccountsPage() {
const loadAccounts = async () => {
setLoading(true);
try {
const params: any = {};
const params: { tag?: string } = {};
if (tagFilter) params.tag = tagFilter;
const data = await accountApi.list(params);
setAccounts(data);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setLoading(false);
}
@@ -50,7 +51,7 @@ export default function AccountsPage() {
const loadUsers = async () => {
try {
const data = await userApi.list();
setUsers(data.filter((u: any) => u.role === 'support'));
setUsers(data.filter((u) => u.role === 'support'));
} catch {}
};
@@ -92,8 +93,8 @@ export default function AccountsPage() {
setImportText('');
loadAccounts();
loadTags();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setImporting(false);
}
@@ -104,8 +105,8 @@ export default function AccountsPage() {
await accountApi.assign(accountId, assignedTo);
message.success('已分配');
loadAccounts();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -115,8 +116,8 @@ export default function AccountsPage() {
message.success('标签已更新');
loadAccounts();
loadTags();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -133,8 +134,8 @@ export default function AccountsPage() {
setSelectedRowKeys([]);
loadAccounts();
loadTags();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -145,8 +146,8 @@ export default function AccountsPage() {
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
loadAccounts();
loadTags();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -161,19 +162,19 @@ export default function AccountsPage() {
setSelectedRowKeys([]);
loadAccounts();
loadTags();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
const columns: any[] = [
const columns = [
{ title: 'ID', dataIndex: 'id', width: 60 },
{ title: '用户名', dataIndex: 'username' },
{
title: '标签',
dataIndex: 'tag',
width: 120,
render: (tag: string, record: any) => {
render: (tag: string, record: AccountItem) => {
if (!tag) {
if (canImport) {
return (
@@ -224,7 +225,7 @@ export default function AccountsPage() {
columns.push({
title: '分配给',
dataIndex: 'assigned_username',
render: (_: any, record: any) => {
render: (_: unknown, record: AccountItem) => {
if (canAssign) {
return (
<Select
@@ -233,7 +234,7 @@ export default function AccountsPage() {
placeholder="未分配"
value={record.assigned_to}
onChange={(val) => handleAssign(record.id, val ?? null)}
options={users.map((u: any) => ({ value: u.id, label: u.username }))}
options={users.map((u) => ({ value: u.id, label: u.username }))}
/>
);
}
@@ -245,7 +246,7 @@ export default function AccountsPage() {
columns.push({
title: '操作',
width: 80,
render: (_: any, record: any) => (
render: (_: unknown, record: AccountItem) => (
<Popconfirm title="确认删除?" onConfirm={() => handleDelete(record.id)}>
<Button danger size="small" icon={<DeleteOutlined />}></Button>
</Popconfirm>
+19 -32
View File
@@ -7,31 +7,18 @@ import {
UserOutlined, CheckCircleOutlined, TeamOutlined,
SwapOutlined, ClearOutlined, UsergroupAddOutlined,
} from '@ant-design/icons';
import { accountApi } from '../api/modules';
import { accountApi, type AccountItem, type SupportUserItem } from '../api/modules';
import { getErrorMessage } from '../utils/error';
const { Text } = Typography;
const STORAGE_KEY_SELECTED_USER = 'assignments_selected_user_id';
interface SupportUser {
id: number;
username: string;
assigned_count: number;
}
interface AccountItem {
id: number;
username: string;
tag: string;
assigned_to: number | null;
assigned_username: string | null;
}
export default function AssignmentsPage() {
const { token } = theme.useToken();
const [accounts, setAccounts] = useState<AccountItem[]>([]);
const [supportUsers, setSupportUsers] = useState<SupportUser[]>([]);
const [selectedUser, setSelectedUser] = useState<SupportUser | null>(null);
const [supportUsers, setSupportUsers] = useState<SupportUserItem[]>([]);
const [selectedUser, setSelectedUser] = useState<SupportUserItem | null>(null);
const [selectedRowKeys, setSelectedRowKeys] = useState<number[]>([]);
const [searchText, setSearchText] = useState('');
const [filterTag, setFilterTag] = useState<string | undefined>(undefined);
@@ -44,8 +31,8 @@ export default function AssignmentsPage() {
const data = await accountApi.assignmentsSummary();
setSupportUsers(data.support_users);
return data.support_users as SupportUser[];
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
return [];
}
};
@@ -55,8 +42,8 @@ export default function AssignmentsPage() {
try {
const all = await accountApi.list({ has_cookie: true });
setAccounts(all);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -67,7 +54,7 @@ export default function AssignmentsPage() {
const users = await loadSummary();
if (users.length > 0) {
const savedId = localStorage.getItem(STORAGE_KEY_SELECTED_USER);
const savedUser = savedId ? users.find((u: SupportUser) => u.id === Number(savedId)) : null;
const savedUser = savedId ? users.find((u: SupportUserItemItem) => u.id === Number(savedId)) : null;
setSelectedUser(savedUser || users[0]);
}
};
@@ -105,7 +92,7 @@ export default function AssignmentsPage() {
}, [activeTab, unassignedAccounts, assignedAccounts, searchText, filterTag]);
// 切换客服
const handleSelectUser = (user: SupportUser) => {
const handleSelectUser = (user: SupportUserItem) => {
setSelectedUser(user);
setSelectedRowKeys([]);
setActiveTab('unassigned');
@@ -131,10 +118,10 @@ export default function AssignmentsPage() {
setSelectedRowKeys([]);
await loadAccounts();
const users = await loadSummary();
const refreshed = users.find((u: SupportUser) => u.id === selectedUser.id);
const refreshed = users.find((u: SupportUserItem) => u.id === selectedUser.id);
if (refreshed) setSelectedUser(refreshed);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setAssigning(false);
}
@@ -153,10 +140,10 @@ export default function AssignmentsPage() {
setSelectedRowKeys([]);
await loadAccounts();
const users = await loadSummary();
const refreshed = users.find((u: SupportUser) => u.id === selectedUser?.id);
const refreshed = users.find((u: SupportUserItem) => u.id === selectedUser?.id);
if (refreshed) setSelectedUser(refreshed);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setAssigning(false);
}
@@ -185,10 +172,10 @@ export default function AssignmentsPage() {
message.success(`已将 ${ids.length} 个账号分配给 ${selectedUser.username}`);
await loadAccounts();
const users = await loadSummary();
const refreshed = users.find((u: SupportUser) => u.id === selectedUser.id);
const refreshed = users.find((u: SupportUserItem) => u.id === selectedUser.id);
if (refreshed) setSelectedUser(refreshed);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setAssigning(false);
}
+18 -17
View File
@@ -1,15 +1,16 @@
import { useEffect, useState } from 'react';
import { Table, Button, Card, Row, Col, Statistic, message, Tag, Popconfirm, Space, Typography, Input, theme, Dropdown } from 'antd';
import { DownloadOutlined, DeleteOutlined, CopyOutlined, SearchOutlined } from '@ant-design/icons';
import { cookieApi } from '../api/modules';
import { cookieApi, type CookieItem } from '../api/modules';
import { getUser, hasPerm } from '../store/auth';
import { formatTime } from '../utils/time';
import { getErrorMessage } from '../utils/error';
const { Text } = Typography;
export default function CookiePage() {
const { token } = theme.useToken();
const [cookies, setCookies] = useState<any[]>([]);
const [cookies, setCookies] = useState<CookieItem[]>([]);
const [loading, setLoading] = useState(false);
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [searchText, setSearchText] = useState('');
@@ -23,8 +24,8 @@ export default function CookiePage() {
try {
const data = await cookieApi.list();
setCookies(data);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setLoading(false);
}
@@ -37,15 +38,15 @@ export default function CookiePage() {
const handleExport = async (format: string = 'csv') => {
try {
const blob = await cookieApi.exportCsv(format);
const url = URL.createObjectURL(blob instanceof Blob ? blob : new Blob([blob as any]));
const url = URL.createObjectURL(blob instanceof Blob ? blob : new Blob([blob]));
const a = document.createElement('a');
a.href = url;
a.download = format === 'custom' ? 'cookies_custom.txt' : 'cookies.csv';
a.click();
URL.revokeObjectURL(url);
message.success('已导出');
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -83,8 +84,8 @@ export default function CookiePage() {
message.success('已删除');
setSelectedRowKeys((prev) => prev.filter((k) => k !== id));
loadCookies();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -98,8 +99,8 @@ export default function CookiePage() {
);
});
const columns: any[] = [
{ title: 'ID', dataIndex: 'id', width: 60, align: 'center' },
const columns = [
{ title: 'ID', dataIndex: 'id', width: 60, align: 'center' as const },
{
title: '账号',
dataIndex: 'account_username',
@@ -110,7 +111,7 @@ export default function CookiePage() {
title: '分配',
dataIndex: 'assigned_username',
width: 100,
align: 'center',
align: 'center' as const,
render: (name: string | null) =>
name ? <Tag color="green">{name}</Tag> : <Text type="secondary" style={{ fontSize: 12 }}></Text>,
},
@@ -127,15 +128,15 @@ export default function CookiePage() {
title: '时间',
dataIndex: 'created_at',
width: 180,
align: 'center',
align: 'center' as const,
render: (val: string) => formatTime(val),
},
{
title: '操作',
width: 130,
align: 'center',
fixed: 'right',
render: (_: any, record: any) => (
align: 'center' as const,
fixed: 'right' as const,
render: (_: unknown, record: CookieItem) => (
<Space size={4}>
<Button
size="small"
@@ -234,4 +235,4 @@ export default function CookiePage() {
/>
</div>
);
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
import { Card, Col, Row, Statistic, theme } from 'antd';
import { useEffect, useState } from 'react';
import { accountApi, loginApi } from '../api/modules';
import { accountApi, loginApi, type LoginTaskItem } from '../api/modules';
export default function DashboardPage() {
const { token } = theme.useToken();
@@ -12,8 +12,8 @@ export default function DashboardPage() {
setStats({
accounts: accounts.length,
tasks: tasks.length,
success: tasks.filter((t: any) => t.status === 'success').length,
failed: tasks.filter((t: any) => ['failed', 'error'].includes(t.status)).length,
success: tasks.filter((t: LoginTaskItem) => t.status === 'success').length,
failed: tasks.filter((t: LoginTaskItem) => ['failed', 'error'].includes(t.status)).length,
});
})
.catch(() => {});
+7 -21
View File
@@ -6,26 +6,12 @@ import {
import {
ReloadOutlined, DeleteOutlined, SearchOutlined, EyeOutlined,
} from '@ant-design/icons';
import { logApi } from '../api/modules';
import { logApi, type HttpLogEntry } from '../api/modules';
import { hasPerm, getUser } from '../store/auth';
import { getErrorMessage } from '../utils/error';
const { Text, Paragraph } = Typography;
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;
}
const CATEGORY_LABELS: Record<string, string> = {
douyu_login: '斗鱼登录',
geetest: '极验',
@@ -63,8 +49,8 @@ export default function HttpLogsPage() {
});
setLogs(res.items || []);
setTotal(res.total || 0);
} catch (e: any) {
message.error(e.message || '获取日志失败');
} catch (e: unknown) {
message.error(getErrorMessage(e) || '获取日志失败');
} finally {
setLoading(false);
}
@@ -87,8 +73,8 @@ export default function HttpLogsPage() {
const res = await logApi.clearHttp();
message.success(`已清空 ${res.cleared} 条日志`);
fetchLogs();
} catch (e: any) {
message.error(e.message || '清空失败');
} catch (e: unknown) {
message.error(getErrorMessage(e) || '清空失败');
}
};
@@ -166,7 +152,7 @@ export default function HttpLogsPage() {
{
title: '操作',
width: 60,
render: (_: any, record: HttpLogEntry) => (
render: (_: unknown, record: HttpLogEntry) => (
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => setDetailEntry(record)} />
),
},
+3 -2
View File
@@ -4,6 +4,7 @@ import { LockOutlined, UserOutlined } from '@ant-design/icons';
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';
const { Title } = Typography;
@@ -27,8 +28,8 @@ export default function LoginPage({ onLogin }: { onLogin?: () => void }) {
message.success('登录成功');
onLogin?.(); // 触发 App 重渲染
navigate('/', { replace: true });
} catch (e: any) {
message.error(e.message || '登录失败');
} catch (e: unknown) {
message.error(getErrorMessage(e) || '登录失败');
} finally {
setLoading(false);
}
+22 -16
View File
@@ -3,9 +3,10 @@ import {
Table, Button, Select, message, Tag, Space, Spin, InputNumber, Tooltip, Popconfirm, theme,
} from 'antd';
import { PlayCircleOutlined, StopOutlined, FilterOutlined, ThunderboltOutlined, ReloadOutlined, DownOutlined, UpOutlined, DeleteOutlined, SwapOutlined } from '@ant-design/icons';
import { accountApi, loginApi } from '../api/modules';
import { accountApi, loginApi, type AccountItem, type LoginTaskItem } from '../api/modules';
import { getUser, hasPerm } from '../store/auth';
import { formatTime } from '../utils/time';
import { getErrorMessage } from '../utils/error';
const STATUS_COLORS: Record<string, string> = {
pending: 'default',
@@ -23,10 +24,15 @@ const STATUS_LABELS: Record<string, string> = {
error: '异常',
};
interface SelectGroupOption {
label: string;
options: { value: number; label: string }[];
}
export default function LoginTasksPage() {
const [accounts, setAccounts] = useState<any[]>([]);
const [accounts, setAccounts] = useState<AccountItem[]>([]);
const [selectedIds, setSelectedIds] = useState<number[]>([]);
const [tasks, setTasks] = useState<any[]>([]);
const [tasks, setTasks] = useState<LoginTaskItem[]>([]);
const [loading, setLoading] = useState(false);
const [batchId, setBatchId] = useState<string | null>(null);
const [logs, setLogs] = useState<{ level: string; message: string }[]>([]);
@@ -67,8 +73,8 @@ export default function LoginTasksPage() {
try {
const data = await accountApi.list();
setAccounts(data);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -158,8 +164,8 @@ export default function LoginTasksPage() {
ws.onerror = () => {
setWsConnected(false);
};
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setLoading(false);
}
@@ -191,8 +197,8 @@ export default function LoginTasksPage() {
try {
await loginApi.stop(batchId);
message.success('已发送停止信号');
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
}
};
@@ -203,8 +209,8 @@ export default function LoginTasksPage() {
message.success('已删除');
loadTasks();
setSelectedRowKeys((prev) => prev.filter((k) => k !== taskId));
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -218,8 +224,8 @@ export default function LoginTasksPage() {
message.success(`已删除 ${selectedRowKeys.length} 个任务`);
setSelectedRowKeys([]);
loadTasks();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -239,7 +245,7 @@ export default function LoginTasksPage() {
{
title: '操作',
width: 120,
render: (_: any, record: any) => (
render: (_: unknown, record: LoginTaskItem) => (
<Space size={4}>
{['failed', 'error'].includes(record.status) && !wsConnected && (
<Button
@@ -298,7 +304,7 @@ export default function LoginTasksPage() {
noTag.push({ value: a.id, label: a.username });
}
});
const result: any[] = [];
const result: SelectGroupOption[] = [];
Object.keys(grouped).sort().forEach((tag) => {
result.push({ label: tag, options: grouped[tag] });
});
@@ -312,7 +318,7 @@ export default function LoginTasksPage() {
size="small"
filterOption={(input, option) => {
if (!option) return false;
const label = (option as any).label as string || '';
const label = (option as { label?: string }).label || '';
return label.toLowerCase().includes(input.toLowerCase());
}}
dropdownRender={(menu) => (
+11 -10
View File
@@ -1,6 +1,7 @@
import { useEffect, useState, useRef } from 'react';
import { Form, Input, Switch, Button, Card, message, Row, Col, theme } from 'antd';
import { proxyApi } from '../api/modules';
import { proxyApi, type ProxyConfig } from '../api/modules';
import { getErrorMessage } from '../utils/error';
const WS_BASE = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}`;
@@ -26,8 +27,8 @@ export default function ProxyPage() {
whitelist_uid: data.whitelist_uid ?? '',
whitelist_ukey: data.whitelist_ukey ?? '',
});
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setConfigLoaded(true);
}
@@ -66,10 +67,10 @@ export default function ProxyPage() {
setLoading(true);
try {
const values = await form.validateFields();
await proxyApi.update(values);
await proxyApi.update(values as ProxyConfig);
message.success('已保存');
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setLoading(false);
}
@@ -81,8 +82,8 @@ export default function ProxyPage() {
try {
const result = await proxyApi.test();
if (result.test_id) connectWs(result.test_id);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
setTesting(false);
}
};
@@ -93,8 +94,8 @@ export default function ProxyPage() {
try {
const result = await proxyApi.testWhitelist();
if (result.test_id) connectWs(result.test_id);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
setTestingWl(false);
}
};
+15 -14
View File
@@ -6,6 +6,7 @@ import {
import { PlusOutlined, EditOutlined, DeleteOutlined, SafetyOutlined } from '@ant-design/icons';
import { userApi, type UserInfo } from '../api/modules';
import { getUser, hasPerm } from '../store/auth';
import { getErrorMessage } from '../utils/error';
const ROLE_OPTIONS = [
{ value: 'super_admin', label: '超级管理员' },
@@ -57,8 +58,8 @@ export default function UsersPage() {
try {
const data = await userApi.list();
setUsers(data);
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setLoading(false);
}
@@ -89,7 +90,7 @@ export default function UsersPage() {
try {
const values = await form.validateFields();
if (editing) {
const updateData: any = { role: values.role, remark: values.remark };
const updateData: { role: string; remark: string; password?: string } = { role: values.role, remark: values.remark };
if (values.password) updateData.password = values.password;
await userApi.update(editing.id, updateData);
message.success('已更新');
@@ -99,8 +100,8 @@ export default function UsersPage() {
}
setModalOpen(false);
loadUsers();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -109,8 +110,8 @@ export default function UsersPage() {
await userApi.delete(id);
message.success('已删除');
loadUsers();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
}
};
@@ -131,8 +132,8 @@ export default function UsersPage() {
setUseCustom(false);
setSelectedPerms(data.role_permissions?.[user.role] || []);
}
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setPermLoading(false);
}
@@ -148,8 +149,8 @@ export default function UsersPage() {
message.success('权限已更新');
setPermModalOpen(false);
loadUsers();
} catch (e: any) {
message.error(e.message);
} catch (e: unknown) {
message.error(getErrorMessage(e));
} finally {
setPermLoading(false);
}
@@ -175,7 +176,7 @@ export default function UsersPage() {
title: '权限模式',
dataIndex: 'custom_permissions',
width: 100,
render: (_: any, record: UserInfo) =>
render: (_: unknown, record: UserInfo) =>
record.custom_permissions !== null && record.custom_permissions !== undefined
? <Tag color="orange"></Tag>
: <Tag></Tag>,
@@ -190,7 +191,7 @@ export default function UsersPage() {
{
title: '操作',
width: 200,
render: (_: any, record: UserInfo) => (
render: (_: unknown, record: UserInfo) => (
<Space>
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}></Button>
{canAssignPerm && (
@@ -330,4 +331,4 @@ export default function UsersPage() {
</Modal>
</div>
);
}
}
+6
View File
@@ -0,0 +1,6 @@
/** 从 unknown 类型的 catch 错误中安全提取 message */
export function getErrorMessage(e: unknown): string {
if (e instanceof Error) return e.message;
if (typeof e === 'string') return e;
return '未知错误';
}