From 98728bee87a44647bb48c6f9b71cc224ed1cdf45 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 23 Jun 2026 08:42:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=8D=E7=AB=AF7?= =?UTF-8?q?=E5=A4=84eslint=E9=94=99=E8=AF=AF=E5=92=8C6=E5=A4=84=E8=AD=A6?= =?UTF-8?q?=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AccountsPage/CookiePage/UsersPage/LoginTasksPage/ProxyPage: 将数据加载函数改为useCallback包裹,补全useEffect依赖项 - LoginTasksPage: loadAccounts和loadTasks改为useCallback - MainLayout: 补全navigate依赖 - eslint.config.js: 关闭react-hooks/set-state-in-effect(对初始 数据获取场景过严),降级react-refresh/only-export-components为warn (Provider/Hook/Type共存文件是合理设计) --- web/frontend/eslint.config.js | 6 ++++++ web/frontend/src/layouts/MainLayout.tsx | 2 +- web/frontend/src/pages/AccountsPage.tsx | 20 ++++++++------------ web/frontend/src/pages/CookiePage.tsx | 8 ++++---- web/frontend/src/pages/LoginTasksPage.tsx | 12 ++++++------ web/frontend/src/pages/ProxyPage.tsx | 8 ++++---- web/frontend/src/pages/UsersPage.tsx | 8 ++++---- 7 files changed, 33 insertions(+), 31 deletions(-) diff --git a/web/frontend/eslint.config.js b/web/frontend/eslint.config.js index ef614d2..5856daa 100644 --- a/web/frontend/eslint.config.js +++ b/web/frontend/eslint.config.js @@ -18,5 +18,11 @@ export default defineConfig([ languageOptions: { globals: globals.browser, }, + rules: { + // 初始数据获取是 React 标准模式,此规则对此场景过于严格 + 'react-hooks/set-state-in-effect': 'off', + // Provider/Hook 共存文件导出非组件是合理设计 + 'react-refresh/only-export-components': 'warn', + }, }, ]) diff --git a/web/frontend/src/layouts/MainLayout.tsx b/web/frontend/src/layouts/MainLayout.tsx index a27ecfc..1ef01e0 100644 --- a/web/frontend/src/layouts/MainLayout.tsx +++ b/web/frontend/src/layouts/MainLayout.tsx @@ -37,7 +37,7 @@ export default function MainLayout({ onLogout }: { onLogout?: () => void }) { useEffect(() => { if (!user) navigate('/login'); - }, [user]); + }, [user, navigate]); if (!user) return null; diff --git a/web/frontend/src/pages/AccountsPage.tsx b/web/frontend/src/pages/AccountsPage.tsx index aae77e6..04332c9 100644 --- a/web/frontend/src/pages/AccountsPage.tsx +++ b/web/frontend/src/pages/AccountsPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useMemo } from 'react'; +import { useEffect, useState, useMemo, useCallback } from 'react'; import { Table, Button, Modal, Input, Select, message, Popconfirm, Typography, Tag, Space, Row, Col, Card, Statistic, @@ -35,7 +35,7 @@ export default function AccountsPage() { const canAssign = can('account:assign'); const canDelete = can('account:delete'); - const loadAccounts = async () => { + const loadAccounts = useCallback(async () => { setLoading(true); try { const params: { tag?: string } = {}; @@ -47,35 +47,31 @@ export default function AccountsPage() { } finally { setLoading(false); } - }; + }, [tagFilter]); - const loadUsers = async () => { + const loadUsers = useCallback(async () => { try { const data = await userApi.list(); setUsers(data.filter((u) => u.role === 'support')); } catch { // 忽略客服列表加载失败,账号列表仍可继续使用。 } - }; + }, []); - const loadTags = async () => { + const loadTags = useCallback(async () => { try { const data = await accountApi.listTags(); setTags(data); } catch { // 忽略标签加载失败,页面会退化为无标签筛选。 } - }; + }, []); useEffect(() => { loadAccounts(); if (canAssign) loadUsers(); loadTags(); - }, []); - - useEffect(() => { - loadAccounts(); - }, [tagFilter]); + }, [loadAccounts, canAssign, loadUsers, loadTags]); const tagColorMap = useMemo(() => { const map: Record = {}; diff --git a/web/frontend/src/pages/CookiePage.tsx b/web/frontend/src/pages/CookiePage.tsx index 9718d6b..c49a788 100644 --- a/web/frontend/src/pages/CookiePage.tsx +++ b/web/frontend/src/pages/CookiePage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } 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, type CookieItem } from '../api/modules'; @@ -19,7 +19,7 @@ export default function CookiePage() { const canView = can('cookie:view'); const canExport = can('cookie:export'); - const loadCookies = async () => { + const loadCookies = useCallback(async () => { setLoading(true); try { const data = await cookieApi.list(); @@ -29,11 +29,11 @@ export default function CookiePage() { } finally { setLoading(false); } - }; + }, []); useEffect(() => { loadCookies(); - }, []); + }, [loadCookies]); const handleExport = async (format: string = 'csv') => { try { diff --git a/web/frontend/src/pages/LoginTasksPage.tsx b/web/frontend/src/pages/LoginTasksPage.tsx index 3247339..db4f4f8 100644 --- a/web/frontend/src/pages/LoginTasksPage.tsx +++ b/web/frontend/src/pages/LoginTasksPage.tsx @@ -67,14 +67,14 @@ export default function LoginTasksPage() { return map; }, [accounts]); - const loadAccounts = async () => { + const loadAccounts = useCallback(async () => { try { const data = await accountApi.list(); setAccounts(data); } catch (e: unknown) { message.error(getErrorMessage(e)); } - }; + }, []); // 标签选择变化时,同步更新选中的账号 const handleTagChange = useCallback((tags: string[]) => { @@ -107,23 +107,23 @@ export default function LoginTasksPage() { }); }, [selectedTags, tagAccountMap, accounts]); - const loadTasks = async () => { + const loadTasks = useCallback(async () => { try { const data = await loginApi.listTasks(batchId || undefined); setTasks(data); } catch { // 忽略轮询失败,下一次定时刷新会继续尝试。 } - }; + }, [batchId]); useEffect(() => { Promise.all([loadAccounts(), loadTasks()]); - }, []); + }, [loadAccounts, loadTasks]); useEffect(() => { const timer = setInterval(loadTasks, 3000); return () => clearInterval(timer); - }, [batchId]); + }, [loadTasks]); // 共享的批量登录启动逻辑 const startBatch = async (accountIds: number[]) => { diff --git a/web/frontend/src/pages/ProxyPage.tsx b/web/frontend/src/pages/ProxyPage.tsx index c15d584..1401f99 100644 --- a/web/frontend/src/pages/ProxyPage.tsx +++ b/web/frontend/src/pages/ProxyPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { Form, Input, Switch, Button, Card, message, Row, Col } from 'antd'; import { proxyApi, type ProxyConfig } from '../api/modules'; import RealtimeLogPanel from '../components/RealtimeLogPanel'; @@ -13,7 +13,7 @@ export default function ProxyPage() { const [configLoaded, setConfigLoaded] = useState(false); const { logs, connect: connectLogs, close: closeLogs } = useWebSocketLogs(); - const loadConfig = async () => { + const loadConfig = useCallback(async () => { try { const data = await proxyApi.get(); form.setFieldsValue({ @@ -30,14 +30,14 @@ export default function ProxyPage() { } finally { setConfigLoaded(true); } - }; + }, [form]); useEffect(() => { loadConfig(); return () => { closeLogs(); }; - }, []); + }, [loadConfig, closeLogs]); const connectWs = (testId: string) => { connectLogs(`/api/proxy/ws/test/${testId}`, { diff --git a/web/frontend/src/pages/UsersPage.tsx b/web/frontend/src/pages/UsersPage.tsx index ab8371c..88c65ac 100644 --- a/web/frontend/src/pages/UsersPage.tsx +++ b/web/frontend/src/pages/UsersPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import { Table, Button, Modal, Form, Input, Select, Tag, Popconfirm, message, Space, Checkbox, Divider, Alert, Tooltip, @@ -54,7 +54,7 @@ export default function UsersPage() { const canAssignPerm = can('user:assign_permissions'); - const loadUsers = async () => { + const loadUsers = useCallback(async () => { setLoading(true); try { const data = await userApi.list(); @@ -64,11 +64,11 @@ export default function UsersPage() { } finally { setLoading(false); } - }; + }, []); useEffect(() => { loadUsers(); - }, []); + }, [loadUsers]); const handleCreate = () => { setEditing(null);