75 lines
5.6 KiB
JavaScript
75 lines
5.6 KiB
JavaScript
'use strict';
|
|
let wallOwner='',wallOrder=[],wallPage=0,wallDragging='',ptzCamera='',ptzGeneration=0;
|
|
const WALL_LAYOUTS={auto:[0,0],1:[1,1],2:[2,1],4:[2,2],6:[3,2],9:[3,3],16:[4,4]};
|
|
function wallStorageKey(){return 'zhaofu-monitor-'+(currentUser?.id||'guest');}
|
|
function wallSave(){try{localStorage.setItem(wallStorageKey(),JSON.stringify({order:wallOrder,layout:$('#wall-layout').value}));}catch{}}
|
|
function wallSync(){
|
|
if(wallOwner!==(currentUser?.id||'')){
|
|
wallOwner=currentUser?.id||'';wallPage=0;let saved={};try{saved=JSON.parse(localStorage.getItem(wallStorageKey()))||{};}catch{}
|
|
wallOrder=Array.isArray(saved.order)?saved.order.filter(x=>/^[a-f0-9]{16}$/.test(x)):[];
|
|
$('#wall-layout').value=Object.hasOwn(WALL_LAYOUTS,saved.layout)?saved.layout:'auto';
|
|
}
|
|
const cameras=filtered(),ids=cameras.map(c=>c.id);
|
|
const allIds=new Set(state.cameras.map(c=>c.id));wallOrder=wallOrder.filter(id=>allIds.has(id));
|
|
for(const id of ids)if(!wallOrder.includes(id))wallOrder.push(id);
|
|
const ordered=wallOrder.filter(id=>ids.includes(id)),layout=$('#wall-layout').value;
|
|
const capacity=Number(layout)||6,pages=Math.max(1,Math.ceil(ordered.length/capacity));
|
|
wallPage=Math.min(wallPage,pages-1);const visible=new Set(ordered.slice(wallPage*capacity,(wallPage+1)*capacity));
|
|
$('#live-grid').dataset.layout=layout;
|
|
for(const card of $$('#live-grid .camera-card')){
|
|
const id=card.querySelector('video').dataset.camera;
|
|
card.style.order=ordered.indexOf(id);card.hidden=!visible.has(id);
|
|
card.querySelector('[data-wall-before]').disabled=ordered.indexOf(id)===0;
|
|
card.querySelector('[data-wall-after]').disabled=ordered.indexOf(id)===ordered.length-1;
|
|
}
|
|
$('#wall-page').textContent=`${wallPage+1} / ${pages} 页 · ${ordered.length} 个画面`;
|
|
$('#wall-prev').disabled=wallPage===0;$('#wall-next').disabled=wallPage===pages-1;
|
|
$('#live-grid').classList.toggle('arranging',$('#wall-arrange').checked);
|
|
$$('[data-ptz-camera]').forEach(b=>b.hidden=!(isAdmin()||currentUser?.ptzControl));
|
|
}
|
|
function wallMove(id,target){
|
|
const from=wallOrder.indexOf(id),to=wallOrder.indexOf(target);if(from<0||to<0||from===to)return;
|
|
wallOrder.splice(from,1);wallOrder.splice(to,0,id);wallSave();wallSync();
|
|
}
|
|
$('#wall-layout').onchange=()=>{wallPage=0;wallSave();wallSync();};
|
|
$('#wall-arrange').onchange=wallSync;
|
|
$('#wall-prev').onclick=()=>{wallPage--;wallSync();};$('#wall-next').onclick=()=>{wallPage++;wallSync();};
|
|
$('#wall-fullscreen').onclick=async()=>{
|
|
try{if(document.fullscreenElement)await document.exitFullscreen();else await $('#page-live').requestFullscreen();}
|
|
catch{toast('浏览器未允许全屏,可按 F11 进入浏览器全屏');}
|
|
};
|
|
document.addEventListener('fullscreenchange',()=>{
|
|
$('#wall-fullscreen').textContent=document.fullscreenElement?'退出全屏 · Esc':'全屏监控';
|
|
document.body.classList.toggle('wall-fullscreen',document.fullscreenElement===$('#page-live'));
|
|
});
|
|
setInterval(()=>{$('#wall-clock').textContent=new Date().toLocaleString('zh-CN',{hour12:false});},1000);
|
|
document.addEventListener('dragstart',e=>{const h=e.target.closest('[data-wall-drag]');if(!h)return;wallDragging=h.dataset.wallDrag;e.dataTransfer.setData('text/plain',wallDragging);e.dataTransfer.effectAllowed='move';});
|
|
$('#live-grid').addEventListener('dragover',e=>{if(wallDragging&&$('#wall-arrange').checked)e.preventDefault();});
|
|
$('#live-grid').addEventListener('drop',e=>{const c=e.target.closest('.camera-card');if(!c||!wallDragging)return;e.preventDefault();wallMove(wallDragging,c.querySelector('video').dataset.camera);wallDragging='';});
|
|
document.addEventListener('dragend',()=>wallDragging='');
|
|
document.addEventListener('click',e=>{
|
|
const b=e.target.closest('button');if(!b)return;
|
|
if(b.dataset.wallBefore||b.dataset.wallAfter){const ids=wallOrder.filter(id=>filtered().some(c=>c.id===id)),id=b.dataset.wallBefore||b.dataset.wallAfter;wallMove(id,ids[ids.indexOf(id)+(b.dataset.wallBefore?-1:1)]);}
|
|
if(b.dataset.ptzCamera)openPtz(b.dataset.ptzCamera);
|
|
});
|
|
async function openPtz(id){
|
|
const generation=++ptzGeneration;ptzCamera=id;$('#ptz-panel').hidden=false;
|
|
$('#ptz-title').textContent=(state.cameras.find(c=>c.id===id)?.name||'摄像头')+' · 云台';
|
|
$('#ptz-status').textContent='正在读取云台能力…';$$('[data-ptz-action]').forEach(b=>b.disabled=true);
|
|
try{const caps=await api('/api/ptz?camera='+id);if(generation!==ptzGeneration)return;$('#ptz-status').textContent=caps.note;$$('[data-ptz-action]').forEach(b=>b.disabled=false);}
|
|
catch(e){if(generation===ptzGeneration)$('#ptz-status').textContent=e.message;}
|
|
}
|
|
async function ptzCommand(action){
|
|
if(!ptzCamera)return;const id=ptzCamera,generation=ptzGeneration;
|
|
$$('[data-ptz-action]:not([data-ptz-action="stop"])').forEach(b=>b.disabled=true);
|
|
$('#ptz-status').textContent=action==='stop'?'正在停止…':'正在微调…';
|
|
try{await api('/api/ptz',{camera:id,action});if(generation===ptzGeneration)$('#ptz-status').textContent='停止指令已确认,可继续微调';}
|
|
catch(e){if(generation===ptzGeneration)$('#ptz-status').textContent=e.message;}
|
|
finally{if(generation===ptzGeneration)$$('[data-ptz-action]').forEach(b=>b.disabled=false);}
|
|
}
|
|
function closePtz(){ptzGeneration++;ptzCamera='';$('#ptz-panel').hidden=true;}
|
|
$('#ptz-close').onclick=closePtz;
|
|
$$('[data-ptz-action]').forEach(b=>b.onclick=()=>ptzCommand(b.dataset.ptzAction));
|
|
document.addEventListener('visibilitychange',()=>{if(document.hidden&&ptzCamera)ptzCommand('stop');});
|
|
window.addEventListener('pagehide',()=>{if(ptzCamera)fetch('/api/ptz',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({camera:ptzCamera,action:'stop'})}).catch(()=>{});});
|