mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-11 10:00:04 +08:00
fix: 修复内存泄漏和添加保存按钮加载状态
- router.js: 防止 hashchange 监听器重复绑定 - sidebar.js: 用事件委托替代每次重新绑定事件 - logs.js: 搜索定时器提升为模块级变量,添加 cleanup 导出 - models/agents/gateway/mcp: 保存按钮添加 disabled + 加载文本
This commit is contained in:
@@ -43,6 +43,8 @@ const ICONS = {
|
||||
deploy: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
|
||||
}
|
||||
|
||||
let _delegated = false
|
||||
|
||||
export function renderSidebar(el) {
|
||||
const current = getCurrentRoute()
|
||||
|
||||
@@ -86,14 +88,22 @@ export function renderSidebar(el) {
|
||||
|
||||
el.innerHTML = html
|
||||
|
||||
// 绑定导航点击事件
|
||||
el.querySelectorAll('.nav-item[data-route]').forEach(item => {
|
||||
item.onclick = () => navigate(item.dataset.route)
|
||||
})
|
||||
|
||||
// 主题切换
|
||||
el.querySelector('#btn-theme-toggle')?.addEventListener('click', () => {
|
||||
toggleTheme()
|
||||
renderSidebar(el)
|
||||
})
|
||||
// 事件委托:只绑定一次,避免重复绑定
|
||||
if (!_delegated) {
|
||||
_delegated = true
|
||||
el.addEventListener('click', (e) => {
|
||||
// 导航点击
|
||||
const navItem = e.target.closest('.nav-item[data-route]')
|
||||
if (navItem) {
|
||||
navigate(navItem.dataset.route)
|
||||
return
|
||||
}
|
||||
// 主题切换
|
||||
const themeBtn = e.target.closest('#btn-theme-toggle')
|
||||
if (themeBtn) {
|
||||
toggleTheme()
|
||||
renderSidebar(el)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,17 @@ export async function render() {
|
||||
const state = { config: null }
|
||||
await loadConfig(page, state)
|
||||
|
||||
page.querySelector('#btn-save-agent').onclick = () => saveConfig(page, state)
|
||||
page.querySelector('#btn-save-agent').onclick = async () => {
|
||||
const btn = page.querySelector('#btn-save-agent')
|
||||
btn.disabled = true
|
||||
btn.textContent = '保存中...'
|
||||
try {
|
||||
await saveConfig(page, state)
|
||||
} finally {
|
||||
btn.disabled = false
|
||||
btn.textContent = '保存配置'
|
||||
}
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,17 @@ export async function render() {
|
||||
|
||||
const state = { config: null }
|
||||
await loadConfig(page, state)
|
||||
page.querySelector('#btn-save-gw').onclick = () => saveConfig(page, state)
|
||||
page.querySelector('#btn-save-gw').onclick = async () => {
|
||||
const btn = page.querySelector('#btn-save-gw')
|
||||
btn.disabled = true
|
||||
btn.textContent = '保存中...'
|
||||
try {
|
||||
await saveConfig(page, state)
|
||||
} finally {
|
||||
btn.disabled = false
|
||||
btn.textContent = '保存配置'
|
||||
}
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ const LOG_TABS = [
|
||||
{ key: 'config-audit', label: '审计日志' },
|
||||
]
|
||||
|
||||
let _searchTimer = null
|
||||
|
||||
export async function render() {
|
||||
const page = document.createElement('div')
|
||||
page.className = 'page'
|
||||
@@ -48,10 +50,9 @@ export async function render() {
|
||||
})
|
||||
|
||||
// 搜索
|
||||
let searchTimer = null
|
||||
page.querySelector('#log-search').addEventListener('input', (e) => {
|
||||
clearTimeout(searchTimer)
|
||||
searchTimer = setTimeout(() => {
|
||||
clearTimeout(_searchTimer)
|
||||
_searchTimer = setTimeout(() => {
|
||||
if (e.target.value.trim()) {
|
||||
searchLog(page, currentTab, e.target.value.trim())
|
||||
} else {
|
||||
@@ -67,6 +68,11 @@ export async function render() {
|
||||
return page
|
||||
}
|
||||
|
||||
export function cleanup() {
|
||||
clearTimeout(_searchTimer)
|
||||
_searchTimer = null
|
||||
}
|
||||
|
||||
async function loadLog(page, logName) {
|
||||
const el = page.querySelector('#log-content')
|
||||
el.innerHTML = '<div style="color:var(--text-tertiary)">加载中...</div>'
|
||||
|
||||
@@ -24,7 +24,17 @@ export async function render() {
|
||||
const state = { config: null }
|
||||
await loadConfig(page, state)
|
||||
|
||||
page.querySelector('#btn-save-mcp').onclick = () => saveConfig(state)
|
||||
page.querySelector('#btn-save-mcp').onclick = async () => {
|
||||
const btn = page.querySelector('#btn-save-mcp')
|
||||
btn.disabled = true
|
||||
btn.textContent = '保存中...'
|
||||
try {
|
||||
await saveConfig(state)
|
||||
} finally {
|
||||
btn.disabled = false
|
||||
btn.textContent = '保存配置'
|
||||
}
|
||||
}
|
||||
page.querySelector('#btn-add-mcp').onclick = () => addServer(page, state)
|
||||
return page
|
||||
}
|
||||
|
||||
@@ -24,7 +24,17 @@ export async function render() {
|
||||
const state = { config: null }
|
||||
await loadConfig(page, state)
|
||||
|
||||
page.querySelector('#btn-save-models').onclick = () => saveConfig(state)
|
||||
page.querySelector('#btn-save-models').onclick = async () => {
|
||||
const btn = page.querySelector('#btn-save-models')
|
||||
btn.disabled = true
|
||||
btn.textContent = '保存中...'
|
||||
try {
|
||||
await saveConfig(state)
|
||||
} finally {
|
||||
btn.disabled = false
|
||||
btn.textContent = '保存配置'
|
||||
}
|
||||
}
|
||||
page.querySelector('#btn-add-provider').onclick = () => addProvider(page, state)
|
||||
|
||||
return page
|
||||
|
||||
@@ -5,6 +5,7 @@ const routes = {}
|
||||
let _contentEl = null
|
||||
let _loadId = 0
|
||||
let _currentCleanup = null
|
||||
let _initialized = false
|
||||
|
||||
export function registerRoute(path, loader) {
|
||||
routes[path] = loader
|
||||
@@ -16,7 +17,10 @@ export function navigate(path) {
|
||||
|
||||
export function initRouter(contentEl) {
|
||||
_contentEl = contentEl
|
||||
window.addEventListener('hashchange', () => loadRoute())
|
||||
if (!_initialized) {
|
||||
window.addEventListener('hashchange', () => loadRoute())
|
||||
_initialized = true
|
||||
}
|
||||
loadRoute()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user