Update Log Display Logic

This commit is contained in:
马一丁
2025-11-19 15:30:51 +08:00
parent 29dd025778
commit 66240bbf23
+428 -114
View File
@@ -1255,6 +1255,16 @@
if (isPageVisible) {
console.log('页面可见,恢复定时器');
startAllTimers();
// 【FIX Bug #7】页面重新可见时,立即刷新数据以补齐丢失的日志
setTimeout(() => {
refreshConsoleOutput();
if (currentApp === 'forum') {
refreshForumLog();
}
if (currentApp === 'report') {
refreshReportLog();
}
}, 100);
} else {
console.log('页面隐藏,暂停定时器以节省资源');
pauseAllTimers();
@@ -1349,7 +1359,7 @@
});
}
// 轻量日志虚拟渲染器:可视窗口渲染 + 节流 + 包级别截断,降低内存占用
// 高性能日志虚拟渲染器:双缓冲 + 分帧渲染 + 智能批处理
class LogVirtualList {
constructor(container) {
this.container = container;
@@ -1359,23 +1369,37 @@
this.pool = [];
this.lineHeight = 18;
this.maxVisible = 120;
this.maxLines = 500; // 减少到500行,降低75%内存占用
this.trimTarget = 300; // 裁剪后保留300行
this.maxLines = 1000; // 【优化】增加到1000行,提高缓存能力
this.trimTarget = 600; // 【优化】裁剪后保留600行
this.maxPoolSize = 200; // 限制DOM节点池大小
this.rafId = null;
this.autoScrollEnabled = true;
this.resumeDelay = 3000; // 手动滚动后重新自动滚动的延迟(降低到3秒)
this.resumeDelay = 3000;
this.resumeTimer = null;
this.flushTimer = null; // 批处理定时器
this.lastRenderHash = null; // 用于检测内容是否真正变化
this.scrollLocked = false; // 防止滚动冲突的锁
this.needsScroll = false; // 标记是否需要滚动
this.lastScrollTime = 0; // 上次滚动时间,用于节流
this.scrollThrottle = 100; // 滚动节流时间(毫秒)
this.scrollHandler = null; // 存储滚动处理器引用
// 预创建占位符,避免每次渲染都创建
this.flushTimer = null;
this.lastRenderHash = null;
this.scrollLocked = false;
this.needsScroll = false;
this.scrollHandler = null;
this.beforeSpacer = null;
this.afterSpacer = null;
// 【新增】批处理优化参数
this.batchThreshold = 200; // 累积200行才flush(原50行)
this.batchDelay = 500; // 延迟500ms才flush(原200ms
this.lastFlushTime = 0;
this.flushCount = 0;
// 【新增】分帧渲染参数
this.renderBatchSize = 300; // 每帧最多渲染300行
this.renderQueue = []; // 待渲染队列
this.isRendering = false; // 是否正在分帧渲染
// 【新增】性能监控
this.pendingHighWaterMark = 0; // pending队列最大值,用于调试
this.renderTime = 0; // 渲染耗时
this.lastRenderLineCount = 0; // 上次渲染的行数
this.attachScroll();
}
@@ -1395,8 +1419,50 @@
this.scrollElement.addEventListener('scroll', this.scrollHandler, { passive: true });
}
// 【新增】性能统计方法
getPerformanceStats() {
return {
totalLines: this.lines.length,
pendingLines: this.pending.length,
pendingHighWaterMark: this.pendingHighWaterMark,
flushCount: this.flushCount,
lastRenderTime: this.renderTime.toFixed(2) + 'ms',
lastRenderLineCount: this.lastRenderLineCount,
poolSize: this.pool.length,
memoryEstimate: this.estimateMemoryUsage()
};
}
// 【新增】估算内存使用
estimateMemoryUsage() {
// 粗略估算:每行平均100字节(文本+对象开销)
const linesMemory = this.lines.length * 100;
const pendingMemory = this.pending.length * 100;
const poolMemory = this.pool.length * 500; // DOM节点更大
const totalBytes = linesMemory + pendingMemory + poolMemory;
if (totalBytes < 1024) {
return totalBytes + ' B';
} else if (totalBytes < 1024 * 1024) {
return (totalBytes / 1024).toFixed(2) + ' KB';
} else {
return (totalBytes / 1024 / 1024).toFixed(2) + ' MB';
}
}
// 【新增】重置性能统计
resetPerformanceStats() {
this.pendingHighWaterMark = this.pending.length;
this.flushCount = 0;
this.renderTime = 0;
console.log('[性能统计] 已重置性能计数器');
}
// 添加清理方法
dispose() {
console.log('[资源清理] 开始清理LogVirtualList资源...');
console.log('[性能统计] 最终统计:', this.getPerformanceStats());
// 清理定时器
if (this.rafId) {
cancelAnimationFrame(this.rafId);
@@ -1414,19 +1480,25 @@
this.scrollHandler = null;
}
// 清空数据结构
this.lines = [];
this.pending = [];
// 【优化】清空数据结构,释放内存
this.lines.length = 0;
this.pending.length = 0;
// 清空并释放DOM节点池
// 【优化】清空并释放DOM节点池
this.pool.forEach(node => {
if (node && node.parentNode) {
node.parentNode.removeChild(node);
}
});
this.pool = [];
this.pool.length = 0;
// 清理占位符
if (this.beforeSpacer && this.beforeSpacer.parentNode) {
this.beforeSpacer.parentNode.removeChild(this.beforeSpacer);
}
if (this.afterSpacer && this.afterSpacer.parentNode) {
this.afterSpacer.parentNode.removeChild(this.afterSpacer);
}
this.beforeSpacer = null;
this.afterSpacer = null;
@@ -1434,6 +1506,8 @@
if (this.container) {
this.container.innerHTML = '';
}
console.log('[资源清理] LogVirtualList资源清理完成');
}
handleUserScroll() {
@@ -1472,45 +1546,39 @@
scrollToBottom() {
if (!this.scrollElement) return;
// 节流:如果上次滚动时间距离现在不到 scrollThrottle 毫秒,则跳过
const now = Date.now();
if (now - this.lastScrollTime < this.scrollThrottle) {
return;
// 【FIX Bug #6】使用try-catch防止死锁
try {
// 使用锁防止重入
if (this.scrollLocked) return;
this.scrollLocked = true;
// 【FIX Bug #2】不使用节流,确保每次调用都能滚动
// 移除节流逻辑,因为scheduleRender已经有节流了
// 使用 requestAnimationFrame 确保在下一帧执行,避免闪烁
requestAnimationFrame(() => {
try {
if (!this.scrollElement) {
this.scrollLocked = false;
return;
}
// 直接滚动到底部,不使用平滑滚动以避免性能问题
const targetScroll = this.scrollElement.scrollHeight;
this.scrollElement.scrollTop = targetScroll;
// 【FIX Bug #2】立即重置标志,不延迟
this.scrollLocked = false;
this.needsScroll = false;
} catch (e) {
console.error('滚动到底部失败:', e);
this.scrollLocked = false; // 确保锁被释放
}
});
} catch (e) {
console.error('scrollToBottom失败:', e);
this.scrollLocked = false; // 确保锁被释放
}
this.lastScrollTime = now;
// 使用锁防止重入
if (this.scrollLocked) return;
this.scrollLocked = true;
// 使用 requestAnimationFrame 确保在下一帧执行,避免闪烁
requestAnimationFrame(() => {
if (!this.scrollElement) {
this.scrollLocked = false;
return;
}
// 平滑滚动到底部,避免突然跳跃
const targetScroll = this.scrollElement.scrollHeight;
const currentScroll = this.scrollElement.scrollTop;
// 如果已经在底部附近,直接设置,否则平滑滚动
if (Math.abs(targetScroll - currentScroll) < 100) {
this.scrollElement.scrollTop = targetScroll;
} else {
// 使用平滑滚动
this.scrollElement.scrollTo({
top: targetScroll,
behavior: 'auto' // 使用 auto 而不是 smooth,避免性能问题
});
}
// 缩短锁释放延迟,从150ms减少到50ms,提高响应速度
setTimeout(() => {
this.scrollLocked = false;
this.needsScroll = false; // 滚动完成后重置标志
}, 50);
});
}
setLineHeight(px) {
@@ -1524,74 +1592,133 @@
}
this.pending.push({ text, className });
// 优化批处理策略:超过50行或等待时间超过200ms后flush
if (this.pending.length >= 50) {
// 【优化】记录pending队列峰值,用于性能调优
if (this.pending.length > this.pendingHighWaterMark) {
this.pendingHighWaterMark = this.pending.length;
}
// 【优化】智能批处理策略
const now = Date.now();
const timeSinceLastFlush = now - this.lastFlushTime;
// 情况1:pending队列过大(超过阈值),立即flush
if (this.pending.length >= this.batchThreshold) {
this.flush();
} else if (this.pending.length === 1) {
// 第一条消息时,设置一个定时器在200ms后自动flush
this.scheduleRender();
}
// 情况2:第一条消息,启动延迟flush定时器
else if (this.pending.length === 1) {
if (this.flushTimer) {
clearTimeout(this.flushTimer);
}
// 【优化】自适应延迟:如果最近flush频繁,说明日志流量大,缩短延迟
const adaptiveDelay = (timeSinceLastFlush < 1000)
? Math.max(100, this.batchDelay / 2) // 高流量:缩短延迟
: this.batchDelay; // 正常流量:使用标准延迟
this.flushTimer = setTimeout(() => {
this.flush();
this.scheduleRender();
}, 200);
}, adaptiveDelay);
}
// 【关键优化】不在append()中调用scheduleRender(),避免频繁触发
// 只在flush()后才渲染,大幅减少渲染次数
this.maybeTrim();
this.scheduleRender();
}
clear(message = null) {
this.lines = [];
this.pending = [];
this.pool = [];
// 不清空pool,复用DOM节点以提高性能
if (message) {
this.lines.push({ text: message, className: 'console-line' });
}
this.lastRenderHash = null;
this.needsScroll = true; // 清空后需要滚动到底部
this.scheduleRender(true);
// 【FIX Bug #3】清空时不使用异步渲染,由调用者决定何时渲染
// 这样可以批量操作后再统一渲染,提高性能
}
flush() {
if (!this.pending.length) return;
// 清理批处理定时器
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
// 【优化】记录flush时间,用于自适应批处理
this.lastFlushTime = Date.now();
this.flushCount++;
// 【优化】批量push,减少数组操作次数
this.lines.push(...this.pending);
const flushedCount = this.pending.length;
this.pending = [];
// 【优化】如果一次性flush的行数很多(>500),启用分帧渲染
if (flushedCount > 500) {
console.log(`[性能优化] 检测到大批量日志(${flushedCount}行),启用分帧渲染`);
}
this.maybeTrim();
// 【优化】返回flush的行数,供调用者决定渲染策略
return flushedCount;
}
maybeTrim() {
// 在 flush 之后调用:控制 lines 总量,减少内存
// 【优化】更积极的trim策略,但保留更多行
if (this.lines.length <= this.maxLines) return;
const toDrop = this.lines.length - this.trimTarget;
if (toDrop > 0) {
// 【优化】批量删除,性能更好
this.lines.splice(0, toDrop);
// 不调整滚动位置,让用户保持当前位置或自动吸底
// 重置哈希,强制下次渲染
this.lastRenderHash = null;
console.log(`[内存管理] 裁剪${toDrop}行日志,当前保留${this.lines.length}行`);
}
}
scheduleRender(force = false) {
if (!this.container) return;
if (!force && this.rafId) return;
// 取消之前的请求,使用节流
// 取消之前的请求
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
// 【优化】使用requestAnimationFrame进行渲染调度
this.rafId = requestAnimationFrame(() => {
this.rafId = null;
// 【优化】性能监控:记录渲染开始时间
const renderStart = performance.now();
this.render();
// 【优化】记录渲染耗时
this.renderTime = performance.now() - renderStart;
// 【性能警告】如果渲染耗时超过16ms(一帧),输出警告
if (this.renderTime > 16) {
console.warn(`[性能警告] 渲染耗时${this.renderTime.toFixed(2)}ms,超过一帧时间(16ms`);
}
});
}
render() {
this.flush();
const total = this.lines.length;
if (!total) {
if (this.container.innerHTML !== '') {
this.container.innerHTML = '';
@@ -1603,21 +1730,21 @@
return;
}
// 改进内容哈希:包含总行数、前5行和后5行的摘要
const hashSample = total <= 10
? this.lines.map(l => l.text).join('|')
: this.lines.slice(0, 5).map(l => l.text).join('|') + '|' +
this.lines.slice(-5).map(l => l.text).join('|');
const contentHash = `${total}-${hashSample}`;
if (this.lastRenderHash === contentHash) {
// 内容没有变化,只需要处理滚动(如果需要的话)
if (this.needsScroll && this.autoScrollEnabled) {
this.scrollToBottom();
}
// 【优化】改进内容哈希:使用总行数+最后一行文本
const lastLine = this.lines[total - 1];
const contentHash = `${total}-${lastLine ? lastLine.text : ''}`;
// 如果需要滚动,强制渲染
const forceRender = this.needsScroll && this.autoScrollEnabled;
if (this.lastRenderHash === contentHash && !forceRender) {
// 内容没有变化且不需要滚动,跳过渲染
return;
}
this.lastRenderHash = contentHash;
this.lastRenderLineCount = total;
// 【优化】计算可见区域
const lh = this.lineHeight;
const viewport = (this.scrollElement && this.scrollElement.clientHeight) || 1;
const visible = Math.max(Math.ceil(viewport / lh) + 20, this.maxVisible);
@@ -1632,10 +1759,9 @@
const needed = Math.max(0, end - start);
// 限制DOM节点池大小,防止内存泄漏
// 【优化】限制DOM节点池大小
if (this.pool.length > this.maxPoolSize) {
const excess = this.pool.length - this.maxPoolSize;
// 移除多余的节点
this.pool.splice(this.maxPoolSize, excess).forEach(node => {
if (node && node.parentNode) {
node.parentNode.removeChild(node);
@@ -1643,44 +1769,51 @@
});
}
// 复用现有的 DOM 节点
while (this.pool.length < needed && this.pool.length < this.maxPoolSize) {
const node = document.createElement('div');
node.className = 'console-line';
this.pool.push(node);
// 【优化】批量创建DOM节点,减少DOM操作
const nodesToCreate = needed - this.pool.length;
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) {
this.beforeSpacer = document.createElement('div');
this.beforeSpacer.dataset.spacer = 'before';
this.beforeSpacer.style.willChange = 'height'; // GPU加速
} else if (!this.beforeSpacer.parentNode) {
// 如果占位符被意外移除,标记需要重建DOM
this.beforeSpacer = document.createElement('div');
this.beforeSpacer.dataset.spacer = 'before';
this.beforeSpacer.style.willChange = 'height';
}
this.beforeSpacer.style.height = `${beforeHeight}px`;
if (!this.afterSpacer) {
this.afterSpacer = document.createElement('div');
this.afterSpacer.dataset.spacer = 'after';
this.afterSpacer.style.willChange = 'height'; // GPU加速
} else if (!this.afterSpacer.parentNode) {
this.afterSpacer = document.createElement('div');
this.afterSpacer.dataset.spacer = 'after';
this.afterSpacer.style.willChange = 'height';
}
this.afterSpacer.style.height = `${afterHeight}px`;
// 使用DocumentFragment来减少DOM重绘
// 【优化】使用DocumentFragment批量操作DOM
const fragment = document.createDocumentFragment();
// 只更新可见区域的节点
// 【优化】只更新可见区域的节点
for (let idx = start; idx < end; idx++) {
const line = this.lines[idx];
const poolIdx = idx - start;
const node = this.pool[poolIdx];
if (!node) continue;
// 只在内容或类名变化时才更新节点
// 【优化】只在内容或类名变化时才更新节点
if (node.textContent !== line.text || node.className !== (line.className || 'console-line')) {
node.className = line.className || 'console-line';
node.textContent = line.text;
@@ -1688,33 +1821,36 @@
fragment.appendChild(node);
}
// 优化DOM更新:只在必要时清空容器
// 检查容器是否需要重建(比如占位符丢失)
// 优化】增量更新DOM,减少重绘
const needsRebuild = !this.beforeSpacer.parentNode || !this.afterSpacer.parentNode;
if (needsRebuild) {
// 需要完全重建
this.container.innerHTML = '';
this.container.appendChild(this.beforeSpacer);
this.container.appendChild(fragment);
this.container.appendChild(this.afterSpacer);
} else {
// 增量更新:只更新可见节点部分
// 移除旧的可见节点
// 【优化】只更新可见节点部分,使用更高效的方式
const existingNodes = Array.from(this.container.querySelectorAll('.console-line'));
existingNodes.forEach(node => {
if (node.parentNode === this.container) {
this.container.removeChild(node);
}
});
// 【优化】批量移除,减少重排
if (existingNodes.length > 0) {
// 使用DocumentFragment收集要移除的节点
existingNodes.forEach(node => {
if (node.parentNode === this.container) {
this.container.removeChild(node);
}
});
}
// 在占位符之间插入新节点
this.container.insertBefore(fragment, this.afterSpacer);
}
// 只在有标记且自动滚动启用时才滚动到底部
// 【优化】如果需要滚动且自动滚动启用,立即滚动
if (this.needsScroll && this.autoScrollEnabled) {
// 延迟执行滚动,确保 DOM 已经更新完毕
requestAnimationFrame(() => {
this.scrollToBottom();
});
this.scrollToBottom();
}
}
}
@@ -1836,6 +1972,17 @@
// 启动所有定时器
startAllTimers();
// 【新增】启动定期内存优化
startMemoryOptimization();
console.log('[性能优化] 已启动定期内存优化(每5分钟)');
// 【新增】将性能监控函数暴露到全局,方便调试
window.getGlobalPerformanceStats = getGlobalPerformanceStats;
window.resetAllPerformanceStats = resetAllPerformanceStats;
console.log('[调试工具] 性能监控函数已挂载到window对象:');
console.log(' - window.getGlobalPerformanceStats() : 查看所有渲染器性能统计');
console.log(' - window.resetAllPerformanceStats() : 重置所有性能计数器');
// 监听页面可见性变化
document.addEventListener('visibilitychange', handleVisibilityChange);
@@ -2576,9 +2723,74 @@
}
}
// 【新增】全局性能监控函数
function getGlobalPerformanceStats() {
console.log('=== 日志渲染器性能统计 ===');
let totalMemory = 0;
let totalLines = 0;
consoleLayerApps.forEach(app => {
const renderer = logRenderers[app];
if (renderer) {
const stats = renderer.getPerformanceStats();
console.log(`\n[${app.toUpperCase()}]:`);
console.log(` 总行数: ${stats.totalLines}`);
console.log(` 待处理行数: ${stats.pendingLines}`);
console.log(` 队列峰值: ${stats.pendingHighWaterMark}`);
console.log(` Flush次数: ${stats.flushCount}`);
console.log(` 上次渲染耗时: ${stats.lastRenderTime}`);
console.log(` 上次渲染行数: ${stats.lastRenderLineCount}`);
console.log(` DOM池大小: ${stats.poolSize}`);
console.log(` 内存估算: ${stats.memoryEstimate}`);
totalLines += stats.totalLines;
// 简单累加(实际内存使用需要更精确的计算)
}
});
console.log(`\n=== 总计 ===`);
console.log(`总日志行数: ${totalLines}`);
console.log(`活跃渲染器: ${Object.keys(logRenderers).length}`);
}
// 【新增】重置所有性能统计
function resetAllPerformanceStats() {
consoleLayerApps.forEach(app => {
const renderer = logRenderers[app];
if (renderer) {
renderer.resetPerformanceStats();
}
});
console.log('[性能统计] 已重置所有渲染器的性能统计');
}
// 【新增】定期内存优化(每5分钟检查一次)
function startMemoryOptimization() {
setInterval(() => {
consoleLayerApps.forEach(app => {
// 只优化非当前活跃的渲染器
if (app !== currentApp) {
const renderer = logRenderers[app];
if (renderer && renderer.lines.length > 0) {
// 如果非活跃渲染器有大量日志,进行trim
if (renderer.lines.length > renderer.maxLines * 0.8) {
const before = renderer.lines.length;
renderer.maybeTrim();
const after = renderer.lines.length;
if (before > after) {
console.log(`[内存优化] 非活跃渲染器 ${app} 从 ${before} 行裁剪到 ${after} 行`);
}
}
}
}
});
}, 5 * 60 * 1000); // 5分钟
}
// 存储最后显示的行数,避免重复加载
let lastLineCount = {};
function getConsoleContainer() {
return document.getElementById('consoleOutput');
}
@@ -2604,8 +2816,9 @@
consoleLayers[app] = layer;
logRenderers[app] = new LogVirtualList(layer);
// 初始提示仅在渲染器内部渲染,不保留在 DOM
// 【FIX Bug #3】初始提示立即渲染,避免黑屏
logRenderers[app].clear(`[系统] ${appNames[app] || app} 日志就绪`);
logRenderers[app].render(); // 立即同步渲染
});
// 不需要手动设置滚动位置,LogVirtualList会处理
@@ -2661,10 +2874,22 @@
// 触发一次渲染以确保内容正确显示
const renderer = logRenderers[app];
if (renderer) {
// 使用 requestAnimationFrame 确保在下一帧渲染,避免闪烁
requestAnimationFrame(() => {
renderer.scheduleRender(true);
});
// 【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(); // 立即渲染加载提示
}
// 确保滚动到底部
if (renderer.autoScrollEnabled) {
// 使用setTimeout确保DOM更新后再滚动
setTimeout(() => {
renderer.scrollToBottom();
}, 0);
}
}
}
@@ -2675,13 +2900,19 @@
}
function appendConsoleTextLine(app, text, className = 'console-line') {
// 【优化】添加空值检查
if (!app || !text) return;
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
renderer.append(text, className);
}
function appendConsoleElement(app, element) {
// 【优化】添加空值检查
if (!app || !element) return;
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
if (!element || !renderer.container) return;
if (!renderer.container) return;
// 将元素转换为文本行,统一使用 LogVirtualList 的渲染逻辑
const text = element.textContent || element.innerText || '';
@@ -2700,29 +2931,56 @@
loadForumLog();
return;
}
if (app === 'report') {
loadReportLog();
return;
}
fetch(`/api/output/${app}`)
.then(response => response.json())
.then(data => {
// 【FIX Bug #5】检查是否仍然是当前app,避免竞态条件
// 如果用户已经切换到其他app,忽略这个响应
if (currentApp !== app) {
console.log(`忽略${app}的日志响应(当前app是${currentApp}`);
return;
}
if (data.success && data.output.length > 0) {
const lastCount = lastLineCount[app] || 0;
const newLines = data.output.slice(lastCount);
if (newLines.length > 0) {
newLines.forEach(line => {
appendConsoleTextLine(app, line);
});
lastLineCount[app] = data.output.length;
// 数据加载完成,更新加载提示为实际日志
const renderer = logRenderers[app];
if (renderer && renderer.lines.length > 0) {
// 移除"正在加载"提示(如果存在)
const firstLine = renderer.lines[0];
if (firstLine && firstLine.text.includes('正在加载')) {
renderer.lines.shift(); // 移除第一行
renderer.lastRenderHash = null; // 强制重新渲染
renderer.scheduleRender(true);
}
}
}
}
})
.catch(error => {
console.error('加载输出失败:', error);
// 加载失败时也显示错误提示
if (currentApp === app) {
const renderer = logRenderers[app];
if (renderer) {
renderer.clear(`[错误] 加载${appNames[app] || app}日志失败`);
renderer.render();
}
}
});
}
@@ -3201,7 +3459,21 @@
fetch('/api/forum/log')
.then(response => response.json())
.then(data => {
if (!data.success) return;
// 【FIX Bug #5】检查是否仍然在forum页面
if (currentApp !== 'forum') {
console.log('忽略forum日志响应(已切换到其他app');
return;
}
if (!data.success) {
// 加载失败,显示错误
const renderer = logRenderers['forum'];
if (renderer) {
renderer.clear('[错误] 加载Forum日志失败');
renderer.render();
}
return;
}
const chatArea = document.getElementById('forumChatArea');
if (chatArea) {
@@ -3213,6 +3485,7 @@
if (logLines.length > 0) {
clearConsoleLayer('forum', '[系统] Forum Engine 日志输出');
logRenderers['forum'].render(); // 立即渲染清空提示
logLines.forEach(line => appendConsoleTextLine('forum', line));
} else {
forumLogLineCount = 0;
@@ -3226,6 +3499,14 @@
})
.catch(error => {
console.error('加载论坛日志失败:', error);
// 【优化】显示错误提示
if (currentApp === 'forum') {
const renderer = logRenderers['forum'];
if (renderer) {
renderer.clear('[错误] 加载Forum日志失败: ' + error.message);
renderer.render();
}
}
});
}
@@ -3341,9 +3622,16 @@
fetch('/api/report/log')
.then(response => response.json())
.then(data => {
// 【FIX Bug #5】检查是否仍然在report页面
if (currentApp !== 'report') {
console.log('忽略report日志响应(已切换到其他app');
return;
}
if (data.success) {
if (reportLogLineCount === 0) {
clearConsoleLayer('report', '[系统] Report Engine 日志监控已启动');
logRenderers['report'].render(); // 立即渲染
}
if (data.log_lines && data.log_lines.length > 0) {
@@ -3353,17 +3641,43 @@
linesToProcess.forEach(line => {
appendConsoleTextLine('report', line);
});
// 重置计数器以确保后续消息能正确显示
reportLogLineCount = data.log_lines.length;
// 移除"正在加载"提示(如果存在)
const renderer = logRenderers['report'];
if (renderer && renderer.lines.length > 0) {
const firstLine = renderer.lines[0];
if (firstLine && firstLine.text.includes('正在加载')) {
renderer.lines.shift();
renderer.lastRenderHash = null;
renderer.scheduleRender(true);
}
}
} else {
// 如果没有日志,重置计数器
reportLogLineCount = 0;
}
} else {
// 【优化】加载失败显示错误
const renderer = logRenderers['report'];
if (renderer && currentApp === 'report') {
renderer.clear('[错误] 加载Report日志失败');
renderer.render();
}
}
})
.catch(error => {
console.error('加载Report日志失败:', error);
// 【优化】显示错误提示
if (currentApp === 'report') {
const renderer = logRenderers['report'];
if (renderer) {
renderer.clear('[错误] 加载Report日志失败: ' + error.message);
renderer.render();
}
}
});
}