'use strict'; let deviceGroups=['type','space'],deviceGroupScope='',deviceViewSignature=''; let devicePresentation='cards'; const networkLabels={unknown:'接入线路待确认',lan:'局域网直连',tailscale:'Tailscale 跨网',public:'公网接入'}; function networkInfo(d){const n=ZhaoDevices.network(d,state);return `
IP/域名
${n.hosts.length?n.hosts.map(esc).join('
'):'待填写'}
网络接入
${esc(networkLabels[n.access]||networkLabels.unknown)}${d.tailscaleHost?'
'+esc(d.tailscaleHost):''}
`;} function renderTopology(devices){ const tree=ZhaoDevices.topology(devices),all=ZhaoDevices.catalog(state),label=id=>all.find(d=>d.id===id)?.name||'关联设备待核对'; const nodes=new Map(),queue=[...(tree.children.get('' )||[])];for(let i=0;i{const info=ZhaoDevices.network(d,state),children=tree.children.get(d.id)||[];return `
  • ${esc(d.deviceType)}

    ${esc(siteName(d.siteId))}${d.point?' · '+esc(d.point):''}

    ${networkInfo(d)}${d.networkCidr?`
    所提供网段
    ${esc(d.networkCidr)}
    `:''}
    物理连接
    ${d.uplinkId?esc(label(d.uplinkId))+' · ':''}${esc({wired:'网线连接',wifi:'Wi-Fi 连接'}[d.connectionType]||(d.uplinkId?'连接方式待确认':'上级及连接方式待确认'))}
    ${info.via.length?`

    取流经录像机:${info.via.map(label).map(esc).join('、')}

    `:''}${info.archiveVia.length?`

    录像归档:${info.archiveVia.map(label).map(esc).join('、')}

    `:''}${info.channels.length?`

    镜头码流:${info.channels.filter(c=>c.ready).length} / ${info.channels.length} 已接通

    `:'

    档案已登记,在线状态待核实

    '}
    ${children.length?``:''}
  • `;}; for(const d of queue.reverse())nodes.set(d.id,node(d)); $('#device-topology').innerHTML=`

    ${esc(currentHome()?.name||'当前场所')} · 网络拓扑

    随设备新增、编辑和区域筛选自动更新。连线表示档案中已确认的上级关系;录像机取流与录像归档另行标注。未登记的交换机端口、AP 及网络设备仍需补充。

    `; } const deviceDimensions={type:'设备类型',space:'区域空间',manager:'负责人',owner:'所有人',tag:'自定义标签'}; function renderDeviceGrouping(){ $('#device-group-order').innerHTML=deviceGroups.map((g,i)=>`
    ${i+1}
    `).join(''); $$('[data-group-preset]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.groupPreset===deviceGroups.join(',')))); $('#device-group-add').disabled=deviceGroups.length>=Object.keys(deviceDimensions).length; } function videoDeviceActions(d){ if(!['camera','recorder'].includes(d.source))return ''; const channels=state.cameras.filter(c=>d.source==='camera'?c.assetId===d.id:c.recorderId===d.id||c.archiveRecorderId===d.id); const target=`data-view-device="${esc(d.id)}" data-device-source="${d.source}"`; return `
    ${isAdmin()?``:''}${channels.length?``:''}
    ${!channels.length?'

    尚未接入画面,请先配置镜头通道。

    ':''}`; } function deviceCard(d){ const location=ZhaoDevices.path(state.sites,d.siteId).map(s=>s.name).join(' / ')||'未指定区域'; const attr=d.source==='camera'?'edit-asset':d.source==='recorder'?'edit-recorder':'edit-device'; const detail=d.source==='camera'?`
    镜头 / 通道
    ${d.lensCount} 个镜头 · ${state.cameras.filter(c=>c.assetId===d.id).length} 个已配置通道
    `:d.source==='recorder'?`
    关联镜头
    ${state.cameras.filter(c=>c.recorderId===d.id||c.archiveRecorderId===d.id).length} 个
    `:'
    接入状态
    仅登记档案,尚未接入控制
    '; return `
    ${esc(d.deviceType)}${isAdmin()?``:''}

    所在区域
    ${esc(location)}
    所有人
    ${esc(d.ownerName||'待指定')}
    负责人
    ${esc(d.managerName||'待指定')}
    品牌型号
    ${esc([d.brand,d.model].filter(Boolean).join(' / ')||'待填写')}
    ${networkInfo(d)}${detail}
    ${videoDeviceActions(d)}${d.tags?.length?`

    ${d.tags.map(t=>`${esc(t)}`).join('')}

    `:''}
    `; } function renderDevices(){ const home=(state.households||[]).find(h=>h.id===state.familyId),saved=home?.deviceGrouping||['type','space']; const signature=JSON.stringify(saved); if(deviceGroupScope!==state.familyId||deviceViewSignature!==signature){deviceGroupScope=state.familyId;deviceViewSignature=signature;deviceGroups=[...saved];renderDeviceGrouping();} $('#device-clear').hidden=!($('#site-filter').value||$('#device-type').value||$('#device-search').value); const search=$('#device-search').value.trim().toLowerCase(); const all=ZhaoDevices.catalog(state),types=[...new Set(all.map(d=>d.deviceType))].sort(); setOptions($('#device-type'),''+types.map(type=>``).join('')); const category=$('#device-type').value; const range=$('#site-filter').value?ZhaoDevices.descendants(state.sites,$('#site-filter').value):null; const devices=all.filter(d=>(!range||range.has(d.siteId))&&(!category||d.deviceType===category)&&ZhaoDevices.matches(d,search,state.sites)); $('#asset-list').hidden=devicePresentation!=='cards';$('#device-topology').hidden=devicePresentation!=='topology'; $('#devices-card-view').setAttribute('aria-pressed',String(devicePresentation==='cards'));$('#devices-topology-view').setAttribute('aria-pressed',String(devicePresentation==='topology')); if(devicePresentation==='topology'&&!window.ZhaoVue)renderTopology(devices); $('.view-presets').hidden=devicePresentation==='topology';$('.group-custom').hidden=devicePresentation==='topology'; $('#device-count').textContent=devicePresentation==='topology'?`${devices.length} 台设备 · 网络连接视图`:`${devices.length} 台设备 · 按 ${deviceGroups.map(g=>deviceDimensions[g]).join(' → ')||'全部平铺'} 查看`; if(window.ZhaoVue){ const tree=ZhaoDevices.topology(devices),catalog=ZhaoDevices.catalog(state),rows=[],stack=(tree.children.get('')||[]).slice().reverse().map(device=>({device,depth:0})); const name=id=>catalog.find(d=>d.id===id)?.name||'关联设备待确认'; while(stack.length){const row=stack.pop(),info=ZhaoDevices.network(row.device,state);rows.push({...row,parentName:row.device.uplinkId?name(row.device.uplinkId):'',via:info.via.map(name),archiveVia:info.archiveVia.map(name)});stack.push(...(tree.children.get(row.device.id)||[]).slice().reverse().map(device=>({device,depth:row.depth+1})));} window.ZhaoVue.setDevices({devices,groups:ZhaoDevices.groups(devices,deviceGroups,state.sites),rows,mode:devicePresentation});return; } $('#asset-list').innerHTML=devices.length?ZhaoDevices.groups(devices,deviceGroups,state.sites).map(group=>`

    ${group.labels.map(esc).join(' ')||'全部设备'} ${group.items.length} 台

    ${group.items.map(deviceCard).join('')}
    `).join(''):empty('当前条件下没有设备','可以清除筛选,或添加摄像机、录像机、灯、手机等设备档案。'); } function resetDeviceView(){devicePresentation='cards';deviceGroupScope='';deviceViewSignature='';deviceGroups=['type','space'];$('#device-search').value='';$('#device-type').innerHTML='';$('#device-group-order').replaceChildren();} function responsibilityFields(form,item){ for(const field of ['managementHost','tailscaleHost','networkCidr','networkAccess','connectionType'])form.elements[field].value=item?.[field]||(['networkAccess','connectionType'].includes(field)?'unknown':''); const uplink=form.elements.uplinkId;uplink.innerHTML=''+ZhaoDevices.catalog(state).filter(d=>d.id!==item?.id).map(d=>``).join('');uplink.value=item?.uplinkId||''; form.elements.managementHost.closest('label').hidden=form.id==='recorder-form'; const direct=form.id==='asset-form'&&state.cameras.some(c=>c.assetId===item?.id&&c.sourceKind!=='recorder'); form.elements.managementHost.readOnly=direct; form.elements.managementHost.title=direct?'此地址来自镜头通道,请在“配置镜头”内修改':''; if(direct)form.elements.managementHost.value=ZhaoDevices.network(item,state).hosts[0]||''; const candidates=state.responsibilityOptions||[]; for(const prefix of ['owner','manager']){ const input=form.elements[prefix+'Name'],select=form.elements[prefix+'Ref']; input.value=item?.[prefix+'Name']||''; const ref=item?.[prefix+'Ref']||'',missing=ref&&!candidates.some(p=>p.ref===ref); select.innerHTML=''+['账号成员','家谱人物'].map(kind=>`${candidates.filter(p=>p.kind===kind).map(p=>``).join('')}`).join('')+(missing?``:'')+''; select.value=ref||(input.value?'custom':''); input.hidden=select.value!=='custom';input.required=select.value==='custom'; select.onchange=()=>{input.hidden=select.value!=='custom';input.required=select.value==='custom';if(select.value==='custom'){input.value='';input.focus();}else input.value=candidates.find(p=>p.ref===select.value)?.name||(select.value===ref?item?.[prefix+'Name']||'':'');}; } form.elements.tags.value=(item?.tags||[]).join('、'); } function deviceValues(data){const result={...data,tags:(data.tags||'').split(/[,,、\n]+/).map(t=>t.trim()).filter(Boolean)};for(const prefix of ['owner','manager']){if(result[prefix+'Ref']==='custom')result[prefix+'Ref']='';else if(!result[prefix+'Ref'])result[prefix+'Name']='';}return result;} function openDevice(id){ const item=(state.devices||[]).find(d=>d.id===id)||{},f=$('#device-form'); f.elements.siteId.innerHTML=''+spaceOptions();fillForm(f,item);if(!item.id)f.elements.siteId.value=$('#site-filter').value||'';responsibilityFields(f,item);$('#device-dialog').showModal(); } function spaceOptions(exclude=''){ const omitted=exclude?ZhaoDevices.descendants(state.sites,exclude):new Set(); return state.sites.filter(s=>!omitted.has(s.id)).map(s=>``).join(''); } function clearDeviceFilters(){$('#site-filter').value='';$('#device-type').value='';$('#device-search').value='';renderDevices();} $('#device-clear').onclick=clearDeviceFilters; $('#devices-card-view').onclick=()=>{devicePresentation='cards';renderDevices();};$('#devices-topology-view').onclick=()=>{devicePresentation='topology';renderDevices();}; $('#device-search').oninput=renderDevices;$('#device-type').onchange=renderDevices; $('#device-group-order').addEventListener('change',e=>{if(e.target.dataset.groupIndex!==undefined){deviceGroups[Number(e.target.dataset.groupIndex)]=e.target.value;renderDeviceGrouping();renderDevices();}}); $('#device-group-add').onclick=()=>{const next=Object.keys(deviceDimensions).find(k=>!deviceGroups.includes(k));if(next){deviceGroups.push(next);renderDeviceGrouping();renderDevices();}}; $('#device-view-save').onclick=async()=>{try{await api('/api/device-view',{groups:deviceGroups});await refresh();toast('已保存为本家庭或场所的默认分组');}catch(e){if(!e.stale)toast(e.message);}}; $('#new-device').onclick=()=>openDevice(); handleForm('device-form','/api/devices',deviceValues); document.addEventListener('click',e=>{ const b=e.target.closest('button');if(!b)return; if(b.dataset.groupPreset){deviceGroups=b.dataset.groupPreset.split(',');renderDeviceGrouping();renderDevices();} if(b.dataset.editDevice)openDevice(b.dataset.editDevice); if(b.dataset.groupRemove!==undefined){deviceGroups.splice(Number(b.dataset.groupRemove),1);renderDeviceGrouping();renderDevices();} if(b.dataset.groupUp!==undefined){const i=Number(b.dataset.groupUp);if(i>0){[deviceGroups[i-1],deviceGroups[i]]=[deviceGroups[i],deviceGroups[i-1]];renderDeviceGrouping();renderDevices();}} });