From d8aeb63584cdb98d889027d964d3c4f41c297453 Mon Sep 17 00:00:00 2001 From: ezequielnick <107352853+ezequielnick@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:17:43 +0800 Subject: [PATCH] feat: optimize and improve subscription switching --- src/main/config/profile.ts | 10 ++- src/main/core/manager.ts | 7 ++- .../src/components/profiles/profile-item.tsx | 62 ++++++++++++------- src/renderer/src/hooks/use-profile-config.tsx | 29 +++++++-- 4 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/main/config/profile.ts b/src/main/config/profile.ts index 179ce7a..2e0bcd1 100644 --- a/src/main/config/profile.ts +++ b/src/main/config/profile.ts @@ -37,15 +37,21 @@ export async function getProfileItem(id: string | undefined): Promise { const config = await getProfileConfig() const current = config.current + + if (current === id) { + return + } + config.current = id await setProfileConfig(config) + try { await restartCore() } catch (e) { + // 如果重启失败,恢复原来的配置 config.current = current - throw e - } finally { await setProfileConfig(config) + throw e } } diff --git a/src/main/core/manager.ts b/src/main/core/manager.ts index e6429c3..a1cece4 100644 --- a/src/main/core/manager.ts +++ b/src/main/core/manager.ts @@ -212,7 +212,12 @@ export async function restartCore(): Promise { try { await startCore() } catch (e) { - dialog.showErrorBox(i18next.t('mihomo.error.coreStartFailed'), `${e}`) + // 记录错误到日志而不是显示阻塞对话框 + await writeFile(logPath(), `[Manager]: restart core failed, ${e}\n`, { + flag: 'a' + }) + // 重新抛出错误,让调用者处理 + throw e } } diff --git a/src/renderer/src/components/profiles/profile-item.tsx b/src/renderer/src/components/profiles/profile-item.tsx index cf84ee5..a87466f 100644 --- a/src/renderer/src/components/profiles/profile-item.tsx +++ b/src/renderer/src/components/profiles/profile-item.tsx @@ -14,7 +14,7 @@ import { import { calcPercent, calcTraffic } from '@renderer/utils/calc' import { IoMdMore, IoMdRefresh } from 'react-icons/io' import dayjs from '@renderer/utils/dayjs' -import React, { Key, useEffect, useMemo, useState } from 'react' +import React, { Key, useMemo, useState } from 'react' import EditFileModal from './edit-file-modal' import EditInfoModal from './edit-info-modal' import { useSortable } from '@dnd-kit/sortable' @@ -72,7 +72,8 @@ const ProfileItem: React.FC = (props) => { id: info.id }) const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null - const [disableSelect, setDisableSelect] = useState(false) + const [isActuallyDragging, setIsActuallyDragging] = useState(false) + const [clickStartPos, setClickStartPos] = useState<{ x: number; y: number } | null>(null) const menuItems: MenuItem[] = useMemo(() => { const list = [ @@ -150,19 +151,35 @@ const ProfileItem: React.FC = (props) => { setDropdownOpen(true) } - useEffect(() => { - if (isDragging) { - setTimeout(() => { - setDisableSelect(true) - }, 200) - } else { - setTimeout(() => { - setDisableSelect(false) - }, 200) + // 智能区分点击和拖拽的事件处理 + const handleMouseDown = (e: React.MouseEvent) => { + setClickStartPos({ x: e.clientX, y: e.clientY }) + setIsActuallyDragging(false) + } + + const handleMouseMove = (e: React.MouseEvent) => { + if (clickStartPos) { + const dx = e.clientX - clickStartPos.x + const dy = e.clientY - clickStartPos.y + // 移动距离超过 5px 认为是拖拽 + if (dx * dx + dy * dy > 25) { + setIsActuallyDragging(true) + } } - }, [isDragging]) + } + const handleMouseUp = () => { + // 如果没有拖拽,则处理为点击事件 + if (!isActuallyDragging && !isDragging && clickStartPos) { + setSelecting(true) + onPress().finally(() => { + setSelecting(false) + }) + } + setClickStartPos(null) + setTimeout(() => setIsActuallyDragging(false), 100) + } return (
= (props) => { { - if (disableSelect) return - setSelecting(true) - onPress().finally(() => { - setSelecting(false) - }) - }} + isPressable={false} onContextMenu={handleContextMenu} - className={`${isCurrent ? 'bg-primary' : ''} ${selecting ? 'blur-sm' : ''}`} + className={`${isCurrent ? 'bg-primary' : ''} ${selecting ? 'blur-sm' : ''} cursor-pointer`} > -
+

= ({ child } const changeCurrentProfile = async (id: string): Promise => { + if (profileConfig?.current === id) { + return + } + + // 乐观更新:立即更新 UI 状态,提供即时反馈 + if (profileConfig) { + const optimisticUpdate = { ...profileConfig, current: id } + mutateProfileConfig(optimisticUpdate, false) + } + try { - await change(id) + // 异步执行后台切换,不阻塞 UI + change(id).then(() => { + window.electron.ipcRenderer.send('updateTrayMenu') + mutateProfileConfig() + }).catch((e) => { + const errorMsg = e?.message || String(e) + // 处理 IPC 超时错误 + if (errorMsg.includes('reply was never sent')) { + setTimeout(() => mutateProfileConfig(), 1000) + } else { + alert(`切换 Profile 失败: ${errorMsg}`) + mutateProfileConfig() + } + }) } catch (e) { - alert(e) - } finally { + alert(`切换 Profile 失败: ${e}`) mutateProfileConfig() - window.electron.ipcRenderer.send('updateTrayMenu') } }