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> { export async function changeCurrentProfile(id: string): Promise<void> {
const config = await getProfileConfig() const config = await getProfileConfig()
const current = config.current const current = config.current
if (current === id) {
return
}
config.current = id config.current = id
await setProfileConfig(config) await setProfileConfig(config)
try { try {
await restartCore() await restartCore()
} catch (e) { } catch (e) {
// 如果重启失败,恢复原来的配置
config.current = current config.current = current
throw e
} finally {
await setProfileConfig(config) await setProfileConfig(config)
throw e
} }
} }

View File

@ -212,7 +212,12 @@ export async function restartCore(): Promise<void> {
try { try {
await startCore() await startCore()
} catch (e) { } 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 { calcPercent, calcTraffic } from '@renderer/utils/calc'
import { IoMdMore, IoMdRefresh } from 'react-icons/io' import { IoMdMore, IoMdRefresh } from 'react-icons/io'
import dayjs from '@renderer/utils/dayjs' 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 EditFileModal from './edit-file-modal'
import EditInfoModal from './edit-info-modal' import EditInfoModal from './edit-info-modal'
import { useSortable } from '@dnd-kit/sortable' import { useSortable } from '@dnd-kit/sortable'
@ -72,7 +72,8 @@ const ProfileItem: React.FC<Props> = (props) => {
id: info.id id: info.id
}) })
const transform = tf ? { x: tf.x, y: tf.y, scaleX: 1, scaleY: 1 } : null 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 menuItems: MenuItem[] = useMemo(() => {
const list = [ const list = [
@ -150,19 +151,35 @@ const ProfileItem: React.FC<Props> = (props) => {
setDropdownOpen(true) setDropdownOpen(true)
} }
useEffect(() => { // 智能区分点击和拖拽的事件处理
if (isDragging) { const handleMouseDown = (e: React.MouseEvent) => {
setTimeout(() => { setClickStartPos({ x: e.clientX, y: e.clientY })
setDisableSelect(true) setIsActuallyDragging(false)
}, 200)
} else {
setTimeout(() => {
setDisableSelect(false)
}, 200)
} }
}, [isDragging])
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)
}
}
}
const handleMouseUp = () => {
// 如果没有拖拽,则处理为点击事件
if (!isActuallyDragging && !isDragging && clickStartPos) {
setSelecting(true)
onPress().finally(() => {
setSelecting(false)
})
}
setClickStartPos(null)
setTimeout(() => setIsActuallyDragging(false), 100)
}
return ( return (
<div <div
@ -186,18 +203,19 @@ const ProfileItem: React.FC<Props> = (props) => {
<Card <Card
as="div" as="div"
fullWidth fullWidth
isPressable isPressable={false}
onPress={() => {
if (disableSelect) return
setSelecting(true)
onPress().finally(() => {
setSelecting(false)
})
}}
onContextMenu={handleContextMenu} 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"
onMouseDownCapture={handleMouseDown}
onMouseMoveCapture={handleMouseMove}
onMouseUpCapture={handleMouseUp}
> >
<div ref={setNodeRef} {...attributes} {...listeners} className="w-full h-full">
<CardBody className="pb-1"> <CardBody className="pb-1">
<div className="flex justify-between h-[32px]"> <div className="flex justify-between h-[32px]">
<h3 <h3

View File

@ -71,13 +71,34 @@ export const ProfileConfigProvider: React.FC<{ children: ReactNode }> = ({ child
} }
const changeCurrentProfile = async (id: string): Promise<void> => { const changeCurrentProfile = async (id: string): Promise<void> => {
if (profileConfig?.current === id) {
return
}
// 乐观更新:立即更新 UI 状态,提供即时反馈
if (profileConfig) {
const optimisticUpdate = { ...profileConfig, current: id }
mutateProfileConfig(optimisticUpdate, false)
}
try { try {
await change(id) // 异步执行后台切换,不阻塞 UI
} catch (e) { change(id).then(() => {
alert(e)
} finally {
mutateProfileConfig()
window.electron.ipcRenderer.send('updateTrayMenu') 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(`切换 Profile 失败: ${e}`)
mutateProfileConfig()
} }
} }