From f6fdb34745bb602565759b405473a8ac754f2dce Mon Sep 17 00:00:00 2001 From: wonfen Date: Fri, 18 Sep 2026 00:41:54 +0800 Subject: [PATCH] perf(home): render proxy dropdown options on demand --- Changelog.md | 1 + src/components/home/current-proxy-card.tsx | 323 ++++++++++++--------- 2 files changed, 185 insertions(+), 139 deletions(-) diff --git a/Changelog.md b/Changelog.md index bc9942156..0a2e682e8 100644 --- a/Changelog.md +++ b/Changelog.md @@ -77,6 +77,7 @@
🚀 优化改进 +- 优化首页节点菜单关闭时的刷新性能 - 优化 TUN 配置优先级:界面设置优先,其他选项仍可通过订阅、覆写和脚本调整 - 优化系统代理失败后的恢复流程,保持系统设置和界面状态一致 - 优化错误提示,支持复制完整原因,托盘和快捷键操作失败时也会提醒 diff --git a/src/components/home/current-proxy-card.tsx b/src/components/home/current-proxy-card.tsx index 9aa459600..e9768b7e4 100644 --- a/src/components/home/current-proxy-card.tsx +++ b/src/components/home/current-proxy-card.tsx @@ -1,6 +1,7 @@ /* eslint-disable @eslint-react/set-state-in-effect */ import { AccessTimeRounded, + ArrowDropDown, ChevronRight, NetworkCheckRounded, WifiOff as SignalError, @@ -15,24 +16,22 @@ import { import { Box, Button, + ButtonBase, Chip, ClickAwayListener, - FormControl, IconButton, - InputLabel, MenuItem, MenuList, Paper, Popper, type PopperProps, - Select, Tooltip, Typography, alpha, useTheme, } from '@mui/material' import { useLockFn } from 'ahooks' -import { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { useNavigate } from 'react-router' @@ -223,34 +222,28 @@ const sortProxyOptions = ( interface PersistentSelectProps { label: string - labelId: string - listboxId: string - value: string - selectedName: string + displayValue: React.ReactNode open: boolean disabled: boolean keepOpenRef: React.RefObject - onOpen: () => void - onClose: () => void - renderValue?: () => React.ReactNode - children: React.ReactNode + onOpenChange: (open: boolean) => void + renderOptions: () => React.ReactNode } const PersistentSelect = ({ label, - labelId, - listboxId, - value, - selectedName, + displayValue, open, disabled, keepOpenRef, - onOpen, - onClose, - renderValue, - children, + onOpenChange, + renderOptions, }: PersistentSelectProps) => { - const anchorRef = useRef(null) + const id = useId() + const labelId = `${id}-label` + const valueId = `${id}-value` + const listboxId = `${id}-listbox` + const anchorRef = useRef(null) const listRef = useRef(null) const popperOptions = useMemo( @@ -283,17 +276,15 @@ const PersistentSelect = ({ const closeFromKeyboard = (event: KeyboardEvent) => { if (event.key === 'Tab') { - onClose() + onOpenChange(false) } else if (event.key === 'Escape') { - onClose() - anchorRef.current - ?.querySelector('[role="combobox"]') - ?.focus() + onOpenChange(false) + anchorRef.current?.focus({ preventScroll: true }) } } document.addEventListener('keydown', closeFromKeyboard) return () => document.removeEventListener('keydown', closeFromKeyboard) - }, [onClose, open]) + }, [onOpenChange, open]) return ( - - {label} - - + + {displayValue} + + + + + + {label} + + + - {children} + {open && renderOptions()} @@ -364,10 +416,9 @@ const PersistentSelect = ({ } interface PersistentProxySelectProps - extends Omit< - PersistentSelectProps, - 'labelId' | 'listboxId' | 'renderValue' | 'children' - > { + extends Omit { + value: string + selectedName: string groupName: string fixed?: string selectedDelay: number @@ -390,11 +441,7 @@ const PersistentProxySelect = ({ return ( ( + displayValue={ )} - )} - > - {options.map((option) => { - const selected = option.value === value - const isFixed = option.name === fixed - const delay = option.disabled - ? -1 - : delayManager.getDelayFix(option.member, groupName) + } + renderOptions={() => + options.map((option) => { + const selected = option.value === value + const isFixed = option.name === fixed + const delay = option.disabled + ? -1 + : delayManager.getDelayFix(option.member, groupName) - return ( - { - if (!option.disabled) onChange(option.value) - }} - sx={{ - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center', - width: '100%', - pr: 1, - }} - > - {isFixed && ( - - 📌 - - )} - - {option.name} - - {!option.disabled && ( - - )} - - ) - })} - + return ( + { + if (!option.disabled) onChange(option.value) + }} + sx={{ + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + width: '100%', + pr: 1, + }} + > + {isFixed && ( + + 📌 + + )} + + {option.name} + + {!option.disabled && ( + + )} + + ) + }) + } + /> ) } @@ -863,7 +911,7 @@ export const CurrentProxyCard = () => { const proxyOptions = useMemo( () => - isDirectMode + isDirectMode || openSelect !== 'proxy' ? [] : sortProxyOptions( unsortedProxyOptions, @@ -874,6 +922,7 @@ export const CurrentProxyCard = () => { [ delays, isDirectMode, + openSelect, unsortedProxyOptions, sortType, defaultLatencyTimeout, @@ -1039,28 +1088,25 @@ export const CurrentProxyCard = () => { {selectedGroupName}} open={openSelect === 'group'} disabled={isGlobalMode || isDirectMode} keepOpenRef={delayButtonRef} - onOpen={() => setOpenSelect('group')} - onClose={() => setOpenSelect(null)} - > - {selectableGroups.map((group) => ( - handleGroupChange(group.name)} - > - {group.name} - - ))} - + onOpenChange={(open) => setOpenSelect(open ? 'group' : null)} + renderOptions={() => + selectableGroups.map((group) => ( + handleGroupChange(group.name)} + > + {group.name} + + )) + } + /> { } selectedName={selectedProxyName} selectedDelay={currentDelay} - options={isDirectMode ? [] : proxyOptions} + options={proxyOptions} open={openSelect === 'proxy'} disabled={isDirectMode} keepOpenRef={delayButtonRef} - onOpen={() => setOpenSelect('proxy')} - onClose={() => setOpenSelect(null)} + onOpenChange={(open) => setOpenSelect(open ? 'proxy' : null)} onChange={handleProxyChange} />