From a5c59ce6897899cd819c494d3bffdd524f558683 Mon Sep 17 00:00:00 2001 From: zjdndjf <186369260+zjdndjf@users.noreply.github.com> Date: Mon, 6 Apr 2026 01:16:03 +0800 Subject: [PATCH] fix: resolve type safety regressions across renderer and config --- src/main/index.ts | 48 ++++----- src/main/resolve/autoUpdater.ts | 24 ++--- src/main/resolve/backup.ts | 9 +- src/main/utils/github.ts | 2 +- .../components/base/base-error-boundary.tsx | 8 +- .../components/network/network-topology.tsx | 101 ++++++++++-------- .../components/profiles/edit-rules-modal.tsx | 2 +- .../src/components/rules/rule-item.tsx | 52 ++++----- .../src/components/sider/conn-card.tsx | 49 +++++---- src/renderer/src/locales/en-US.json | 3 +- src/renderer/src/locales/fa-IR.json | 2 +- src/renderer/src/locales/ru-RU.json | 2 +- src/renderer/src/locales/zh-CN.json | 2 +- src/renderer/src/locales/zh-TW.json | 2 +- src/renderer/src/pages/connections.tsx | 3 +- src/renderer/src/pages/dns.tsx | 9 +- src/renderer/src/pages/network.tsx | 8 +- src/renderer/src/pages/proxies.tsx | 9 +- src/shared/types.d.ts | 2 + 19 files changed, 175 insertions(+), 162 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index 1d19dd2..fcced38 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,30 +1,6 @@ import { execSync } from 'child_process' import { electronApp, optimizer } from '@electron-toolkit/utils' import { app, dialog } from 'electron' - -if (process.platform === 'win32') { - try { - const stdout = execSync('powershell -NoProfile -Command "$PSVersionTable.PSVersion.Major"', { - encoding: 'utf8', - timeout: 5000 - }) - const major = parseInt(stdout.trim(), 10) - if (!isNaN(major) && major < 5) { - const isZh = Intl.DateTimeFormat().resolvedOptions().locale?.startsWith('zh') - const title = isZh ? '需要更新 PowerShell' : 'PowerShell Update Required' - const message = isZh - ? `检测到您的 PowerShell 版本为 ${major}.x,部分功能需要 PowerShell 5.1 才能正常运行。\\n\\n请访问 Microsoft 官网下载并安装 Windows Management Framework 5.1。` - : `Detected PowerShell version ${major}.x. Some features require PowerShell 5.1.\\n\\nPlease install Windows Management Framework 5.1 from the Microsoft website.` - execSync( - `mshta "javascript:var sh=new ActiveXObject('WScript.Shell');sh.Popup('${message}',0,'${title}',48);close()"`, - { timeout: 60000 } - ) - process.exit(0) - } - } catch { - // ignore - } -} import i18next from 'i18next' import { initI18n } from '../shared/i18n' import { registerIpcMainHandlers } from './utils/ipc' @@ -61,6 +37,30 @@ import { getSystemLanguage } from './lifecycle' +if (process.platform === 'win32') { + try { + const stdout = execSync('powershell -NoProfile -Command "$PSVersionTable.PSVersion.Major"', { + encoding: 'utf8', + timeout: 5000 + }) + const major = parseInt(stdout.trim(), 10) + if (!isNaN(major) && major < 5) { + const isZh = Intl.DateTimeFormat().resolvedOptions().locale?.startsWith('zh') + const title = isZh ? '需要更新 PowerShell' : 'PowerShell Update Required' + const message = isZh + ? `检测到您的 PowerShell 版本为 ${major}.x,部分功能需要 PowerShell 5.1 才能正常运行。\\n\\n请访问 Microsoft 官网下载并安装 Windows Management Framework 5.1。` + : `Detected PowerShell version ${major}.x. Some features require PowerShell 5.1.\\n\\nPlease install Windows Management Framework 5.1 from the Microsoft website.` + execSync( + `mshta "javascript:var sh=new ActiveXObject('WScript.Shell');sh.Popup('${message}',0,'${title}',48);close()"`, + { timeout: 60000 } + ) + process.exit(0) + } + } catch { + // ignore + } +} + const mainLogger = createLogger('Main') export { mainWindow, showMainWindow, triggerMainWindow, closeMainWindow } diff --git a/src/main/resolve/autoUpdater.ts b/src/main/resolve/autoUpdater.ts index 90519d3..e804b91 100644 --- a/src/main/resolve/autoUpdater.ts +++ b/src/main/resolve/autoUpdater.ts @@ -117,19 +117,17 @@ export async function downloadAndInstallUpdate(version: string): Promise { { proxy, responseType: 'text' } ) const expectedHash = (sha256Res.data as string).trim().split(/\s+/)[0] - const res = await tryDownload( - buildDownloadUrls(`${githubBase}${file}`, githubProxy), - { - responseType: 'arraybuffer', - timeout: 0, - proxy, - headers: { 'Content-Type': 'application/octet-stream' }, - onProgress: (loaded: number, total: number) => { - mainWindow?.webContents.send('updateDownloadProgress', { - status: 'downloading', - percent: Math.round((loaded / total) * 100) - }) - } + const res = await tryDownload(buildDownloadUrls(`${githubBase}${file}`, githubProxy), { + responseType: 'arraybuffer', + timeout: 0, + proxy, + headers: { 'Content-Type': 'application/octet-stream' }, + onProgress: (loaded: number, total: number) => { + mainWindow?.webContents.send('updateDownloadProgress', { + status: 'downloading', + percent: Math.round((loaded / total) * 100) + }) + } }) mainWindow?.webContents.send('updateDownloadProgress', { status: 'verifying' }) const fileBuffer = Buffer.from(res.data as ArrayBuffer) diff --git a/src/main/resolve/backup.ts b/src/main/resolve/backup.ts index 8f07bba..2cf797e 100644 --- a/src/main/resolve/backup.ts +++ b/src/main/resolve/backup.ts @@ -104,8 +104,7 @@ export async function webdavBackup(): Promise { if (webdavMaxBackups > 0) { try { - const files = await client.getDirectoryContents(webdavDir, { glob: '*.zip' }) - const fileList = Array.isArray(files) ? files : files.data + const fileList = await client.getDirectoryContents(webdavDir, { glob: '*.zip' }) const currentPlatformFiles = fileList.filter((file) => { return file.basename.startsWith(`${process.platform}_`) @@ -147,11 +146,7 @@ export async function webdavRestore(filename: string): Promise { export async function listWebdavBackups(): Promise { const { client, webdavDir } = await getWebDAVClient() const files = await client.getDirectoryContents(webdavDir, { glob: '*.zip' }) - if (Array.isArray(files)) { - return files.map((file) => file.basename) - } else { - return files.data.map((file) => file.basename) - } + return files.map((file) => file.basename) } export async function webdavDelete(filename: string): Promise { diff --git a/src/main/utils/github.ts b/src/main/utils/github.ts index 801d017..5ba4ef9 100644 --- a/src/main/utils/github.ts +++ b/src/main/utils/github.ts @@ -6,10 +6,10 @@ import { join } from 'path' import { createGunzip } from 'zlib' import AdmZip from 'adm-zip' import { stopCore } from '../core/manager' +import { getAppConfig } from '../config' import { mihomoCoreDir } from './dirs' import * as chromeRequest from './chromeRequest' import { createLogger } from './logger' -import { getAppConfig } from '../config' const log = createLogger('GitHub') diff --git a/src/renderer/src/components/base/base-error-boundary.tsx b/src/renderer/src/components/base/base-error-boundary.tsx index b3dc69d..49cf190 100644 --- a/src/renderer/src/components/base/base-error-boundary.tsx +++ b/src/renderer/src/components/base/base-error-boundary.tsx @@ -5,6 +5,8 @@ import { useTranslation } from 'react-i18next' const ErrorFallback = ({ error }: FallbackProps): React.ReactElement => { const { t } = useTranslation() + const errorMessage = error instanceof Error ? error.message : String(error) + const errorStack = error instanceof Error ? (error.stack ?? '') : '' return (
@@ -33,17 +35,17 @@ const ErrorFallback = ({ error }: FallbackProps): React.ReactElement => { variant="flat" className="ml-2" onPress={() => - navigator.clipboard.writeText('```\n' + error.message + '\n' + error.stack + '\n```') + navigator.clipboard.writeText('```\n' + errorMessage + '\n' + errorStack + '\n```') } > {t('common.error.copyErrorMessage')} -

{error.message}

+

{errorMessage}

Error Stack -
{error.stack}
+
{errorStack}
) diff --git a/src/renderer/src/components/network/network-topology.tsx b/src/renderer/src/components/network/network-topology.tsx index 1863798..03da2a6 100644 --- a/src/renderer/src/components/network/network-topology.tsx +++ b/src/renderer/src/components/network/network-topology.tsx @@ -1,8 +1,10 @@ import React, { useEffect, useRef, useState, useMemo, useCallback } from 'react' import * as d3 from 'd3' import { Button } from '@heroui/react' -import { IoPauseOutline, IoPlayOutline, IoGitNetworkOutline } from 'react-icons/io5' import { + IoPauseOutline, + IoPlayOutline, + IoGitNetworkOutline, IoDesktopOutline, IoServerOutline, IoFunnelOutline @@ -101,7 +103,8 @@ function buildHierarchy( proxies: new Map() }) } - const groupEntry = groupsMap.get(group)! + const groupEntry = groupsMap.get(group) + if (!groupEntry) continue groupEntry.data.connections++ groupEntry.data.traffic += traffic @@ -117,7 +120,8 @@ function buildHierarchy( rules: new Map() }) } - const proxyEntry = groupEntry.proxies.get(proxy)! + const proxyEntry = groupEntry.proxies.get(proxy) + if (!proxyEntry) continue proxyEntry.data.connections++ proxyEntry.data.traffic += traffic @@ -133,7 +137,8 @@ function buildHierarchy( clients: new Map() }) } - const ruleEntry = proxyEntry.rules.get(fullRule)! + const ruleEntry = proxyEntry.rules.get(fullRule) + if (!ruleEntry) continue ruleEntry.data.connections++ ruleEntry.data.traffic += traffic @@ -149,7 +154,8 @@ function buildHierarchy( ports: new Map() }) } - const clientEntry = ruleEntry.clients.get(clientIP)! + const clientEntry = ruleEntry.clients.get(clientIP) + if (!clientEntry) continue clientEntry.data.connections++ clientEntry.data.traffic += traffic @@ -162,7 +168,8 @@ function buildHierarchy( traffic: 0 }) } - const portNode = clientEntry.ports.get(sourcePort)! + const portNode = clientEntry.ports.get(sourcePort) + if (!portNode) continue portNode.connections++ portNode.traffic += traffic } @@ -179,13 +186,16 @@ function buildHierarchy( } groupsMap.forEach((groupEntry) => { - const groupNode: TopologyNodeData = { ...groupEntry.data, children: [] } + const groupChildren: TopologyNodeData[] = [] + const groupNode: TopologyNodeData = { ...groupEntry.data, children: groupChildren } groupEntry.proxies.forEach((proxyEntry) => { - const proxyNode: TopologyNodeData = { ...proxyEntry.data, children: [] } + const proxyChildren: TopologyNodeData[] = [] + const proxyNode: TopologyNodeData = { ...proxyEntry.data, children: proxyChildren } proxyEntry.rules.forEach((ruleEntry) => { - const ruleNode: TopologyNodeData = { ...ruleEntry.data, children: [] } + const ruleChildren: TopologyNodeData[] = [] + const ruleNode: TopologyNodeData = { ...ruleEntry.data, children: ruleChildren } ruleEntry.clients.forEach((clientEntry) => { const portChildren = Array.from(clientEntry.ports.values()) @@ -193,21 +203,21 @@ function buildHierarchy( const clientNode: TopologyNodeData = isClientCollapsed ? { ...clientEntry.data, _children: portChildren, children: undefined, collapsed: true } : { ...clientEntry.data, children: portChildren, collapsed: false } - ruleNode.children!.push(clientNode) + ruleChildren.push(clientNode) }) const isRuleCollapsed = !collapsedNodes.has(`expanded-${ruleEntry.data.id}`) - if (isRuleCollapsed && ruleNode.children!.length > 0) { - ruleNode._children = ruleNode.children + if (isRuleCollapsed && ruleChildren.length > 0) { + ruleNode._children = ruleChildren ruleNode.children = undefined ruleNode.collapsed = true } else { ruleNode.collapsed = false } - proxyNode.children!.push(ruleNode) + proxyChildren.push(ruleNode) }) - groupNode.children!.push(applyCollapse(proxyNode)) + groupChildren.push(applyCollapse(proxyNode)) }) rootChildren.push(applyCollapse(groupNode)) @@ -293,29 +303,26 @@ const NetworkTopologyCard: React.FC = () => { // Toggle collapse const toggleCollapseRef = useRef<(nodeId: string, isCollapsed: boolean) => void>(() => {}) - toggleCollapseRef.current = useCallback( - (nodeId: string, isCurrentlyCollapsed: boolean) => { - const expandedKey = `expanded-${nodeId}` - setCollapsedNodes((prev) => { - const next = new Set(prev) - if (isCurrentlyCollapsed) { - if (nodeId.startsWith('rule-') || nodeId.startsWith('client-')) { - next.add(expandedKey) - } else { - next.delete(nodeId) - } + toggleCollapseRef.current = useCallback((nodeId: string, isCurrentlyCollapsed: boolean) => { + const expandedKey = `expanded-${nodeId}` + setCollapsedNodes((prev) => { + const next = new Set(prev) + if (isCurrentlyCollapsed) { + if (nodeId.startsWith('rule-') || nodeId.startsWith('client-')) { + next.add(expandedKey) } else { - if (nodeId.startsWith('rule-') || nodeId.startsWith('client-')) { - next.delete(expandedKey) - } else { - next.add(nodeId) - } + next.delete(nodeId) } - return next - }) - }, - [] - ) + } else { + if (nodeId.startsWith('rule-') || nodeId.startsWith('client-')) { + next.delete(expandedKey) + } else { + next.add(nodeId) + } + } + return next + }) + }, []) // D3 render useEffect(() => { @@ -357,8 +364,7 @@ const NetworkTopologyCard: React.FC = () => { } } - const getNodeWidth = (d: d3.HierarchyNode) => - nodeWidths.get(d.data.id) ?? 80 + const getNodeWidth = (d: d3.HierarchyNode) => nodeWidths.get(d.data.id) ?? 80 // Max width per depth const maxWidthPerLevel = new Map() @@ -487,7 +493,7 @@ const NetworkTopologyCard: React.FC = () => { .filter((d) => Boolean( (d.data.children && d.data.children.length > 0) || - (d.data._children && d.data._children.length > 0) + (d.data._children && d.data._children.length > 0) ) ) .append('text') @@ -513,8 +519,7 @@ const NetworkTopologyCard: React.FC = () => { nodes .append('title') .text( - (d) => - `${d.data.name}\n${d.data.connections} connections\n${calcTraffic(d.data.traffic)}` + (d) => `${d.data.name}\n${d.data.connections} connections\n${calcTraffic(d.data.traffic)}` ) }, [hierarchyData, resolvedTheme]) @@ -555,13 +560,21 @@ const NetworkTopologyCard: React.FC = () => {
{/* Stats */}
- {stats.clientCount} {t('network.topology.clients')} + + {stats.clientCount} {t('network.topology.clients')} + · - {stats.ruleCount} {t('network.topology.rules')} + + {stats.ruleCount} {t('network.topology.rules')} + · - {stats.groupCount} {t('network.topology.groups')} + + {stats.groupCount} {t('network.topology.groups')} + · - {stats.proxyCount} {t('network.topology.nodes')} + + {stats.proxyCount} {t('network.topology.nodes')} + · {calcTraffic(stats.totalTraffic)}
diff --git a/src/renderer/src/components/profiles/edit-rules-modal.tsx b/src/renderer/src/components/profiles/edit-rules-modal.tsx index f1236e1..5c20604 100644 --- a/src/renderer/src/components/profiles/edit-rules-modal.tsx +++ b/src/renderer/src/components/profiles/edit-rules-modal.tsx @@ -1346,7 +1346,7 @@ const EditRulesModal: React.FC = (props) => { const originalIndex = ruleIndexMap.get(rule) ?? -1 return `${originalIndex}-${index}` }} - itemContent={(index, rule) => { + itemContent={(_index, rule) => { const originalIndex = ruleIndexMap.get(rule) ?? -1 const isDeleted = deletedRules.has(originalIndex) const isPrependOrAppend = diff --git a/src/renderer/src/components/rules/rule-item.tsx b/src/renderer/src/components/rules/rule-item.tsx index b2fba09..703e534 100644 --- a/src/renderer/src/components/rules/rule-item.tsx +++ b/src/renderer/src/components/rules/rule-item.tsx @@ -59,34 +59,34 @@ const RuleItem: React.FC = (props) => {
- - {extra && (() => { - const total = extra.hitCount + extra.missCount - const rate = total > 0 ? (extra.hitCount / total) * 100 : 0 - return ( - <> -
- - {formatRelativeTime(extra.hitAt || extra.missAt)} - - - {extra.hitCount}/{total} - - - {rate.toFixed(1)}% - -
- - - ) - })()} + {extra && + (() => { + const total = extra.hitCount + extra.missCount + const rate = total > 0 ? (extra.hitCount / total) * 100 : 0 + return ( + <> +
+ + {formatRelativeTime(extra.hitAt || extra.missAt)} + + + {extra.hitCount}/{total} + + + {rate.toFixed(1)}% + +
+ + + ) + })()} diff --git a/src/renderer/src/components/sider/conn-card.tsx b/src/renderer/src/components/sider/conn-card.tsx index 3ec5811..fa6005c 100644 --- a/src/renderer/src/components/sider/conn-card.tsx +++ b/src/renderer/src/components/sider/conn-card.tsx @@ -128,30 +128,27 @@ const ConnCard: React.FC = (props) => { const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null // 使用 useCallback 创建稳定的 handler 引用,通过 ref 读取 showTraffic 避免重建 - const handleTraffic = useCallback( - (_e: unknown, ...args: unknown[]) => { - const info = args[0] as IMihomoTrafficInfo - setUpload(info.up) - setDownload(info.down) - setSeries((prev) => { - const data = [...prev] - data.shift() - data.push(info.up + info.down) - return data - }) - if (platform === 'darwin' && showTrafficRef.current) { - const up = info.up - const down = info.down - if (up !== currentUploadRef.current || down !== currentDownloadRef.current) { - currentUploadRef.current = up - currentDownloadRef.current = down - const png = renderTrafficIcon(up, down) - window.electron.ipcRenderer.send('trayIconUpdate', png, true) - } + const handleTraffic = useCallback((_e: unknown, ...args: unknown[]) => { + const info = args[0] as IMihomoTrafficInfo + setUpload(info.up) + setDownload(info.down) + setSeries((prev) => { + const data = [...prev] + data.shift() + data.push(info.up + info.down) + return data + }) + if (platform === 'darwin' && showTrafficRef.current) { + const up = info.up + const down = info.down + if (up !== currentUploadRef.current || down !== currentDownloadRef.current) { + currentUploadRef.current = up + currentDownloadRef.current = down + const png = renderTrafficIcon(up, down) + window.electron.ipcRenderer.send('trayIconUpdate', png, true) } - }, - [] // eslint-disable-line react-hooks/exhaustive-deps - ) + } + }, []) useEffect(() => { window.electron.ipcRenderer.on('mihomoTraffic', handleTraffic) @@ -295,7 +292,6 @@ const ConnCard: React.FC = (props) => { export default ConnCard - const trayIconBase64 = `data:image/png;base64,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` // 固定宽高,同步渲染避免闪烁 @@ -317,7 +313,10 @@ function renderTrafficIcon(upload: number, download: number): string { trafficCanvas.height = ICON_H trafficCtx = trafficCanvas.getContext('2d') } - const ctx = trafficCtx! + if (!trafficCtx) { + return trayIconBase64 + } + const ctx = trafficCtx ctx.clearRect(0, 0, ICON_W, ICON_H) if (trafficIconLoaded) { ctx.drawImage(trafficIcon, 0, 0, ICON_H, ICON_H) diff --git a/src/renderer/src/locales/en-US.json b/src/renderer/src/locales/en-US.json index ccb1a81..877d3e0 100644 --- a/src/renderer/src/locales/en-US.json +++ b/src/renderer/src/locales/en-US.json @@ -708,8 +708,7 @@ "network.topology.waiting": "Waiting for connections...", "network.topology.pause": "Pause", "network.topology.resume": "Resume", - "guide.end.description": "Now that you understand the basic usage of the software, import your subscription and start using it. Enjoy!\nYou can also join our official Telegram group for the latest news.", "settings.githubProxy": "GitHub Download Proxy", "settings.githubProxy.auto": "Auto (proxy first)", "settings.githubProxy.direct": "Direct" -} \ No newline at end of file +} diff --git a/src/renderer/src/locales/fa-IR.json b/src/renderer/src/locales/fa-IR.json index d26b98a..7ccdddb 100644 --- a/src/renderer/src/locales/fa-IR.json +++ b/src/renderer/src/locales/fa-IR.json @@ -675,4 +675,4 @@ "settings.githubProxy": "پروکسی دانلود GitHub", "settings.githubProxy.auto": "خودکار (پروکسی اول)", "settings.githubProxy.direct": "مستقیم" -} \ No newline at end of file +} diff --git a/src/renderer/src/locales/ru-RU.json b/src/renderer/src/locales/ru-RU.json index 5446f32..2c2a3c5 100644 --- a/src/renderer/src/locales/ru-RU.json +++ b/src/renderer/src/locales/ru-RU.json @@ -677,4 +677,4 @@ "settings.githubProxy": "Прокси для загрузки GitHub", "settings.githubProxy.auto": "Авто (сначала прокси)", "settings.githubProxy.direct": "Прямое подключение" -} \ No newline at end of file +} diff --git a/src/renderer/src/locales/zh-CN.json b/src/renderer/src/locales/zh-CN.json index 032e694..15537c1 100644 --- a/src/renderer/src/locales/zh-CN.json +++ b/src/renderer/src/locales/zh-CN.json @@ -711,4 +711,4 @@ "settings.githubProxy": "GitHub 下载代理", "settings.githubProxy.auto": "自动(优先代理)", "settings.githubProxy.direct": "直连" -} \ No newline at end of file +} diff --git a/src/renderer/src/locales/zh-TW.json b/src/renderer/src/locales/zh-TW.json index 5cbbe46..4f0516c 100644 --- a/src/renderer/src/locales/zh-TW.json +++ b/src/renderer/src/locales/zh-TW.json @@ -711,4 +711,4 @@ "settings.githubProxy": "GitHub 下載代理", "settings.githubProxy.auto": "自動(優先代理)", "settings.githubProxy.direct": "直連" -} \ No newline at end of file +} diff --git a/src/renderer/src/pages/connections.tsx b/src/renderer/src/pages/connections.tsx index b2d0e2d..62a6fad 100644 --- a/src/renderer/src/pages/connections.tsx +++ b/src/renderer/src/pages/connections.tsx @@ -50,6 +50,7 @@ const Connections: React.FC = () => { const { 'find-process-mode': findProcessMode = 'always' } = controledMihomoConfig || {} const [filter, setFilter] = useState('') const { appConfig, patchAppConfig } = useAppConfig() + const appConfigValues: Partial = appConfig ?? {} const { connectionDirection = 'asc', connectionOrderBy = 'time', @@ -73,7 +74,7 @@ const Connections: React.FC = () => { connectionTableSortDirection, displayIcon = true, displayAppName = true - } = appConfig || {} + } = appConfigValues const [connectionsInfo, setConnectionsInfo] = useState() const [allConnections, setAllConnections] = useState(cachedConnections) const [activeConnections, setActiveConnections] = useState([]) diff --git a/src/renderer/src/pages/dns.tsx b/src/renderer/src/pages/dns.tsx index 678def1..be4b67d 100644 --- a/src/renderer/src/pages/dns.tsx +++ b/src/renderer/src/pages/dns.tsx @@ -123,10 +123,11 @@ const DNS: React.FC = () => { const handleSubkeyChange = (type: string, domain: string, value: string, index: number): void => { const list = [...values[type]] - const parts = value.split(',').map((s: string) => s.trim()).filter(Boolean) - const processedValue = type === 'hosts' - ? parts - : (parts.length > 1 ? parts : value.trim()) + const parts = value + .split(',') + .map((s: string) => s.trim()) + .filter(Boolean) + const processedValue = type === 'hosts' ? parts : parts.length > 1 ? parts : value.trim() if (domain || parts.length > 0) list[index] = { domain: domain.trim(), value: processedValue } else list.splice(index, 1) setValues({ ...values, [type]: list }) diff --git a/src/renderer/src/pages/network.tsx b/src/renderer/src/pages/network.tsx index 1db9436..b81352c 100644 --- a/src/renderer/src/pages/network.tsx +++ b/src/renderer/src/pages/network.tsx @@ -196,10 +196,10 @@ const IPPage: React.FC = () => { const averageLatency = (() => { const successes = LATENCY_TARGETS.map((t) => latencyResults[t.url]).filter( - (r) => r?.status === 'success' && r.latency !== null + (r): r is LatencyResult & { latency: number } => r?.status === 'success' && r.latency !== null ) if (successes.length === 0) return null - return Math.round(successes.reduce((acc, r) => acc + (r!.latency ?? 0), 0) / successes.length) + return Math.round(successes.reduce((acc, r) => acc + r.latency, 0) / successes.length) })() const fetchIP = useCallback( @@ -292,7 +292,9 @@ const IPPage: React.FC = () => {
{/* IP 地址高亮行(负 margin 贴边) */}
- {t('network.ipAddress')} + + {t('network.ipAddress')} +
{hidden ? '••••••••••••••' : ipInfo.ip} diff --git a/src/renderer/src/pages/proxies.tsx b/src/renderer/src/pages/proxies.tsx index dedc492..145e734 100644 --- a/src/renderer/src/pages/proxies.tsx +++ b/src/renderer/src/pages/proxies.tsx @@ -401,13 +401,14 @@ const Proxies: React.FC = () => { groupCounts, isOpen, proxyDisplayMode, + t, searchValue, delaying, - cols, - allProxies, - virtuosoRef, - t, + mutate, setIsOpen, + allProxies, + cols, + virtuosoRef, onGroupDelay ] ) diff --git a/src/shared/types.d.ts b/src/shared/types.d.ts index 6025b3f..b519684 100644 --- a/src/shared/types.d.ts +++ b/src/shared/types.d.ts @@ -258,6 +258,8 @@ interface IAppConfig { connectionTableColumnWidths?: Record connectionTableSortColumn?: string connectionTableSortDirection?: 'asc' | 'desc' + displayIcon?: boolean + displayAppName?: boolean spinFloatingIcon?: boolean disableTray?: boolean swapTrayClick?: boolean