马一丁

Optimize Log Display Logic

... ... @@ -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);
});
}
}
}
... ...