From cf0bb34ea2b1860f07d31d9433446a7c5677afed Mon Sep 17 00:00:00 2001 From: eafonyang Date: Thu, 6 Aug 2026 11:09:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20bug=EF=BC=9AEQ=20=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2=E8=B0=83=E8=8A=82=E4=BB=BB=E6=84=8F=E5=8F=82=E6=95=B0?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E8=80=B3=E6=9C=BA=E4=BC=9A=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=88=87=E6=8D=A2=E5=88=B0=E5=88=AB=E7=9A=84=E8=80=B3=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/EQPage.tsx | 92 +++++++++++++++++++++++++------------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/client/src/pages/EQPage.tsx b/client/src/pages/EQPage.tsx index 3226bb9..e733307 100644 --- a/client/src/pages/EQPage.tsx +++ b/client/src/pages/EQPage.tsx @@ -205,6 +205,10 @@ export default function EQPage() { const syncingHeadphoneRef = useRef(false); const peqSyncTimerRef = useRef(null); const peqPresetCacheRef = useRef>({}); + /** Anchor UI selection by preset name so poll/catalog reorder won't mis-switch headphones. */ + const selectedPeqNameRef = useRef(""); + const devicePeqSelectRef = useRef(deviceState?.peqSelect); + devicePeqSelectRef.current = deviceState?.peqSelect; const skipPeqAutoSyncRef = useRef(false); const skipBandsSyncFromAbToggleRef = useRef(false); const headphoneMenuRef = useRef(null); @@ -502,6 +506,11 @@ export default function EQPage() { }); }, []); + const selectHeadphone = useCallback((idx: number, name?: string) => { + selectedPeqNameRef.current = name ?? ""; + setHeadphoneIdx(idx); + }, []); + const syncPeqCatalogFromState = useCallback( ( remote: { peq?: PeqCatalogItem[]; peqSelect?: number }, @@ -511,14 +520,24 @@ export default function EQPage() { setPeqItems(items as typeof peqItems); setHeadphoneModels(items.map(item => item.name)); if (items.length === 0) { + selectedPeqNameRef.current = ""; setHeadphoneIdx(0); - return; + return 0; } - const nextIdx = Math.min( - Math.max(devicePeqSelect ?? remote.peqSelect ?? 0, 0), - items.length - 1 - ); + const preferred = selectedPeqNameRef.current; + let nextIdx = + preferred !== "" + ? items.findIndex(item => item.name === preferred) + : -1; + if (nextIdx < 0) { + nextIdx = Math.min( + Math.max(devicePeqSelect ?? remote.peqSelect ?? 0, 0), + items.length - 1 + ); + } + selectedPeqNameRef.current = items[nextIdx]?.name ?? ""; setHeadphoneIdx(nextIdx); + return nextIdx; }, [mergeRemotePeqCatalog] ); @@ -542,7 +561,7 @@ export default function EQPage() { remote, resolvedPeqSelect ); - syncPeqCatalogFromState(remote, resolvedPeqSelect); + const catalogIdx = syncPeqCatalogFromState(remote, resolvedPeqSelect); const items = remote.peq ?? []; if (items.length === 0) { @@ -551,12 +570,8 @@ export default function EQPage() { return; } - const nextIdx = Math.min( - Math.max(resolvedPeqSelect, 0), - items.length - 1 - ); const nextBands = normalizeFiltersFromPeq( - items[nextIdx] as { filters?: any[] | string } + items[catalogIdx] as { filters?: any[] | string } ); if (nextBands.length > 0) { setBandsForBothModes(nextBands); @@ -676,7 +691,7 @@ export default function EQPage() { const createdIndex = latest.peq?.findIndex(item => item.name === nextName) ?? -1; if (createdIndex >= 0) { - setHeadphoneIdx(createdIndex); + selectHeadphone(createdIndex, nextName); updateSetting({ peqSelect: createdIndex }); } } else { @@ -709,7 +724,7 @@ export default function EQPage() { canDel: 1, }, ]); - setHeadphoneIdx(headphoneModels.length); + selectHeadphone(headphoneModels.length, nextName); } setIsAddPresetDialogOpen(false); toast.success(eqUi.toastAddPresetOk); @@ -764,7 +779,7 @@ export default function EQPage() { const createdIndex = latest.peq?.findIndex(item => item.name === nextName) ?? -1; if (createdIndex >= 0) { - setHeadphoneIdx(createdIndex); + selectHeadphone(createdIndex, nextName); updateSetting({ peqSelect: createdIndex }); } } else { @@ -779,7 +794,7 @@ export default function EQPage() { canDel: currentPeq?.canDel ?? 1, }, ]); - setHeadphoneIdx(headphoneModels.length); + selectHeadphone(headphoneModels.length, nextName); } setIsSaveBDialogOpen(false); toast.success(eqUi.toastSaveBOk); @@ -856,7 +871,7 @@ export default function EQPage() { const targetIdx = latest.peq?.findIndex(item => item.name === nextName) ?? -1; if (targetIdx >= 0) { - setHeadphoneIdx(targetIdx); + selectHeadphone(targetIdx, nextName); updateSetting({ peqSelect: targetIdx }); } } else if (isOverwrite) { @@ -878,7 +893,7 @@ export default function EQPage() { : item ) ); - setHeadphoneIdx(existingIdx); + selectHeadphone(existingIdx, nextName); } else { setHeadphoneModels(prev => [...prev, nextName]); setPeqItems(prev => [ @@ -895,7 +910,7 @@ export default function EQPage() { canDel: 1, }, ]); - setHeadphoneIdx(headphoneModels.length); + selectHeadphone(headphoneModels.length, nextName); } toast.success(eqUi.importSuccess); setIsShareDialogOpen(false); @@ -925,11 +940,14 @@ export default function EQPage() { // 加载耳机列表并初始化曲线 useEffect(() => { + let cancelled = false; + async function loadHeadphones() { peqHydrationPendingRef.current = true; allowPeqRemoteSyncRef.current = false; try { if (isDemoMode || !api) { + if (cancelled) return; // 演示模式使用默认列表 const defaultModels = [ "Sennheiser HD 650", @@ -941,23 +959,28 @@ export default function EQPage() { ]; setHeadphoneModels(defaultModels); setPeqItems(defaultModels.map(name => ({ name, filters: [] }))); + selectHeadphone(0, defaultModels[0]); setCurrentRawCurve(null); return; } try { const loadedPeq = await fetchEqSyncPeq(api, "loadHeadphones"); + if (cancelled) return; if (loadedPeq.peq && loadedPeq.peq.length > 0) { const mergedPeq = mergeRemotePeqCatalog(loadedPeq.peq); setHeadphoneModels(mergedPeq.map(h => h.name)); setPeqItems(mergedPeq); const nextIdx = Math.min( - Math.max(deviceState?.peqSelect ?? loadedPeq.peqSelect ?? 0, 0), + Math.max( + devicePeqSelectRef.current ?? loadedPeq.peqSelect ?? 0, + 0 + ), mergedPeq.length - 1 ); - setHeadphoneIdx(nextIdx); + selectHeadphone(nextIdx, mergedPeq[nextIdx]?.name); lastPeqCatalogSyncKeyRef.current = buildPeqCatalogSyncKey( loadedPeq, - deviceState?.peqSelect + devicePeqSelectRef.current ?? loadedPeq.peqSelect ); // 初始化当前选中的耳机曲线 @@ -987,9 +1010,11 @@ export default function EQPage() { ]; setHeadphoneModels(defaultModels); setPeqItems(defaultModels.map(name => ({ name, filters: [] }))); + selectHeadphone(0, defaultModels[0]); setCurrentRawCurve(null); } } catch { + if (cancelled) return; // 出错时使用默认列表 const defaultModels = [ "Sennheiser HD 650", @@ -1001,23 +1026,28 @@ export default function EQPage() { ]; setHeadphoneModels(defaultModels); setPeqItems(defaultModels.map(name => ({ name, filters: [] }))); + selectHeadphone(0, defaultModels[0]); setCurrentRawCurve(null); } } finally { + if (cancelled) return; allowPeqRemoteSyncRef.current = true; skipPeqAutoSyncRef.current = true; queueMicrotask(() => { - peqHydrationPendingRef.current = false; + if (!cancelled) peqHydrationPendingRef.current = false; }); } } - loadHeadphones(); + void loadHeadphones(); + return () => { + cancelled = true; + }; }, [ api, isDemoMode, - loadRawCurveForPeq, mergeRemotePeqCatalog, - deviceState?.peqSelect, + selectHeadphone, + setCurrentRawCurve, ]); /** 切换耳机型号时变;不含 filters,避免编辑写回 peqItems 后反复加载 bands 并触发 peqChange 循环 */ @@ -1122,7 +1152,7 @@ export default function EQPage() { window.clearTimeout(peqSyncTimerRef.current); peqSyncTimerRef.current = null; } - setHeadphoneIdx(idx); + selectHeadphone(idx, peqItems[idx]?.name); updateSetting({ peqSelect: idx }); setIsHeadphoneMenuOpen(false); }, @@ -1132,6 +1162,7 @@ export default function EQPage() { peqItems, persistPresetEditsToPeqItem, rememberPeqPresetCache, + selectHeadphone, updateSetting, ] ); @@ -2007,6 +2038,9 @@ export default function EQPage() { }} onRenamePreset={async (oldName, newName, item) => { try { + if (selectedPeqNameRef.current === oldName) { + selectedPeqNameRef.current = newName; + } if (isDemoMode || !api) { const nextItems = peqItems.map(it => it.name === oldName ? { ...it, name: newName } : it @@ -2164,7 +2198,7 @@ export default function EQPage() { latest.peq?.findIndex(item => item.name === createdName) ?? -1; if (targetIdx >= 0) { - setHeadphoneIdx(targetIdx); + selectHeadphone(targetIdx, createdName); updateSetting({ peqSelect: targetIdx }); } } else if (isOverwrite) { @@ -2186,7 +2220,7 @@ export default function EQPage() { : item ) ); - setHeadphoneIdx(existingIdx); + selectHeadphone(existingIdx, createdName); } else { let nextIndex = 0; setPeqItems(prev => [ @@ -2207,7 +2241,7 @@ export default function EQPage() { nextIndex = prev.length; return [...prev, createdName]; }); - setHeadphoneIdx(nextIndex); + selectHeadphone(nextIndex, createdName); } setIsBrandDrawerOpen(false); } catch {