Improves the Front-End Console Experience

This commit is contained in:
马一丁
2025-11-21 00:25:48 +08:00
parent b10dd449fa
commit de8f253fa0
+287 -40
View File
@@ -23,6 +23,10 @@
overflow-x: hidden; overflow-x: hidden;
} }
:root {
--console-offset: 52px; /* app切换行 + 状态条的预留高度,运行时同步 */
}
.container { .container {
max-width: 100vw; max-width: 100vw;
height: 100vh; /* 固定高度为视口高度 */ height: 100vh; /* 固定高度为视口高度 */
@@ -177,7 +181,7 @@
.upload-button input[type="file"] { .upload-button input[type="file"] {
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 52px; /* 运行时再用JS同步为app-switcher的真实高度 */
width: 100%; width: 100%;
height: 100%; height: 100%;
opacity: 0; opacity: 0;
@@ -238,6 +242,7 @@
background-color: #ffffff; background-color: #ffffff;
min-height: 0; /* 允许子元素缩小 */ min-height: 0; /* 允许子元素缩小 */
overflow: hidden; /* 防止内容溢出 */ overflow: hidden; /* 防止内容溢出 */
position: relative; /* 让状态栏悬浮不占用布局空间 */
} }
/* 应用切换按钮 */ /* 应用切换按钮 */
@@ -330,7 +335,11 @@
/* 控制台状态栏 - 显示系统消息而不干扰日志查看 */ /* 控制台状态栏 - 显示系统消息而不干扰日志查看 */
.console-status-bar { .console-status-bar {
height: 0; position: absolute;
top: 0;
left: 0;
right: 0;
height: 26px;
overflow: hidden; overflow: hidden;
background: linear-gradient(180deg, rgba(0, 120, 0, 0.95) 0%, rgba(0, 100, 0, 0.9) 100%); background: linear-gradient(180deg, rgba(0, 120, 0, 0.95) 0%, rgba(0, 100, 0, 0.9) 100%);
color: #00ff00; color: #00ff00;
@@ -342,13 +351,17 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
transition: height 0.3s ease, padding 0.3s ease; pointer-events: none; /* 不抢占滚动 */
z-index: 2;
opacity: 0;
transform: translateY(-110%);
transition: transform 0.2s ease, opacity 0.2s ease;
line-height: 26px; line-height: 26px;
} }
.console-status-bar.visible { .console-status-bar.visible {
height: 26px; opacity: 1;
padding: 0 15px; transform: translateY(0);
} }
.console-status-bar .status-message { .console-status-bar .status-message {
@@ -372,7 +385,7 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; /* 填满整个黑色框 */ height: 100%; /* 填满整个黑色框 */
padding: 15px; /* 图层内边距 */ padding: var(--console-offset, 52px) 15px 15px; /* 顶部预留给状态栏与按钮,不影响滚动计算 */
overflow-y: auto; /* 允许独立滚动 */ overflow-y: auto; /* 允许独立滚动 */
overflow-x: hidden; overflow-x: hidden;
box-sizing: border-box; /* 包含padding在width/height内 */ box-sizing: border-box; /* 包含padding在width/height内 */
@@ -1540,8 +1553,13 @@
this.maxVisible = 150; // ↑ from 120(增加可见行数) this.maxVisible = 150; // ↑ from 120(增加可见行数)
this.maxLines = 50000; // ↑ from 100005倍提升,约5MB/app this.maxLines = 50000; // ↑ from 100005倍提升,约5MB/app
this.trimTarget = 40000; // ↑ from 8000(保留更多历史) this.trimTarget = 40000; // ↑ from 8000(保留更多历史)
this.maxPoolSize = 300; // ↑ from 200(更大DOM池)
this.preRenderBuffer = 90; // 新增:上下各预渲染90行,提升滚动流畅度 this.preRenderBuffer = 90; // 新增:上下各预渲染90行,提升滚动流畅度
this.minPoolSize = 220; // 保底DOM池,避免窗口渲染空白
this.poolHardLimit = 800; // 池子硬上限,防止撑爆内存
this.maxPoolSize = Math.max(
this.minPoolSize,
this.maxVisible + this.preRenderBuffer * 2 + 50
); // 默认覆盖可视窗口+缓冲区,再留一些余量
this.rafId = null; this.rafId = null;
this.autoScrollEnabled = true; this.autoScrollEnabled = true;
this.resumeDelay = 3000; this.resumeDelay = 3000;
@@ -1553,6 +1571,10 @@
this.scrollHandler = null; this.scrollHandler = null;
this.beforeSpacer = null; this.beforeSpacer = null;
this.afterSpacer = null; this.afterSpacer = null;
this.watchdogTimer = null;
this.lastRenderAt = 0;
this.idleTimer = null;
this.idleTimeout = 3000; // 用户3秒不滚动后自动吸附
// 【优化吸附逻辑】记录用户滚动位置,用于智能恢复 // 【优化吸附逻辑】记录用户滚动位置,用于智能恢复
this.lastUserScrollPosition = 0; // 用户上次滚动的位置 this.lastUserScrollPosition = 0; // 用户上次滚动的位置
@@ -1605,6 +1627,90 @@
} }
} }
clearIdleTimer() {
if (this.idleTimer) {
clearTimeout(this.idleTimer);
this.idleTimer = null;
}
}
startIdleTimer() {
this.clearIdleTimer();
this.idleTimer = setTimeout(() => {
this.autoScrollEnabled = true;
this.needsScroll = true;
this.clearResumeTimer();
this.scrollToBottom();
}, this.idleTimeout);
}
// 保障DOM池容量,避免可视窗口比池子大导致空白
ensurePoolCapacity(neededCount) {
if (!Number.isFinite(neededCount)) return;
// 根据需求量和基线计算DOM池大小,避免窗口比池子大
const baseline = Math.min(this.minPoolSize, neededCount + 80);
const targetSize = Math.min(
Math.max(neededCount, baseline, this.pool.length),
this.poolHardLimit
);
if (targetSize > this.maxPoolSize) {
this.maxPoolSize = targetSize;
}
const missing = Math.max(0, targetSize - this.pool.length);
for (let i = 0; i < missing; i++) {
const node = document.createElement('div');
node.className = 'console-line';
this.pool.push(node);
}
}
// DOM意外空白时强制刷新
forceRenderIfBlank() {
if (!this.container) return false;
if (this.lines.length === 0) return false;
if (this.container.querySelector('.console-line')) return false;
this.lastRenderHash = null;
this.needsScroll = true;
this.scheduleRender(true);
return true;
}
startWatchdog() {
if (this.watchdogTimer) return;
this.watchdogTimer = setInterval(() => {
if (!this.container) return;
// 黑屏兜底:有数据但没有节点时强制渲染
const hasLines = this.lines.length > 0;
const hasNodes = !!this.container.querySelector('.console-line');
if (hasLines && !hasNodes && !this.isRendering) {
this.lastRenderHash = null;
this.needsScroll = true;
this.updateStatusBar('正在恢复显示...', '检测到渲染延迟', false, 1200);
this.scheduleRender(true);
return;
}
// 渲染长时间未推进时,提醒并触发一次刷新
const now = performance.now();
if (hasLines && this.pending.length > 0 && now - this.lastRenderAt > 1500 && !this.isRendering) {
this.lastRenderHash = null;
this.needsScroll = true;
this.updateStatusBar('日志渲染中...', `${this.pending.length} 条待显示`, false, 1200);
this.scheduleRender(true);
}
}, 800);
}
stopWatchdog() {
if (this.watchdogTimer) {
clearInterval(this.watchdogTimer);
this.watchdogTimer = null;
}
}
attachScroll() { attachScroll() {
if (!this.scrollElement) return; if (!this.scrollElement) return;
if (this.scrollHandler) return; // 防止重复绑定 if (this.scrollHandler) return; // 防止重复绑定
@@ -1673,6 +1779,9 @@
// 显示状态栏 // 显示状态栏
this.statusBar.classList.add('visible'); this.statusBar.classList.add('visible');
if (this.layoutCache) {
this.layoutCache.invalidate();
}
// 清除之前的自动隐藏定时器 // 清除之前的自动隐藏定时器
if (this.statusBarTimer) { if (this.statusBarTimer) {
@@ -1686,6 +1795,9 @@
if (this.statusBar) { if (this.statusBar) {
this.statusBar.classList.remove('visible'); this.statusBar.classList.remove('visible');
} }
if (this.layoutCache) {
this.layoutCache.invalidate();
}
this.statusBarTimer = null; this.statusBarTimer = null;
}, duration); }, duration);
} }
@@ -1699,6 +1811,9 @@
this.statusBarTimer = null; this.statusBarTimer = null;
} }
this.statusBar.classList.remove('visible'); this.statusBar.classList.remove('visible');
if (this.layoutCache) {
this.layoutCache.invalidate();
}
} }
// 添加清理方法 // 添加清理方法
@@ -1707,6 +1822,7 @@
console.log('[性能统计] 最终统计:', this.getPerformanceStats()); console.log('[性能统计] 最终统计:', this.getPerformanceStats());
// 清理定时器 // 清理定时器
this.stopWatchdog();
if (this.rafId) { if (this.rafId) {
cancelAnimationFrame(this.rafId); cancelAnimationFrame(this.rafId);
this.rafId = null; this.rafId = null;
@@ -1716,6 +1832,7 @@
clearTimeout(this.flushTimer); clearTimeout(this.flushTimer);
this.flushTimer = null; this.flushTimer = null;
} }
this.clearIdleTimer();
if (this.statusBarTimer) { if (this.statusBarTimer) {
clearTimeout(this.statusBarTimer); clearTimeout(this.statusBarTimer);
this.statusBarTimer = null; this.statusBarTimer = null;
@@ -1779,7 +1896,7 @@
// 计算距离底部的距离 // 计算距离底部的距离
const distanceFromBottom = scrollHeight - clientHeight - currentScrollTop; const distanceFromBottom = scrollHeight - clientHeight - currentScrollTop;
const atBottom = distanceFromBottom <= 50; // 50px阈值 const atBottom = distanceFromBottom <= 80; // 更宽松的阈值,减少误判
if (atBottom) { if (atBottom) {
// 用户主动滚动到底部,立即启用自动滚动 // 用户主动滚动到底部,立即启用自动滚动
@@ -1808,6 +1925,7 @@
this.clearResumeTimer(); this.clearResumeTimer();
const delay = this.calculateResumeDelay(distanceFromBottom); const delay = this.calculateResumeDelay(distanceFromBottom);
this.startResumeTimer(delay); this.startResumeTimer(delay);
this.startIdleTimer();
return; return;
} }
@@ -1815,10 +1933,12 @@
console.log(`[自动吸附] 用户离开底部 ${Math.round(distanceFromBottom)}px,禁用自动滚动`); console.log(`[自动吸附] 用户离开底部 ${Math.round(distanceFromBottom)}px,禁用自动滚动`);
this.autoScrollEnabled = false; this.autoScrollEnabled = false;
this.clearResumeTimer(); this.clearResumeTimer();
this.clearIdleTimer();
// 智能计算恢复时间 // 智能计算恢复时间
const delay = this.calculateResumeDelay(distanceFromBottom); const delay = this.calculateResumeDelay(distanceFromBottom);
this.startResumeTimer(delay); this.startResumeTimer(delay);
this.startIdleTimer();
} }
/** /**
@@ -1847,6 +1967,7 @@
console.log(`[自动吸附] ${delay}ms后恢复自动滚动,吸附到最新日志`); console.log(`[自动吸附] ${delay}ms后恢复自动滚动,吸附到最新日志`);
this.autoScrollEnabled = true; this.autoScrollEnabled = true;
this.userScrollDistance = 0; this.userScrollDistance = 0;
this.needsScroll = true;
// 【优化】恢复时立即吸附到最新 // 【优化】恢复时立即吸附到最新
// 如果有新内容已经渲染完成,直接滚动到最新位置 // 如果有新内容已经渲染完成,直接滚动到最新位置
@@ -1891,10 +2012,11 @@
} }
// 【优化判断】智能阈值: // 【优化判断】智能阈值:
// - 如果内容少(scrollHeight < 1000px阈值30px宽容 // - 超小内容时使用30px
// - 如果内容多,阈值50px(标准) // - 常规50px
// 这样能更好地适应不同内容量的情况 // - 当存在状态条/顶部padding时,再增加20px缓冲
const threshold = scrollHeight < 1000 ? 30 : 50; const baseThreshold = scrollHeight < 1000 ? 30 : 50;
const threshold = baseThreshold + 20;
const distanceFromBottom = scrollHeight - clientHeight - scrollTop; const distanceFromBottom = scrollHeight - clientHeight - scrollTop;
return distanceFromBottom <= threshold; return distanceFromBottom <= threshold;
@@ -1915,9 +2037,42 @@
return this.isNearBottom(); return this.isNearBottom();
} }
// 将最新一行吸附到可视区域内,而不是盲目对齐到底部
scrollLatestIntoView(margin = 12) {
if (!this.scrollElement || this.scrollLocked) return false;
const lastLine = this.scrollElement.querySelector('.console-line:last-of-type');
if (!lastLine) return false;
const container = this.scrollElement;
// 最新一行希望落在视口内并留出少量下边距
const desiredBottom = container.clientHeight - margin;
const delta = lastLine.offsetTop + lastLine.offsetHeight - desiredBottom;
if (delta > 1 || delta < -1) {
this.scrollLocked = true;
requestAnimationFrame(() => {
container.scrollTop = Math.max(0, container.scrollTop + delta);
if (this.layoutCache) {
this.layoutCache.invalidate();
}
setTimeout(() => {
this.scrollLocked = false;
}, 80);
});
}
return true;
}
scrollToBottom() { scrollToBottom() {
if (!this.scrollElement) return; if (!this.scrollElement) return;
// 优先将最新一行吸附到视口内,保留边距,避免“对齐页面底部”的跳动
const snapped = this.scrollLatestIntoView(16);
if (snapped) {
this.needsScroll = false;
this.startIdleTimer();
return;
}
// 【修复黑屏】如果正在渲染,延迟滚动避免冲突 // 【修复黑屏】如果正在渲染,延迟滚动避免冲突
if (this.isRendering) { if (this.isRendering) {
requestAnimationFrame(() => this.scrollToBottom()); requestAnimationFrame(() => this.scrollToBottom());
@@ -1989,6 +2144,7 @@
} }
} }
this.needsScroll = false; this.needsScroll = false;
this.startIdleTimer();
// 解锁滚动(平滑滚动需要更长时间) // 解锁滚动(平滑滚动需要更长时间)
setTimeout(() => { setTimeout(() => {
@@ -2016,9 +2172,12 @@
this.isActive = active; this.isActive = active;
if (active) { if (active) {
this.startWatchdog();
// 【新增逻辑】切换引擎时,重置为自动吸附状态 // 【新增逻辑】切换引擎时,重置为自动吸附状态
// 默认用户鼠标未滑动3秒以上 // 默认用户鼠标未滑动3秒以上
this.autoScrollEnabled = true; this.autoScrollEnabled = true;
this.needsScroll = true;
this.clearResumeTimer(); this.clearResumeTimer();
// 【修复】窗口激活时,清除渲染哈希,确保强制渲染 // 【修复】窗口激活时,清除渲染哈希,确保强制渲染
@@ -2112,6 +2271,17 @@
}); });
} }
} }
// 保证切换后立即同步最新内容
this.scheduleRender(true);
this.startIdleTimer();
if (this.lines.length > 0) {
requestAnimationFrame(() => this.scrollToBottom());
}
} else {
this.stopWatchdog();
this.clearIdleTimer();
} }
} }
@@ -2179,12 +2349,25 @@
} }
append(text, className = 'console-line') { append(text, className = 'console-line') {
const nearBottom = this.isNearBottom();
// 在添加内容前检查是否在底部,如果是则标记需要滚动 // 在添加内容前检查是否在底部,如果是则标记需要滚动
if (this.autoScrollEnabled && this.isNearBottom()) { if (this.autoScrollEnabled && nearBottom) {
this.needsScroll = true; this.needsScroll = true;
} else if (!this.autoScrollEnabled && nearBottom) {
// 用户并未真正离开底部,自动恢复吸附
this.autoScrollEnabled = true;
this.needsScroll = true;
this.clearResumeTimer();
} }
this.pending.push({ text, className }); this.pending.push({ text, className });
this.pendingHighWaterMark = Math.max(this.pendingHighWaterMark, this.pending.length);
// 队列堆积时给出进度提示(仅当前活动窗口)
if (this.isActive && this.pending.length > 150) {
this.updateStatusBar('日志渲染中...', `${this.pending.length} 条排队`, false, 1200);
}
// 【修复黑屏】统一批处理逻辑 - 所有窗口都实时渲染 // 【修复黑屏】统一批处理逻辑 - 所有窗口都实时渲染
// 清除之前的定时器 // 清除之前的定时器
@@ -2206,6 +2389,9 @@
}, this.batchDelay); }, this.batchDelay);
} }
// 如果容器当前是空的,强制渲染一次,避免“黑屏等待”
this.forceRenderIfBlank();
this.maybeTrim(); this.maybeTrim();
} }
@@ -2332,6 +2518,7 @@
this.beforeSpacer = null; this.beforeSpacer = null;
this.afterSpacer = null; this.afterSpacer = null;
} }
this.lastRenderAt = performance.now();
return; return;
} }
@@ -2349,6 +2536,7 @@
} }
this.lastRenderHash = contentHash; this.lastRenderHash = contentHash;
this.lastRenderLineCount = total; this.lastRenderLineCount = total;
this.lastRenderAt = performance.now();
// 【优化】计算可见区域 // 【优化】计算可见区域
const lh = this.lineHeight; const lh = this.lineHeight;
@@ -2374,14 +2562,33 @@
const bufferStart = Math.max(0, rawStart - this.preRenderBuffer); const bufferStart = Math.max(0, rawStart - this.preRenderBuffer);
const bufferEnd = Math.min(total, rawStart + visible + this.preRenderBuffer); const bufferEnd = Math.min(total, rawStart + visible + this.preRenderBuffer);
const start = bufferStart; let start = bufferStart;
const end = bufferEnd; let end = bufferEnd;
let needed = Math.max(0, end - start);
// 【修复白屏】先保证DOM池容量足够覆盖当前窗口
this.ensurePoolCapacity(needed);
// 如果窗口仍超过池容量,收缩窗口贴近最新日志,防止中间缺口
if (needed > this.pool.length) {
const windowSize = this.pool.length || this.minPoolSize;
end = Math.min(total, Math.max(end, windowSize));
start = Math.max(0, end - windowSize);
needed = Math.max(0, end - start);
}
if (needed === 0 && total > 0) {
// 极端情况下兜底展示最新日志,避免空白
end = total;
start = Math.max(0, end - Math.min(this.maxVisible, total));
needed = end - start;
this.ensurePoolCapacity(needed);
}
const beforeHeight = start * lh; const beforeHeight = start * lh;
const afterHeight = (total - end) * lh; const afterHeight = (total - end) * lh;
const needed = Math.max(0, end - start); // 【优化】限制DOM节点池大小(在更新池容量后执行)
// 【优化】限制DOM节点池大小
if (this.pool.length > this.maxPoolSize) { if (this.pool.length > this.maxPoolSize) {
const excess = this.pool.length - this.maxPoolSize; const excess = this.pool.length - this.maxPoolSize;
this.pool.splice(this.maxPoolSize, excess).forEach(node => { this.pool.splice(this.maxPoolSize, excess).forEach(node => {
@@ -2391,16 +2598,8 @@
}); });
} }
// 【优化】批量创建DOM节点,减少DOM操作 // 再次兜底,确保池容量与窗口一致
const nodesToCreate = needed - this.pool.length; this.ensurePoolCapacity(needed);
if (nodesToCreate > 0 && this.pool.length < this.maxPoolSize) {
const fragment = document.createDocumentFragment();
for (let i = 0; i < Math.min(nodesToCreate, this.maxPoolSize - this.pool.length); i++) {
const node = document.createElement('div');
node.className = 'console-line';
this.pool.push(node);
}
}
// 【优化】复用或创建占位符 // 【优化】复用或创建占位符
if (!this.beforeSpacer) { if (!this.beforeSpacer) {
@@ -2479,6 +2678,8 @@
this.scrollToBottom(); this.scrollToBottom();
}); });
} }
this.lastRenderAt = performance.now();
}); });
} else { } else {
// 【优化】增量更新:智能diff算法,只更新必要的节点 // 【优化】增量更新:智能diff算法,只更新必要的节点
@@ -2579,6 +2780,8 @@
this.scrollToBottom(); this.scrollToBottom();
}); });
} }
this.lastRenderAt = performance.now();
} }
/** /**
@@ -2607,6 +2810,7 @@
const batchSize = 200; // 每批渲染200行 const batchSize = 200; // 每批渲染200行
let currentBatch = 0; let currentBatch = 0;
const totalBatches = Math.ceil(total / batchSize); const totalBatches = Math.ceil(total / batchSize);
this.lastRenderAt = performance.now();
// 显示渲染进度 // 显示渲染进度
const showProgress = () => { const showProgress = () => {
@@ -2654,6 +2858,7 @@
// 隐藏状态栏 // 隐藏状态栏
this.hideStatusBar(); this.hideStatusBar();
this.lastRenderAt = performance.now();
// 【优化吸附】渲染完成后的智能滚动 // 【优化吸附】渲染完成后的智能滚动
// 1. 自动滚动已启用 -> 直接吸附 // 1. 自动滚动已启用 -> 直接吸附
@@ -2783,6 +2988,7 @@
// 初始化 // 初始化
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
initializeConsoleLayers(); initializeConsoleLayers();
syncStatusBarPosition();
initializeSocket(); initializeSocket();
initializeEventListeners(); initializeEventListeners();
ensureSystemReadyOnLoad(); ensureSystemReadyOnLoad();
@@ -2829,6 +3035,9 @@
// 连接探测定时器(保持运行) // 连接探测定时器(保持运行)
startConnectionProbe(); startConnectionProbe();
// 窗口尺寸变化时同步状态栏位置
window.addEventListener('resize', syncStatusBarPosition);
}); });
// Socket.IO连接 // Socket.IO连接
@@ -3628,6 +3837,20 @@
return document.getElementById('consoleOutput'); return document.getElementById('consoleOutput');
} }
// 同步状态栏位置,避免覆盖应用切换按钮
function syncStatusBarPosition() {
const bar = document.getElementById('consoleStatusBar');
const switcher = document.querySelector('.app-switcher');
if (!bar || !switcher) return;
const offset = switcher.offsetHeight || 0;
const barHeight = bar.offsetHeight || 26;
const totalOffset = offset + barHeight + 6; // 额外预留6px缓冲
bar.style.top = `${offset}px`;
document.documentElement.style.setProperty('--console-offset', `${totalOffset}px`);
}
function initializeConsoleLayers() { function initializeConsoleLayers() {
const container = getConsoleContainer(); const container = getConsoleContainer();
if (!container) return; if (!container) return;
@@ -3646,11 +3869,7 @@
container.appendChild(layer); container.appendChild(layer);
consoleLayers[app] = layer; consoleLayers[app] = layer;
logRenderers[app] = new LogVirtualList(layer); logRenderers[app] = new LogVirtualList(layer);
logRenderers[app].setActive(app === currentApp);
// 【图层优化】标记活动窗口
if (app === currentApp) {
logRenderers[app].isActive = true;
}
// 【FIX Bug #3】初始提示立即渲染,避免黑屏 // 【FIX Bug #3】初始提示立即渲染,避免黑屏
logRenderers[app].clear(`[系统] ${appNames[app] || app} 日志就绪`); logRenderers[app].clear(`[系统] ${appNames[app] || app} 日志就绪`);
@@ -3680,11 +3899,7 @@
container.appendChild(layer); container.appendChild(layer);
consoleLayers[app] = layer; consoleLayers[app] = layer;
logRenderers[app] = new LogVirtualList(layer); logRenderers[app] = new LogVirtualList(layer);
logRenderers[app].setActive(app === currentApp);
// 【图层优化】标记活动窗口
if (app === currentApp) {
logRenderers[app].isActive = true;
}
return layer; return layer;
} }
@@ -3718,6 +3933,8 @@
const renderer = logRenderers[app]; const renderer = logRenderers[app];
if (renderer) { if (renderer) {
renderer.setActive(true); // 会在内部异步渲染待处理内容 renderer.setActive(true); // 会在内部异步渲染待处理内容
renderer.needsScroll = true;
renderer.scheduleRender(true);
// 确保滚动到底部 // 确保滚动到底部
if (renderer.autoScrollEnabled) { if (renderer.autoScrollEnabled) {
@@ -4485,6 +4702,28 @@
// 创建全局日志管理器实例 // 创建全局日志管理器实例
const reportLogManager = new ReportLogManager(); const reportLogManager = new ReportLogManager();
// 新任务时重置报告日志,避免残留历史输出
function resetReportLogsForNewTask(taskId, reason = '开始新的报告任务,日志已重置') {
if (!taskId) return;
if (reportTaskId === taskId) return; // 已是同一任务,无需重复清空
// 停止当前流与轮询,防止旧日志混入
safeCloseReportStream();
reportLogManager.stop();
reportLogManager.reset();
// 重置前端计数与缓存
reportLogLineCount = 0;
lastLineCount['report'] = 0;
clearConsoleLayer('report', `[系统] ${reason}`);
resetReportStreamOutput('Report Engine 正在启动...');
// 重新启动轮询,确保新任务日志即时接入
reportLogManager.start();
reportTaskId = taskId;
}
// 【调试】测试日志管理器 // 【调试】测试日志管理器
window.testReportLogManager = function() { window.testReportLogManager = function() {
console.log('[测试] ===== 开始测试Report日志管理器 ====='); console.log('[测试] ===== 开始测试Report日志管理器 =====');
@@ -5025,12 +5264,14 @@
if (statusData.current_task) { if (statusData.current_task) {
updateTaskProgressStatus(statusData.current_task); updateTaskProgressStatus(statusData.current_task);
if (statusData.current_task.status === 'running') { if (statusData.current_task.status === 'running') {
reportTaskId = statusData.current_task.task_id; const taskId = statusData.current_task.task_id;
resetReportLogsForNewTask(taskId, '检测到正在运行的报告任务,日志已重新开始');
reportTaskId = taskId;
reportAutoPreviewLoaded = false; reportAutoPreviewLoaded = false;
if (window.EventSource) { if (window.EventSource) {
openReportStream(reportTaskId); openReportStream(reportTaskId);
} else { } else {
startProgressPolling(reportTaskId); startProgressPolling(taskId);
} }
} else if (statusData.current_task.status === 'completed') { } else if (statusData.current_task.status === 'completed') {
lastCompletedReportTask = statusData.current_task; lastCompletedReportTask = statusData.current_task;
@@ -5527,6 +5768,9 @@
// 更新进度显示(保持向后兼容) // 更新进度显示(保持向后兼容)
function updateProgressDisplay(task) { function updateProgressDisplay(task) {
if (task && task.task_id && task.status === 'running') {
resetReportLogsForNewTask(task.task_id, '检测到新的报告任务,日志已同步重置');
}
updateTaskProgressStatus(task); updateTaskProgressStatus(task);
} }
@@ -5735,6 +5979,9 @@
const task = payload.task; const task = payload.task;
if (eventType === 'status' && task) { if (eventType === 'status' && task) {
if (task.status === 'running') {
resetReportLogsForNewTask(task.task_id, '收到流式状态事件,已重置日志');
}
updateTaskProgressStatus(task); updateTaskProgressStatus(task);
reportTaskId = task.status === 'running' ? task.task_id : null; reportTaskId = task.status === 'running' ? task.task_id : null;
if (task.status === 'completed') { if (task.status === 'completed') {