feat: M0-M2 骨架落地(服务端/前端/宿主/数据库/积分引擎)
- server: Go 单二进制骨架,embed 内嵌前端,配置/迁移/HTTP 路由 - store: system.db + event.db 两套迁移,业务表 + 种子数据 - domain: 积分引擎(并列三口径/计分通道/流水/排名/破纪录)+ 68 个测试 - web: Vite + Vue 三入口骨架 + shared 层(类型/API/WebSocket 客户端) - console: Electron 宿主骨架(选屏/防熄屏/进程守护/托盘)
This commit is contained in:
1 parent
a61d793d79
commit
7d5d94f815
76 files changed
+10131
No files matched your search
@@ -0,0 +1,130 @@
|
||||
'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')
|
||||
}
|
||||
|
||||
const DOT_CLASS = {
|
||||
running: 'ok',
|
||||
starting: 'warn',
|
||||
stopping: 'warn',
|
||||
crashed: 'err',
|
||||
stopped: ''
|
||||
}
|
||||
|
||||
let lastStatus = null
|
||||
|
||||
function renderStatus(status) {
|
||||
lastStatus = 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 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', () => {
|
||||
const url = (lastStatus && lastStatus.adminUrl) || 'http://127.0.0.1:8080/admin'
|
||||
window.consoleApi.openExternal(url)
|
||||
})
|
||||
}
|
||||
|
||||
async function boot() {
|
||||
bindActions()
|
||||
window.consoleApi.onStatus(renderStatus)
|
||||
window.consoleApi.onLog(appendLog)
|
||||
setLog(await window.consoleApi.getLog())
|
||||
await refresh()
|
||||
}
|
||||
|
||||
boot()
|
||||
Reference in new issue
Block a user