From 30f05a7a39b39cb86f2d8c6cf2ae46f45ebee2e1 Mon Sep 17 00:00:00 2001 From: wonfen Date: Mon, 5 Oct 2026 22:00:28 +0800 Subject: [PATCH] refactor(port): build the port dialog rows from one listener table --- Changelog.md | 1 + .../setting/mods/clash-port-viewer.tsx | 388 +++++++----------- 2 files changed, 160 insertions(+), 229 deletions(-) diff --git a/Changelog.md b/Changelog.md index 742d404d1..1d0b13ffb 100644 --- a/Changelog.md +++ b/Changelog.md @@ -23,6 +23,7 @@
🚀 优化改进 +- 优化端口设置窗口的实现,精简重复代码 - 优化侧边栏流量图表的 CPU 占用 **🖥️ Windows** diff --git a/src/components/setting/mods/clash-port-viewer.tsx b/src/components/setting/mods/clash-port-viewer.tsx index 19a452ad9..e0ffbf6f7 100644 --- a/src/components/setting/mods/clash-port-viewer.tsx +++ b/src/components/setting/mods/clash-port-viewer.tsx @@ -16,8 +16,9 @@ import { BaseDialog, Switch } from '@/components/base' import { useClashInfo } from '@/hooks/use-clash' import { useDisplayedMixedPort } from '@/hooks/use-displayed-mixed-port' import { useVerge } from '@/hooks/use-verge' -import { saveProxyPorts } from '@/services/cmds' +import { type ProxyPortSettings, saveProxyPorts } from '@/services/cmds' import { showNotice } from '@/services/notice-service' +import type { TranslationKey } from '@/types/generated/i18n-keys' import getSystem from '@/utils/get-system' const OS = getSystem() @@ -27,9 +28,125 @@ interface ClashPortViewerRef { close: () => void } +type ListenerKey = Exclude + +const LISTENERS: { + key: ListenerKey + label: TranslationKey + visible: boolean +}[] = [ + { + key: 'socks', + label: 'settings.modals.clashPort.fields.socks', + visible: true, + }, + { + key: 'http', + label: 'settings.modals.clashPort.fields.http', + visible: true, + }, + { + key: 'redir', + label: 'settings.modals.clashPort.fields.redir', + visible: OS !== 'windows', + }, + { + key: 'tproxy', + label: 'settings.modals.clashPort.fields.tproxy', + visible: OS === 'linux', + }, +] + const generateRandomPort = () => Math.floor(Math.random() * (65535 - 1025 + 1)) + 1025 +const isValidPort = (port: number) => port >= 1 && port <= 65535 + +const readPortSettings = ( + verge: IVergeConfig | undefined, + mixedPort: number, +): ProxyPortSettings => ({ + mixedPort, + socks: { + enabled: verge?.verge_socks_enabled ?? false, + port: verge?.verge_socks_port ?? 7898, + }, + http: { + enabled: verge?.verge_http_enabled ?? false, + port: verge?.verge_port ?? 7899, + }, + redir: { + enabled: verge?.verge_redir_enabled ?? false, + port: verge?.verge_redir_port ?? 7895, + }, + tproxy: { + enabled: verge?.verge_tproxy_enabled ?? false, + port: verge?.verge_tproxy_port ?? 7896, + }, +}) + +interface PortRowProps { + label: string + secondary?: string + port: number + enabled: boolean + onPortChange: (port: number) => void + // Without it the switch stays locked on, as the mixed port cannot be disabled. + onEnabledChange?: (enabled: boolean) => void +} + +const PortRow = ({ + label, + secondary, + port, + enabled, + onPortChange, + onEnabledChange, +}: PortRowProps) => { + const { t } = useTranslation() + + return ( + + +
+ + onPortChange(+e.target.value?.replace(/\D+/, '').slice(0, 5)) + } + disabled={!enabled} + slotProps={{ htmlInput: { style: { fontSize: 12 } } }} + /> + onPortChange(generateRandomPort())} + title={t('settings.modals.clashPort.actions.random')} + disabled={!enabled} + sx={{ mr: 0.5 }} + > + + + onEnabledChange?.(c)} + sx={{ ml: 0.5, opacity: onEnabledChange ? undefined : 0.7 }} + /> +
+
+ ) +} + export const ClashPortViewer = forwardRef((_, ref) => { const { t } = useTranslation() const { verge } = useVerge() @@ -38,27 +155,14 @@ export const ClashPortViewer = forwardRef((_, ref) => { verge?.verge_mixed_port ?? clashInfo?.mixed_port ?? 7897 const displayedMixedPort = useDisplayedMixedPort() const [open, setOpen] = useState(false) + const [ports, setPorts] = useState(() => + readPortSettings(verge, configuredMixedPort), + ) - // Mixed Port - const [mixedPort, setMixedPort] = useState(configuredMixedPort) - - // 其他端口状态 - const [socksPort, setSocksPort] = useState(verge?.verge_socks_port ?? 7898) - const [socksEnabled, setSocksEnabled] = useState( - verge?.verge_socks_enabled ?? false, - ) - const [httpPort, setHttpPort] = useState(verge?.verge_port ?? 7899) - const [httpEnabled, setHttpEnabled] = useState( - verge?.verge_http_enabled ?? false, - ) - const [redirPort, setRedirPort] = useState(verge?.verge_redir_port ?? 7895) - const [redirEnabled, setRedirEnabled] = useState( - verge?.verge_redir_enabled ?? false, - ) - const [tproxyPort, setTproxyPort] = useState(verge?.verge_tproxy_port ?? 7896) - const [tproxyEnabled, setTproxyEnabled] = useState( - verge?.verge_tproxy_enabled ?? false, - ) + const updateListener = ( + key: ListenerKey, + patch: Partial, + ) => setPorts((prev) => ({ ...prev, [key]: { ...prev[key], ...patch } })) // 添加保存请求,防止GUI卡死 const { loading, runAsync: saveSettings } = useRequest(saveProxyPorts, { @@ -80,51 +184,33 @@ export const ClashPortViewer = forwardRef((_, ref) => { useImperativeHandle(ref, () => ({ open: () => { - setMixedPort(configuredMixedPort) - setSocksPort(verge?.verge_socks_port ?? 7898) - setSocksEnabled(verge?.verge_socks_enabled ?? false) - setHttpPort(verge?.verge_port ?? 7899) - setHttpEnabled(verge?.verge_http_enabled ?? false) - setRedirPort(verge?.verge_redir_port ?? 7895) - setRedirEnabled(verge?.verge_redir_enabled ?? false) - setTproxyPort(verge?.verge_tproxy_port ?? 7896) - setTproxyEnabled(verge?.verge_tproxy_enabled ?? false) + setPorts(readPortSettings(verge, configuredMixedPort)) setOpen(true) }, close: () => setOpen(false), })) - // TODO 减少代码复杂度,性能开支 const onSave = useLockFn(async () => { + const listeners = LISTENERS.map(({ key }) => ports[key]) + // The backend also rejects out-of-range ports on disabled listeners. - const isValidPort = (port: number) => port >= 1 && port <= 65535 - const allPorts = [mixedPort, socksPort, httpPort, redirPort, tproxyPort] + const allPorts = [ports.mixedPort, ...listeners.map(({ port }) => port)] if (!allPorts.every(isValidPort)) { showNotice.error('settings.modals.clashPort.messages.invalidPort') return } // 端口冲突检测 - const portList = [ - mixedPort, - socksEnabled ? socksPort : -1, - httpEnabled ? httpPort : -1, - redirEnabled ? redirPort : -1, - tproxyEnabled ? tproxyPort : -1, - ].filter((p) => p !== -1) - - if (new Set(portList).size !== portList.length) { + const enabledPorts = [ + ports.mixedPort, + ...listeners.filter(({ enabled }) => enabled).map(({ port }) => port), + ] + if (new Set(enabledPorts).size !== enabledPorts.length) { showNotice.error('settings.modals.clashPort.messages.duplicatePort') return } - await saveSettings({ - mixedPort, - socks: { enabled: socksEnabled, port: socksPort }, - http: { enabled: httpEnabled, port: httpPort }, - redir: { enabled: redirEnabled, port: redirPort }, - tproxy: { enabled: tproxyEnabled, port: tproxyPort }, - }) + await saveSettings(ports) }) return ( @@ -150,187 +236,31 @@ export const ClashPortViewer = forwardRef((_, ref) => { onOk={onSave} > - - + setPorts((prev) => ({ ...prev, mixedPort })) + } + /> + {LISTENERS.filter(({ visible }) => visible).map(({ key, label }) => ( + updateListener(key, { port })} + onEnabledChange={(enabled) => updateListener(key, { enabled })} /> -
- - setMixedPort(+e.target.value?.replace(/\D+/, '').slice(0, 5)) - } - slotProps={{ htmlInput: { style: { fontSize: 12 } } }} - /> - setMixedPort(generateRandomPort())} - title={t('settings.modals.clashPort.actions.random')} - sx={{ mr: 0.5 }} - > - - - -
-
- - - -
- - setSocksPort(+e.target.value?.replace(/\D+/, '').slice(0, 5)) - } - disabled={!socksEnabled} - slotProps={{ htmlInput: { style: { fontSize: 12 } } }} - /> - setSocksPort(generateRandomPort())} - title={t('settings.modals.clashPort.actions.random')} - disabled={!socksEnabled} - sx={{ mr: 0.5 }} - > - - - setSocksEnabled(c)} - sx={{ ml: 0.5 }} - /> -
-
- - - -
- - setHttpPort(+e.target.value?.replace(/\D+/, '').slice(0, 5)) - } - disabled={!httpEnabled} - slotProps={{ htmlInput: { style: { fontSize: 12 } } }} - /> - setHttpPort(generateRandomPort())} - title={t('settings.modals.clashPort.actions.random')} - disabled={!httpEnabled} - sx={{ mr: 0.5 }} - > - - - setHttpEnabled(c)} - sx={{ ml: 0.5 }} - /> -
-
- - {OS !== 'windows' && ( - - -
- - setRedirPort(+e.target.value?.replace(/\D+/, '').slice(0, 5)) - } - disabled={!redirEnabled} - slotProps={{ htmlInput: { style: { fontSize: 12 } } }} - /> - setRedirPort(generateRandomPort())} - title={t('settings.modals.clashPort.actions.random')} - disabled={!redirEnabled} - sx={{ mr: 0.5 }} - > - - - setRedirEnabled(c)} - sx={{ ml: 0.5 }} - /> -
-
- )} - - {OS === 'linux' && ( - - -
- - setTproxyPort(+e.target.value?.replace(/\D+/, '').slice(0, 5)) - } - disabled={!tproxyEnabled} - slotProps={{ htmlInput: { style: { fontSize: 12 } } }} - /> - setTproxyPort(generateRandomPort())} - title={t('settings.modals.clashPort.actions.random')} - disabled={!tproxyEnabled} - sx={{ mr: 0.5 }} - > - - - setTproxyEnabled(c)} - sx={{ ml: 0.5 }} - /> -
-
- )} + ))}
)