Files
DPM/backend/static/admin.js
T
Pine 1a95ecbacb feat: 管理后台补齐完整功能
- 页面切换卡片:数据大屏/数字孪生/AI 助手/媒体轮播 → MQTT navigate 控制所有大屏
- 立即播放:新增 POST /api/playlist/play(加入播放列表 + MQTT play_target),
  MediaScreen 监听 play_target 跳转并播放指定媒体
- 远程 URL 支持两种操作:添加到媒体库 / 加入播放列表
- 本地媒体卡片显示 FastAPI 输出 URL(/file/...)+ 一键复制
- 预览弹窗增加「立即播放」;新增 page-switch/media-url/copy-btn 样式
2026-08-17 21:38:04 +08:00

271 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =========================================================
Admin 管理后台前端逻辑(Jinja 页面配套)
动态操作调用 FastAPI REST,完成后整页刷新(服务端重渲染)
========================================================= */
(function () {
'use strict';
var init = window.__DPM_INIT || { fullscreen: true, autostart: false };
var fullscreen = !!init.fullscreen;
var autostart = !!init.autostart;
/* ---------- Toast ---------- */
var toastContainer = document.getElementById('toastContainer');
function toast(msg, type) {
if (!toastContainer) return;
var el = document.createElement('div');
el.className = 'toast ' + (type || 'success');
el.textContent = msg;
toastContainer.appendChild(el);
setTimeout(function () { el.remove(); }, 2800);
}
/* ---------- 通用请求 ---------- */
function post(url, body, cb) {
fetch(url, {
method: 'POST',
headers: body ? { 'Content-Type': 'application/json' } : undefined,
body: body ? JSON.stringify(body) : undefined,
})
.then(function (r) { return r.json().catch(function () { return {}; }); })
.then(function (d) {
if (d && d.ok === false) { toast(d.error || '操作失败', 'error'); return; }
if (cb) cb(d);
})
.catch(function () { toast('网络错误', 'error'); });
}
function reload(msg) {
if (msg) toast(msg);
setTimeout(function () { location.reload(); }, 350);
}
/* ---------- 播放状态(SSE ---------- */
var statusText = document.getElementById('statusText');
try {
var es = new EventSource('/api/events');
es.onmessage = function (e) {
var msg; try { msg = JSON.parse(e.data); } catch (err) { return; }
if (msg.action === 'state_update' && msg.state && statusText) {
var s = msg.state;
if (!s.status || s.status === 'idle' || !s.name) statusText.textContent = '等待大屏连接...';
else if (s.status === 'playing') statusText.textContent = '正在播放 — ' + s.name;
else if (s.status === 'paused') statusText.textContent = '已暂停 — ' + s.name;
}
if (msg.action === 'playlist_changed') reload();
};
} catch (err) { /* ignore */ }
/* ---------- 页面切换(MQTT 控制大屏) ---------- */
document.querySelectorAll('[data-nav]').forEach(function (btn) {
btn.addEventListener('click', function () {
var page = btn.getAttribute('data-nav');
post('/api/display/command', { action: 'navigate', params: { page: page } }, function () {
toast('已切换大屏至「' + btn.textContent.trim() + '」');
});
});
});
/* ---------- 播放控制 ---------- */
document.querySelectorAll('[data-control]').forEach(function (btn) {
btn.addEventListener('click', function () {
var action = btn.getAttribute('data-control');
if (action === 'minimize') {
post('/api/display/command', { action: 'minimize', params: {} });
} else {
post('/api/control', { action: action });
}
});
});
/* ---------- 播放设置 ---------- */
var playMode = document.getElementById('playMode');
if (playMode) playMode.addEventListener('change', function () {
post('/api/settings', { play_mode: playMode.value }, function () { toast('播放模式已更新'); });
});
var imageDuration = document.getElementById('imageDuration');
if (imageDuration) imageDuration.addEventListener('change', function () {
var v = Number(imageDuration.value);
if (v < 1 || v > 300) { toast('时长需在 1-300 秒', 'error'); imageDuration.value = 5; return; }
post('/api/settings', { image_duration: v }, function () { toast('图片时长已更新'); });
});
var volumeRange = document.getElementById('volumeRange');
var volumeLabel = document.getElementById('volumeLabel');
var volTimer = null;
if (volumeRange && volumeLabel) {
volumeRange.addEventListener('input', function () {
volumeLabel.textContent = volumeRange.value + '%';
if (volTimer) clearTimeout(volTimer);
volTimer = setTimeout(function () {
post('/api/settings', { volume: Number(volumeRange.value) }, function () { toast('音量已更新'); });
}, 300);
});
}
/* ---------- 显示控制 ---------- */
var btnFullscreen = document.getElementById('btnFullscreen');
function renderDisplay() {
if (btnFullscreen) btnFullscreen.textContent = fullscreen ? '切换窗口模式' : '切换大屏模式';
var btnAutostart = document.getElementById('btnAutostart');
if (btnAutostart) { btnAutostart.textContent = autostart ? '已开启' : '已关闭'; btnAutostart.classList.toggle('active', autostart); }
}
if (btnFullscreen) btnFullscreen.addEventListener('click', function () {
fullscreen = !fullscreen;
renderDisplay();
post('/api/settings', { fullscreen: fullscreen });
});
var btnAutostart = document.getElementById('btnAutostart');
if (btnAutostart) btnAutostart.addEventListener('click', function () {
autostart = !autostart;
renderDisplay();
post('/api/settings', { autostart: autostart });
});
renderDisplay();
/* ---------- 上传(带进度) ---------- */
var btnUpload = document.getElementById('btnUpload');
if (btnUpload) btnUpload.addEventListener('click', function () {
var inp = document.getElementById('fileInput');
var fileList = Array.from(inp.files || []);
if (fileList.length === 0) { toast('请选择文件', 'error'); return; }
var prog = document.getElementById('uploadProgress');
var fill = document.getElementById('progressFill');
var text = document.getElementById('progressText');
prog.style.display = 'block';
var done = 0;
fileList.forEach(function (file) {
var fd = new FormData();
fd.append('file', file);
var xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
var pct = Math.round((e.loaded / e.total) * 100);
var overall = Math.round(((done + e.loaded / e.total) / fileList.length) * 100);
fill.style.width = overall + '%';
text.textContent = file.name + ' (' + (done + 1) + '/' + fileList.length + ') — ' + pct + '%';
}
};
xhr.onload = function () {
done += 1;
if (done === fileList.length) reload('上传完成');
};
xhr.onerror = function () { toast(file.name + ' 上传失败', 'error'); done += 1; };
xhr.send(fd);
});
});
/* ---------- 远程 URL ---------- */
var btnAddUrl = document.getElementById('btnAddUrl');
var btnAddUrlPlay = document.getElementById('btnAddUrlPlay');
function addUrlThen(play) {
var inp = document.getElementById('urlInput');
var url = (inp.value || '').trim();
if (!url) { toast('请输入 URL', 'error'); return; }
post('/api/media/add-url', { url: url }, function (d) {
if (play) {
post('/api/playlist/play', { path: url }, function () { reload('已加入并开始播放'); });
} else {
reload(d && d.duplicate ? 'URL 已存在于媒体库' : '已添加到媒体库');
}
});
}
if (btnAddUrl) btnAddUrl.addEventListener('click', function () { addUrlThen(false); });
if (btnAddUrlPlay) btnAddUrlPlay.addEventListener('click', function () { addUrlThen(true); });
/* ---------- 复制资源 URLFastAPI 输出) ---------- */
document.querySelectorAll('.copy-btn').forEach(function (btn) {
btn.addEventListener('click', function (e) {
e.stopPropagation();
var url = btn.closest('.media-url').getAttribute('data-url') || '';
var full = /^https?:\/\//.test(url) ? url : window.location.origin + url;
var done = function () { toast('链接已复制'); };
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(full).then(done).catch(function () { fallbackCopy(full); done(); });
} else {
fallbackCopy(full); done();
}
});
});
function fallbackCopy(text) {
var ta = document.createElement('textarea');
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0';
document.body.appendChild(ta); ta.select();
try { document.execCommand('copy'); } catch (err) { /* ignore */ }
document.body.removeChild(ta);
}
/* ---------- 媒体操作(立即播放 / 加入播放 / 移出 / 删除) ---------- */
document.querySelectorAll('[data-action]').forEach(function (btn) {
btn.addEventListener('click', function (e) {
e.stopPropagation();
var card = btn.closest('.media-card');
var path = card.getAttribute('data-path');
var action = btn.getAttribute('data-action');
if (action === 'play-now') {
post('/api/playlist/play', { path: path }, function () { toast('正在播放'); });
} else if (action === 'add-playlist') {
post('/api/playlist/add', { path: path }, function () { reload('已加入播放列表'); });
} else if (action === 'remove-playlist') {
post('/api/playlist/remove', { path: path }, function () { reload('已移出播放列表'); });
} else if (action === 'delete') {
if (!window.confirm('确定删除该文件?')) return;
post('/api/delete', { path: path }, function () { reload('已删除'); });
}
});
});
/* ---------- 预览弹窗 ---------- */
var modal = document.getElementById('previewModal');
var modalBody = document.getElementById('previewBody');
var current = null;
function openPreview(card) {
var path = card.getAttribute('data-path');
var type = card.getAttribute('data-type');
var source = card.getAttribute('data-source');
var name = card.getAttribute('data-name');
var url = source === 'url' ? path : '/file/' + encodeURI(path);
current = { path: path, url: url, type: type, name: name };
document.getElementById('previewTitle').textContent = name;
modalBody.innerHTML = type === 'image'
? '<img src="' + url + '" alt="' + name + '">'
: '<video src="' + url + '" controls autoplay playsinline></video>';
document.getElementById('previewMeta').innerHTML =
'<span class="badge ' + (source === 'url' ? 'badge-url' : 'badge-local') + '">' + (source === 'url' ? '远程' : '本地') + '</span>' +
'<span class="badge ' + (type === 'video' ? 'badge-video' : 'badge-image') + '">' + (type === 'video' ? '视频' : '图片') + '</span>';
modal.style.display = 'flex';
}
function closePreview() {
var v = modalBody.querySelector('video');
if (v) { v.pause(); v.removeAttribute('src'); v.load(); }
modalBody.innerHTML = '';
modal.style.display = 'none';
current = null;
}
document.querySelectorAll('[data-preview]').forEach(function (el) {
el.addEventListener('click', function () { openPreview(el.closest('.media-card')); });
});
document.getElementById('previewClose').addEventListener('click', closePreview);
modal.addEventListener('click', function (e) {
if (e.target === modal) closePreview();
});
document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closePreview(); });
document.getElementById('previewAdd').addEventListener('click', function () {
if (!current) return;
post('/api/playlist/add', { path: current.path }, function () { closePreview(); reload('已加入播放列表'); });
});
document.getElementById('previewPlay').addEventListener('click', function () {
if (!current) return;
post('/api/playlist/play', { path: current.path }, function () { closePreview(); toast('正在播放'); });
});
document.getElementById('previewDelete').addEventListener('click', function () {
if (!current) return;
if (!window.confirm('确定删除该文件?')) return;
post('/api/delete', { path: current.path }, function () { closePreview(); reload('已删除'); });
});
})();