From 269b2ec5dd0b2e736425c688fd423c84d54e2823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E4=B8=80=E4=B8=81?= <1769123563@qq.com> Date: Wed, 19 Nov 2025 20:12:37 +0800 Subject: [PATCH] Optimize Log Display Logic --- templates/index.html | 128 ++++++++++++++++++++++++++++++------------- 1 file changed, 90 insertions(+), 38 deletions(-) diff --git a/templates/index.html b/templates/index.html index 397f033..3049f05 100644 --- a/templates/index.html +++ b/templates/index.html @@ -317,26 +317,35 @@ /* 控制台输出 */ .console-output { flex: 1; - padding: 15px; background-color: #000000; color: #00ff00; font-family: 'Courier New', monospace; font-size: 12px; - overflow-y: auto; - overflow-x: hidden; + overflow: hidden; /* 【图层优化】容器本身不滚动,由console-layer滚动 */ white-space: pre-wrap; word-break: break-all; min-height: 0; /* 允许内容缩小 */ + position: relative; /* 【图层优化】作为定位上下文,让console-layer相对于此容器定位 */ } .console-layer { - display: none; + visibility: hidden; /* 使用visibility替代display,避免重排 */ + position: absolute; /* 相对于.console-output绝对定位 */ + top: 0; + left: 0; width: 100%; - min-height: 100%; + height: 100%; /* 填满整个黑色框 */ + padding: 15px; /* 图层内边距 */ + overflow-y: auto; /* 允许独立滚动 */ + overflow-x: hidden; + pointer-events: none; /* 隐藏层不响应交互 */ + box-sizing: border-box; /* 包含padding在width/height内 */ } .console-layer.active { - display: block; + visibility: visible; /* 显示活动层 */ + pointer-events: auto; /* 活动层响应交互 */ + z-index: 1; /* 置顶显示 */ } .console-line { @@ -1363,7 +1372,8 @@ class LogVirtualList { constructor(container) { this.container = container; - this.scrollElement = document.getElementById('consoleOutput') || container; + // 【图层优化】scrollElement 就是 container(.console-layer),因为每个图层独立滚动 + this.scrollElement = container; this.lines = []; this.pending = []; this.pool = []; @@ -1400,6 +1410,10 @@ this.renderTime = 0; // 渲染耗时 this.lastRenderLineCount = 0; // 上次渲染的行数 + // 【图层优化】窗口激活状态管理 + this.isActive = false; // 当前窗口是否为活动窗口 + this.needsRender = false; // 非活动窗口是否有待渲染内容 + this.attachScroll(); } @@ -1585,6 +1599,24 @@ if (px > 0) this.lineHeight = px; } + /** + * 【图层优化】设置窗口激活状态 + * @param {boolean} active - 是否为活动窗口 + */ + setActive(active) { + this.isActive = active; + if (active && this.needsRender) { + // 窗口激活时,如果有待渲染内容,异步渲染 + requestIdleCallback(() => { + if (this.pending.length > 0) { + this.flush(); + } + this.scheduleRender(true); + this.needsRender = false; + }, { timeout: 50 }); + } + } + append(text, className = 'console-line') { // 在添加内容前检查是否在底部,如果是则标记需要滚动 if (this.autoScrollEnabled && this.isNearBottom()) { @@ -1598,7 +1630,18 @@ this.pendingHighWaterMark = this.pending.length; } - // 【优化】智能批处理策略 + // 【图层优化】非活动窗口处理策略 + if (!this.isActive) { + // 非活动窗口:只累积数据,不触发渲染 + // 设置队列上限,防止内存溢出 + if (this.pending.length >= 1000) { + this.flush(); // 定期flush避免内存溢出 + this.needsRender = true; // 标记需要渲染 + } + return; // 跳过后续渲染逻辑 + } + + // 【优化】活动窗口:智能批处理策略 const now = Date.now(); const timeSinceLastFlush = now - this.lastFlushTime; @@ -1691,6 +1734,16 @@ if (!this.container) return; if (!force && this.rafId) return; + // 【图层优化】检查窗口是否可见 + if (!force && !this.isActive) { + // 非活动窗口:只flush数据,不渲染DOM + if (this.pending.length > 0) { + this.flush(); // 保存数据到lines + this.needsRender = true; // 标记需要渲染 + } + return; // 跳过DOM操作 + } + // 取消之前的请求 if (this.rafId) { cancelAnimationFrame(this.rafId); @@ -2698,17 +2751,16 @@ // 更新当前应用 currentApp = app; - // 切换控制台层(不添加系统提示,避免频繁输出) + // 【图层优化】切换控制台层(纯CSS图层切换,瞬间完成) setActiveConsoleLayer(app); // 更新嵌入页面(右侧内容区域) updateEmbeddedPage(app); - // 加载对应的控制台输出(只在必要时加载) - if (app === 'forum') { - loadForumLog(); - } else if (app === 'report') { - loadReportLog(); + // 【图层优化】移除重复加载逻辑 + // 日志数据已通过Socket.IO/SSE实时同步,无需重新加载 + // 仅保留特殊页面的初始化逻辑 + if (app === 'report') { // 只在报告界面未初始化时才重新加载 const reportContent = document.getElementById('reportContent'); if (!reportContent || reportContent.children.length === 0) { @@ -2718,8 +2770,6 @@ setTimeout(() => { checkReportLockStatus(); }, 500); - } else { - loadConsoleOutput(app); } } @@ -2806,16 +2856,19 @@ layer.dataset.app = app; if (app === currentApp) { layer.classList.add('active'); - layer.style.display = 'block'; activeConsoleLayer = app; - } else { - layer.style.display = 'none'; } + // 【图层优化】不再设置style.display,完全由CSS类控制 container.appendChild(layer); consoleLayers[app] = layer; logRenderers[app] = new LogVirtualList(layer); + // 【图层优化】标记活动窗口 + if (app === currentApp) { + logRenderers[app].isActive = true; + } + // 【FIX Bug #3】初始提示立即渲染,避免黑屏 logRenderers[app].clear(`[系统] ${appNames[app] || app} 日志就绪`); logRenderers[app].render(); // 立即同步渲染 @@ -2835,7 +2888,7 @@ const layer = document.createElement('div'); layer.className = 'console-layer'; layer.dataset.app = app; - layer.style.display = app === currentApp ? 'block' : 'none'; + // 【图层优化】不再设置style.display,完全由CSS类控制 if (app === currentApp) { layer.classList.add('active'); activeConsoleLayer = app; @@ -2844,6 +2897,12 @@ container.appendChild(layer); consoleLayers[app] = layer; logRenderers[app] = new LogVirtualList(layer); + + // 【图层优化】标记活动窗口 + if (app === currentApp) { + logRenderers[app].isActive = true; + } + return layer; } @@ -2852,43 +2911,36 @@ if (!container) return; // 如果已经是当前激活的层,跳过 - if (activeConsoleLayer === app && consoleLayers[app] && consoleLayers[app].style.display === 'block') { + if (activeConsoleLayer === app && consoleLayers[app] && consoleLayers[app].classList.contains('active')) { return; } - // 隐藏旧的激活层 + // 【图层优化】标记旧窗口为非活动 if (activeConsoleLayer && consoleLayers[activeConsoleLayer]) { consoleLayers[activeConsoleLayer].classList.remove('active'); - consoleLayers[activeConsoleLayer].style.display = 'none'; + if (logRenderers[activeConsoleLayer]) { + logRenderers[activeConsoleLayer].setActive(false); + } } // 获取或创建目标层 const targetLayer = getConsoleLayer(app); if (!targetLayer) return; - // 显示新的激活层 - targetLayer.style.display = 'block'; + // 【图层优化】显示新的激活层(纯CSS类切换,不修改style.display) targetLayer.classList.add('active'); activeConsoleLayer = app; - // 触发一次渲染以确保内容正确显示 + // 【图层优化】标记新窗口为活动,触发异步渲染 const renderer = logRenderers[app]; if (renderer) { - // 【FIX Bug #1/#3】如果已有数据,立即同步渲染,避免黑屏 - if (renderer.lines.length > 0 || renderer.pending.length > 0) { - renderer.flush(); // 先将pending数据合并到lines - renderer.render(); // 立即同步渲染,不使用异步 - } else { - // 如果没有数据,显示加载提示(同步) - renderer.clear(`[系统] 正在加载 ${appNames[app] || app} 日志...`); - renderer.render(); // 立即渲染加载提示 - } + renderer.setActive(true); // 会在内部异步渲染待处理内容 + // 确保滚动到底部 if (renderer.autoScrollEnabled) { - // 使用setTimeout确保DOM更新后再滚动 - setTimeout(() => { + requestAnimationFrame(() => { renderer.scrollToBottom(); - }, 0); + }); } } }