diff --git a/static/app.js b/static/app.js index a77df40..a6e2791 100644 --- a/static/app.js +++ b/static/app.js @@ -13,6 +13,8 @@ const state = { history: {}, // { scriptPath: [record, ...] } scripts: [], // 配置中的全部脚本 autoscroll: true, + expandedLogs: new Set(), // 已展开的历史日志 record ID(re-render 后恢复) + lastLiveKey: null, // 上次渲染的"当前任务身份"(用于判断是否需要重建 DOM) }; // ============ Helpers ============ @@ -119,6 +121,7 @@ function renderRunning() { stateBadge.className = "badge badge-grey"; body.innerHTML = `
当前没有任务在执行
`; sizeEl.textContent = ""; + state.lastLiveKey = null; return; } @@ -126,30 +129,49 @@ function renderRunning() { stateBadge.textContent = "执行中"; stateBadge.className = "badge badge-warn"; - body.innerHTML = ` - - - `; - - const logBox = $("#live-log"); + const liveKey = `${c.task.script}|${c.task.repo}|${c.task.ref}|${c.startedAt}`; // 仅渲染尾部以避免长日志卡顿 const fullLog = c.log || ""; const tail = fullLog.length > LOG_TAIL_CHARS ? fullLog.slice(fullLog.length - LOG_TAIL_CHARS) : fullLog; + + if (state.lastLiveKey !== liveKey) { + // 任务身份变化:重建结构并贴底 + body.innerHTML = ` + + + `; + state.lastLiveKey = liveKey; + const logBox = $("#live-log"); + logBox.textContent = tail || "(等待脚本输出…)"; + sizeEl.textContent = fullLog.length + ? `${(fullLog.length / 1024).toFixed(1)} KB` + : ""; + requestAnimationFrame(() => { + logBox.scrollTop = logBox.scrollHeight; + }); + return; + } + + // 同一任务:仅更新日志内容,保持 DOM 节点稳定 + const logBox = $("#live-log"); + const distanceFromBottom = logBox.scrollHeight - logBox.scrollTop - logBox.clientHeight; + const atBottom = distanceFromBottom <= 24; logBox.textContent = tail || "(等待脚本输出…)"; sizeEl.textContent = fullLog.length ? `${(fullLog.length / 1024).toFixed(1)} KB` : ""; - - if (state.autoscroll) { - logBox.scrollTop = logBox.scrollHeight; + if (state.autoscroll && atBottom) { + requestAnimationFrame(() => { + logBox.scrollTop = logBox.scrollHeight; + }); } } @@ -195,7 +217,7 @@ function renderHistory() {