马一丁

Optimize Log Display Logic

@@ -317,26 +317,35 @@ @@ -317,26 +317,35 @@
317 /* 控制台输出 */ 317 /* 控制台输出 */
318 .console-output { 318 .console-output {
319 flex: 1; 319 flex: 1;
320 - padding: 15px;  
321 background-color: #000000; 320 background-color: #000000;
322 color: #00ff00; 321 color: #00ff00;
323 font-family: 'Courier New', monospace; 322 font-family: 'Courier New', monospace;
324 font-size: 12px; 323 font-size: 12px;
325 - overflow-y: auto;  
326 - overflow-x: hidden; 324 + overflow: hidden; /* 【图层优化】容器本身不滚动,由console-layer滚动 */
327 white-space: pre-wrap; 325 white-space: pre-wrap;
328 word-break: break-all; 326 word-break: break-all;
329 min-height: 0; /* 允许内容缩小 */ 327 min-height: 0; /* 允许内容缩小 */
  328 + position: relative; /* 【图层优化】作为定位上下文,让console-layer相对于此容器定位 */
330 } 329 }
331 330
332 .console-layer { 331 .console-layer {
333 - display: none; 332 + visibility: hidden; /* 使用visibility替代display,避免重排 */
  333 + position: absolute; /* 相对于.console-output绝对定位 */
  334 + top: 0;
  335 + left: 0;
334 width: 100%; 336 width: 100%;
335 - min-height: 100%; 337 + height: 100%; /* 填满整个黑色框 */
  338 + padding: 15px; /* 图层内边距 */
  339 + overflow-y: auto; /* 允许独立滚动 */
  340 + overflow-x: hidden;
  341 + pointer-events: none; /* 隐藏层不响应交互 */
  342 + box-sizing: border-box; /* 包含padding在width/height内 */
336 } 343 }
337 344
338 .console-layer.active { 345 .console-layer.active {
339 - display: block; 346 + visibility: visible; /* 显示活动层 */
  347 + pointer-events: auto; /* 活动层响应交互 */
  348 + z-index: 1; /* 置顶显示 */
340 } 349 }
341 350
342 .console-line { 351 .console-line {
@@ -1363,7 +1372,8 @@ @@ -1363,7 +1372,8 @@
1363 class LogVirtualList { 1372 class LogVirtualList {
1364 constructor(container) { 1373 constructor(container) {
1365 this.container = container; 1374 this.container = container;
1366 - this.scrollElement = document.getElementById('consoleOutput') || container; 1375 + // 【图层优化】scrollElement 就是 container(.console-layer),因为每个图层独立滚动
  1376 + this.scrollElement = container;
1367 this.lines = []; 1377 this.lines = [];
1368 this.pending = []; 1378 this.pending = [];
1369 this.pool = []; 1379 this.pool = [];
@@ -1400,6 +1410,10 @@ @@ -1400,6 +1410,10 @@
1400 this.renderTime = 0; // 渲染耗时 1410 this.renderTime = 0; // 渲染耗时
1401 this.lastRenderLineCount = 0; // 上次渲染的行数 1411 this.lastRenderLineCount = 0; // 上次渲染的行数
1402 1412
  1413 + // 【图层优化】窗口激活状态管理
  1414 + this.isActive = false; // 当前窗口是否为活动窗口
  1415 + this.needsRender = false; // 非活动窗口是否有待渲染内容
  1416 +
1403 this.attachScroll(); 1417 this.attachScroll();
1404 } 1418 }
1405 1419
@@ -1585,6 +1599,24 @@ @@ -1585,6 +1599,24 @@
1585 if (px > 0) this.lineHeight = px; 1599 if (px > 0) this.lineHeight = px;
1586 } 1600 }
1587 1601
  1602 + /**
  1603 + * 【图层优化】设置窗口激活状态
  1604 + * @param {boolean} active - 是否为活动窗口
  1605 + */
  1606 + setActive(active) {
  1607 + this.isActive = active;
  1608 + if (active && this.needsRender) {
  1609 + // 窗口激活时,如果有待渲染内容,异步渲染
  1610 + requestIdleCallback(() => {
  1611 + if (this.pending.length > 0) {
  1612 + this.flush();
  1613 + }
  1614 + this.scheduleRender(true);
  1615 + this.needsRender = false;
  1616 + }, { timeout: 50 });
  1617 + }
  1618 + }
  1619 +
1588 append(text, className = 'console-line') { 1620 append(text, className = 'console-line') {
1589 // 在添加内容前检查是否在底部,如果是则标记需要滚动 1621 // 在添加内容前检查是否在底部,如果是则标记需要滚动
1590 if (this.autoScrollEnabled && this.isNearBottom()) { 1622 if (this.autoScrollEnabled && this.isNearBottom()) {
@@ -1598,7 +1630,18 @@ @@ -1598,7 +1630,18 @@
1598 this.pendingHighWaterMark = this.pending.length; 1630 this.pendingHighWaterMark = this.pending.length;
1599 } 1631 }
1600 1632
1601 - // 【优化】智能批处理策略 1633 + // 【图层优化】非活动窗口处理策略
  1634 + if (!this.isActive) {
  1635 + // 非活动窗口:只累积数据,不触发渲染
  1636 + // 设置队列上限,防止内存溢出
  1637 + if (this.pending.length >= 1000) {
  1638 + this.flush(); // 定期flush避免内存溢出
  1639 + this.needsRender = true; // 标记需要渲染
  1640 + }
  1641 + return; // 跳过后续渲染逻辑
  1642 + }
  1643 +
  1644 + // 【优化】活动窗口:智能批处理策略
1602 const now = Date.now(); 1645 const now = Date.now();
1603 const timeSinceLastFlush = now - this.lastFlushTime; 1646 const timeSinceLastFlush = now - this.lastFlushTime;
1604 1647
@@ -1691,6 +1734,16 @@ @@ -1691,6 +1734,16 @@
1691 if (!this.container) return; 1734 if (!this.container) return;
1692 if (!force && this.rafId) return; 1735 if (!force && this.rafId) return;
1693 1736
  1737 + // 【图层优化】检查窗口是否可见
  1738 + if (!force && !this.isActive) {
  1739 + // 非活动窗口:只flush数据,不渲染DOM
  1740 + if (this.pending.length > 0) {
  1741 + this.flush(); // 保存数据到lines
  1742 + this.needsRender = true; // 标记需要渲染
  1743 + }
  1744 + return; // 跳过DOM操作
  1745 + }
  1746 +
1694 // 取消之前的请求 1747 // 取消之前的请求
1695 if (this.rafId) { 1748 if (this.rafId) {
1696 cancelAnimationFrame(this.rafId); 1749 cancelAnimationFrame(this.rafId);
@@ -2698,17 +2751,16 @@ @@ -2698,17 +2751,16 @@
2698 // 更新当前应用 2751 // 更新当前应用
2699 currentApp = app; 2752 currentApp = app;
2700 2753
2701 - // 切换控制台层(不添加系统提示,避免频繁输出 2754 + // 【图层优化】切换控制台层(纯CSS图层切换,瞬间完成
2702 setActiveConsoleLayer(app); 2755 setActiveConsoleLayer(app);
2703 2756
2704 // 更新嵌入页面(右侧内容区域) 2757 // 更新嵌入页面(右侧内容区域)
2705 updateEmbeddedPage(app); 2758 updateEmbeddedPage(app);
2706 2759
2707 - // 加载对应的控制台输出(只在必要时加载)  
2708 - if (app === 'forum') {  
2709 - loadForumLog();  
2710 - } else if (app === 'report') {  
2711 - loadReportLog(); 2760 + // 【图层优化】移除重复加载逻辑
  2761 + // 日志数据已通过Socket.IO/SSE实时同步,无需重新加载
  2762 + // 仅保留特殊页面的初始化逻辑
  2763 + if (app === 'report') {
2712 // 只在报告界面未初始化时才重新加载 2764 // 只在报告界面未初始化时才重新加载
2713 const reportContent = document.getElementById('reportContent'); 2765 const reportContent = document.getElementById('reportContent');
2714 if (!reportContent || reportContent.children.length === 0) { 2766 if (!reportContent || reportContent.children.length === 0) {
@@ -2718,8 +2770,6 @@ @@ -2718,8 +2770,6 @@
2718 setTimeout(() => { 2770 setTimeout(() => {
2719 checkReportLockStatus(); 2771 checkReportLockStatus();
2720 }, 500); 2772 }, 500);
2721 - } else {  
2722 - loadConsoleOutput(app);  
2723 } 2773 }
2724 } 2774 }
2725 2775
@@ -2806,16 +2856,19 @@ @@ -2806,16 +2856,19 @@
2806 layer.dataset.app = app; 2856 layer.dataset.app = app;
2807 if (app === currentApp) { 2857 if (app === currentApp) {
2808 layer.classList.add('active'); 2858 layer.classList.add('active');
2809 - layer.style.display = 'block';  
2810 activeConsoleLayer = app; 2859 activeConsoleLayer = app;
2811 - } else {  
2812 - layer.style.display = 'none';  
2813 } 2860 }
  2861 + // 【图层优化】不再设置style.display,完全由CSS类控制
2814 2862
2815 container.appendChild(layer); 2863 container.appendChild(layer);
2816 consoleLayers[app] = layer; 2864 consoleLayers[app] = layer;
2817 logRenderers[app] = new LogVirtualList(layer); 2865 logRenderers[app] = new LogVirtualList(layer);
2818 2866
  2867 + // 【图层优化】标记活动窗口
  2868 + if (app === currentApp) {
  2869 + logRenderers[app].isActive = true;
  2870 + }
  2871 +
2819 // 【FIX Bug #3】初始提示立即渲染,避免黑屏 2872 // 【FIX Bug #3】初始提示立即渲染,避免黑屏
2820 logRenderers[app].clear(`[系统] ${appNames[app] || app} 日志就绪`); 2873 logRenderers[app].clear(`[系统] ${appNames[app] || app} 日志就绪`);
2821 logRenderers[app].render(); // 立即同步渲染 2874 logRenderers[app].render(); // 立即同步渲染
@@ -2835,7 +2888,7 @@ @@ -2835,7 +2888,7 @@
2835 const layer = document.createElement('div'); 2888 const layer = document.createElement('div');
2836 layer.className = 'console-layer'; 2889 layer.className = 'console-layer';
2837 layer.dataset.app = app; 2890 layer.dataset.app = app;
2838 - layer.style.display = app === currentApp ? 'block' : 'none'; 2891 + // 【图层优化】不再设置style.display,完全由CSS类控制
2839 if (app === currentApp) { 2892 if (app === currentApp) {
2840 layer.classList.add('active'); 2893 layer.classList.add('active');
2841 activeConsoleLayer = app; 2894 activeConsoleLayer = app;
@@ -2844,6 +2897,12 @@ @@ -2844,6 +2897,12 @@
2844 container.appendChild(layer); 2897 container.appendChild(layer);
2845 consoleLayers[app] = layer; 2898 consoleLayers[app] = layer;
2846 logRenderers[app] = new LogVirtualList(layer); 2899 logRenderers[app] = new LogVirtualList(layer);
  2900 +
  2901 + // 【图层优化】标记活动窗口
  2902 + if (app === currentApp) {
  2903 + logRenderers[app].isActive = true;
  2904 + }
  2905 +
2847 return layer; 2906 return layer;
2848 } 2907 }
2849 2908
@@ -2852,43 +2911,36 @@ @@ -2852,43 +2911,36 @@
2852 if (!container) return; 2911 if (!container) return;
2853 2912
2854 // 如果已经是当前激活的层,跳过 2913 // 如果已经是当前激活的层,跳过
2855 - if (activeConsoleLayer === app && consoleLayers[app] && consoleLayers[app].style.display === 'block') { 2914 + if (activeConsoleLayer === app && consoleLayers[app] && consoleLayers[app].classList.contains('active')) {
2856 return; 2915 return;
2857 } 2916 }
2858 2917
2859 - // 隐藏旧的激活层 2918 + // 【图层优化】标记旧窗口为非活动
2860 if (activeConsoleLayer && consoleLayers[activeConsoleLayer]) { 2919 if (activeConsoleLayer && consoleLayers[activeConsoleLayer]) {
2861 consoleLayers[activeConsoleLayer].classList.remove('active'); 2920 consoleLayers[activeConsoleLayer].classList.remove('active');
2862 - consoleLayers[activeConsoleLayer].style.display = 'none'; 2921 + if (logRenderers[activeConsoleLayer]) {
  2922 + logRenderers[activeConsoleLayer].setActive(false);
  2923 + }
2863 } 2924 }
2864 2925
2865 // 获取或创建目标层 2926 // 获取或创建目标层
2866 const targetLayer = getConsoleLayer(app); 2927 const targetLayer = getConsoleLayer(app);
2867 if (!targetLayer) return; 2928 if (!targetLayer) return;
2868 2929
2869 - // 显示新的激活层  
2870 - targetLayer.style.display = 'block'; 2930 + // 【图层优化】显示新的激活层(纯CSS类切换,不修改style.display)
2871 targetLayer.classList.add('active'); 2931 targetLayer.classList.add('active');
2872 activeConsoleLayer = app; 2932 activeConsoleLayer = app;
2873 2933
2874 - // 触发一次渲染以确保内容正确显示 2934 + // 【图层优化】标记新窗口为活动,触发异步渲染
2875 const renderer = logRenderers[app]; 2935 const renderer = logRenderers[app];
2876 if (renderer) { 2936 if (renderer) {
2877 - // 【FIX Bug #1/#3】如果已有数据,立即同步渲染,避免黑屏  
2878 - if (renderer.lines.length > 0 || renderer.pending.length > 0) {  
2879 - renderer.flush(); // 先将pending数据合并到lines  
2880 - renderer.render(); // 立即同步渲染,不使用异步  
2881 - } else {  
2882 - // 如果没有数据,显示加载提示(同步)  
2883 - renderer.clear(`[系统] 正在加载 ${appNames[app] || app} 日志...`);  
2884 - renderer.render(); // 立即渲染加载提示  
2885 - } 2937 + renderer.setActive(true); // 会在内部异步渲染待处理内容
  2938 +
2886 // 确保滚动到底部 2939 // 确保滚动到底部
2887 if (renderer.autoScrollEnabled) { 2940 if (renderer.autoScrollEnabled) {
2888 - // 使用setTimeout确保DOM更新后再滚动  
2889 - setTimeout(() => { 2941 + requestAnimationFrame(() => {
2890 renderer.scrollToBottom(); 2942 renderer.scrollToBottom();
2891 - }, 0); 2943 + });
2892 } 2944 }
2893 } 2945 }
2894 } 2946 }