Optimize Front-End Memory Usage
This commit is contained in:
+119
-24
@@ -1226,6 +1226,111 @@
|
|||||||
const consoleLayers = {};
|
const consoleLayers = {};
|
||||||
const consoleLayerScrollPositions = {};
|
const consoleLayerScrollPositions = {};
|
||||||
let activeConsoleLayer = currentApp;
|
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 CONFIG_ENDPOINT = '/api/config';
|
||||||
const SYSTEM_STATUS_ENDPOINT = '/api/system/status';
|
const SYSTEM_STATUS_ENDPOINT = '/api/system/status';
|
||||||
@@ -2109,6 +2214,7 @@
|
|||||||
placeholder.className = 'console-line';
|
placeholder.className = 'console-line';
|
||||||
placeholder.textContent = `[系统] ${appNames[app] || app} 日志就绪`;
|
placeholder.textContent = `[系统] ${appNames[app] || app} 日志就绪`;
|
||||||
layer.appendChild(placeholder);
|
layer.appendChild(placeholder);
|
||||||
|
logRenderers[app] = new LogVirtualList(layer);
|
||||||
|
|
||||||
container.appendChild(layer);
|
container.appendChild(layer);
|
||||||
consoleLayers[app] = layer;
|
consoleLayers[app] = layer;
|
||||||
@@ -2136,6 +2242,7 @@
|
|||||||
|
|
||||||
container.appendChild(layer);
|
container.appendChild(layer);
|
||||||
consoleLayers[app] = layer;
|
consoleLayers[app] = layer;
|
||||||
|
logRenderers[app] = new LogVirtualList(layer);
|
||||||
return layer;
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2169,40 +2276,28 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const container = getConsoleContainer();
|
const renderer = logRenderers[app];
|
||||||
if (container) {
|
if (renderer && renderer.container) {
|
||||||
container.scrollTop = container.scrollHeight;
|
renderer.container.scrollTop = renderer.container.scrollHeight;
|
||||||
consoleLayerScrollPositions[app] = container.scrollTop;
|
consoleLayerScrollPositions[app] = renderer.container.scrollTop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendConsoleTextLine(app, text, className = 'console-line') {
|
function appendConsoleTextLine(app, text, className = 'console-line') {
|
||||||
const layer = getConsoleLayer(app);
|
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
|
||||||
if (!layer) return;
|
renderer.append(text, className);
|
||||||
|
|
||||||
const line = document.createElement('div');
|
|
||||||
line.className = className;
|
|
||||||
line.textContent = text;
|
|
||||||
layer.appendChild(line);
|
|
||||||
syncConsoleScroll(app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function appendConsoleElement(app, element) {
|
function appendConsoleElement(app, element) {
|
||||||
const layer = getConsoleLayer(app);
|
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
|
||||||
if (!layer || !element) return;
|
if (!element || !renderer.container) return;
|
||||||
|
renderer.container.appendChild(element);
|
||||||
layer.appendChild(element);
|
renderer.scheduleRender(true);
|
||||||
syncConsoleScroll(app);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearConsoleLayer(app, message = null) {
|
function clearConsoleLayer(app, message = null) {
|
||||||
const layer = getConsoleLayer(app);
|
const renderer = logRenderers[app] || (logRenderers[app] = new LogVirtualList(getConsoleLayer(app)));
|
||||||
if (!layer) return;
|
renderer.clear(message);
|
||||||
|
|
||||||
layer.innerHTML = '';
|
|
||||||
if (message) {
|
|
||||||
appendConsoleTextLine(app, message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载控制台输出
|
// 加载控制台输出
|
||||||
|
|||||||
Reference in New Issue
Block a user