'use strict' const el = { version: document.getElementById('app-version'), stateDot: document.getElementById('state-dot'), stateLabel: document.getElementById('state-label'), stateDetail: document.getElementById('state-detail'), url: document.getElementById('k-url'), pid: document.getElementById('k-pid'), restarts: document.getElementById('k-restarts'), power: document.getElementById('k-power'), exe: document.getElementById('k-exe'), config: document.getElementById('k-config'), configWarning: document.getElementById('config-warning'), displayList: document.getElementById('display-list'), log: document.getElementById('log'), autoscroll: document.getElementById('autoscroll'), btnStart: document.getElementById('btn-start'), btnStop: document.getElementById('btn-stop'), btnRestart: document.getElementById('btn-restart'), btnRelayout: document.getElementById('btn-relayout'), btnOpenAdmin: document.getElementById('btn-open-admin'), btnClearLog: document.getElementById('btn-clear-log'), titlebar: document.getElementById('titlebar'), winMin: document.getElementById('win-min'), winMax: document.getElementById('win-max'), winClose: document.getElementById('win-close') } const DOT_CLASS = { running: 'ok', starting: 'warn', stopping: 'warn', crashed: 'err', stopped: '' } function renderStatus(status) { el.version.textContent = status.appVersion ? `v${status.appVersion}` : '' el.stateDot.className = `dot ${status.healthy ? 'ok' : DOT_CLASS[status.state] || ''}` el.stateLabel.textContent = status.stateLabel || status.state el.stateDetail.textContent = status.healthy ? '健康检查通过' : status.lastError || (status.state === 'stopped' ? '服务未运行' : '等待健康检查') el.url.textContent = status.url || '—' el.pid.textContent = status.pid || '—' el.restarts.textContent = status.restarts ? `${status.restarts} 次` : '0 次' el.power.textContent = status.powerSaveBlocked ? '已启用(阻止显示器休眠)' : '未启用' el.exe.textContent = status.exePath || '—' el.config.textContent = status.configPath || '—' if (status.configWarning) { el.configWarning.hidden = false el.configWarning.textContent = status.configWarning } else { el.configWarning.hidden = true } const busy = ['starting', 'running', 'stopping'].includes(status.state) el.btnStart.disabled = busy el.btnStop.disabled = !busy el.btnRestart.disabled = !busy renderDisplays(status.displays || []) } function renderDisplays(displays) { el.displayList.textContent = '' if (!displays.length) { const li = document.createElement('li') li.className = 'muted' li.textContent = '尚未打开大屏窗口' el.displayList.append(li) return } for (const item of displays) { const li = document.createElement('li') const page = document.createElement('span') page.className = 'page' page.textContent = `屏 ${item.displayId} · ${item.page}` const url = document.createElement('span') url.className = 'url' url.textContent = item.url || '(加载中)' li.append(page, url) el.displayList.append(li) } } function appendLog(line) { const atBottom = el.log.scrollTop + el.log.clientHeight >= el.log.scrollHeight - 8 el.log.textContent += `${line}\n` if (el.autoscroll.checked && atBottom) { el.log.scrollTop = el.log.scrollHeight } } function setLog(lines) { el.log.textContent = lines.length ? `${lines.join('\n')}\n` : '' el.log.scrollTop = el.log.scrollHeight } async function refresh() { renderStatus(await window.consoleApi.getStatus()) } function renderMaximized(maximized) { el.winMax.classList.toggle('is-maximized', maximized) const label = maximized ? '还原' : '最大化' el.winMax.title = label el.winMax.setAttribute('aria-label', label) } function bindWindowControls() { el.winMin.addEventListener('click', () => window.consoleApi.minimize()) el.winMax.addEventListener('click', () => window.consoleApi.maximize()) el.winClose.addEventListener('click', () => window.consoleApi.close()) // 双击标题栏切换最大化;点在按钮上的双击不算,否则连点会误触 el.titlebar.addEventListener('dblclick', (event) => { if (event.target instanceof Element && event.target.closest('.win-btn')) return window.consoleApi.maximize() }) window.consoleApi.onMaximizeChange(renderMaximized) window.consoleApi.isMaximized().then(renderMaximized) } function bindActions() { el.btnStart.addEventListener('click', async () => renderStatus(await window.consoleApi.startService())) el.btnStop.addEventListener('click', async () => renderStatus(await window.consoleApi.stopService())) el.btnRestart.addEventListener('click', async () => renderStatus(await window.consoleApi.restartService())) el.btnRelayout.addEventListener('click', async () => renderStatus(await window.consoleApi.relayoutDisplays())) el.btnClearLog.addEventListener('click', async () => { await window.consoleApi.clearLog() setLog([]) }) el.btnOpenAdmin.addEventListener('click', () => window.consoleApi.openServerAdmin()) } async function boot() { bindActions() bindWindowControls() window.consoleApi.onStatus(renderStatus) window.consoleApi.onLog(appendLog) setLog(await window.consoleApi.getLog()) await refresh() } boot()