From 87751c7db7c3e942201d46f04dc831a69821eb4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=90=83=E9=A6=99=E8=8F=87?= <1416774788@qq.com> Date: Thu, 14 May 2026 21:59:55 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=8D=A2=E6=A5=BC?= =?UTF-8?q?=E5=90=8E=E9=98=9F=E5=8F=8B=E6=88=98=E7=BB=A9=E5=88=86=E6=9E=90?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=B8=8D=E6=9B=B4=E6=96=B0=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当队友在选人阶段交换楼层(trade/swap)后,analyzeTeamPower 功能发送 的聊天消息不会更新,导致楼层与 KDA/胜率数据对应错误。 新增 CHAMP_SELECT 的 Update 事件监听,换楼后检测 myTeam 签名变化, 自动重新拉取数据并发送消息。 Co-Authored-By: Claude Opus 4.7 --- src/lib/features.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/lib/features.ts b/src/lib/features.ts index 0a6c430..115478d 100644 --- a/src/lib/features.ts +++ b/src/lib/features.ts @@ -626,6 +626,23 @@ async function analyzeTeammates() { } let analyzeTeamPowerUnsub: (() => void) | null = null +let analyzeTeamPowerUpdateUnsub: (() => void) | null = null +let lastAnalyzedTeamSignature = '' + +/** 换楼后重新获取数据并发送聊天消息 */ +function onAnalyzeTeamPowerSwap(event: LCUEventMessage) { + if (event.eventType !== 'Update') return + + const session = event.data as ChampSelectSession + if (!session?.myTeam) return + + const nextSignature = getTeamDisplaySignature(session) + if (nextSignature === lastAnalyzedTeamSignature) return + + logger.info('[AnalyzeTeamPower] 检测到换楼,重新发送队友战绩分析') + lastAnalyzedTeamSignature = nextSignature + analyzeTeammates() +} function updateAnalyzeTeamPower(enabled: boolean) { if (enabled && !analyzeTeamPowerUnsub) { @@ -635,10 +652,16 @@ function updateAnalyzeTeamPower(enabled: boolean) { analyzeTeammates() } }) + analyzeTeamPowerUpdateUnsub = lcu.observe(LcuEventUri.CHAMP_SELECT, onAnalyzeTeamPowerSwap) logger.info('Analyze team power enabled ✓') } else if (!enabled && analyzeTeamPowerUnsub) { analyzeTeamPowerUnsub() analyzeTeamPowerUnsub = null + if (analyzeTeamPowerUpdateUnsub) { + analyzeTeamPowerUpdateUnsub() + analyzeTeamPowerUpdateUnsub = null + } + lastAnalyzedTeamSignature = '' logger.info('Analyze team power disabled') } } From c661c0c94b1c3f91dce710d73de35a42bf452ee0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=90=83=E9=A6=99=E8=8F=87?= <1416774788@qq.com> Date: Thu, 14 May 2026 22:56:13 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix:=20=E6=94=B9=E7=94=A8=20trades=20?= =?UTF-8?q?=E5=BF=AB=E7=85=A7=E6=A3=80=E6=B5=8B=E6=8D=A2=E6=A5=BC=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B6=88=E6=81=AF=E4=B8=8D=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原来的方案用 myTeam 签名(puuid:cellId)检测换楼,但实际换楼时 myTeam 顺序可能不会在 CHAMP_SELECT Update 中立即变化。改为监听 trades 字段的序列化快照,trades 变化即说明发生了换楼操作。 同时新增 statsByPuuid 缓存,换楼后直接从缓存按新 myTeam 顺序重建 消息发送,无需重复请求 SGP 接口。 --- src/lib/features.ts | 73 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/src/lib/features.ts b/src/lib/features.ts index 115478d..2b8234e 100644 --- a/src/lib/features.ts +++ b/src/lib/features.ts @@ -576,6 +576,13 @@ async function analyzeTeammates() { try { const { stats } = await fetchTeamStats() + // 缓存 stats 供换楼后重建消息(避免重复请求 SGP) + analyzeTeamPowerStatsByPuuid.clear() + for (const s of stats) { + if (s.puuid) analyzeTeamPowerStatsByPuuid.set(s.puuid, s) + if (s.summonerId) analyzeTeamPowerStatsByPuuid.set(`sid:${s.summonerId}`, s) + } + logger.info('┌─── 队友战绩分析 ───') const chatLines: string[] = ['Sona助手 ♫ 队友卡池一览(本模式战绩):\n'] @@ -627,21 +634,70 @@ async function analyzeTeammates() { let analyzeTeamPowerUnsub: (() => void) | null = null let analyzeTeamPowerUpdateUnsub: (() => void) | null = null -let lastAnalyzedTeamSignature = '' +/** 上次发送聊天消息时的 trades 快照,换楼会导致 trades 变化从而触发重发 */ +let lastAnalyzedTradesSnapshot = '' +/** puuid → TeammateStats 缓存,换楼后直接从缓存重建消息,避免重复请求 SGP */ +const analyzeTeamPowerStatsByPuuid = new Map() + +/** 根据 myTeam 顺序 + 缓存的 stats 构造聊天消息并发送 */ +async function resendAnalyzeMessageFromCache() { + try { + const session = await lcu.getChampSelectSession() + if (!session?.myTeam) return + + const chatLines: string[] = ['Sona助手 ♫ 队友卡池一览(本模式战绩):\n'] + for (let i = 0; i < session.myTeam.length; i++) { + const player = session.myTeam[i] + const floor = `${i + 1}楼` + const stat = (player.puuid ? analyzeTeamPowerStatsByPuuid.get(player.puuid) : undefined) + ?? (player.summonerId ? analyzeTeamPowerStatsByPuuid.get(`sid:${player.summonerId}`) : undefined) -/** 换楼后重新获取数据并发送聊天消息 */ + if (!stat || stat.winRate == null) { + chatLines.push(`${floor}: 🆕 萌新上线 (无战绩)`) + continue + } + + const winRate = stat.winRate.toFixed(1) + const kdaStr = stat.kdaNum >= 99 ? 'Perfect' : stat.kdaNum.toFixed(2) + const rating = getRating(stat.winRate, stat.kdaNum) + chatLines.push(`${floor}: ${rating} | 胜率${winRate}% | KDA ${kdaStr}`) + } + + const msg = chatLines.join('\n') + const msgType = store.get('analyzeTeamPowerMsgType') || 'celebration' + for (let attempt = 0; attempt < 10; attempt++) { + try { + await lcu.sendChampSelectMessage(msg, msgType) + logger.info('队友分析已发送到聊天框 ✓') + break + } catch { + if (attempt < 9) { + await sleep(1000) + } else { + logger.warn('聊天发送失败,聊天室始终未就绪') + } + } + } + } catch (err) { + logger.error('队友战绩分析失败:', err) + } +} + +/** 监听 CHAMP_SELECT Update,通过 trades 变化检测换楼 */ function onAnalyzeTeamPowerSwap(event: LCUEventMessage) { if (event.eventType !== 'Update') return const session = event.data as ChampSelectSession - if (!session?.myTeam) return + if (!session) return - const nextSignature = getTeamDisplaySignature(session) - if (nextSignature === lastAnalyzedTeamSignature) return + // 换楼时 trades 状态会变化:发起 → ACCEPTED → 消失 + // 用 trades 的序列化快照检测 + const tradesSnapshot = JSON.stringify(session.trades ?? []) + if (tradesSnapshot === lastAnalyzedTradesSnapshot) return logger.info('[AnalyzeTeamPower] 检测到换楼,重新发送队友战绩分析') - lastAnalyzedTeamSignature = nextSignature - analyzeTeammates() + lastAnalyzedTradesSnapshot = tradesSnapshot + resendAnalyzeMessageFromCache() } function updateAnalyzeTeamPower(enabled: boolean) { @@ -661,7 +717,8 @@ function updateAnalyzeTeamPower(enabled: boolean) { analyzeTeamPowerUpdateUnsub() analyzeTeamPowerUpdateUnsub = null } - lastAnalyzedTeamSignature = '' + lastAnalyzedTradesSnapshot = '' + analyzeTeamPowerStatsByPuuid.clear() logger.info('Analyze team power disabled') } } From 50682bd561bc5304f8f4d6fcb58132f815e5d1c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=90=83=E9=A6=99=E8=8F=87?= <1416774788@qq.com> Date: Fri, 15 May 2026 21:37:42 +0800 Subject: [PATCH 3/5] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E9=98=9F?= =?UTF-8?q?=E5=8F=8B=E6=88=98=E7=BB=A9=E6=B6=88=E6=81=AF=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E5=A2=9E=E5=8A=A0=E5=88=86=E9=9A=94=E7=BA=BF=E4=B8=8E?= =?UTF-8?q?=E9=98=B6=E6=AE=B5=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 getPhaseLabel 函数,将 timer.phase 映射为中文阶段名 - 新增 sendTeamStatsMessage 统一消息构造与发送逻辑 - analyzeTeammates 和 resendAnalyzeMessageFromCache 共用同一发送函数 - 消息顶部增加双横线分隔 + 阶段标记,尾部保留 Sona 签名 - 换楼阶段标记为"英雄换楼阶段",与普通阶段区分 --- src/lib/features.ts | 141 ++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 72 deletions(-) diff --git a/src/lib/features.ts b/src/lib/features.ts index 2b8234e..bb5f6c1 100644 --- a/src/lib/features.ts +++ b/src/lib/features.ts @@ -572,8 +572,59 @@ export function getRating(winRate: number, kda: number): string { return '☠️ 演员已就位' } +/** 将 timer.phase 或自定义阶段映射为中文 */ +function getPhaseLabel(phase: string): string { + switch (phase) { + case 'PLANNING': return '预选英雄阶段' + case 'BAN_PICK': return '禁用/选择阶段' + case 'FINALIZATION': return '锁定英雄阶段' + case 'trade_swap': return '英雄换楼阶段' + default: return '数据刷新' + } +} + +const SEPARATOR = '━━━━━━━━'.repeat(2) + +/** 统一的队友战绩消息构造与发送 */ +async function sendTeamStatsMessage(stats: TeammateStats[], phaseLabel: string) { + const header = `${SEPARATOR} 队友卡池一览(${phaseLabel})${SEPARATOR}` + const chatLines: string[] = [header, ''] + + for (const s of stats) { + const floor = `${s.floor}楼` + if (s.winRate == null) { + chatLines.push(`${floor}: 🆕 萌新上线 (无战绩)`) + continue + } + const winRate = s.winRate.toFixed(1) + const kdaStr = s.kdaNum >= 99 ? 'Perfect' : s.kdaNum.toFixed(2) + const rating = getRating(s.winRate, s.kdaNum) + chatLines.push(`${floor}: ${rating} | 胜率${winRate}% | KDA ${kdaStr}`) + } + + chatLines.push('') + chatLines.push('Sona助手 ♫') + + const msg = chatLines.join('\n') + const msgType = store.get('analyzeTeamPowerMsgType') || 'celebration' + for (let attempt = 0; attempt < 10; attempt++) { + try { + await lcu.sendChampSelectMessage(msg, msgType) + logger.info('队友分析已发送到聊天框 ✓ (%s)', phaseLabel) + break + } catch { + if (attempt < 9) { + await sleep(1000) + } else { + logger.warn('聊天发送失败,聊天室始终未就绪') + } + } + } +} + async function analyzeTeammates() { try { + const session = await lcu.getChampSelectSession() const { stats } = await fetchTeamStats() // 缓存 stats 供换楼后重建消息(避免重复请求 SGP) @@ -583,50 +634,8 @@ async function analyzeTeammates() { if (s.summonerId) analyzeTeamPowerStatsByPuuid.set(`sid:${s.summonerId}`, s) } - logger.info('┌─── 队友战绩分析 ───') - - const chatLines: string[] = ['Sona助手 ♫ 队友卡池一览(本模式战绩):\n'] - - for (const s of stats) { - const floor = `${s.floor}楼` - if (s.winRate == null) { - logger.info('│ %s — %s#%s — 无近期战绩或查询失败', floor, s.gameName, s.tagLine) - chatLines.push(`${floor}: 🆕 萌新上线 (无战绩)`) - continue - } - - const winRate = s.winRate.toFixed(1) - const kdaStr = s.kdaNum >= 99 ? 'Perfect' : s.kdaNum.toFixed(2) - const rating = getRating(s.winRate, s.kdaNum) - - logger.info( - '│ %s — %s#%s — 近%d场 胜率: %s%% (%d胜%d负) | KDA: %s (%.1f/%.1f/%.1f) | %s', - floor, s.gameName, s.tagLine, - s.total, winRate, s.wins, s.total - s.wins, - kdaStr, s.avgK, s.avgD, s.avgA, rating, - ) - - chatLines.push(`${floor}: ${rating} | 胜率${winRate}% | KDA ${kdaStr}`) - } - - logger.info('└────────────────────') - - // 等待聊天室就绪后发送 - const msg = chatLines.join('\n') - const msgType = store.get('analyzeTeamPowerMsgType') || 'celebration' - for (let attempt = 0; attempt < 10; attempt++) { - try { - await lcu.sendChampSelectMessage(msg, msgType) - logger.info('队友分析已发送到聊天框 ✓') - break - } catch { - if (attempt < 9) { - await sleep(1000) - } else { - logger.warn('聊天发送失败,聊天室始终未就绪') - } - } - } + const phaseLabel = getPhaseLabel(session.timer?.phase ?? '') + await sendTeamStatsMessage(stats, phaseLabel) } catch (err) { logger.error('队友战绩分析失败:', err) } @@ -645,39 +654,29 @@ async function resendAnalyzeMessageFromCache() { const session = await lcu.getChampSelectSession() if (!session?.myTeam) return - const chatLines: string[] = ['Sona助手 ♫ 队友卡池一览(本模式战绩):\n'] + const stats: TeammateStats[] = [] for (let i = 0; i < session.myTeam.length; i++) { const player = session.myTeam[i] - const floor = `${i + 1}楼` const stat = (player.puuid ? analyzeTeamPowerStatsByPuuid.get(player.puuid) : undefined) ?? (player.summonerId ? analyzeTeamPowerStatsByPuuid.get(`sid:${player.summonerId}`) : undefined) - if (!stat || stat.winRate == null) { - chatLines.push(`${floor}: 🆕 萌新上线 (无战绩)`) - continue - } - - const winRate = stat.winRate.toFixed(1) - const kdaStr = stat.kdaNum >= 99 ? 'Perfect' : stat.kdaNum.toFixed(2) - const rating = getRating(stat.winRate, stat.kdaNum) - chatLines.push(`${floor}: ${rating} | 胜率${winRate}% | KDA ${kdaStr}`) + stats.push(stat ?? { + floor: i + 1, + summonerId: player.summonerId, + puuid: player.puuid, + gameName: player.gameName, + tagLine: player.tagLine, + winRate: null, + wins: 0, + total: 0, + avgK: 0, + avgD: 0, + avgA: 0, + kdaNum: 0, + }) } - const msg = chatLines.join('\n') - const msgType = store.get('analyzeTeamPowerMsgType') || 'celebration' - for (let attempt = 0; attempt < 10; attempt++) { - try { - await lcu.sendChampSelectMessage(msg, msgType) - logger.info('队友分析已发送到聊天框 ✓') - break - } catch { - if (attempt < 9) { - await sleep(1000) - } else { - logger.warn('聊天发送失败,聊天室始终未就绪') - } - } - } + await sendTeamStatsMessage(stats, getPhaseLabel('trade_swap')) } catch (err) { logger.error('队友战绩分析失败:', err) } @@ -690,8 +689,6 @@ function onAnalyzeTeamPowerSwap(event: LCUEventMessage) { const session = event.data as ChampSelectSession if (!session) return - // 换楼时 trades 状态会变化:发起 → ACCEPTED → 消失 - // 用 trades 的序列化快照检测 const tradesSnapshot = JSON.stringify(session.trades ?? []) if (tradesSnapshot === lastAnalyzedTradesSnapshot) return From b1e4f98899aab8152d47353bfbbfdcb1ad1eedd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=90=83=E9=A6=99=E8=8F=87?= <1416774788@qq.com> Date: Fri, 15 May 2026 23:16:05 +0800 Subject: [PATCH 4/5] =?UTF-8?q?chore:=20=E7=BC=A9=E7=9F=AD=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=88=86=E9=9A=94=E7=BA=BF=E9=81=BF=E5=85=8D=E8=B7=A8?= =?UTF-8?q?=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 --- src/lib/features.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/features.ts b/src/lib/features.ts index bb5f6c1..cd33961 100644 --- a/src/lib/features.ts +++ b/src/lib/features.ts @@ -583,7 +583,7 @@ function getPhaseLabel(phase: string): string { } } -const SEPARATOR = '━━━━━━━━'.repeat(2) +const SEPARATOR = '━━━' /** 统一的队友战绩消息构造与发送 */ async function sendTeamStatsMessage(stats: TeammateStats[], phaseLabel: string) { From 8606de2a778222a77291eb88213e5e6ac9c99ac3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=90=83=E9=A6=99=E8=8F=87?= <1416774788@qq.com> Date: Sat, 16 May 2026 11:25:39 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20LCU=20=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=E7=AA=97=E5=8F=A3=E5=B0=BA=E5=AF=B8=E5=BC=82?= =?UTF-8?q?=E5=B8=B8=E9=97=AE=E9=A2=98=20(#2,=20#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - global-particle: 限制 canvas 最大尺寸 1920x1200,子窗口跳过挂载 - 新增 fix-lcu-window 功能:监听 resize 事件,检测异常尺寸自动修复 - 注册到 store / features.ts / ToolsPage 界面分组 Closes #2 Closes #15 Co-Authored-By: Claude Opus 4.7 --- src/components/pages/ToolsPage.tsx | 11 +++++ src/lib/features.ts | 3 ++ src/lib/features/fix-lcu-window.ts | 67 +++++++++++++++++++++++++++++ src/lib/features/global-particle.ts | 8 +++- src/lib/store.ts | 3 ++ 5 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/lib/features/fix-lcu-window.ts diff --git a/src/components/pages/ToolsPage.tsx b/src/components/pages/ToolsPage.tsx index 8dd3ddc..f0b819c 100644 --- a/src/components/pages/ToolsPage.tsx +++ b/src/components/pages/ToolsPage.tsx @@ -108,6 +108,7 @@ export function ToolsPage() { const [benchNoCooldown, setBenchNoCooldown] = useState(store.get('benchNoCooldown')) const [hideTFT, setHideTFT] = useState(store.get('hideTFT')) const [hideRightNavText, setHideRightNavText] = useState(store.get('hideRightNavText')) + const [fixLcuWindow, setFixLcuWindow] = useState(store.get('fixLcuWindow')) const [windowEffect, setWindowEffect] = useState(store.get('windowEffect')) const [champSelectAssist, setChampSelectAssist] = useState(store.get('champSelectAssist')) const [opggBuildRecommendation, setOpggBuildRecommendation] = useState(store.get('opggBuildRecommendation')) @@ -163,6 +164,7 @@ export function ToolsPage() { store.onChange('unlockChromas', setUnlockChromas), store.onChange('benchNoCooldown', setBenchNoCooldown), store.onChange('hideTFT', setHideTFT), + store.onChange('fixLcuWindow', setFixLcuWindow), store.onChange('windowEffect', setWindowEffect), store.onChange('champSelectAssist', setChampSelectAssist), store.onChange('opggBuildRecommendation', setOpggBuildRecommendation), @@ -644,6 +646,15 @@ export function ToolsPage() { onChange={(v) => { setHideRightNavText(v); store.set('hideRightNavText', v) }} /> + + { setFixLcuWindow(v); store.set('fixLcuWindow', v) }} + /> + { // 模式变化时,如果功能已启用,重新注册以应用新模式 if (store.get('autoReturnToLobby')) { diff --git a/src/lib/features/fix-lcu-window.ts b/src/lib/features/fix-lcu-window.ts new file mode 100644 index 0000000..ad38959 --- /dev/null +++ b/src/lib/features/fix-lcu-window.ts @@ -0,0 +1,67 @@ +import { logger } from '@/index' + +// ==================== 客户端窗口异常修复 ==================== + +let fixLcuWindowCleanup: (() => void) | null = null + +/** LCU 客户端标准尺寸范围 */ +const MIN_WIDTH = 1024 +const MIN_HEIGHT = 576 +const MAX_WIDTH = 1920 +const MAX_HEIGHT = 1200 + +function clampWindowSize() { + const body = document.body + if (!body) return + + const iw = window.innerWidth + const ih = window.innerHeight + const needsClamp = + iw > MAX_WIDTH || + ih > MAX_HEIGHT || + (iw > 0 && iw < MIN_WIDTH) || + (ih > 0 && ih < MIN_HEIGHT) + + if (needsClamp) { + body.style.maxWidth = `${MAX_WIDTH}px` + body.style.maxHeight = `${MAX_HEIGHT}px` + body.style.minWidth = `${MIN_WIDTH}px` + body.style.minHeight = `${MIN_HEIGHT}px` + body.style.overflow = 'auto' + } else { + body.style.maxWidth = '' + body.style.maxHeight = '' + body.style.minWidth = '' + body.style.minHeight = '' + body.style.overflow = '' + } +} + +export function updateFixLcuWindow(enabled: boolean) { + if (enabled && !fixLcuWindowCleanup) { + let debounceTimer: number + const handler = () => { + window.clearTimeout(debounceTimer) + debounceTimer = window.setTimeout(clampWindowSize, 100) + } + window.addEventListener('resize', handler) + clampWindowSize() + fixLcuWindowCleanup = () => { + window.removeEventListener('resize', handler) + window.clearTimeout(debounceTimer) + const b = document.body + if (b) { + b.style.maxWidth = '' + b.style.maxHeight = '' + b.style.minWidth = '' + b.style.minHeight = '' + b.style.overflow = '' + } + } + logger.info('Fix LCU window enabled ✓') + } else if (!enabled && fixLcuWindowCleanup) { + fixLcuWindowCleanup() + fixLcuWindowCleanup = null + logger.info('Fix LCU window disabled') + } +} diff --git a/src/lib/features/global-particle.ts b/src/lib/features/global-particle.ts index efcd79f..330a3f0 100644 --- a/src/lib/features/global-particle.ts +++ b/src/lib/features/global-particle.ts @@ -19,6 +19,9 @@ function getGlobalParticleHost(): HTMLElement | null { * 必须等待客户端主视图宿主就绪后才挂载,避免首启时被 loading/iframe 层遮挡 */ function tryInjectGlobalParticle(): boolean { + // 只在 LCU 主窗口挂载粒子特效,跳过子窗口(如 Wegame 对局助手、天赋选择弹窗) + if (window.innerWidth < 800 || window.innerHeight < 550) return true + const host = getGlobalParticleHost() if (!host) return false @@ -49,8 +52,9 @@ function tryInjectGlobalParticle(): boolean { }> = [] const resize = () => { - canvas.width = window.innerWidth - canvas.height = window.innerHeight + // 限制 canvas 最大尺寸,防止 CEF 子窗口(如 Wegame 对局助手)被异常撑大 + canvas.width = Math.min(window.innerWidth, 1920) + canvas.height = Math.min(window.innerHeight, 1200) } const initParticles = () => { diff --git a/src/lib/store.ts b/src/lib/store.ts index e1552c8..881ae3d 100644 --- a/src/lib/store.ts +++ b/src/lib/store.ts @@ -138,6 +138,8 @@ export interface SonaConfig { autoReturnToLobby: boolean /** 自动返回模式: queue=自动排队, lobby=仅返回房间 */ autoReturnMode: string + /** 修复客户端窗口异常(最小化恢复或子窗口尺寸异常时自动校正) */ + fixLcuWindow: boolean } @@ -192,6 +194,7 @@ const DEFAULT_CONFIG: SonaConfig = { gameAnalysisPopup: false, autoReturnToLobby: false, autoReturnMode: 'queue', + fixLcuWindow: false, }