From 91c33f78a4c384c69df5e4ebc3766a5282208ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=B4=E5=A4=A9?= Date: Thu, 26 Feb 2026 23:23:11 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=B7=AF=E7=94=B1?= =?UTF-8?q?=E7=AB=9E=E6=80=81=E3=80=81=E5=88=A0=E9=99=A4=E7=A1=AE=E8=AE=A4?= =?UTF-8?q?=E3=80=81=E8=BE=93=E5=85=A5=E5=90=8C=E6=AD=A5=E7=AD=89=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - router.js 添加竞态防护和页面清理钩子 - logs.js 切换 Tab 时清空搜索框 - models.js 删除 Provider 添加确认提示,输入框改 oninput 实时同步 - mcp.js 删除 Server 添加确认提示 - gateway.js Tailscale 地址为空时保留原配置 --- src/pages/gateway.js | 2 +- src/pages/logs.js | 1 + src/pages/mcp.js | 1 + src/pages/models.js | 5 +++-- src/router.js | 21 ++++++++++++++++++++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/pages/gateway.js b/src/pages/gateway.js index 4c6adf2..b477abe 100644 --- a/src/pages/gateway.js +++ b/src/pages/gateway.js @@ -107,7 +107,7 @@ async function saveConfig(page, state) { state.config.gateway = { ...state.config.gateway, port, bind, mode, authToken, - tailscale: tailscaleAddr ? { address: tailscaleAddr } : undefined, + tailscale: tailscaleAddr ? { address: tailscaleAddr } : (state.config.gateway?.tailscale || undefined), } try { diff --git a/src/pages/logs.js b/src/pages/logs.js index fe99bba..5105ee4 100644 --- a/src/pages/logs.js +++ b/src/pages/logs.js @@ -42,6 +42,7 @@ export async function render() { page.querySelectorAll('.tab').forEach(t => t.classList.remove('active')) tab.classList.add('active') currentTab = tab.dataset.tab + page.querySelector('#log-search').value = '' loadLog(page, currentTab) } }) diff --git a/src/pages/mcp.js b/src/pages/mcp.js index 661c1ce..ba5cfb9 100644 --- a/src/pages/mcp.js +++ b/src/pages/mcp.js @@ -76,6 +76,7 @@ function renderServers(page, state) { const action = btn.dataset.action if (action === 'delete') { + if (!confirm(`确定删除 MCP Server "${key}"?`)) return if (state.config.mcpServers) delete state.config.mcpServers[key] else delete state.config[key] renderServers(page, state) diff --git a/src/pages/models.js b/src/pages/models.js index 5e0dfd7..93fa1aa 100644 --- a/src/pages/models.js +++ b/src/pages/models.js @@ -102,6 +102,7 @@ function renderProviders(page, state) { const models = section.querySelector('.provider-models') models.style.display = models.style.display === 'none' ? 'block' : 'none' } else if (action === 'delete-provider') { + if (!confirm(`确定删除 Provider "${providerKey}"?`)) return delete state.config.models.providers[providerKey] renderProviders(page, state) toast(`已删除 ${providerKey}`, 'info') @@ -124,9 +125,9 @@ function renderProviders(page, state) { } }) - // 输入框变更同步到 state + // 输入框变更实时同步到 state listEl.querySelectorAll('[data-field]').forEach(input => { - input.onchange = () => { + input.oninput = () => { const providerKey = input.closest('[data-provider]').dataset.provider state.config.models.providers[providerKey][input.dataset.field] = input.value } diff --git a/src/router.js b/src/router.js index 06ff7ee..af34f2d 100644 --- a/src/router.js +++ b/src/router.js @@ -3,6 +3,8 @@ */ const routes = {} let _contentEl = null +let _loadId = 0 +let _currentCleanup = null export function registerRoute(path, loader) { routes[path] = loader @@ -23,16 +25,33 @@ async function loadRoute() { const loader = routes[hash] if (!loader || !_contentEl) return + // 竞态防护:记录本次加载 ID + const thisLoad = ++_loadId + + // 清理上一个页面 + if (_currentCleanup) { + try { _currentCleanup() } catch (_) {} + _currentCleanup = null + } + _contentEl.innerHTML = '' const mod = await loader() - // 动态 import 返回模块对象,调用 render() 获取页面元素 + + // 如果加载期间路由又变了,丢弃本次结果 + if (thisLoad !== _loadId) return + const page = mod.render ? await mod.render() : mod.default ? await mod.default() : mod + if (thisLoad !== _loadId) return + if (typeof page === 'string') { _contentEl.innerHTML = page } else if (page instanceof HTMLElement) { _contentEl.appendChild(page) } + // 保存页面清理函数 + _currentCleanup = mod.cleanup || null + // 更新侧边栏激活状态 document.querySelectorAll('.nav-item').forEach(item => { item.classList.toggle('active', item.dataset.route === hash)