diff --git a/src/components/setting/mods/sysproxy-viewer.tsx b/src/components/setting/mods/sysproxy-viewer.tsx index a438817c..3e8810d5 100644 --- a/src/components/setting/mods/sysproxy-viewer.tsx +++ b/src/components/setting/mods/sysproxy-viewer.tsx @@ -202,44 +202,138 @@ export const SysproxyViewer = forwardRef((props, ref) => { })); const onSave = useLockFn(async () => { - if (value.duration < 1) { showNotice("error", t("Proxy Daemon Duration Cannot be Less than 1 Second")); return; } - if (value.bypass && !validReg.test(value.bypass)) { showNotice("error", t("Invalid Bypass Format")); return; } - const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; - const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; - const hostnameRegex = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][a-zA-Z0-9\-]*[A-Za-z0-9])$/; - if (!ipv4Regex.test(value.proxy_host) && !ipv6Regex.test(value.proxy_host) && !hostnameRegex.test(value.proxy_host)) { showNotice("error", t("Invalid Proxy Host Format")); return; } + if (value.duration < 1) { + showNotice( + "error", + t("Proxy Daemon Duration Cannot be Less than 1 Second"), + ); + return; + } + if (value.bypass && !validReg.test(value.bypass)) { + showNotice("error", t("Invalid Bypass Format")); + return; + } + + // 修改验证规则,允许IP和主机名 + const ipv4Regex = + /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const ipv6Regex = + /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + const hostnameRegex = + /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; + + if ( + !ipv4Regex.test(value.proxy_host) && + !ipv6Regex.test(value.proxy_host) && + !hostnameRegex.test(value.proxy_host) + ) { + showNotice("error", t("Invalid Proxy Host Format")); + return; + } setSaving(true); - let proxyHost = value.proxy_host; - if (ipv6Regex.test(proxyHost) && !proxyHost.startsWith("[") && !proxyHost.endsWith("]")) { proxyHost = `[${proxyHost}]`; } + setOpen(false); + setSaving(false); + const patch: Partial = {}; + + if (value.guard !== enable_proxy_guard) { + patch.enable_proxy_guard = value.guard; + } + if (value.duration !== proxy_guard_duration) { + patch.proxy_guard_duration = value.duration; + } + if (value.bypass !== system_proxy_bypass) { + patch.system_proxy_bypass = value.bypass; + } + if (value.pac !== proxy_auto_config) { + patch.proxy_auto_config = value.pac; + } + if (value.use_default !== use_default_bypass) { + patch.use_default_bypass = value.use_default; + } - const patch: Partial = { - enable_proxy_guard: value.guard, - proxy_guard_duration: value.duration, - system_proxy_bypass: value.bypass, - proxy_auto_config: value.pac, - use_default_bypass: value.use_default, - proxy_host: proxyHost, - }; let pacContent = value.pac_content; if (pacContent) { pacContent = pacContent.replace(/%proxy_host%/g, value.proxy_host); + // 将 mixed-port 转换为字符串 const mixedPortStr = (clashConfig?.["mixed-port"] || "").toString(); pacContent = pacContent.replace(/%mixed-port%/g, mixedPortStr); } - patch.pac_file_content = pacContent; - try { - await patchVerge(patch); - setTimeout(() => { - if (enabled) resetSystemProxy(); - }, 50); - } catch (err: any) { - showNotice("error", err.toString()); - } finally { - setSaving(false); - setOpen(false); + if (pacContent !== pac_file_content) { + patch.pac_file_content = pacContent; } + + // 处理IPv6地址,如果是IPv6地址但没有被方括号包围,则添加方括号 + let proxyHost = value.proxy_host; + if ( + ipv6Regex.test(proxyHost) && + !proxyHost.startsWith("[") && + !proxyHost.endsWith("]") + ) { + proxyHost = `[${proxyHost}]`; + } + + if (proxyHost !== proxy_host) { + patch.proxy_host = proxyHost; + } + + // 判断是否需要重置系统代理 + const needResetProxy = + value.pac !== proxy_auto_config || + proxyHost !== proxy_host || + pacContent !== pac_file_content || + value.bypass !== system_proxy_bypass || + value.use_default !== use_default_bypass; + + Promise.resolve().then(async () => { + try { + // 乐观更新本地状态 + if (Object.keys(patch).length > 0) { + mutateVerge({ ...verge, ...patch }, false); + } + if (Object.keys(patch).length > 0) { + await patchVerge(patch); + } + setTimeout(async () => { + try { + await Promise.all([ + mutate("getSystemProxy"), + mutate("getAutotemProxy"), + ]); + + // 如果需要重置代理且代理当前启用 + if (needResetProxy && enabled) { + const [currentSysProxy, currentAutoProxy] = await Promise.all([ + getSystemProxy(), + getAutotemProxy(), + ]); + + const isProxyActive = value.pac + ? currentAutoProxy?.enable + : currentSysProxy?.enable; + + if (isProxyActive) { + await patchVergeConfig({ enable_system_proxy: false }); + await new Promise((resolve) => setTimeout(resolve, 50)); + await patchVergeConfig({ enable_system_proxy: true }); + await Promise.all([ + mutate("getSystemProxy"), + mutate("getAutotemProxy"), + ]); + } + } + } catch (err) { + console.warn("代理状态更新失败:", err); + } + }, 50); + } catch (err: any) { + console.error("配置保存失败:", err); + mutateVerge(); + showNotice("error", err.toString()); + // setOpen(true); + } + }); }); return ( diff --git a/src/components/setting/setting-system.tsx b/src/components/setting/setting-system.tsx index e23170f5..db2499a6 100644 --- a/src/components/setting/setting-system.tsx +++ b/src/components/setting/setting-system.tsx @@ -47,14 +47,11 @@ const SettingSystem = ({ onError }: Props) => { const { verge, patchVerge, mutateVerge } = useVerge(); const { installServiceAndRestartCore } = useServiceInstaller(); - // --- НАЧАЛО ИСПРАВЛЕНИЯ --- - // Используем синтаксис переименования: `actualState` становится `systemProxyActualState` const { actualState: systemProxyActualState, indicator: systemProxyIndicator, toggleSystemProxy, } = useSystemProxyState(); - // --- КОНЕЦ ИСПРАВЛЕНИЯ --- const { isAdminMode, isServiceMode, mutateRunningMode } = useSystemState(); const isTunAvailable = isServiceMode || isAdminMode;