856ff88440
后端(backend/,FastAPI :10085): - 全部数据 REST 接口:dashboard 快照 / 企业分区 / 播放列表 / 媒体 / 设置 - MQTT 控制通道:opc/display/command(切页/播放/卡片/通知), 管理端 POST /api/display/command → MQTT 广播 → 所有大屏同步响应 - 阿里云 DashScope:通义千问 LLM + 函数调用(工具经 MQTT 广播), paraformer-realtime-v2 语音识别 /api/ai/asr - 媒体资源统一由后端存储返回(上传/列表/静态服务) - SSE /api/events 保留作 MQTT 不可用时的兼容回退 前端: - config.js + .env.local 配置后端地址与 MQTT 账号(前端 dpm / 服务端 dpmserver) - mqtt.js 客户端 + useMqttControl(MQTT 驱动切页/媒体/卡片/通知) - DpmOverlays 全局覆盖层(通知 toast + 企业/分区/总览卡片) - useParkSim 改为后端 API 数据源(离线回退本地模拟) - AiChatPanel:对话走后端 LLM(工具调用),语音走本地录音 + 后端 ASR - MediaScreen:媒体控制走 MQTT(保留 SSE 回退),修复 useEffect TDZ Rust:lib.rs 移除内嵌 HTTP 服务器,只保留薄壳(窗口/权限/自启/npc 隧道) 安全:backend/.env、.env.local、media、data.json 已 gitignore
83 lines
2.3 KiB
JavaScript
83 lines
2.3 KiB
JavaScript
import { useState, useEffect } from 'react';
|
||
import mqtt from 'mqtt';
|
||
import { MQTT_URL, MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK, MQTT_USERNAME, MQTT_PASSWORD } from '../config';
|
||
|
||
/* =========================================================
|
||
MQTT 客户端(mqtt.js)—— 后端控制通道
|
||
订阅 opc/display/command(页面/媒体/卡片控制)与 opc/dashboard/tick(数据快照)
|
||
断线自动重连;未连接时静默降级(页面仍可本地操作)
|
||
========================================================= */
|
||
|
||
let client = null;
|
||
let status = 'connecting'; // connecting | connected | offline
|
||
const listeners = new Set();
|
||
|
||
function notifyStatus() {
|
||
window.dispatchEvent(new CustomEvent('dpm:mqtt-status', { detail: { status } }));
|
||
}
|
||
|
||
export function connectMqtt() {
|
||
if (client) return;
|
||
try {
|
||
client = mqtt.connect(MQTT_URL, {
|
||
reconnectPeriod: 3000,
|
||
connectTimeout: 8000,
|
||
clientId: `dpm-screen-${Math.random().toString(16).slice(2, 8)}`,
|
||
clean: true,
|
||
username: MQTT_USERNAME || undefined,
|
||
password: MQTT_PASSWORD || undefined,
|
||
});
|
||
client.on('connect', () => {
|
||
status = 'connected';
|
||
notifyStatus();
|
||
client.subscribe([MQTT_TOPIC_COMMAND, MQTT_TOPIC_TICK], { qos: 1 });
|
||
});
|
||
client.on('reconnect', () => {
|
||
status = 'connecting';
|
||
notifyStatus();
|
||
});
|
||
client.on('close', () => {
|
||
status = 'offline';
|
||
notifyStatus();
|
||
});
|
||
client.on('error', () => {
|
||
status = 'offline';
|
||
notifyStatus();
|
||
});
|
||
client.on('message', (topic, payload) => {
|
||
let data = null;
|
||
try {
|
||
data = JSON.parse(payload.toString());
|
||
} catch {
|
||
return;
|
||
}
|
||
listeners.forEach((fn) => fn(topic, data));
|
||
});
|
||
} catch (e) {
|
||
status = 'offline';
|
||
notifyStatus();
|
||
}
|
||
}
|
||
|
||
export function getMqttStatus() {
|
||
return status;
|
||
}
|
||
|
||
/** 订阅 MQTT 消息,返回取消订阅函数 */
|
||
export function onMqttMessage(fn) {
|
||
listeners.add(fn);
|
||
return () => listeners.delete(fn);
|
||
}
|
||
|
||
/** React Hook:MQTT 连接状态 */
|
||
export function useMqttStatus() {
|
||
const [s, setS] = useState(status);
|
||
useEffect(() => {
|
||
connectMqtt();
|
||
const on = (e) => setS(e.detail.status);
|
||
window.addEventListener('dpm:mqtt-status', on);
|
||
return () => window.removeEventListener('dpm:mqtt-status', on);
|
||
}, []);
|
||
return s;
|
||
}
|