Files
2026-07-17 02:10:08 +08:00

162 lines
5.6 KiB
HTML

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LED 大屏控制台</title>
<link rel="stylesheet" href="/static/admin/styles.css" />
</head>
<body>
<main id="adminApp" class="shell" v-cloak>
<header class="topbar">
<div>
<h1>LED 大屏投放控制台</h1>
<p>{{ statusText }}</p>
</div>
<div class="connection">
<span class="dot" :class="{ ok: wsConnected }"></span>
<span>{{ wsText }}</span>
</div>
</header>
<section class="consoleLayout">
<section class="panel previewPanel">
<div class="panelHead">
<div>
<h2>画面回显</h2>
<p class="panelHint">14880 x 3510 拼接预览,只读不 ACK</p>
</div>
<div class="previewLinks">
<a href="/output/left" target="_blank">左输出</a>
<a href="/output/right" target="_blank">右输出</a>
</div>
</div>
<div class="previewGrid">
<article class="previewTile">
<header>
<strong>left</strong>
<span>{{ previewLabels.left }}</span>
</header>
<iframe title="left 输出预览" src="/output/left?preview=1"></iframe>
</article>
<article class="previewTile">
<header>
<strong>right</strong>
<span>{{ previewLabels.right }}</span>
</header>
<iframe title="right 输出预览" src="/output/right?preview=1"></iframe>
</article>
</div>
</section>
<section class="panel scenes">
<div class="panelHead">
<h2>场景切换</h2>
<button type="button" @click="loadScenes">刷新</button>
</div>
<div class="sceneList">
<article
v-for="scene in scenes"
:key="scene.id"
class="scene"
:class="{ active: scene.id === state?.active_scene_id }"
>
<div>
<h3>{{ scene.name }}</h3>
<p>{{ scene.view }} | {{ scene.url }}</p>
<p>{{ scene.description }}</p>
</div>
<button
type="button"
:disabled="busySceneId === scene.id"
@click="switchScene(scene.id)"
>
{{ busySceneId === scene.id ? "计划中" : "切换" }}
</button>
</article>
</div>
</section>
<section class="panel">
<div class="panelHead">
<h2>局部动作</h2>
</div>
<div class="actionGrid">
<button
v-for="action in quickActions"
:key="action.action + JSON.stringify(action.args || {})"
type="button"
@click="postAction(action.action, action.args || {})"
>
{{ action.label }}
</button>
</div>
<label class="customAction">
<span>自定义结构化动作</span>
<textarea v-model="actionInput" rows="3"></textarea>
</label>
<button type="button" class="primary" @click="runCustomAction">同步执行</button>
</section>
<section class="panel">
<div class="panelHead">
<h2>输出与同步</h2>
</div>
<div class="linkGrid">
<a href="/output/left" target="_blank">打开左输出</a>
<a href="/output/right" target="_blank">打开右输出</a>
<a href="/api/sync/status" target="_blank">同步状态</a>
<a href="/docs" target="_blank">API 文档</a>
</div>
<div class="syncStatus">
<div class="syncRow">
<span>输出节点</span>
<strong>{{ nodes.length }}/{{ targetTiles.length }}</strong>
</div>
<div v-for="node in nodes" :key="node.node_id" class="syncRow">
<span>{{ node.tile_id }} {{ node.node_id.slice(0, 16) }}</span>
<span>{{ nodeMetric(node) }}</span>
</div>
</div>
</section>
<section class="panel">
<div class="panelHead compact">
<div>
<h2>生产命令回显</h2>
<p class="panelHint">真实输出端 ACK,不代表上方预览</p>
</div>
</div>
<div class="commandEcho">
<div v-if="recentCommands.length === 0" class="emptyEcho">暂无命令回显</div>
<article v-for="command in recentCommands" :key="command.command_id" class="echoCard">
<div class="echoHead">
<div>
<h4>{{ commandTitle(command) }}</h4>
<p>{{ command.command_id.slice(0, 12) }} | 计划 {{ formatClock(command.apply_at_ms) }} | {{ releaseText(command) }}</p>
<p>{{ commandNote(command) }}</p>
</div>
<span class="echoState" :class="commandStateClass(command)">{{ commandStateText(command) }}</span>
</div>
<div class="tileEchoGrid">
<div
v-for="tileId in targetTiles"
:key="command.command_id + tileId"
class="tileEcho"
:class="tileEchoClass(command, tileId)"
>
<span>{{ tileId }}</span>
<strong>{{ tileEchoLabel(command, tileId) }}</strong>
<small>{{ tileEchoDetail(command, tileId) }}</small>
</div>
</div>
</article>
</div>
</section>
</section>
</main>
<script src="/static/vendor/vue.global.prod.js"></script>
<script src="/static/admin/main.js"></script>
</body>
</html>