Refactor LED control UI to Vue3

This commit is contained in:
sealks
2026-07-17 02:10:08 +08:00
parent fe906e028b
commit ba9f0044f3
16 changed files with 1906 additions and 752 deletions
+117 -23
View File
@@ -7,25 +7,95 @@
<link rel="stylesheet" href="/static/admin/styles.css" />
</head>
<body>
<main class="shell">
<main id="adminApp" class="shell" v-cloak>
<header class="topbar">
<div>
<h1>LED 大屏投放控制台</h1>
<p id="stateText">正在连接控制服务...</p>
<p>{{ statusText }}</p>
</div>
<div class="connection">
<span id="wsDot" class="dot"></span>
<span id="wsText">WebSocket</span>
<span class="dot" :class="{ ok: wsConnected }"></span>
<span>{{ wsText }}</span>
</div>
</header>
<section class="grid">
<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 id="refreshBtn">刷新</button>
<button type="button" @click="loadScenes">刷新</button>
</div>
<div id="sceneList" class="sceneList"></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">
@@ -38,30 +108,54 @@
<a href="/api/sync/status" target="_blank">同步状态</a>
<a href="/docs" target="_blank">API 文档</a>
</div>
<div id="syncStatus" class="syncStatus"></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">
<h2>局部动作</h2>
<div class="panelHead compact">
<div>
<h2>生产命令回显</h2>
<p class="panelHint">真实输出端 ACK,不代表上方预览</p>
</div>
</div>
<div class="actionGrid">
<button data-action="page.prev">上一页</button>
<button data-action="page.next">下一页</button>
<button data-action="energy.mode" data-args='{"mode":"削峰"}'>削峰模式</button>
<button data-action="energy.mode" data-args='{"mode":"保供"}'>保供模式</button>
<button data-action="security.alert" data-args='{"text":"门禁异常联动"}'>新增告警</button>
<button data-action="timeline.pause">暂停时间轴</button>
<button data-action="timeline.resume">恢复时间轴</button>
<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>
<label class="customAction">
<span>自定义结构化动作</span>
<textarea id="actionInput" rows="5" placeholder='{"action":"security.alert","args":{"text":"消防通道占用"}}'></textarea>
</label>
<button id="runActionBtn" class="primary">同步执行</button>
</section>
</section>
</main>
<script src="/static/vendor/vue.global.prod.js"></script>
<script src="/static/admin/main.js"></script>
</body>
</html>