'use strict'; pageInfo.households=['我的家庭与场所','家庭、办公室或工厂,分别组织,各自授权。']; const scopeKind=h=>({home:'家庭',office:'办公场所',factory:'工厂'}[h.kind||'home']||'家庭'); let coverSaving=false; let homeLayoutDraft=null,homeLayoutOwner='',homeLayoutDirty=false,homeLayoutSaving=false,homePage=0,homeArrange=false,homeDragId=''; let homePresentation='cards'; function homeOrder(homes,order){const byId=new Map(homes.map(h=>[h.id,h]));return [...new Set([...order,...homes.map(h=>h.id)])].filter(id=>byId.has(id)).map(id=>byId.get(id));} function homeMove(id,step,target=''){ if(homeLayoutSaving)return; const order=homeOrder(state.households||[],homeLayoutDraft.order).map(h=>h.id),from=order.indexOf(id),to=target?order.indexOf(target):from+step; if(from<0||to<0||to>=order.length||from===to)return; order.splice(from,1);order.splice(to,0,id);homeLayoutDraft.order=order;homeLayoutDirty=true;homePage=Math.floor(to/(homeLayoutDraft.columns*homeLayoutDraft.rows));renderHouseholds(); } function homeArrangeActions(id,homes){const index=homes.findIndex(h=>h.id===id);return homeArrange?`
拖动卡片或移动顺序
`:'';} function renderHouseholds(){ if(typeof window!=='undefined'&&window.ZhaoVue){setOptions($('#household-filter'),(state.households||[]).map(h=>``).join(''));$('#household-filter').value=state.familyId||'';return;} const saved=state.householdLayout||{revision:0,columns:2,rows:2,groupCity:false,order:[]}; if(homeLayoutOwner!==currentUser?.id){homeLayoutOwner=currentUser?.id||'';homeLayoutDirty=false;homeLayoutDraft=null;homePage=0;homeArrange=false;} if(!homeLayoutDraft||!homeLayoutDirty&&!homeLayoutSaving&&homeLayoutDraft.revision!==saved.revision)homeLayoutDraft={...saved,order:[...saved.order]}; const homes=homeOrder(state.households||[],homeLayoutDraft.order); setOptions($('#household-filter'),homes.map(h=>``).join('')); $('#household-filter').value=state.familyId||''; if(activePage!=='households')return; $('#home-columns').value=homeLayoutDraft.columns;$('#home-rows').value=homeLayoutDraft.rows;$('#home-group-city').checked=homeLayoutDraft.groupCity; if(homeLayoutDraft.groupCity)homeArrange=false; $('#home-arrange').checked=homeArrange;$('#home-arrange').disabled=homeLayoutDraft.groupCity||homeLayoutSaving; $('#home-layout-save').disabled=!homeLayoutDirty||homeLayoutSaving;$('#home-layout-reset').disabled=!homeLayoutDirty||homeLayoutSaving; for(const id of ['home-columns','home-rows','home-group-city'])$('#'+id).disabled=homeLayoutSaving; $('#home-layout-status').textContent=homeLayoutSaving?'正在保存布局…':homeLayoutDirty?'布局已调整,点击“保存我的布局”后下次打开会沿用。':homeLayoutDraft.groupCity?'按城市分组展示;取消分组后可自由排列卡片。':'可设置每页列数和行数;手机端自动收为单列。布局仅影响当前账号。'; const capacity=homeLayoutDraft.columns*homeLayoutDraft.rows,pages=Math.max(1,Math.ceil(homes.length/capacity));homePage=Math.min(homePage,pages-1); const visible=homes.slice(homePage*capacity,(homePage+1)*capacity); $('#home-prev').disabled=homePage===0;$('#home-next').disabled=homePage>=pages-1;$('#home-page-info').textContent=`第 ${homePage+1} / ${pages} 页 · 共 ${homes.length} 个 · 每页最多 ${capacity} 张`; $('#household-list').style.setProperty('--home-columns',homeLayoutDraft.columns); const cards=h=>`
${h.coverUrl?`${esc(h.name)}封面照片`:''}
${h.id===state.familyId?'当前归属 · ':''}${scopeKind(h)}${isAdmin()?``:''}

${esc(h.name)}

${esc(ZhaoGeography.describe(h)||'地区待填写')}

${homeArrangeActions(h.id,homes)}

${esc(h.note||'区域与设备归属独立管理')}

${!h.kind||h.kind==='home'?'一家之主':'主要负责人'}
${esc(h.ownerName||'待指定')}
备用负责人
${esc(h.successorName||'待指定')}
账号 / 空间
${h.members} 个 / ${h.sites} 处
${!h.successorId?'

尚未指定备用负责人,建议提前安排可信人员接管。

':''}
${h.canAssignOwner?``:''}
${h.leadershipHistory?.length?`
交接记录${h.leadershipHistory.slice().reverse().map(r=>`

${esc(new Date(r.at).toLocaleString())} · ${esc(r.actor)}
${esc(r.reason)}

`).join('')}
`:''}
`; const groups=new Map();for(const h of visible){const key=[h.geography?.body&&h.geography.body!=='地球'?h.geography.body:'',h.geography?.country||h.country||'',h.province,h.city||'未填写城市/区域'].filter(Boolean).join(' / ');if(!groups.has(key))groups.set(key,[]);groups.get(key).push(h);} $('#household-list').innerHTML=homeLayoutDraft.groupCity?[...groups].map(([city,rows])=>`

${esc(city)}

${rows.map(cards).join('')}
`).join(''):`
${visible.map(cards).join('')}
`; $('#household-list').hidden=homePresentation!=='cards';$('#home-global-topology').hidden=homePresentation!=='topology';$('.home-layout-controls').hidden=homePresentation!=='cards';$('.home-pagination').hidden=homePresentation!=='cards'; $('#home-card-view').setAttribute('aria-pressed',String(homePresentation==='cards'));$('#home-topology-view').setAttribute('aria-pressed',String(homePresentation==='topology')); if(homePresentation==='topology')$('#home-global-topology').innerHTML=`

${esc(typeof branding==='object'?branding.name:'赵府')} · 数字管家

${homes.length} 个已授权家庭/场所 · 点击进入内部拓扑

此图表示平台与场所的管理关系;网络标签来自设备档案,连线不代表物理专线或实时在线状态。

${homes.map(h=>``).join('')}
`; for(const card of $$('#household-list .home-card')){card.draggable=homeArrange&&!homeLayoutSaving;card.ondragstart=e=>{if(e.target.closest('button,a,input,select')){e.preventDefault();return;}homeDragId=card.dataset.homeId;e.dataTransfer.setData('text/plain',homeDragId);};card.ondragover=e=>{if(homeArrange&&homeDragId)e.preventDefault();};card.ondrop=e=>{e.preventDefault();if(homeArrange&&homeDragId)homeMove(homeDragId,0,card.dataset.homeId);homeDragId='';};card.ondragend=()=>{homeDragId='';};} } if(typeof window==='undefined'||!window.ZhaoVue){ for(const [id,key] of [['home-columns','columns'],['home-rows','rows'],['home-group-city','groupCity']])$('#'+id).onchange=e=>{homeLayoutDraft[key]=key==='groupCity'?e.target.checked:Number(e.target.value);homePage=0;homeLayoutDirty=true;renderHouseholds();}; $('#home-arrange').onchange=e=>{homeArrange=e.target.checked;renderHouseholds();}; $('#home-prev').onclick=()=>{homePage=Math.max(0,homePage-1);renderHouseholds();};$('#home-next').onclick=()=>{homePage++;renderHouseholds();}; $('#home-card-view').onclick=()=>{homePresentation='cards';renderHouseholds();};$('#home-topology-view').onclick=()=>{homePresentation='topology';renderHouseholds();}; $('#home-global-topology').addEventListener('click',async e=>{const b=e.target.closest('[data-home-network]');if(!b)return;if(b.dataset.homeNetwork!==state.familyId&&!await switchHousehold(b.dataset.homeNetwork))return;devicePresentation='topology';showPage('objects');}); $('#home-layout-reset').onclick=()=>{homeLayoutDirty=false;homeLayoutDraft=null;homePage=0;renderHouseholds();}; $('#household-list').addEventListener('click',e=>{const button=e.target.closest('[data-home-move]');if(button)homeMove(button.dataset.homeMove,Number(button.dataset.step));}); $('#home-layout-save').onclick=async()=>{if(homeLayoutSaving||!homeLayoutDirty)return;const generation=sessionGeneration;homeLayoutSaving=true;renderHouseholds();try{const saved=await api('/api/households/layout',homeLayoutDraft);if(generation!==sessionGeneration)return;state.householdLayout=saved;homeLayoutDraft={...saved,order:[...saved.order]};homeLayoutDirty=false;toast('卡片布局与顺序已保存');}catch(e){if(!e.stale)toast(e.message);}finally{homeLayoutSaving=false;if(generation===sessionGeneration)renderHouseholds();}}; } async function switchHousehold(id){ if(id===state.familyId)return true; if(!isPlatformAdmin()||!id)return false; if(typeof confirmJournalDiscard==='function'&&!confirmJournalDiscard())return false; const user=currentUser; // Invalidate outstanding API responses, video players and dialogs before changing scope. clearPrivateView(); householdScope=id;currentUser=user;loggedIn=true; await auth();return loggedIn; } function openHousehold(id){ if(coverSaving){toast('档案仍在保存,请稍候。');return;} if(!isAdmin())return; const item=(state.households||[]).find(h=>h.id===id); if(!item&&!isPlatformAdmin())return; nextImageJob('cover');$('#household-cover-status').dataset.error='false';fillForm($('#household-form'),{profileRevision:0,...(item||{})});coverDraft=undefined;ZhaoGeography.fill($('#household-form'),item||{});$('#household-cover-preview').hidden=!item?.coverUrl;if(item?.coverUrl)$('#household-cover-preview').src=item.coverUrl;else $('#household-cover-preview').removeAttribute('src');$('#household-cover-status').textContent='封面存入百度网盘,原图可选 30 MB 以内;浏览器会先压缩。仅向获授权账号开放。';$('#household-dialog').showModal(); } let leadershipUsers=[],leadershipReturnHome='',leadershipReturnDraft=null; function leadershipCandidates(rows,familyId){return rows.filter(u=>u.familyId===familyId&&!u.disabled&&['family_admin','member'].includes(u.role));} function syncLeadershipSelection(){ const f=$('#leadership-form'),selected=[f.elements.ownerId.value,f.elements.successorId.value]; for(const option of f.elements.successorId.options)option.disabled=!!option.value&&option.value===selected[0]; if(selected[0]&&selected[0]===selected[1])f.elements.successorId.value=''; const grants=leadershipUsers.filter(u=>[f.elements.ownerId.value,f.elements.successorId.value].includes(u.id)&&u.role==='member'); $('#leadership-grant').hidden=!grants.length;f.elements.grantManagement.required=!!grants.length; $('#leadership-grant-note').textContent=grants.length?'确认将 '+grants.map(u=>u.name).join('、')+' 设为本家庭 / 场所管理员,可管理其全部成员、区域和设备。':''; f.querySelector('[type=submit]').disabled=!leadershipUsers.length; } async function openLeadership(id){ try{ if(id!==state.familyId&&!await switchHousehold(id))return; const home=(state.households||[]).find(h=>h.id===id); if(!home?.canAssignOwner)return; const result=await api('/api/users'); leadershipUsers=leadershipCandidates(result.users,id); const f=$('#leadership-form'),options=leadershipUsers.map(u=>``).join(''); f.elements.ownerId.innerHTML=''+options; f.elements.successorId.innerHTML=''+options; fillForm(f,{id:home.id,revision:home.leadershipRevision||0,ownerId:home.ownerId||'',successorId:home.successorId||''}); $('#leadership-scope').textContent='当前家庭 / 场所:'+home.name; $('#leadership-empty').hidden=!!leadershipUsers.length; f.elements.ownerId.disabled=!leadershipUsers.length;f.elements.successorId.disabled=!leadershipUsers.length; f.elements.grantManagement.checked=false;syncLeadershipSelection(); $('#leadership-dialog').showModal(); }catch(error){if(!error.stale)toast(error.message);} } $('#leadership-form').addEventListener('submit',async e=>{ e.preventDefault();const f=e.currentTarget,b=f.querySelector('[type=submit]');b.disabled=true; try{const d=formValues(f);d.revision=Number(d.revision);d.grantManagement=f.elements.grantManagement.checked;await api('/api/households/leadership',d);f.closest('dialog').close();await refresh();showPage('households');toast('负责人已更新,交接记录已保存');} catch(error){if(!error.stale)f.querySelector('.form-error').textContent=error.message;}finally{b.disabled=false;} }); for(const name of ['ownerId','successorId'])$('#leadership-form').elements[name].addEventListener('change',()=>{$('#leadership-form').elements.grantManagement.checked=false;syncLeadershipSelection();}); $('#leadership-members').onclick=()=>{$('#leadership-dialog').close();showPage('users');}; $('#leadership-new').onclick=async()=>{const id=state.familyId;leadershipReturnDraft=formValues($('#leadership-form'));$('#leadership-dialog').close();if(!await loadUsers()||state.familyId!==id||!loggedIn)return;openUser();$('#user-form').elements.role.value='family_admin';leadershipReturnHome=id;}; $('#user-dialog').addEventListener('close',()=>{leadershipReturnHome='';}); $('#household-filter').onchange=async e=>{const switched=await switchHousehold(e.target.value);if(switched&&loggedIn)showPage('overview');else e.target.value=state.familyId;}; $('#new-household').onclick=()=>openHousehold(); $('#household-form').addEventListener('submit',async e=>{ e.preventDefault();const f=e.currentTarget;if(coverSaving)return; if(imageJobPending('cover')){f.querySelector('.form-error').textContent='照片仍在处理,请完成后保存。';return;} if(imageJobs.get('cover')?.error){f.querySelector('.form-error').textContent='封面未准备成功:请重新选择图片,或点击“取消本次图片选择”。';return;} const payload={...formValues(f),profileRevision:Number(f.elements.profileRevision.value||0),...(coverDraft!==undefined?{coverData:coverDraft}:{})}; const controls=[...f.elements],disabled=controls.map(x=>x.disabled),generation=sessionGeneration; coverSaving=true;controls.forEach(x=>x.disabled=true);f.querySelector('.form-error').textContent=payload.coverData?'正在上传封面到百度网盘并校验,请稍候…':'正在保存档案…'; try{ const saved=await api('/api/households',payload);if(generation!==sessionGeneration)return; if(payload.coverData&&!saved.coverRef?.fsId)throw Error('服务器未确认封面保存,请重试。'); // Adopt the returned revision even if a later state refresh fails. f.elements.id.value=saved.id;f.elements.profileRevision.value=saved.profileRevision; const view={...saved,coverUrl:saved.coverData||saved.coverRef?'/api/households/cover?id='+encodeURIComponent(saved.id)+'&v='+saved.coverRevision:''}; delete view.coverData;delete view.coverRef;const previous=(state.households||[]).find(x=>x.id===saved.id); if(previous)Object.assign(previous,view);else (state.households||=[]).push(view); renderHouseholds();f.closest('dialog').close();toast(payload.coverData?'封面已存百度网盘,归属档案已保存':payload.coverData===''?'归属档案已保存,已使用默认封面':'归属档案已保存'); try{await refresh();}catch(error){if(!error.stale&&generation===sessionGeneration)toast('档案已保存,列表刷新暂未完成,请稍后刷新页面。无需重复保存。');} }catch(error){if(!error.stale&&generation===sessionGeneration)f.querySelector('.form-error').textContent=error.message;} finally{coverSaving=false;controls.forEach((x,i)=>x.disabled=disabled[i]);} }); document.addEventListener('click',async e=>{ const b=e.target.closest('button');if(!b)return; if(b.dataset.editHousehold)openHousehold(b.dataset.editHousehold); if(b.dataset.leadership)await openLeadership(b.dataset.leadership); if(b.dataset.enterHousehold){const switched=await switchHousehold(b.dataset.enterHousehold);if(switched&&loggedIn)showPage('overview');} }); $('#household-cover-file').onchange=async e=>{ const file=e.target.files[0];if(!file)return; const job=nextImageJob('cover'),generation=sessionGeneration,status=$('#household-cover-status'); imageJobs.get('cover').pending=true;status.dataset.error='false';status.textContent='正在压缩照片,请稍候…';$('#household-form .form-error').textContent=''; try{const image=await readImage(file,960);if(generation!==sessionGeneration||!imageJobCurrent('cover',job)||!$('#household-dialog').open)return; coverDraft=image;$('#household-cover-preview').src=image;$('#household-cover-preview').hidden=false;status.textContent='封面预览已就绪(约 '+Math.ceil((image.length-22)*.75/1024)+' KB),保存后上传百度网盘并更新卡片。'; }catch(error){if(generation===sessionGeneration&&imageJobCurrent('cover',job)){imageJobs.get('cover').error=true;status.dataset.error='true';status.textContent=error.message+'。本次图片未保存,请重新选择或取消本次选择。';}} finally{if(imageJobCurrent('cover',job))imageJobs.get('cover').pending=false;} }; $('#household-cover-cancel').onclick=()=>{ nextImageJob('cover');coverDraft=undefined;$('#household-cover-file').value=''; const item=(state.households||[]).find(x=>x.id===$('#household-form').elements.id.value),img=$('#household-cover-preview'); img.hidden=!item?.coverUrl;if(item?.coverUrl)img.src=item.coverUrl;else img.removeAttribute('src'); $('#household-cover-status').dataset.error='false';$('#household-cover-status').textContent='已取消本次图片选择,保留原有封面。';$('#household-form .form-error').textContent=''; }; $('#household-cover-reset').onclick=()=>{nextImageJob('cover');coverDraft='';$('#household-cover-file').value='';$('#household-cover-preview').hidden=true;$('#household-cover-preview').removeAttribute('src');$('#household-cover-status').dataset.error='false';$('#household-cover-status').textContent='保存后使用默认封面。';$('#household-form .form-error').textContent='';}; $('#household-dialog').addEventListener('close',()=>nextImageJob('cover')); $('#household-dialog').addEventListener('cancel',event=>{if(coverSaving)event.preventDefault();}); $('#household-cover-preview').addEventListener('error',()=>{$('#household-cover-status').textContent='封面暂时无法从网盘读取,请稍后重新打开;原有记录仍保留。';});