feat(admin): 管理后台加入「大屏控制(MQTT 指令)」面板,覆盖全部前端命令
- 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 提问/通知输入框回车发送
This commit is contained in:
+15
-3
@@ -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")
|
||||
|
||||
@@ -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; }
|
||||
|
||||
+39
-1
@@ -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');
|
||||
|
||||
@@ -118,6 +118,84 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 大屏 MQTT 控制(全部前端命令) -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-icon"><svg viewBox="0 0 24 24" width="20" height="20" fill="none" stroke="#1D5DCE" stroke-width="1.6"><path d="M2 6a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2z"/><path d="M8 10h8M8 14h4"/></svg></div>
|
||||
<h3>大屏控制(MQTT 指令)</h3>
|
||||
</div>
|
||||
|
||||
<!-- 页面导航 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">页面导航</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"navigate","params":{"page":"/voice"}}'>语音对话</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"navigate_rel","params":{"delta":-1}}'>← 向左</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"navigate_rel","params":{"delta":1}}'>向右 →</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视觉识别 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">人物识别(全局)</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"vision_set","params":{"enabled":true}}'>开启识别</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"vision_set","params":{"enabled":false}}'>关闭识别</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AI 助手页 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">AI 助手页</div>
|
||||
<div class="mqtt-row">
|
||||
<input class="mqtt-input" id="mqttAiInput" type="text" placeholder="输入问题,回车发送到 AI 页">
|
||||
<button class="page-switch-btn" id="mqttAiSend">发送问题</button>
|
||||
</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_preset","params":{"index":0}}'>预设①入驻</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_preset","params":{"index":1}}'>预设②政策</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_preset","params":{"index":2}}'>预设③企业</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_company","params":{"action":"next"}}'>下一个企业</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_company","params":{"action":"prev"}}'>上一个企业</button>
|
||||
</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_zone","params":{"zone":"加速区"}}'>加速区</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_zone","params":{"zone":"国际区"}}'>国际区</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"ai_zone","params":{"zone":"成长区"}}'>成长区</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 语音对话页 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">语音对话页</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"voice_start","params":{}}'>启动对话</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"voice_stop","params":{}}'>关闭对话</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"voice_refresh","params":{}}'>刷新页面</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 媒体控制 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">媒体轮播</div>
|
||||
<div class="mqtt-row">
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"prev","params":{}}'>上一段</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"play","params":{}}'>播放</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"pause","params":{}}'>暂停</button>
|
||||
<button class="page-switch-btn" data-mqtt='{"action":"next","params":{}}'>下一段</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通知 -->
|
||||
<div class="mqtt-group">
|
||||
<div class="mqtt-group-title">全局通知</div>
|
||||
<div class="mqtt-row">
|
||||
<input class="mqtt-input" id="mqttAlertText" type="text" placeholder="通知内容,回车广播">
|
||||
<button class="page-switch-btn" id="mqttAlertSend">广播通知</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 播放设置 -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
|
||||
Reference in New Issue
Block a user