From 66b6a7fbbd4ec4d328d2cd772d2f284a82accad6 Mon Sep 17 00:00:00 2001 From: Pine Date: Tue, 18 Aug 2026 02:32:27 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E7=AE=A1=E7=90=86=E5=90=8E?= =?UTF-8?q?=E5=8F=B0=E5=8A=A0=E5=85=A5=E3=80=8C=E5=A4=A7=E5=B1=8F=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=EF=BC=88MQTT=20=E6=8C=87=E4=BB=A4=EF=BC=89=E3=80=8D?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=EF=BC=8C=E8=A6=86=E7=9B=96=E5=85=A8=E9=83=A8?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - routers.py:_VALID_DISPLAY_ACTIONS 扩展为全命令白名单 (navigate/navigate_rel/vision_set/ai_input/ai_preset/ai_company/ai_zone/ voice_start/voice_stop/voice_refresh/play/pause/next/prev/set_mode/play_target/ alert/show_card/minimize/settings_changed/playlist_changed) - admin.html:新增控制面板(页面导航含/voice+左右、视觉开关、AI 提问/预设/企业/分区、 语音启停/刷新、媒体控制、全局通知) - admin.js:data-mqtt 按钮(完整指令 JSON)→ /api/display/command; AI 提问/通知输入框回车发送 --- backend/app/routers.py | 18 +++++++-- backend/static/admin.css | 17 ++++++++ backend/static/admin.js | 40 +++++++++++++++++- backend/templates/admin.html | 78 ++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 4 deletions(-) diff --git a/backend/app/routers.py b/backend/app/routers.py index e839428..8f3ef8c 100644 --- a/backend/app/routers.py +++ b/backend/app/routers.py @@ -347,9 +347,21 @@ async def ai_asr(file: UploadFile = File(...), format: str = "m4a"): # ==================== 展示控制(管理端 → MQTT) ==================== - -_VALID_DISPLAY_ACTIONS = {"navigate", "navigate_rel", "play", "pause", "next", "prev", - "alert", "show_card", "set_mode", "minimize"} +# 全部 MQTT 前端控制命令白名单(与 docs/mqtt-commands.md 保持一致) +_VALID_DISPLAY_ACTIONS = { + # 页面 + "navigate", "navigate_rel", + # 全局视觉 + "vision_set", + # AI 助手页 + "ai_input", "ai_preset", "ai_company", "ai_zone", + # 语音对话页 + "voice_start", "voice_stop", "voice_refresh", + # 媒体 + "play", "pause", "next", "prev", "set_mode", "play_target", + # 全局 + "alert", "show_card", "minimize", "settings_changed", "playlist_changed", +} @router.post("/api/display/command") diff --git a/backend/static/admin.css b/backend/static/admin.css index 4d66bc0..6bd0cee 100644 --- a/backend/static/admin.css +++ b/backend/static/admin.css @@ -1241,3 +1241,20 @@ button { font-family: "Poppins", "PingFang SC", sans-serif; cursor: pointer; tra overflow: hidden; text-overflow: ellipsis; } + +/* ============ 大屏控制(MQTT 指令)面板 ============ */ +.mqtt-group { margin: 10px 0 4px; } +.mqtt-group-title { + font-size: 12px; font-weight: 600; color: #5a6b8c; + margin-bottom: 6px; letter-spacing: 0.04em; +} +.mqtt-row { + display: flex; flex-wrap: wrap; gap: 6px; align-items: center; + margin-bottom: 6px; +} +.mqtt-input { + flex: 1; min-width: 180px; + padding: 6px 10px; border: 1px solid #d7e1f0; border-radius: 8px; + font-size: 13px; color: #1d2c44; outline: none; +} +.mqtt-input:focus { border-color: #2f6bff; } diff --git a/backend/static/admin.js b/backend/static/admin.js index eba9d69..d6a4191 100644 --- a/backend/static/admin.js +++ b/backend/static/admin.js @@ -99,8 +99,15 @@ /* ========================================================= 事件委托:页面切换 / 播放控制 / 媒体操作 ========================================================= */ + // 输入框回车提交 + document.addEventListener('keydown', function (e) { + if (e.key !== 'Enter') return; + if (e.target && e.target.id === 'mqttAiInput') { document.getElementById('mqttAiSend').click(); } + else if (e.target && e.target.id === 'mqttAlertText') { document.getElementById('mqttAlertSend').click(); } + }); + document.addEventListener('click', function (e) { - var btn = e.target.closest ? e.target.closest('[data-nav], [data-control], [data-action], .copy-btn, [data-preview]') : null; + var btn = e.target.closest ? e.target.closest('[data-nav], [data-mqtt], [data-control], [data-action], .copy-btn, [data-preview], #mqttAiSend, #mqttAlertSend') : null; if (!btn) return; e.stopPropagation(); @@ -118,6 +125,37 @@ return; } + // 大屏 MQTT 指令(data-mqtt 携带完整 {action, params} JSON) + if (btn.hasAttribute('data-mqtt')) { + var cmd = null; + try { cmd = JSON.parse(btn.getAttribute('data-mqtt')); } catch (err) { toast('指令 JSON 错误', 'error'); return; } + post('/api/display/command', cmd, function (d) { + toast(d && d.ok !== false ? '指令已发送:' + (cmd.action || '') : '发送失败(MQTT 未连接?)', + d && d.ok !== false ? 'ok' : 'error'); + }); + return; + } + + // AI 提问输入框 + if (btn.id === 'mqttAiSend') { + var text = (document.getElementById('mqttAiInput').value || '').trim(); + if (!text) { toast('请输入问题', 'error'); return; } + post('/api/display/command', { action: 'ai_input', params: { text: text } }, function () { + toast('已发送问题到大屏 AI 页'); + }); + return; + } + + // 通知输入框 + if (btn.id === 'mqttAlertSend') { + var alertText = (document.getElementById('mqttAlertText').value || '').trim(); + if (!alertText) { toast('请输入通知内容', 'error'); return; } + post('/api/display/command', { action: 'alert', params: { text: alertText } }, function () { + toast('通知已广播'); + }); + return; + } + // 页面切换 if (btn.hasAttribute('data-nav')) { var page = btn.getAttribute('data-nav'); diff --git a/backend/templates/admin.html b/backend/templates/admin.html index 7da49ae..f92ddf3 100644 --- a/backend/templates/admin.html +++ b/backend/templates/admin.html @@ -118,6 +118,84 @@ + +
+
+
+

大屏控制(MQTT 指令)

+
+ + +
+
页面导航
+
+ + + +
+
+ + +
+
人物识别(全局)
+
+ + +
+
+ + +
+
AI 助手页
+
+ + +
+
+ + + + + +
+
+ + + +
+
+ + +
+
语音对话页
+
+ + + +
+
+ + +
+
媒体轮播
+
+ + + + +
+
+ + +
+
全局通知
+
+ + +
+
+
+