mirror of
https://gh.catmak.name/https://github.com/mihomo-party-org/mihomo-party
synced 2025-12-27 05:00:30 +08:00
feat: optimize and improve subscription switching
This commit is contained in:
parent
2e4090460d
commit
866cdb4661
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user