Optimize Front-End Memory Usage

This commit is contained in:
马一丁
2025-11-18 02:10:31 +08:00
parent d9f72313a3
commit 80bbd0d243

View File

@@ -1226,6 +1226,111 @@
const consoleLayers = {};
const consoleLayerScrollPositions = {};
let activeConsoleLayer = currentApp;
const logRenderers = {};
// 轻量日志虚拟渲染器:不限制总行数,使用可视窗口渲染 + 节流
class LogVirtualList {
constructor(container) {
this.container = container;
this.lines = [];
this.pending = [];
this.pool = [];
this.lineHeight = 18;
this.maxVisible = 120;
this.rafId = null;
this.attachScroll();
}
attachScroll() {
if (!this.container) return;
this.container.addEventListener('scroll', () => this.scheduleRender());
}
setLineHeight(px) {
if (px > 0) this.lineHeight = px;
}
append(text, className = 'console-line') {
this.pending.push({ text, className });
if (this.pending.length > 200) {
this.flush();
}
this.scheduleRender();
}
clear(message = null) {
this.lines = [];
this.pending = [];
this.pool = [];
if (message) {
this.lines.push({ text: message, className: 'console-line' });
}
this.scheduleRender(true);
}
flush() {
if (!this.pending.length) return;
this.lines.push(...this.pending);
this.pending = [];
}
scheduleRender(force = false) {
if (!this.container) return;
if (!force && this.rafId) return;
this.rafId = requestAnimationFrame(() => {
this.rafId = null;
this.render();
});
}
render() {
this.flush();
const total = this.lines.length;
if (!total) {
this.container.innerHTML = '';
return;
}
const lh = this.lineHeight;
const viewport = this.container.clientHeight || 1;
const visible = Math.max(Math.ceil(viewport / lh) + 20, this.maxVisible);
const start = Math.max(0, Math.floor(this.container.scrollTop / lh) - Math.floor(visible / 2));
const end = Math.min(total, start + visible);
const beforeHeight = start * lh;
const afterHeight = (total - end) * lh;
const needed = end - start;
while (this.pool.length < needed) {
const node = document.createElement('div');
node.className = 'console-line';
this.pool.push(node);
}
const fragment = document.createDocumentFragment();
for (let idx = start; idx < end; idx++) {
const line = this.lines[idx];
const node = this.pool[idx - start];
node.className = line.className || 'console-line';
node.textContent = line.text;
fragment.appendChild(node);
}
this.container.innerHTML = '';
const beforeSpacer = document.createElement('div');
beforeSpacer.style.height = `${beforeHeight}px`;
const afterSpacer = document.createElement('div');
afterSpacer.style.height = `${afterHeight}px`;
this.container.appendChild(beforeSpacer);
this.container.appendChild(fragment);
this.container.appendChild(afterSpacer);
const shouldStick = (this.container.scrollTop + this.container.clientHeight) >= (this.container.scrollHeight - lh * 2);
if (shouldStick) {
this.container.scrollTop = this.container.scrollHeight;
}
}
}
const CONFIG_ENDPOINT = '/api/config';
const SYSTEM_STATUS_ENDPOINT = '/api/system/status';
@@ -2109,6 +2214,7 @@
placeholder.className = 'console-line';
placeholder.textContent = `[系统] ${appNames[app] || app} 日志就绪`;
layer.appendChild(placeholder);
logRenderers[app] = new LogVirtualList(layer);
container.appendChild(layer);
consoleLayers[app] = layer;
@@ -2136,6 +2242,7 @@
container.appendChild(layer);
consoleLayers[app] = layer;
logRenderers[app] = new LogVirtualList(layer);
return layer;
}
@@ -2169,40 +2276,28 @@
return;
}
const container = getConsoleContainer();
if (container) {
container.scrollTop = container.scrollHeight;
consoleLayerScrollPositions[app] = container.scrollTop;
const renderer = logRenderers[app];
if (renderer && renderer.container) {
renderer.container.scrollTop = renderer.container.scrollHeight;
consoleLayerScrollPositions[app] = renderer.container.scrollTop;
}
}
function appendConsoleTextLine(app, text, className = 'console-line') {
const layer = getConsoleLayer(app);
if (!layer) return;
const line = document.createElement('div');
line.className = className;
line.textContent = text;
layer.appendChild(line);
syncConsoleScroll(app);
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
renderer.append(text, className);
}
function appendConsoleElement(app, element) {
const layer = getConsoleLayer(app);
if (!layer || !element) return;
layer.appendChild(element);
syncConsoleScroll(app);
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
if (!element || !renderer.container) return;
renderer.container.appendChild(element);
renderer.scheduleRender(true);
}
function clearConsoleLayer(app, message = null) {
const layer = getConsoleLayer(app);
if (!layer) return;
layer.innerHTML = '';
if (message) {
appendConsoleTextLine(app, message);
}
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
renderer.clear(message);
}
// 加载控制台输出