From cd5156fb8a1363b1d9b3bb09fa034e8068350fa3 Mon Sep 17 00:00:00 2001 From: Tunglies <77394545+Tunglies@users.noreply.github.com> Date: Mon, 9 Nov 2026 09:59:35 +0800 Subject: [PATCH] feat(frontend): preserve page filters during navigation Closes #7788 --- Changelog.md | 1 + src/hooks/use-page-search.ts | 38 ++++++++++++++++++++++++++++++++++++ src/pages/connections.tsx | 21 ++++++++------------ src/pages/logs.tsx | 14 ++++++------- src/pages/rules.tsx | 9 +++++++-- 5 files changed, 60 insertions(+), 23 deletions(-) create mode 100644 src/hooks/use-page-search.ts diff --git a/Changelog.md b/Changelog.md index 18b0bbbde..307e88622 100644 --- a/Changelog.md +++ b/Changelog.md @@ -45,6 +45,7 @@
🚀 优化改进 +- 优化连接、规则和日志筛选:切换页面后保留过滤条件 - 优化全局模式代理列表:仅显示 `GLOBAL` 节点,选中其中的策略组时才显示该组 **🍎 macOS** diff --git a/src/hooks/use-page-search.ts b/src/hooks/use-page-search.ts new file mode 100644 index 000000000..848c19170 --- /dev/null +++ b/src/hooks/use-page-search.ts @@ -0,0 +1,38 @@ +import { useCallback, useMemo, useState } from 'react' + +import type { SearchState } from '@/components/base' +import { compileStringMatcher } from '@/utils/search-matcher' + +const initialSearch: SearchState = { + text: '', + matchCase: false, + matchWholeWord: false, + useRegularExpression: false, +} + +const searches: Record<'connections' | 'rules' | 'logs', SearchState> = { + connections: initialSearch, + rules: initialSearch, + logs: initialSearch, +} + +const saveSearch = (page: keyof typeof searches, state: SearchState) => { + searches[page] = state +} + +export const usePageSearch = (page: keyof typeof searches) => { + const [searchState, setSearchState] = useState(() => searches[page]) + const match = useMemo( + () => compileStringMatcher(searchState.text, searchState).matcher, + [searchState], + ) + const onSearch = useCallback( + (_match: (content: string) => boolean, state: SearchState) => { + saveSearch(page, state) + setSearchState(state) + }, + [page], + ) + + return { searchState, match, onSearch } +} diff --git a/src/pages/connections.tsx b/src/pages/connections.tsx index 9a0158226..fbfe9d378 100644 --- a/src/pages/connections.tsx +++ b/src/pages/connections.tsx @@ -24,7 +24,6 @@ import { BasePage, BaseSearchBox, BaseStyledSelect, - type SearchState, VirtualList, } from '@/components/base' import { @@ -39,6 +38,7 @@ import { import { ConnectionTable } from '@/components/connection/connection-table' import { useConnectionData } from '@/hooks/use-connection-data' import { useConnectionSetting } from '@/hooks/use-connection-setting' +import { usePageSearch } from '@/hooks/use-page-search' import { useTrafficData } from '@/hooks/use-traffic-data' import { useVisibility } from '@/hooks/use-visibility' import { mutate } from '@/services/mutate' @@ -83,10 +83,8 @@ const EMPTY_CONNECTIONS: IConnectionsItem[] = [] const ConnectionsPage = () => { const { t } = useTranslation() const pageVisible = useVisibility() - const [match, setMatch] = useState<(input: string) => boolean>( - () => () => true, - ) - const [hasSearch, setHasSearch] = useState(false) + const { searchState, match, onSearch } = usePageSearch('connections') + const hasSearch = searchState.text.length > 0 const [curOrderOpt, setCurOrderOpt] = useState('default') const [connectionsType, setConnectionsType] = useState<'active' | 'closed'>( 'active', @@ -160,13 +158,6 @@ const ConnectionsPage = () => { }), ) - const handleSearch = useCallback( - (match: (content: string) => boolean, state: SearchState) => { - setMatch(() => match) - setHasSearch(state.text.length > 0) - }, - [], - ) const hasTableData = filterConn.length > 0 return ( @@ -274,7 +265,11 @@ const ConnectionsPage = () => { }, }} > - + {isTableLayout && hasTableData && ( diff --git a/src/pages/logs.tsx b/src/pages/logs.tsx index 99ff285c8..ca42bc8a9 100644 --- a/src/pages/logs.tsx +++ b/src/pages/logs.tsx @@ -4,7 +4,7 @@ import { SwapVertRounded, } from '@mui/icons-material' import { Box, Button, IconButton, MenuItem } from '@mui/material' -import { useEffect, useMemo, useRef, useState } from 'react' +import { useEffect, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import { @@ -12,13 +12,13 @@ import { BasePage, BaseSearchBox, BaseStyledSelect, - type SearchState, VirtualList, type VirtualListHandle, } from '@/components/base' import LogItem from '@/components/log/log-item' import { useClashLog } from '@/hooks/use-clash-log' import { useLogData } from '@/hooks/use-log-data' +import { usePageSearch } from '@/hooks/use-page-search' const LogPage = () => { const { t } = useTranslation() @@ -28,8 +28,7 @@ const LogPage = () => { const logOrder = clashLog.logOrder ?? 'asc' const isDescending = logOrder === 'desc' - const [match, setMatch] = useState(() => (_: string) => true) - const [searchState, setSearchState] = useState() + const { searchState, match, onSearch } = usePageSearch('logs') const { response: { data: logData }, refreshGetClashLog, @@ -174,10 +173,9 @@ const LogPage = () => { {t('shared.filters.logLevels.error')} { - setMatch(() => matcher) - setSearchState(state) - }} + defaultValue={searchState.text} + {...searchState} + onSearch={onSearch} /> diff --git a/src/pages/rules.tsx b/src/pages/rules.tsx index e4bf73946..5fc812a78 100644 --- a/src/pages/rules.tsx +++ b/src/pages/rules.tsx @@ -12,6 +12,7 @@ import { import { ScrollTopButton } from '@/components/layout/scroll-top-button' import { ProviderButton } from '@/components/rule/provider-button' import RuleItem from '@/components/rule/rule-item' +import { usePageSearch } from '@/hooks/use-page-search' import { useVisibility } from '@/hooks/use-visibility' import { useAppRefreshers, useRulesData } from '@/providers/app-data-context' @@ -19,7 +20,7 @@ const RulesPage = () => { const { t } = useTranslation() const { rules = [] } = useRulesData() const { refreshRules, refreshRuleProviders } = useAppRefreshers() - const [match, setMatch] = useState(() => (_: string) => true) + const { searchState, match, onSearch } = usePageSearch('rules') const virtuosoRef = useRef(null) const [showScrollTop, setShowScrollTop] = useState(false) const pageVisible = useVisibility() @@ -76,7 +77,11 @@ const RulesPage = () => { alignItems: 'center', }} > - setMatch(() => match)} /> + {filteredRules && filteredRules.length > 0 ? (