feat: optimize and improve subscription switching

This commit is contained in:
ezequielnick 2025-08-04 13:17:43 +08:00
parent 2e4090460d
commit 866cdb4661
4 changed files with 79 additions and 29 deletions

View File

@ -37,15 +37,21 @@ export async function getProfileItem(id: string | undefined): Promise<IProfileIt
export async function changeCurrentProfile(id: string): Promise<void> {
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
}
}

View File

@ -212,7 +212,12 @@ export async function restartCore(): Promise<void> {
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
}
}

View File

@ -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> = (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> = (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 (
<div
@ -186,18 +203,19 @@ const ProfileItem: React.FC<Props> = (props) => {
<Card
as="div"
fullWidth
isPressable
onPress={() => {
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`}
>
<div ref={setNodeRef} {...attributes} {...listeners} className="w-full h-full">
<div
ref={setNodeRef}
{...attributes}
{...listeners}
className="w-full h-full"
onMouseDownCapture={handleMouseDown}
onMouseMoveCapture={handleMouseMove}
onMouseUpCapture={handleMouseUp}
>
<CardBody className="pb-1">
<div className="flex justify-between h-[32px]">
<h3

View File

@ -71,13 +71,34 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
}
const changeCurrentProfile = async (id: string): Promise<void> => {
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')
}
}