226 lines
9.6 KiB
JavaScript
226 lines
9.6 KiB
JavaScript
|
|
/* =========================================================
|
|||
|
|
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 */ }
|
|||
|
|
|
|||
|
|
/* ---------- 播放控制 ---------- */
|
|||
|
|
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');
|
|||
|
|
if (btnAddUrl) btnAddUrl.addEventListener('click', function () {
|
|||
|
|
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) {
|
|||
|
|
reload(d && d.duplicate ? 'URL 已存在于媒体库' : '已添加到媒体库');
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
/* ---------- 媒体操作(加入播放 / 移出 / 删除) ---------- */
|
|||
|
|
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 === '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('previewDelete').addEventListener('click', function () {
|
|||
|
|
if (!current) return;
|
|||
|
|
if (!window.confirm('确定删除该文件?')) return;
|
|||
|
|
post('/api/delete', { path: current.path }, function () { closePreview(); reload('已删除'); });
|
|||
|
|
});
|
|||
|
|
})();
|