162 lines
5.6 KiB
HTML
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>
|