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
157 lines
3.9 KiB
JavaScript
157 lines
3.9 KiB
JavaScript
// API 基地址 —— 独立 FastAPI 后端(配置见 src/config.js,可用 window.__DPM_API__ 覆盖)
|
|
import { API_BASE as BASE } from '../config';
|
|
|
|
export const API_BASE = BASE;
|
|
|
|
async function request(url, options = {}) {
|
|
const res = await fetch(`${BASE}${url}`, {
|
|
headers: { 'Content-Type': 'application/json' },
|
|
...options,
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
function formEncode(data) {
|
|
return Object.entries(data)
|
|
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
.join('&');
|
|
}
|
|
|
|
// ============ Auth ============
|
|
export async function login(username, password) {
|
|
const res = await fetch(`${BASE}/api/login`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
body: formEncode({ username, password }),
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
// ============ Settings ============
|
|
export async function getSettings() {
|
|
const res = await fetch(`${BASE}/api/settings`);
|
|
return res.json();
|
|
}
|
|
|
|
export async function updateSettings(data) {
|
|
return request('/api/settings', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
}
|
|
|
|
// ============ Media Files ============
|
|
export async function getMediaFiles() {
|
|
const res = await fetch(`${BASE}/media`);
|
|
return res.json();
|
|
}
|
|
|
|
export async function uploadFiles(files) {
|
|
for (const file of files) {
|
|
const fd = new FormData();
|
|
fd.append('file', file);
|
|
const res = await fetch(`${BASE}/upload`, { method: 'POST', body: fd });
|
|
if (!res.ok) throw new Error(`上传失败 (${res.status})`);
|
|
}
|
|
}
|
|
|
|
export async function uploadFilesWithProgress(files, onProgress) {
|
|
for (let i = 0; i < files.length; i++) {
|
|
const file = files[i];
|
|
await new Promise((resolve, reject) => {
|
|
const xhr = new XMLHttpRequest();
|
|
xhr.open('POST', `${BASE}/upload`);
|
|
|
|
xhr.upload.onprogress = (e) => {
|
|
if (e.lengthComputable) {
|
|
onProgress({
|
|
percent: Math.round((e.loaded / e.total) * 100),
|
|
loaded: e.loaded,
|
|
total: e.total,
|
|
fileName: file.name,
|
|
current: i + 1,
|
|
total: files.length,
|
|
});
|
|
}
|
|
};
|
|
|
|
xhr.onload = () => {
|
|
if (xhr.status >= 200 && xhr.status < 300) {
|
|
resolve();
|
|
} else {
|
|
reject(new Error(`上传失败 (${xhr.status})`));
|
|
}
|
|
};
|
|
|
|
xhr.onerror = () => reject(new Error('网络错误,请检查连接'));
|
|
xhr.ontimeout = () => reject(new Error('上传超时'));
|
|
|
|
const fd = new FormData();
|
|
fd.append('file', file);
|
|
xhr.send(fd);
|
|
});
|
|
}
|
|
}
|
|
|
|
export async function addUrlMedia(url) {
|
|
return request('/api/media/add-url', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ url }),
|
|
});
|
|
}
|
|
|
|
export async function deleteMedia(path) {
|
|
return request('/api/delete', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ path }),
|
|
});
|
|
}
|
|
|
|
// ============ Playlist ============
|
|
export async function getPlaylist() {
|
|
const res = await fetch(`${BASE}/api/playlist`);
|
|
return res.json();
|
|
}
|
|
|
|
export async function addToPlaylist(path) {
|
|
return request('/api/playlist/add', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ path }),
|
|
});
|
|
}
|
|
|
|
export async function removeFromPlaylist(path) {
|
|
return request('/api/playlist/remove', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ path }),
|
|
});
|
|
}
|
|
|
|
// ============ Playback Control ============
|
|
export async function sendControl(action) {
|
|
return request('/api/control', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ action }),
|
|
});
|
|
}
|
|
|
|
export async function getState() {
|
|
const res = await fetch(`${BASE}/api/state`);
|
|
return res.json();
|
|
}
|
|
|
|
export async function updateState(state) {
|
|
return request('/api/state', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ state }),
|
|
});
|
|
}
|
|
|
|
// ============ Display Commands ============
|
|
export async function sendDisplayCommand(action) {
|
|
return request('/api/display-command', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ action }),
|
|
});
|
|
}
|