Commit e3b335fb by 久林

feat: 监控点位、整点曲线与环境总览体验优化

设施摄像头三维显隐与通信参数;大屏曲线整点小时;环境总览湿度曲线与房间中文名;全景底栏飞主视图;点位图标缩小。

Co-authored-by: Cursor <cursoragent@cursor.com>
parent e1a7ff5a
import{d as v,j as b,u as y,c,b as e,t,h as n,F as p,x as h,p as S,o as r}from"./index-BssDqbym.js";import{u as k}from"./realtime-lmP-Cubp.js";const x={class:"page-fill",style:{"flex-direction":"row"}},C={class:"panel",style:{flex:"1",overflow:"auto"}},T={class:"panel-head"},w={class:"panel-title"},D={class:"badge"},z={class:"table"},N=["disabled","onClick"],A={style:{"margin-top":"12px","font-size":"12px",color:"var(--label)"}},O={class:"panel",style:{flex:"1",overflow:"auto"}},P={class:"panel-head"},R={class:"panel-title"},B={class:"table"},W=v({__name:"AccessView",setup(E){const{t:s,locale:i}=b(),u=y(),d=k();function f(a){const o=i.value==="zh-CN"?"zh-CN":i.value==="zh-TW"?"zh-TW":i.value==="pt"?"pt-PT":"en-US";return new Date(a).toLocaleString(o,{hour12:!1})}function m(a){return a==="open"?s("pages.doorOpen"):a==="closed"?s("pages.doorClosed"):a==="alarm"?s("doorStatus.alarm"):a==="offline"?s("doorStatus.offline"):a}function _(a){return a==="success"?s("pages.accessSuccess"):a==="denied"?s("pages.accessDenied"):a==="alarm"?s("doorStatus.alarm"):a}async function g(a){if(!u.isAdmin)return;const o=await S("/api/realtime/access/open",{method:"POST",token:u.token,body:JSON.stringify({doorId:a})});alert(o.message)}return(a,o)=>(r(),c("div",x,[e("section",C,[e("div",T,[e("div",w,[o[0]||(o[0]=e("div",{class:"vbar"},null,-1)),e("h3",null,t(n(s)("pages.accessTitle")),1)]),e("div",D,t(n(s)("pages.accessToday",{n:n(d).snapshot.access.todayPassCount})),1)]),e("table",z,[e("thead",null,[e("tr",null,[e("th",null,t(n(s)("pages.accessDoor")),1),e("th",null,t(n(s)("screen.status")),1),e("th",null,t(n(s)("common.action")),1)])]),e("tbody",null,[(r(!0),c(p,null,h(n(d).snapshot.access.doors,l=>(r(),c("tr",{key:l.id},[e("td",null,t(l.name),1),e("td",null,t(m(l.status)),1),e("td",null,[e("button",{class:"btn",disabled:!n(u).isAdmin,onClick:F=>g(l.id)},t(n(s)("pages.accessOpenRemote")),9,N)])]))),128))])]),e("p",A,t(n(s)("pages.accessFoot")),1)]),e("section",O,[e("div",P,[e("div",R,[o[1]||(o[1]=e("div",{class:"vbar"},null,-1)),e("h3",null,t(n(s)("pages.accessEvents")),1)])]),e("table",B,[e("thead",null,[e("tr",null,[e("th",null,t(n(s)("pages.accessTime")),1),e("th",null,t(n(s)("pages.accessDoor")),1),e("th",null,t(n(s)("pages.accessPerson")),1),e("th",null,t(n(s)("pages.accessResult")),1)])]),e("tbody",null,[(r(!0),c(p,null,h(n(d).snapshot.access.recentEvents,l=>(r(),c("tr",{key:l.id},[e("td",null,t(f(l.time)),1),e("td",null,t(l.doorName),1),e("td",null,t(l.person),1),e("td",null,t(_(l.result)),1)]))),128))])])])]))}});export{W as default};
import{d as f,u as h,c as n,b as t,t as e,h as i,F as u,z as c,o,l as b}from"./index-Bs_x-_cK.js";import{u as v}from"./realtime-CI_KLy_N.js";const _={class:"page-fill",style:{"flex-direction":"row"}},y={class:"panel",style:{flex:"1",overflow:"auto"}},k={class:"panel-head"},g={class:"badge"},w={class:"table"},x=["disabled","onClick"],S={class:"panel",style:{flex:"1",overflow:"auto"}},C={class:"table"},B=f({__name:"AccessView",setup(N){const r=h(),d=v();function p(a){return new Date(a).toLocaleString("zh-CN",{hour12:!1})}async function m(a){if(!r.isAdmin)return;const s=await b("/api/realtime/access/open",{method:"POST",token:r.token,body:JSON.stringify({doorId:a})});alert(s.message)}return(a,s)=>(o(),n("div",_,[t("section",y,[t("div",k,[s[0]||(s[0]=t("div",{class:"panel-title"},[t("div",{class:"vbar"}),t("h3",null,"门禁状态")],-1)),t("div",g,"今日通行 "+e(i(d).snapshot.access.todayPassCount),1)]),t("table",w,[s[1]||(s[1]=t("thead",null,[t("tr",null,[t("th",null,"门点"),t("th",null,"状态"),t("th",null,"操作")])],-1)),t("tbody",null,[(o(!0),n(u,null,c(i(d).snapshot.access.doors,l=>(o(),n("tr",{key:l.id},[t("td",null,e(l.name),1),t("td",null,e({open:"开启",closed:"关闭",alarm:"报警",offline:"离线"}[l.status]),1),t("td",null,[t("button",{class:"btn",disabled:!i(r).isAdmin,onClick:A=>m(l.id)},"远程开门",8,x)])]))),128))])]),s[2]||(s[2]=t("p",{style:{"margin-top":"12px","font-size":"12px",color:"var(--label)"}}," 协议:海康 HCNetSDK(Windows 桥接)。一期为 Mock,二期接入桥接服务。 ",-1))]),t("section",S,[s[4]||(s[4]=t("div",{class:"panel-head"},[t("div",{class:"panel-title"},[t("div",{class:"vbar"}),t("h3",null,"通行记录")])],-1)),t("table",C,[s[3]||(s[3]=t("thead",null,[t("tr",null,[t("th",null,"时间"),t("th",null,"门点"),t("th",null,"人员"),t("th",null,"结果")])],-1)),t("tbody",null,[(o(!0),n(u,null,c(i(d).snapshot.access.recentEvents,l=>(o(),n("tr",{key:l.id},[t("td",null,e(p(l.time)),1),t("td",null,e(l.doorName),1),t("td",null,e(l.person),1),t("td",null,e({success:"成功",denied:"拒绝",alarm:"报警"}[l.result]),1)]))),128))])])])]))}});export{B as default};
import{d as z,u as V,c as i,b as s,a as w,t as e,h as r,F as h,z as m,D as B,B as N,i as x,o as a,x as R,E as S,n as D,H as v,K as E}from"./index-Bs_x-_cK.js";import{u as H}from"./useClock-liJ_G-iS.js";const O={class:"tops-root"},I={class:"tops-header"},F={class:"tops-header__chips"},W={class:"tops-chip tops-tone--ok"},$={class:"tops-header__end"},j={class:"tops-clock"},J={class:"tops-body"},K={class:"tops-side","aria-label":"管理菜单"},P={class:"tops-menu"},T={class:"tops-menu__icon","aria-hidden":"true"},U={viewBox:"0 0 18 18",fill:"none",stroke:"currentColor","stroke-width":"1.3"},q=["d"],G={class:"tops-menu__copy"},Q={class:"tops-menu__label"},X={class:"tops-menu__hint"},Y={class:"tops-main"},os=z({__name:"AdminLayout",setup(Z){const n=V(),c=x(),d=E(),{time:b}=H(),k={devices:["M3 4.2h12v9.6H3z","M6.2 15.4h5.6","M7.4 13.8v1.6","M10.6 13.8v1.6"],cameras:["M3.2 6.2h8.2l1.6-1.8h3.2v11.2H3.2z","M9.2 11.2a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4z"],metering:["M3.2 3.2h11.6v11.6H3.2z","M6 12.2V7.4","M9 12.2V5.6","M12 12.2V8.8"],users:["M6.6 8.6a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z","M12.2 8.4a2.1 2.1 0 1 0 0-4.2","M2 15.4c0-2.4 2.1-3.8 4.6-3.8s4.6 1.4 4.6 3.8","M13.6 15.4c0-1.7-.8-2.9-2.2-3.4"],settings:["M9 11.2a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4z","M9 2.4l1.1 1.8 2.1-.2.9 1.9 1.8 1.1-.2 2.1 1.8 1.1-1.8 1.1.2 2.1-1.8 1.1-.9 1.9-2.1-.2L9 15.6l-1.1 1.8-2.1.2-.9-1.9-1.8-1.1.2-2.1L1.5 11l1.8-1.1-.2-2.1 1.8-1.1.9-1.9 2.1.2L9 2.4z"]},g=N(()=>[{to:"/admin/devices",label:"设备管理",hint:"点位 / 网关",tone:"accent",icon:"devices"},{to:"/admin/cameras",label:"摄像头管理",hint:"列表 / 三维打点",tone:"accent",icon:"cameras"},{to:"/admin/metering",label:"计量计费",hint:"能耗 / 费率 / 报表",tone:"accent",icon:"metering"},{to:"/admin/history",label:"历史曲线",hint:"时序 / 指标",tone:"accent",icon:"devices"},{to:"/admin/users",label:"用户管理",hint:"账号 / 角色",tone:"warn",icon:"users",adminOnly:!0},{to:"/admin/settings",label:"系统设置",hint:"采集 / 模式",tone:"muted",icon:"settings"}].filter(l=>!l.adminOnly||n.isAdmin));function M(l){return d.path===l||d.path.startsWith(`${l}/`)}function f(){n.logout(),c.push("/login")}function u(){c.push("/dashboard")}return(l,t)=>{var p,_;const y=v("RouterLink"),C=v("RouterView");return a(),i("div",O,[s("header",I,[s("div",{class:"tops-brand",onClick:u},[...t[0]||(t[0]=[w('<span class="tops-brand__mark" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 12c0-2.5 2-4.5 4.5-4.5S21 9.4 21 12c0 4.4-3.6 8-8 8-5 0-9-4-9-9 0-5.5 4.5-10 10-10" stroke-linecap="round"></path><circle cx="12" cy="12" r="1.7"></circle></svg></span><span class="tops-brand__copy"><strong>管理后台</strong><small>ADMIN CONSOLE</small></span>',2)])]),s("div",F,[t[3]||(t[3]=s("span",{class:"tops-chip"},[s("small",null,"系统"),s("b",null,"JH-LAB")],-1)),s("span",W,[t[1]||(t[1]=s("span",{class:"tops-dot","aria-hidden":"true"},null,-1)),t[2]||(t[2]=s("small",null,"角色",-1)),s("b",null,e(r(n).isAdmin?"ADMIN":"VIEWER"),1)])]),s("div",$,[s("div",j,[s("small",null,e(((p=r(n).user)==null?void 0:p.displayName)||((_=r(n).user)==null?void 0:_.username))+" · "+e(r(n).isAdmin?"管理员":"只读"),1),s("b",null,e(r(b)),1)]),s("button",{type:"button",class:"tops-logout",onClick:u},"返回大屏"),s("button",{type:"button",class:"tops-logout",onClick:f},"退出")])]),s("div",J,[s("nav",K,[t[4]||(t[4]=s("div",{class:"tops-side__head"},[s("strong",null,"菜单"),s("small",null,"MODULES")],-1)),s("div",P,[(a(!0),i(h,null,m(g.value,o=>(a(),R(y,{key:o.to,to:o.to,class:D(["tops-menu__item",[`tops-tone--${o.tone}`,{"is-on":M(o.to)}]])},{default:S(()=>[s("span",T,[(a(),i("svg",U,[(a(!0),i(h,null,m(k[o.icon],(A,L)=>(a(),i("path",{key:L,d:A,"stroke-linejoin":"round"},null,8,q))),128))]))]),s("span",G,[s("span",Q,e(o.label),1),s("span",X,e(o.hint),1)])]),_:2},1032,["to","class"]))),128))]),t[5]||(t[5]=s("div",{class:"tops-side__foot"},[s("span",{class:"tops-dot tops-tone--ok","aria-hidden":"true"}),s("span",null,"协议点表已接入")],-1))]),s("main",Y,[B(C)])])])}}});export{os as default};
import{d as V,u as w,c as i,b as s,a as z,t as e,h as r,F as h,x as m,H as N,s as x,J as v,i as B,D as O,I as R,n as S,O as D,o as a}from"./index-BssDqbym.js";import{u as H}from"./useClock-BjFim-x6.js";const I={class:"tops-root"},E={class:"tops-header"},F={class:"tops-header__chips"},J={class:"tops-chip tops-tone--ok"},W={class:"tops-header__end"},$={class:"tops-clock"},j={class:"tops-body"},P={class:"tops-side","aria-label":"管理菜单"},T={class:"tops-menu"},U={class:"tops-menu__icon","aria-hidden":"true"},q={viewBox:"0 0 18 18",fill:"none",stroke:"currentColor","stroke-width":"1.3"},G=["d"],K={class:"tops-menu__copy"},Q={class:"tops-menu__label"},X={class:"tops-menu__hint"},Y={class:"tops-main"},os=V({__name:"AdminLayout",setup(Z){const n=w(),c=B(),d=D(),{time:b}=H(),k={devices:["M3 4.2h12v9.6H3z","M6.2 15.4h5.6","M7.4 13.8v1.6","M10.6 13.8v1.6"],cameras:["M3.2 6.2h8.2l1.6-1.8h3.2v11.2H3.2z","M9.2 11.2a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4z"],metering:["M3.2 3.2h11.6v11.6H3.2z","M6 12.2V7.4","M9 12.2V5.6","M12 12.2V8.8"],users:["M6.6 8.6a2.6 2.6 0 1 0 0-5.2 2.6 2.6 0 0 0 0 5.2z","M12.2 8.4a2.1 2.1 0 1 0 0-4.2","M2 15.4c0-2.4 2.1-3.8 4.6-3.8s4.6 1.4 4.6 3.8","M13.6 15.4c0-1.7-.8-2.9-2.2-3.4"],settings:["M9 11.2a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4z","M9 2.4l1.1 1.8 2.1-.2.9 1.9 1.8 1.1-.2 2.1 1.8 1.1-1.8 1.1.2 2.1-1.8 1.1-.9 1.9-2.1-.2L9 15.6l-1.1 1.8-2.1.2-.9-1.9-1.8-1.1.2-2.1L1.5 11l1.8-1.1-.2-2.1 1.8-1.1.9-1.9 2.1.2L9 2.4z"]},g=x(()=>[{to:"/admin/devices",label:"数据采集",hint:"设备 / 点位 / 网关",tone:"accent",icon:"devices"},{to:"/admin/sensors",label:"设施管理",hint:"压差 / 照明 / 监控 / 门禁",tone:"accent",icon:"devices"},{to:"/admin/metering",label:"计量计费",hint:"能耗 / 费率 / 报表",tone:"accent",icon:"metering"},{to:"/admin/history",label:"历史曲线",hint:"时序 / 指标",tone:"accent",icon:"devices"},{to:"/admin/users",label:"用户管理",hint:"账号 / 角色",tone:"warn",icon:"users",adminOnly:!0},{to:"/admin/settings",label:"系统设置",hint:"采集 / 模式",tone:"muted",icon:"settings"}].filter(l=>!l.adminOnly||n.isAdmin));function M(l){return d.path===l||d.path.startsWith(`${l}/`)}function f(){n.logout(),c.push("/login")}function u(){c.push("/dashboard")}return(l,t)=>{var p,_;const y=v("RouterLink"),C=v("RouterView");return a(),i("div",I,[s("header",E,[s("div",{class:"tops-brand",onClick:u},[...t[0]||(t[0]=[z('<span class="tops-brand__mark" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6"><path d="M12 12c0-2.5 2-4.5 4.5-4.5S21 9.4 21 12c0 4.4-3.6 8-8 8-5 0-9-4-9-9 0-5.5 4.5-10 10-10" stroke-linecap="round"></path><circle cx="12" cy="12" r="1.7"></circle></svg></span><span class="tops-brand__copy"><strong>管理后台</strong><small>ADMIN CONSOLE</small></span>',2)])]),s("div",F,[t[3]||(t[3]=s("span",{class:"tops-chip"},[s("small",null,"系统"),s("b",null,"JH-LAB")],-1)),s("span",J,[t[1]||(t[1]=s("span",{class:"tops-dot","aria-hidden":"true"},null,-1)),t[2]||(t[2]=s("small",null,"角色",-1)),s("b",null,e(r(n).isAdmin?"ADMIN":"VIEWER"),1)])]),s("div",W,[s("div",$,[s("small",null,e(((p=r(n).user)==null?void 0:p.displayName)||((_=r(n).user)==null?void 0:_.username))+" · "+e(r(n).isAdmin?"管理员":"只读"),1),s("b",null,e(r(b)),1)]),s("button",{type:"button",class:"tops-logout",onClick:u},"返回大屏"),s("button",{type:"button",class:"tops-logout",onClick:f},"退出")])]),s("div",j,[s("nav",P,[t[4]||(t[4]=s("div",{class:"tops-side__head"},[s("strong",null,"菜单"),s("small",null,"MODULES")],-1)),s("div",T,[(a(!0),i(h,null,m(g.value,o=>(a(),O(y,{key:o.to,to:o.to,class:S(["tops-menu__item",[`tops-tone--${o.tone}`,{"is-on":M(o.to)}]])},{default:R(()=>[s("span",U,[(a(),i("svg",q,[(a(!0),i(h,null,m(k[o.icon],(A,L)=>(a(),i("path",{key:L,d:A,"stroke-linejoin":"round"},null,8,G))),128))]))]),s("span",K,[s("span",Q,e(o.label),1),s("span",X,e(o.hint),1)])]),_:2},1032,["to","class"]))),128))]),t[5]||(t[5]=s("div",{class:"tops-side__foot"},[s("span",{class:"tops-dot tops-tone--ok","aria-hidden":"true"}),s("span",null,"协议点表已接入")],-1))]),s("main",Y,[N(C)])])])}}});export{os as default};
.mask[data-v-684cbf61]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:80;background:#000814b8;display:grid;place-items:center;padding:24px}.dialog[data-v-684cbf61]{width:min(1440px,98vw);height:min(860px,94vh);display:flex;flex-direction:column;background:#071426;border:1px solid rgba(0,247,255,.28);border-radius:8px;overflow:hidden;box-shadow:0 20px 60px #00000073}.head[data-v-684cbf61]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid rgba(0,247,255,.16)}.head h3[data-v-684cbf61]{margin:0;font-size:16px;color:#f4feff;letter-spacing:.06em}.hint[data-v-684cbf61]{margin:0;padding:8px 16px;font-size:12px;color:#7eb6d6;background:#00284859}.body[data-v-684cbf61]{flex:1;min-height:0;display:grid;grid-template-columns:280px 1fr}.icon-pane[data-v-684cbf61]{display:flex;flex-direction:column;min-height:0;border-right:1px solid rgba(0,247,255,.14);background:#001024a6}.icon-head[data-v-684cbf61]{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 12px;font-size:12px;color:#f4feff;border-bottom:1px solid rgba(0,247,255,.12)}.icon-search[data-v-684cbf61]{width:120px;height:28px;border-radius:4px;border:1px solid rgba(0,247,255,.25);background:#00000059;color:#d7f4ff;padding:0 8px;font-size:12px}.icon-err[data-v-684cbf61]{margin:8px 12px 0;font-size:12px;color:#ffb0b0}.icon-grid[data-v-684cbf61]{flex:1;min-height:0;overflow:auto;padding:8px;display:grid;grid-template-columns:1fr 1fr;gap:6px;align-content:start}.icon-item[data-v-684cbf61]{display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px 4px;border-radius:6px;border:1px solid rgba(0,247,255,.16);background:#081c3873;color:#9ec9e0;cursor:pointer;font-size:10px}.icon-item img[data-v-684cbf61]{width:36px;height:36px;object-fit:contain}.icon-item span[data-v-684cbf61]{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-item[data-v-684cbf61]:hover{border-color:#00f7ff73}.icon-item.active[data-v-684cbf61]{border-color:#00f7ff;background:#0064a059;color:#fff;box-shadow:0 0 0 1px #00f7ff40 inset}.icon-current[data-v-684cbf61]{display:flex;align-items:center;gap:8px;padding:8px 12px;border-top:1px solid rgba(0,247,255,.12);font-size:11px;color:#7eb6d6}.icon-current img[data-v-684cbf61]{width:22px;height:22px;object-fit:contain}.icon-current b[data-v-684cbf61]{color:#f4feff;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.frame-wrap[data-v-684cbf61]{min-height:0;background:#000}.frame[data-v-684cbf61]{width:100%;height:100%;border:0;display:block}.foot[data-v-684cbf61]{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-top:1px solid rgba(0,247,255,.16)}.coords[data-v-684cbf61]{font-family:var(--digital-font);font-size:13px;color:var(--accent)}.coords.muted[data-v-684cbf61]{color:var(--panel-muted)}.actions[data-v-684cbf61]{display:flex;gap:8px}.mask[data-v-a26cda54]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:330;background:#000814a6;display:grid;place-items:center;padding:24px;pointer-events:auto}.dialog[data-v-a26cda54]{width:min(960px,92vw);background:#071426;border:1px solid rgba(0,247,255,.28);border-radius:8px;overflow:hidden;box-shadow:0 20px 60px #00000073}.head[data-v-a26cda54]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid rgba(0,247,255,.16)}.head h3[data-v-a26cda54]{margin:0;font-size:16px;color:#f4feff}.body[data-v-a26cda54]{padding:12px;min-height:360px;background:#000}.empty[data-v-a26cda54]{min-height:320px;display:grid;place-items:center;color:#b9d6f08c;font-size:13px}.btn[data-v-a26cda54]{border:1px solid rgba(0,247,255,.28);background:transparent;color:#d7f7ff;border-radius:4px;padding:6px 12px;cursor:pointer;font-size:12px}.btn[data-v-a26cda54]:hover{border-color:#00f7ff8c}.cameras-page[data-v-f516d601]{min-height:0}.list-panel[data-v-f516d601]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-f516d601]{display:flex;gap:8px}.create-row[data-v-f516d601]{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;margin-bottom:12px}.create-row .field[data-v-f516d601]{min-width:120px}.table-wrap[data-v-f516d601]{flex:1;min-height:0;overflow:auto}.ops[data-v-f516d601]{white-space:nowrap}.ops .btn[data-v-f516d601]{margin-right:4px}.mono[data-v-f516d601]{font-family:var(--digital-font);font-size:12px}.st[data-v-f516d601]{display:inline-block;padding:1px 6px;border-radius:2px;font-size:11px;margin-right:4px}.st.online[data-v-f516d601]{background:#52c41a26;color:#7dff9a}.st.offline[data-v-f516d601]{background:#8ea3b826;color:#9bb0c4}.st.alarm[data-v-f516d601]{background:#ff4d4f26;color:#ffb0b0}.st.off[data-v-f516d601]{background:#faad141f;color:#ffd666}.empty[data-v-f516d601]{text-align:center;color:var(--panel-muted);padding:24px}.msg.ok[data-v-f516d601]{color:#7dcea0}.msg.err[data-v-f516d601]{color:#ffb0b0}.tip[data-v-f516d601]{margin-top:10px;font-size:12px;color:var(--panel-muted)}
import{d as E,j as k,l as B,C,c as d,b as e,t as a,h as t,H as _,F as S,x as H,r as c,s as v,E as I,o as r,p as y,_ as T}from"./index-BssDqbym.js";import{E as g,m as f}from"./historyChart-DJ1B6lTP.js";import{u as V}from"./realtime-lmP-Cubp.js";const F={class:"page-fill cold-page"},L={class:"panel",style:{flex:"1","min-height":"0",display:"flex","flex-direction":"column",gap:"10px"}},M={class:"panel-head"},O={class:"panel-title"},R={class:"badge"},N={class:"charts"},j={class:"chart-card"},z={class:"chart-title"},D={class:"chart-card"},P={class:"chart-title"},U={class:"table"},q=E({__name:"ColdView",setup(A){const{t:s}=k(),x=V(),u=c([]),m=c([]),l=c(!1);let n;const b=v(()=>f(u.value,{unit:"℃",maxSeries:12})),w=v(()=>f(m.value,{unit:"%",maxSeries:12}));async function p(){l.value=!0;try{const[h,i]=await Promise.all([y("/api/realtime/history?source=fridge&metric=temp&hours=24&bucketMinutes=60"),y("/api/realtime/history?source=fridge&metric=humidity&hours=24&bucketMinutes=60")]);u.value=h.series||[],m.value=i.series||[]}finally{l.value=!1}}return B(()=>{p(),n=window.setInterval(()=>void p(),6e4)}),C(()=>{n&&window.clearInterval(n)}),(h,i)=>(r(),d("div",F,[e("section",L,[e("div",M,[e("div",O,[i[0]||(i[0]=e("div",{class:"vbar"},null,-1)),e("h3",null,a(t(s)("pages.coldTitle")),1)]),e("div",R,a(t(s)("pages.coldBadge")),1)]),e("div",N,[e("div",j,[e("div",z,a(t(s)("pages.coldTemp")),1),_(g,{option:b.value,loading:l.value,"empty-text":t(s)("pages.coldTempEmpty")},null,8,["option","loading","empty-text"])]),e("div",D,[e("div",P,a(t(s)("pages.coldHumidity")),1),_(g,{option:w.value,loading:l.value,"empty-text":t(s)("pages.coldHumidityEmpty")},null,8,["option","loading","empty-text"])])]),e("table",U,[e("thead",null,[e("tr",null,[e("th",null,a(t(s)("common.device")),1),e("th",null,a(t(s)("pages.colTemp")),1),e("th",null,a(t(s)("pages.colHumidity")),1),e("th",null,a(t(s)("screen.status")),1)])]),e("tbody",null,[(r(!0),d(S,null,H(t(x).snapshot.fridges,o=>(r(),d("tr",{key:o.id},[e("td",null,a(o.name),1),e("td",{style:I({color:o.temp>-19?"var(--amber)":"var(--green)"})},a(o.temp.toFixed(1))+" ℃ ",5),e("td",null,a(o.humidity)+" %",1),e("td",null,a(o.temp>-19?t(s)("status.overLimit"):t(s)("common.normal")),1)]))),128))])])])]))}}),Q=T(q,[["__scopeId","data-v-5562f3c6"]]);export{Q as default};
.cold-page[data-v-8bd876a6]{display:flex;min-height:0}.charts[data-v-8bd876a6]{display:grid;grid-template-columns:1fr 1fr;gap:10px;min-height:260px;flex:1}.chart-card[data-v-8bd876a6]{min-height:0;display:flex;flex-direction:column;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.chart-title[data-v-8bd876a6]{font-size:12px;color:var(--label);margin-bottom:4px}.chart-card[data-v-8bd876a6] .echart-wrap{flex:1;min-height:200px}
import{d as b,j as w,s as k,c as o,b as t,D as p,F as B,z as S,h as C,r as n,B as v,o as r,t as a,y as E,l as h,_ as V}from"./index-Bs_x-_cK.js";import{E as y,m as f}from"./historyChart-DTdcyadg.js";import{u as F}from"./realtime-CI_KLy_N.js";const I={class:"page-fill cold-page"},M={class:"panel",style:{flex:"1","min-height":"0",display:"flex","flex-direction":"column",gap:"10px"}},O={class:"charts"},z={class:"chart-card"},D={class:"chart-card"},L={class:"table"},N=b({__name:"ColdView",setup(R){const _=F(),d=n([]),u=n([]),l=n(!1);let i;const g=v(()=>f(d.value,{unit:"℃",maxSeries:12})),x=v(()=>f(u.value,{unit:"%",maxSeries:12}));async function c(){l.value=!0;try{const[m,e]=await Promise.all([h("/api/realtime/history?source=fridge&metric=temp&hours=24&bucketMinutes=15"),h("/api/realtime/history?source=fridge&metric=humidity&hours=24&bucketMinutes=15")]);d.value=m.series||[],u.value=e.series||[]}finally{l.value=!1}}return w(()=>{c(),i=window.setInterval(()=>void c(),6e4)}),k(()=>{i&&window.clearInterval(i)}),(m,e)=>(r(),o("div",I,[t("section",M,[e[3]||(e[3]=t("div",{class:"panel-head"},[t("div",{class:"panel-title"},[t("div",{class:"vbar"}),t("h3",null,"冷链监测 · 历史曲线")]),t("div",{class:"badge"},"E栋 · 近 24h")],-1)),t("div",O,[t("div",z,[e[0]||(e[0]=t("div",{class:"chart-title"},"温度 ℃",-1)),p(y,{option:g.value,loading:l.value,"empty-text":"温度历史积累中"},null,8,["option","loading"])]),t("div",D,[e[1]||(e[1]=t("div",{class:"chart-title"},"湿度 %RH",-1)),p(y,{option:x.value,loading:l.value,"empty-text":"湿度历史积累中"},null,8,["option","loading"])])]),t("table",L,[e[2]||(e[2]=t("thead",null,[t("tr",null,[t("th",null,"设备"),t("th",null,"温度 (℃)"),t("th",null,"湿度 (%)"),t("th",null,"状态")])],-1)),t("tbody",null,[(r(!0),o(B,null,S(C(_).snapshot.fridges,s=>(r(),o("tr",{key:s.id},[t("td",null,a(s.name),1),t("td",{style:E({color:s.temp>-19?"var(--amber)":"var(--green)"})},a(s.temp.toFixed(1))+" ℃ ",5),t("td",null,a(s.humidity)+" %",1),t("td",null,a(s.temp>-19?"超限":"正常"),1)]))),128))])])])]))}}),U=V(N,[["__scopeId","data-v-8bd876a6"]]);export{U as default};
.cold-page[data-v-5562f3c6]{display:flex;min-height:0}.charts[data-v-5562f3c6]{display:grid;grid-template-columns:1fr 1fr;gap:10px;min-height:260px;flex:1}.chart-card[data-v-5562f3c6]{min-height:0;display:flex;flex-direction:column;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.chart-title[data-v-5562f3c6]{font-size:12px;color:var(--label);margin-bottom:4px}.chart-card[data-v-5562f3c6] .echart-wrap{flex:1;min-height:200px}
.devices-page[data-v-a043261c]{min-height:0}.list-panel[data-v-a043261c]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;gap:10px}.panel-title[data-v-a043261c]{gap:10px}.head-actions[data-v-a043261c]{display:flex;flex-wrap:wrap;align-items:center;gap:8px;justify-content:flex-end}.tab-row[data-v-a043261c],.filters[data-v-a043261c]{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}.field[data-v-a043261c]{display:flex;flex-direction:column;gap:4px;min-width:140px}.field label[data-v-a043261c]{font-size:12px;opacity:.75}.field input[data-v-a043261c],.field select[data-v-a043261c],.csv-area[data-v-a043261c]{background:#060c14a6;box-shadow:0 0 0 1px #3dbeff47 inset;border:none;color:inherit;border-radius:2px;padding:6px 8px}.csv-area[data-v-a043261c]{width:100%;min-height:160px;margin-top:4px;font-family:var(--digital-font),ui-monospace,monospace;font-size:12px}.filter-hint[data-v-a043261c]{font-size:12px;color:var(--panel-muted);margin-bottom:6px}.check[data-v-a043261c]{display:flex;align-items:center;gap:6px;font-size:13px;margin-bottom:4px}.table-wrap[data-v-a043261c]{flex:1;min-height:0;overflow:auto}.msg.ok[data-v-a043261c]{color:var(--ok);font-size:13px}.msg.err[data-v-a043261c]{color:#ffb0b0;font-size:13px}.hint[data-v-a043261c]{font-size:12px;opacity:.7;margin-top:10px}.mono[data-v-a043261c]{font-family:var(--digital-font),ui-monospace,monospace;font-size:11px;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ops[data-v-a043261c]{display:flex;gap:6px}.row-click[data-v-a043261c]{cursor:pointer}.row-click:hover td[data-v-a043261c]{background:#3dbeff1f}.device-meta[data-v-a043261c]{display:flex;flex-wrap:wrap;gap:10px 16px;margin-bottom:12px}.device-meta span[data-v-a043261c]{display:inline-flex;align-items:baseline;gap:6px;padding:4px 8px;border:1px solid var(--panel-border);border-radius:2px;background:#060c1473}.device-meta small[data-v-a043261c]{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--panel-muted)}.device-meta b[data-v-a043261c]{font-size:12px;font-weight:600;color:var(--panel-title)}.detail-table-wrap[data-v-a043261c]{max-height:min(68vh,720px);overflow:auto}.eng[data-v-a043261c]{font-family:var(--digital-font),ui-monospace,monospace;font-size:14px;font-weight:700;color:var(--accent)}.eng.muted[data-v-a043261c]{color:var(--panel-muted);font-weight:600}.ts[data-v-a043261c]{font-size:11px;color:var(--panel-muted)}.empty[data-v-a043261c]{text-align:center;color:var(--panel-muted);padding:28px 10px!important}.gw-strip[data-v-a043261c]{margin:0 0 10px;padding:10px 12px;border:1px solid var(--panel-border);border-radius:4px;background:#06101c73}.gw-strip-title[data-v-a043261c]{font-size:12px;color:var(--panel-muted);margin-bottom:8px}.gw-chips[data-v-a043261c]{display:flex;flex-wrap:wrap;gap:8px}.gw-chip[data-v-a043261c]{display:inline-flex;align-items:center;gap:8px;padding:6px 10px;border-radius:4px;border:1px solid rgba(0,247,255,.2);background:#00284859;font-size:12px;color:#cfe8f6}.gw-chip b[data-v-a043261c]{font-weight:600;color:#eaf4ff}.gw-chip i[data-v-a043261c]{font-style:normal;font-size:11px}.gw-chip i.on[data-v-a043261c]{color:var(--ok, #4dffb5)}.gw-chip i.off[data-v-a043261c]{color:#ffb0b0}.gw-hint[data-v-a043261c]{margin:8px 0 0}.modal-mask[data-v-a043261c]{position:fixed;top:0;right:0;bottom:0;left:0;background:#04080eb8;display:flex;align-items:center;justify-content:center;z-index:50}.modal[data-v-a043261c]{width:min(640px,92vw);max-height:90vh;overflow:auto}.modal-lg[data-v-a043261c]{width:min(860px,96vw)}.modal-xl[data-v-a043261c]{width:min(1280px,98vw);max-height:92vh}.modal-actions[data-v-a043261c]{margin-top:14px;display:flex;gap:10px}.form-grid[data-v-a043261c]{display:grid;grid-template-columns:1fr 1fr;gap:10px}.write-cell[data-v-a043261c]{display:inline-flex;align-items:center;gap:6px;min-width:140px;white-space:nowrap}.ops[data-v-a043261c]{white-space:nowrap}.ops .btn[data-v-a043261c]{margin-right:4px}.write-input[data-v-a043261c]{width:88px;padding:4px 6px;border-radius:6px;border:1px solid rgba(255,255,255,.12);background:#00000040;color:inherit}.bool-on[data-v-a043261c],.bool-off[data-v-a043261c]{min-width:44px;padding:4px 10px}.muted[data-v-a043261c]{color:var(--panel-muted)}.msg.ok[data-v-a043261c]{color:#7dcea0}
.devices-page[data-v-b0783294]{min-height:0}.list-panel[data-v-b0783294]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;gap:10px}.panel-title[data-v-b0783294]{gap:10px}.head-actions[data-v-b0783294]{display:flex;flex-wrap:wrap;align-items:center;gap:8px;justify-content:flex-end}.tab-row[data-v-b0783294],.filters[data-v-b0783294]{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}.field[data-v-b0783294]{display:flex;flex-direction:column;gap:4px;min-width:140px}.field label[data-v-b0783294]{font-size:12px;opacity:.75}.field input[data-v-b0783294],.field select[data-v-b0783294],.csv-area[data-v-b0783294]{background:#060c14a6;box-shadow:0 0 0 1px #3dbeff47 inset;border:none;color:inherit;border-radius:2px;padding:6px 8px}.csv-area[data-v-b0783294]{width:100%;min-height:160px;margin-top:4px;font-family:var(--digital-font),ui-monospace,monospace;font-size:12px}.filter-hint[data-v-b0783294]{font-size:12px;color:var(--panel-muted);margin-bottom:6px}.check[data-v-b0783294]{display:flex;align-items:center;gap:6px;font-size:13px;margin-bottom:4px}.table-wrap[data-v-b0783294]{flex:1;min-height:0;overflow:auto}.msg.ok[data-v-b0783294]{color:var(--ok);font-size:13px}.msg.err[data-v-b0783294]{color:#ffb0b0;font-size:13px}.hint[data-v-b0783294]{font-size:12px;opacity:.7;margin-top:10px}.mono[data-v-b0783294]{font-family:var(--digital-font),ui-monospace,monospace;font-size:11px;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.ops[data-v-b0783294]{display:flex;gap:6px}.row-click[data-v-b0783294]{cursor:pointer}.row-click:hover td[data-v-b0783294]{background:#3dbeff1f}.device-meta[data-v-b0783294]{display:flex;flex-wrap:wrap;gap:10px 16px;margin-bottom:12px}.device-meta span[data-v-b0783294]{display:inline-flex;align-items:baseline;gap:6px;padding:4px 8px;border:1px solid var(--panel-border);border-radius:2px;background:#060c1473}.device-meta small[data-v-b0783294]{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--panel-muted)}.device-meta b[data-v-b0783294]{font-size:12px;font-weight:600;color:var(--panel-title)}.detail-table-wrap[data-v-b0783294]{max-height:min(68vh,720px);overflow:auto}.eng[data-v-b0783294]{font-family:var(--digital-font),ui-monospace,monospace;font-size:14px;font-weight:700;color:var(--accent)}.eng.muted[data-v-b0783294]{color:var(--panel-muted);font-weight:600}.ts[data-v-b0783294]{font-size:11px;color:var(--panel-muted)}.empty[data-v-b0783294]{text-align:center;color:var(--panel-muted);padding:28px 10px!important}.gw-status[data-v-b0783294]{margin-bottom:8px}.modal-mask[data-v-b0783294]{position:fixed;top:0;right:0;bottom:0;left:0;background:#04080eb8;display:flex;align-items:center;justify-content:center;z-index:50}.modal[data-v-b0783294]{width:min(640px,92vw);max-height:90vh;overflow:auto}.modal-lg[data-v-b0783294]{width:min(860px,96vw)}.modal-xl[data-v-b0783294]{width:min(1280px,98vw);max-height:92vh}.modal-actions[data-v-b0783294]{margin-top:14px;display:flex;gap:10px}.form-grid[data-v-b0783294]{display:grid;grid-template-columns:1fr 1fr;gap:10px}.write-cell[data-v-b0783294]{display:inline-flex;align-items:center;gap:6px;min-width:140px;white-space:nowrap}.ops[data-v-b0783294]{white-space:nowrap}.ops .btn[data-v-b0783294]{margin-right:4px}.write-input[data-v-b0783294]{width:88px;padding:4px 6px;border-radius:6px;border:1px solid rgba(255,255,255,.12);background:#00000040;color:inherit}.bool-on[data-v-b0783294],.bool-off[data-v-b0783294]{min-width:44px;padding:4px 10px}.muted[data-v-b0783294]{color:var(--panel-muted)}.msg.ok[data-v-b0783294]{color:#7dcea0}
.history-page[data-v-2bf42be6]{display:flex;min-height:0}.history-panel[data-v-2bf42be6]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.filters[data-v-2bf42be6]{display:flex;flex-wrap:wrap;gap:10px 14px;align-items:end;padding:8px 4px 12px}.filters label[data-v-2bf42be6]{display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--label, #8eb0c4)}.filters select[data-v-2bf42be6]{min-width:120px;height:30px;border-radius:4px;border:1px solid rgba(0,247,255,.25);background:#041428d9;color:#e8f6ff;padding:0 8px}.btn[data-v-2bf42be6]{height:30px;padding:0 14px;border-radius:4px;border:1px solid rgba(0,247,255,.35);background:#0078a059;color:#dff8ff;cursor:pointer}.btn[data-v-2bf42be6]:disabled{opacity:.5;cursor:default}.chart-box[data-v-2bf42be6]{flex:1;min-height:280px;border:1px solid rgba(0,247,255,.15);border-radius:6px;background:#04102066;padding:6px}.table-wrap[data-v-2bf42be6]{margin-top:10px;max-height:220px;overflow:auto}.muted[data-v-2bf42be6]{text-align:center;color:var(--label)}.err[data-v-2bf42be6]{color:#ffb0b0;font-size:12px;margin-bottom:8px}
import{d as $,q as M,j as D,c as u,b as t,t as s,g,e as y,L as h,F as k,z as V,f as S,D as q,r,B as w,l as F,o as i,_ as H}from"./index-Bs_x-_cK.js";import{E as O,m as P}from"./historyChart-DTdcyadg.js";const j={class:"page-fill history-page"},z={class:"panel history-panel"},R={class:"panel-head"},T={class:"badge"},A={class:"filters"},G=["value"],J=["value"],K=["value"],Q=["disabled"],W={key:0,class:"err"},X={class:"chart-box"},Y={class:"table-wrap"},Z={class:"table"},ee={key:0},te=$({__name:"HistoryView",setup(le){const d=r([]),c=r(!1),m=r(""),f=r(24),x=r(15),n=r(""),o=r(""),b=r(""),v=r([]),L=w(()=>{const a=new Map;for(const e of d.value)a.set(e.source,e.sourceLabel);return[...a.entries()].map(([e,l])=>({value:e,label:l}))}),U=w(()=>{const a=new Map;for(const e of d.value)n.value&&e.source!==n.value||a.set(e.metric,e.metricLabel);return[...a.entries()].map(([e,l])=>({value:e,label:l}))}),C=w(()=>d.value.filter(a=>(!n.value||a.source===n.value)&&(!o.value||a.metric===o.value)).map(a=>({value:a.deviceId,label:a.name,points:a.points}))),E=w(()=>P(v.value,{maxSeries:16,unit:o.value==="temp"||o.value.includes("temp")?"℃":o.value==="pressure"?"Pa":""})),B=w(()=>{const a=v.value.length,e=v.value.reduce((l,p)=>l+p.points.length,0);return`${a} 条序列 · ${e} 个聚合点 · 近 ${f.value}h`});async function I(){c.value=!0,m.value="";try{const a=await F("/api/realtime/history/catalog");d.value=a.catalog||[],!n.value&&d.value.length&&(n.value=d.value[0].source,o.value=d.value[0].metric)}catch(a){m.value=a instanceof Error?a.message:"目录加载失败"}finally{c.value=!1}}async function _(){if(!n.value||!o.value){v.value=[];return}c.value=!0,m.value="";try{const a=new URLSearchParams({source:n.value,metric:o.value,hours:String(f.value),bucketMinutes:String(x.value)});b.value&&a.set("deviceId",b.value);const e=await F(`/api/realtime/history?${a}`);v.value=e.series||[]}catch(a){m.value=a instanceof Error?a.message:"曲线加载失败",v.value=[]}finally{c.value=!1}}return M([n,o],()=>{b.value=""}),M([n,o,b,f,x],()=>{_()}),D(async()=>{await I(),await _()}),(a,e)=>(i(),u("div",j,[t("section",z,[t("div",R,[e[5]||(e[5]=t("div",{class:"panel-title"},[t("div",{class:"vbar"}),t("h3",null,"历史数据与曲线")],-1)),t("div",T,"时序预聚合 · "+s(B.value),1)]),t("div",A,[t("label",null,[e[6]||(e[6]=g(" 类别 ",-1)),y(t("select",{"onUpdate:modelValue":e[0]||(e[0]=l=>n.value=l)},[(i(!0),u(k,null,V(L.value,l=>(i(),u("option",{key:l.value,value:l.value},s(l.label),9,G))),128))],512),[[h,n.value]])]),t("label",null,[e[7]||(e[7]=g(" 指标 ",-1)),y(t("select",{"onUpdate:modelValue":e[1]||(e[1]=l=>o.value=l)},[(i(!0),u(k,null,V(U.value,l=>(i(),u("option",{key:l.value,value:l.value},s(l.label),9,J))),128))],512),[[h,o.value]])]),t("label",null,[e[9]||(e[9]=g(" 设备 ",-1)),y(t("select",{"onUpdate:modelValue":e[2]||(e[2]=l=>b.value=l)},[e[8]||(e[8]=t("option",{value:""},"全部设备",-1)),(i(!0),u(k,null,V(C.value,l=>(i(),u("option",{key:l.value,value:l.value},s(l.label)+" ("+s(l.points)+") ",9,K))),128))],512),[[h,b.value]])]),t("label",null,[e[11]||(e[11]=g(" 时长 ",-1)),y(t("select",{"onUpdate:modelValue":e[3]||(e[3]=l=>f.value=l)},[...e[10]||(e[10]=[t("option",{value:6},"近 6 小时",-1),t("option",{value:24},"近 24 小时",-1),t("option",{value:72},"近 3 天",-1),t("option",{value:168},"近 7 天",-1)])],512),[[h,f.value,void 0,{number:!0}]])]),t("label",null,[e[13]||(e[13]=g(" 粒度 ",-1)),y(t("select",{"onUpdate:modelValue":e[4]||(e[4]=l=>x.value=l)},[...e[12]||(e[12]=[t("option",{value:5},"5 分钟",-1),t("option",{value:15},"15 分钟",-1),t("option",{value:60},"1 小时",-1)])],512),[[h,x.value,void 0,{number:!0}]])]),t("button",{type:"button",class:"btn",disabled:c.value,onClick:_},"刷新",8,Q)]),m.value?(i(),u("div",W,s(m.value),1)):S("",!0),t("div",X,[q(O,{option:E.value,loading:c.value,"empty-text":"暂无该指标历史数据,采集后将自动出现"},null,8,["option","loading"])]),t("div",Y,[t("table",Z,[e[15]||(e[15]=t("thead",null,[t("tr",null,[t("th",null,"设备"),t("th",null,"点数"),t("th",null,"最新"),t("th",null,"最低"),t("th",null,"最高"),t("th",null,"平均")])],-1)),t("tbody",null,[(i(!0),u(k,null,V(v.value,l=>(i(),u("tr",{key:l.id},[t("td",null,s(l.name||l.id),1),t("td",null,s(l.points.length),1),t("td",null,s(l.points.length?l.points[l.points.length-1].v.toFixed(2):"—"),1),t("td",null,s(l.points.length?Math.min(...l.points.map(p=>p.v)).toFixed(2):"—"),1),t("td",null,s(l.points.length?Math.max(...l.points.map(p=>p.v)).toFixed(2):"—"),1),t("td",null,s(l.points.length?(l.points.reduce((p,N)=>p+N.v,0)/l.points.length).toFixed(2):"—"),1)]))),128)),v.value.length?S("",!0):(i(),u("tr",ee,[...e[14]||(e[14]=[t("td",{colspan:"6",class:"muted"},"无数据",-1)])]))])])])])]))}}),oe=H(te,[["__scopeId","data-v-c9fbd5ec"]]);export{oe as default};
import{d as P,j as A,k as F,l as O,c as r,b as e,t as a,h as n,g as f,e as b,P as x,F as w,x as V,f as C,H as T,r as v,s as M,p as H,o as c,_ as j}from"./index-BssDqbym.js";import{E as q,m as R}from"./historyChart-DJ1B6lTP.js";const $={class:"page-fill history-page"},z={class:"panel history-panel"},G={class:"panel-head"},J={class:"panel-title"},K={class:"badge"},Q={class:"filters"},W=["value"],X=["value"],Y={value:""},Z=["value"],ee={value:6},te={value:24},ae={value:72},se={value:168},le={value:5},oe={value:15},ne={value:60},ie=["disabled"],ue={key:0,class:"err"},re={class:"chart-box"},ce={class:"table-wrap"},ve={class:"table"},pe={key:0},de={colspan:"6",class:"muted"},he=P({__name:"HistoryView",setup(me){const{t:s}=A(),d=v([]),m=v(!1),g=v(""),_=v(24),k=v(15),i=v(""),u=v(""),y=v(""),p=v([]),D=M(()=>{const o=new Map;for(const l of d.value)o.set(l.source,l.sourceLabel);return[...o.entries()].map(([l,t])=>({value:l,label:t}))}),E=M(()=>{const o=new Map;for(const l of d.value)i.value&&l.source!==i.value||o.set(l.metric,l.metricLabel);return[...o.entries()].map(([l,t])=>({value:l,label:t}))}),L=M(()=>d.value.filter(o=>(!i.value||o.source===i.value)&&(!u.value||o.metric===u.value)).map(o=>({value:o.deviceId,label:o.name,points:o.points}))),U=M(()=>R(p.value,{maxSeries:16,unit:u.value==="temp"||u.value.includes("temp")?"℃":u.value==="pressure"?"Pa":""})),B=M(()=>{const o=p.value.length,l=p.value.reduce((t,h)=>t+h.points.length,0);return s("pages.historySummary",{n:o,pts:l,hours:_.value})});async function I(){m.value=!0,g.value="";try{const o=await H("/api/realtime/history/catalog");d.value=o.catalog||[],!i.value&&d.value.length&&(i.value=d.value[0].source,u.value=d.value[0].metric)}catch(o){g.value=o instanceof Error?o.message:s("pages.historyCatalogFail")}finally{m.value=!1}}async function S(){if(!i.value||!u.value){p.value=[];return}m.value=!0,g.value="";try{const o=new URLSearchParams({source:i.value,metric:u.value,hours:String(_.value),bucketMinutes:String(k.value)});y.value&&o.set("deviceId",y.value);const l=await H(`/api/realtime/history?${o}`);p.value=l.series||[]}catch(o){g.value=o instanceof Error?o.message:s("pages.historySeriesFail"),p.value=[]}finally{m.value=!1}}return F([i,u],()=>{y.value=""}),F([i,u,y,_,k],()=>{S()}),O(async()=>{await I(),await S()}),(o,l)=>(c(),r("div",$,[e("section",z,[e("div",G,[e("div",J,[l[5]||(l[5]=e("div",{class:"vbar"},null,-1)),e("h3",null,a(n(s)("pages.historyTitle")),1)]),e("div",K,a(n(s)("pages.historyBadge",{summary:B.value})),1)]),e("div",Q,[e("label",null,[f(a(n(s)("pages.historyCat"))+" ",1),b(e("select",{"onUpdate:modelValue":l[0]||(l[0]=t=>i.value=t)},[(c(!0),r(w,null,V(D.value,t=>(c(),r("option",{key:t.value,value:t.value},a(t.label),9,W))),128))],512),[[x,i.value]])]),e("label",null,[f(a(n(s)("pages.historyMetric"))+" ",1),b(e("select",{"onUpdate:modelValue":l[1]||(l[1]=t=>u.value=t)},[(c(!0),r(w,null,V(E.value,t=>(c(),r("option",{key:t.value,value:t.value},a(t.label),9,X))),128))],512),[[x,u.value]])]),e("label",null,[f(a(n(s)("pages.historyDevice"))+" ",1),b(e("select",{"onUpdate:modelValue":l[2]||(l[2]=t=>y.value=t)},[e("option",Y,a(n(s)("pages.historyAllDevices")),1),(c(!0),r(w,null,V(L.value,t=>(c(),r("option",{key:t.value,value:t.value},a(t.label)+" ("+a(t.points)+") ",9,Z))),128))],512),[[x,y.value]])]),e("label",null,[f(a(n(s)("pages.historyDuration"))+" ",1),b(e("select",{"onUpdate:modelValue":l[3]||(l[3]=t=>_.value=t)},[e("option",ee,a(n(s)("pages.historyH6")),1),e("option",te,a(n(s)("pages.historyH24")),1),e("option",ae,a(n(s)("pages.historyH72")),1),e("option",se,a(n(s)("pages.historyH168")),1)],512),[[x,_.value,void 0,{number:!0}]])]),e("label",null,[f(a(n(s)("pages.historyBucket"))+" ",1),b(e("select",{"onUpdate:modelValue":l[4]||(l[4]=t=>k.value=t)},[e("option",le,a(n(s)("pages.historyM5")),1),e("option",oe,a(n(s)("pages.historyM15")),1),e("option",ne,a(n(s)("pages.historyM60")),1)],512),[[x,k.value,void 0,{number:!0}]])]),e("button",{type:"button",class:"btn",disabled:m.value,onClick:S},a(n(s)("common.refresh")),9,ie)]),g.value?(c(),r("div",ue,a(g.value),1)):C("",!0),e("div",re,[T(q,{option:U.value,loading:m.value,"empty-text":n(s)("pages.historyEmpty")},null,8,["option","loading","empty-text"])]),e("div",ce,[e("table",ve,[e("thead",null,[e("tr",null,[e("th",null,a(n(s)("common.device")),1),e("th",null,a(n(s)("pages.historyPoints")),1),e("th",null,a(n(s)("pages.historyLatest")),1),e("th",null,a(n(s)("pages.historyMin")),1),e("th",null,a(n(s)("pages.historyMax")),1),e("th",null,a(n(s)("pages.historyAvg")),1)])]),e("tbody",null,[(c(!0),r(w,null,V(p.value,t=>(c(),r("tr",{key:t.id},[e("td",null,a(t.name||t.id),1),e("td",null,a(t.points.length),1),e("td",null,a(t.points.length?t.points[t.points.length-1].v.toFixed(2):"—"),1),e("td",null,a(t.points.length?Math.min(...t.points.map(h=>h.v)).toFixed(2):"—"),1),e("td",null,a(t.points.length?Math.max(...t.points.map(h=>h.v)).toFixed(2):"—"),1),e("td",null,a(t.points.length?(t.points.reduce((h,N)=>h+N.v,0)/t.points.length).toFixed(2):"—"),1)]))),128)),p.value.length?C("",!0):(c(),r("tr",pe,[e("td",de,a(n(s)("pages.historyNoData")),1)]))])])])])]))}}),_e=j(he,[["__scopeId","data-v-2bf42be6"]]);export{_e as default};
.history-page[data-v-c9fbd5ec]{display:flex;min-height:0}.history-panel[data-v-c9fbd5ec]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.filters[data-v-c9fbd5ec]{display:flex;flex-wrap:wrap;gap:10px 14px;align-items:end;padding:8px 4px 12px}.filters label[data-v-c9fbd5ec]{display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--label, #8eb0c4)}.filters select[data-v-c9fbd5ec]{min-width:120px;height:30px;border-radius:4px;border:1px solid rgba(0,247,255,.25);background:#041428d9;color:#e8f6ff;padding:0 8px}.btn[data-v-c9fbd5ec]{height:30px;padding:0 14px;border-radius:4px;border:1px solid rgba(0,247,255,.35);background:#0078a059;color:#dff8ff;cursor:pointer}.btn[data-v-c9fbd5ec]:disabled{opacity:.5;cursor:default}.chart-box[data-v-c9fbd5ec]{flex:1;min-height:280px;border:1px solid rgba(0,247,255,.15);border-radius:6px;background:#04102066;padding:6px}.table-wrap[data-v-c9fbd5ec]{margin-top:10px;max-height:220px;overflow:auto}.muted[data-v-c9fbd5ec]{text-align:center;color:var(--label)}.err[data-v-c9fbd5ec]{color:#ffb0b0;font-size:12px;margin-bottom:8px}
import{d as x,j as P,u as A,c as T,b as s,t as a,h as e,H,n as S,e as w,v as k,r as E,s as p,p as V,o as C,_ as F}from"./index-BssDqbym.js";import{u as O}from"./realtime-lmP-Cubp.js";import{E as z,m as B}from"./historyChart-DJ1B6lTP.js";import{u as c}from"./useMetricHistory-CLFOMuUx.js";const N={class:"page-fill"},j={class:"panel"},D={class:"panel-head"},I={class:"panel-title"},L={class:"badge"},M={class:"metric-grid",style:{"grid-template-columns":"repeat(3, 1fr)"}},R={class:"metric"},U={class:"m-label"},J={class:"m-value"},q={class:"metric"},G={class:"m-label"},K={class:"m-value"},Q={class:"metric"},W={class:"m-label"},X={class:"m-value"},Y={class:"metric"},Z={class:"m-label"},$={class:"m-value"},ss={class:"metric"},es={class:"m-label"},ts={class:"m-value"},as={class:"metric"},is={class:"m-label"},ls={class:"m-value"},os={class:"hist"},ns={class:"chart-title"},ds={class:"panel"},cs={class:"panel-head"},ps={class:"panel-title"},us={class:"form-grid",style:{"max-width":"520px"}},rs={class:"field"},vs=["disabled"],ms={class:"field",style:{"justify-content":"flex-end"}},_s=["disabled"],hs=x({__name:"HvacView",setup(ys){const{t}=P(),o=A(),l=O(),d=E(180),{series:u,loading:r}=c(()=>({source:"hvac",metric:"supply_temp"})),{series:v,loading:m}=c(()=>({source:"hvac",metric:"supply_pressure"})),{series:_,loading:h}=c(()=>({source:"hvac",metric:"exhaust_pressure"})),y=p(()=>B([...u.value.map(n=>({...n,name:t("pau.supplyTemp")})),...v.value.map(n=>({...n,name:t("pau.supplyPressure")})),..._.value.map(n=>({...n,name:t("pau.exhaustPressure")}))],{maxSeries:6})),b=p(()=>r.value||m.value||h.value);async function f(){o.isAdmin&&(await V("/api/realtime/write",{method:"POST",token:o.token,body:JSON.stringify({target:"PAU201",action:"set_supply_pressure",value:d.value})}),alert(t("pages.hvacApplied")))}return(n,i)=>(C(),T("div",N,[s("section",j,[s("div",D,[s("div",I,[i[1]||(i[1]=s("div",{class:"vbar"},null,-1)),s("h3",null,a(e(t)("pages.hvacTitle")),1)]),s("div",L,a(e(l).snapshot.hvac.systemRunning?e(t)("screen.running"):e(t)("status.stopped")),1)]),s("div",M,[s("div",R,[s("div",null,[s("div",U,a(e(t)("pau.supplyTemp")),1),s("div",J,a(e(l).snapshot.hvac.supplyTemp.toFixed(1)),1)])]),s("div",q,[s("div",null,[s("div",G,a(e(t)("pau.supplyHumidity")),1),s("div",K,a(e(l).snapshot.hvac.supplyHumidity),1)])]),s("div",Q,[s("div",null,[s("div",W,a(e(t)("pau.supplyPressure")),1),s("div",X,a(e(l).snapshot.hvac.supplyPressure),1)])]),s("div",Y,[s("div",null,[s("div",Z,a(e(t)("pau.fanHz")),1),s("div",$,a(e(l).snapshot.hvac.supplyFanHz.toFixed(1)),1)])]),s("div",ss,[s("div",null,[s("div",es,a(e(t)("pau.exhaustPressure")),1),s("div",ts,a(e(l).snapshot.hvac.exhaustPressure),1)])]),s("div",as,[s("div",null,[s("div",is,a(e(t)("pau.freshAirTemp")),1),s("div",ls,a(e(l).snapshot.hvac.freshAirTemp.toFixed(1)),1)])])]),s("div",os,[s("div",ns,a(e(t)("pages.hvacHistTitle")),1),H(z,{option:y.value,loading:b.value,"empty-text":e(t)("pages.hvacEmpty")},null,8,["option","loading","empty-text"])])]),s("section",ds,[s("div",cs,[s("div",ps,[i[2]||(i[2]=s("div",{class:"vbar"},null,-1)),s("h3",null,a(e(t)("pages.hvacParams")),1)]),s("div",{class:S(["badge",e(o).isAdmin?"":"amber"])},a(e(o).isAdmin?e(t)("common.writable"):e(t)("common.readonly")),3)]),s("div",us,[s("div",rs,[s("label",null,a(e(t)("pages.hvacSetPressure")),1),w(s("input",{"onUpdate:modelValue":i[0]||(i[0]=g=>d.value=g),type:"number",disabled:!e(o).isAdmin},null,8,vs),[[k,d.value,void 0,{number:!0}]])]),s("div",ms,[i[3]||(i[3]=s("label",null," ",-1)),s("button",{class:"btn",disabled:!e(o).isAdmin,onClick:f},a(e(t)("pages.hvacApply")),9,_s)])])])]))}}),Ps=F(hs,[["__scopeId","data-v-868cf5e9"]]);export{Ps as default};
.hist[data-v-c796d554]{margin-top:12px;min-height:220px;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.chart-title[data-v-c796d554]{font-size:12px;color:var(--label);margin-bottom:4px}.hist[data-v-c796d554] .echart-wrap{height:200px}
import{d as g,u as x,c as A,b as s,t as i,h as t,D as P,n as S,e as k,v as w,r as H,B as r,l as T,o as V,_ as B}from"./index-Bs_x-_cK.js";import{u as C}from"./realtime-CI_KLy_N.js";import{E,m as F}from"./historyChart-DTdcyadg.js";import{u as n}from"./useMetricHistory-Ck1zYYEr.js";const O={class:"page-fill"},D={class:"panel"},M={class:"panel-head"},N={class:"badge"},z={class:"metric-grid",style:{"grid-template-columns":"repeat(3, 1fr)"}},L={class:"metric"},R={class:"m-value"},U={class:"metric"},j={class:"m-value"},I={class:"metric"},J={class:"m-value"},q={class:"metric"},G={class:"m-value"},K={class:"metric"},Q={class:"m-value"},W={class:"metric"},X={class:"m-value"},Y={class:"hist"},Z={class:"panel"},$={class:"panel-head"},ss={class:"form-grid",style:{"max-width":"520px"}},es={class:"field"},ts=["disabled"],is={class:"field",style:{"justify-content":"flex-end"}},ls=["disabled"],as=g({__name:"HvacView",setup(os){const a=x(),l=C(),d=H(180),{series:u,loading:v}=n(()=>({source:"hvac",metric:"supply_temp"})),{series:c,loading:p}=n(()=>({source:"hvac",metric:"supply_pressure"})),{series:m,loading:_}=n(()=>({source:"hvac",metric:"exhaust_pressure"})),h=r(()=>F([...u.value.map(o=>({...o,name:"送风温度"})),...c.value.map(o=>({...o,name:"送风压力"})),...m.value.map(o=>({...o,name:"排风压力"}))],{maxSeries:6})),y=r(()=>v.value||p.value||_.value);async function b(){a.isAdmin&&(await T("/api/realtime/write",{method:"POST",token:a.token,body:JSON.stringify({target:"PAU201",action:"set_supply_pressure",value:d.value})}),alert("送风压力设定已下发(Mock)"))}return(o,e)=>(V(),A("div",O,[s("section",D,[s("div",M,[e[1]||(e[1]=s("div",{class:"panel-title"},[s("div",{class:"vbar"}),s("h3",null,"空调及排风机组")],-1)),s("div",N,i(t(l).snapshot.hvac.systemRunning?"运行":"停机"),1)]),s("div",z,[s("div",L,[s("div",null,[e[2]||(e[2]=s("div",{class:"m-label"},"送风温度",-1)),s("div",R,i(t(l).snapshot.hvac.supplyTemp.toFixed(1)),1)])]),s("div",U,[s("div",null,[e[3]||(e[3]=s("div",{class:"m-label"},"送风湿度",-1)),s("div",j,i(t(l).snapshot.hvac.supplyHumidity),1)])]),s("div",I,[s("div",null,[e[4]||(e[4]=s("div",{class:"m-label"},"送风压力",-1)),s("div",J,i(t(l).snapshot.hvac.supplyPressure),1)])]),s("div",q,[s("div",null,[e[5]||(e[5]=s("div",{class:"m-label"},"风机频率",-1)),s("div",G,i(t(l).snapshot.hvac.supplyFanHz.toFixed(1)),1)])]),s("div",K,[s("div",null,[e[6]||(e[6]=s("div",{class:"m-label"},"排风压力",-1)),s("div",Q,i(t(l).snapshot.hvac.exhaustPressure),1)])]),s("div",W,[s("div",null,[e[7]||(e[7]=s("div",{class:"m-label"},"新风温度",-1)),s("div",X,i(t(l).snapshot.hvac.freshAirTemp.toFixed(1)),1)])])]),s("div",Y,[e[8]||(e[8]=s("div",{class:"chart-title"},"近 24h 历史曲线",-1)),P(E,{option:h.value,loading:y.value,"empty-text":"机组历史积累中"},null,8,["option","loading"])])]),s("section",Z,[s("div",$,[e[9]||(e[9]=s("div",{class:"panel-title"},[s("div",{class:"vbar"}),s("h3",null,"参数设定")],-1)),s("div",{class:S(["badge",t(a).isAdmin?"":"amber"])},i(t(a).isAdmin?"管理员可写":"只读"),3)]),s("div",ss,[s("div",es,[e[10]||(e[10]=s("label",null,"送风压力设定 (Pa)",-1)),k(s("input",{"onUpdate:modelValue":e[0]||(e[0]=f=>d.value=f),type:"number",disabled:!t(a).isAdmin},null,8,ts),[[w,d.value,void 0,{number:!0}]])]),s("div",is,[e[11]||(e[11]=s("label",null," ",-1)),s("button",{class:"btn",disabled:!t(a).isAdmin,onClick:b},"下发设定",8,ls)])])])]))}}),vs=B(as,[["__scopeId","data-v-c796d554"]]);export{vs as default};
.hist[data-v-868cf5e9]{margin-top:12px;min-height:220px;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.chart-title[data-v-868cf5e9]{font-size:12px;color:var(--label);margin-bottom:4px}.hist[data-v-868cf5e9] .echart-wrap{height:200px}
.light-page[data-v-8fd9846d]{gap:12px}.light-tabs[data-v-8fd9846d]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-8fd9846d]{border-radius:6px;border:1px solid rgba(62,200,255,.22);background:#122a4e66;color:var(--muted);padding:6px 14px;font-size:12px;cursor:pointer}.tab.active[data-v-8fd9846d]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-8fd9846d]{flex:1}.msg[data-v-8fd9846d]{font-size:12px;color:var(--cyan-lt)}.hint[data-v-8fd9846d]{font-size:12px;color:var(--label);line-height:1.6;margin-bottom:12px}.hint.inline[data-v-8fd9846d]{margin-bottom:0}.foot-hint[data-v-8fd9846d]{margin-top:10px;font-size:12px;color:var(--label)}.form-grid.three[data-v-8fd9846d]{grid-template-columns:repeat(3,1fr);max-width:1100px}.row-actions[data-v-8fd9846d]{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:14px}.head-right[data-v-8fd9846d]{display:flex;align-items:center;gap:8px}.switch-field .switch[data-v-8fd9846d]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.list-panel[data-v-8fd9846d],.config-panel[data-v-8fd9846d]{flex:1;min-height:0;display:flex;flex-direction:column}.table-wrap[data-v-8fd9846d]{flex:1;min-height:0;overflow:auto}.ops[data-v-8fd9846d]{display:flex;flex-wrap:wrap;gap:6px}.empty[data-v-8fd9846d]{text-align:center;color:var(--label);padding:24px!important}.muted[data-v-8fd9846d]{color:var(--label);font-size:11px}.st[data-v-8fd9846d],.pwr[data-v-8fd9846d]{display:inline-block;padding:1px 8px;border-radius:4px;font-size:11px}.st.online[data-v-8fd9846d]{background:#2fd6a126;color:var(--green)}.st.offline[data-v-8fd9846d]{background:#8eb4d41f;color:var(--muted)}.st.fault[data-v-8fd9846d]{background:#f0b4292e;color:var(--amber)}.pwr.on[data-v-8fd9846d]{background:#fbbf2433;color:#fbbf24}.pwr.off[data-v-8fd9846d]{background:#8eb4d41f;color:var(--muted)}.switch-btn.is-on[data-v-8fd9846d]{border-color:#fbbf248c;background:#fbbf2438;color:#ffe08a}.switch-btn.is-on[data-v-8fd9846d]:hover:not(:disabled){background:#fbbf2461}.switch-btn.is-off[data-v-8fd9846d]{border-color:#3ac8ff73;background:#1a6fff47;color:#eaf9ff}.switch-btn.is-off[data-v-8fd9846d]:hover:not(:disabled){background:#1a6fff73}
.light-page[data-v-1e4e582a]{gap:12px}.light-tabs[data-v-1e4e582a]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-1e4e582a]{border-radius:6px;border:1px solid rgba(62,200,255,.22);background:#122a4e66;color:var(--muted);padding:6px 14px;font-size:12px;cursor:pointer}.tab.active[data-v-1e4e582a]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-1e4e582a]{flex:1}.msg[data-v-1e4e582a]{font-size:12px;color:var(--cyan-lt)}.hint[data-v-1e4e582a]{font-size:12px;color:var(--label);line-height:1.6;margin-bottom:12px}.hint.inline[data-v-1e4e582a]{margin-bottom:0}.foot-hint[data-v-1e4e582a]{margin-top:10px;font-size:12px;color:var(--label)}.form-grid.three[data-v-1e4e582a]{grid-template-columns:repeat(3,1fr);max-width:1100px}.row-actions[data-v-1e4e582a]{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:14px}.head-right[data-v-1e4e582a]{display:flex;align-items:center;gap:8px}.switch-field .switch[data-v-1e4e582a]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.list-panel[data-v-1e4e582a],.config-panel[data-v-1e4e582a]{flex:1;min-height:0;display:flex;flex-direction:column}.table-wrap[data-v-1e4e582a]{flex:1;min-height:0;overflow:auto}.ops[data-v-1e4e582a]{display:flex;flex-wrap:wrap;gap:6px}.empty[data-v-1e4e582a]{text-align:center;color:var(--label);padding:24px!important}.muted[data-v-1e4e582a]{color:var(--label);font-size:11px}.st[data-v-1e4e582a],.pwr[data-v-1e4e582a]{display:inline-block;padding:1px 8px;border-radius:4px;font-size:11px}.st.online[data-v-1e4e582a]{background:#2fd6a126;color:var(--green)}.st.offline[data-v-1e4e582a]{background:#8eb4d41f;color:var(--muted)}.st.fault[data-v-1e4e582a]{background:#f0b4292e;color:var(--amber)}.pwr.on[data-v-1e4e582a]{background:#fbbf2433;color:#fbbf24}.pwr.off[data-v-1e4e582a]{background:#8eb4d41f;color:var(--muted)}.switch-btn.is-on[data-v-1e4e582a]{border-color:#fbbf248c;background:#fbbf2438;color:#ffe08a}.switch-btn.is-on[data-v-1e4e582a]:hover:not(:disabled){background:#fbbf2461}.switch-btn.is-off[data-v-1e4e582a]{border-color:#3ac8ff73;background:#1a6fff47;color:#eaf9ff}.switch-btn.is-off[data-v-1e4e582a]:hover:not(:disabled){background:#1a6fff73}
import{d as b,u as y,c as p,a as V,b as t,w as k,n as A,e as v,v as _,t as g,f as C,g as m,h as q,i as x,r as o,o as f,A as N}from"./index-Bs_x-_cK.js";const S={class:"tops-login"},h={class:"tops-login__stage"},B={class:"tops-login__field"},R={class:"tops-login__field"},T={key:0,class:"tops-login__error",role:"alert"},z=["disabled"],L=b({__name:"LoginView",setup(D){const c=y(),l=x(),r=o("admin"),u=o("Admin@123"),i=o(""),a=o(!1),d=o(!1);async function w(){i.value="",a.value=!0;try{await c.login(r.value.trim(),u.value);const e=typeof l.currentRoute.value.query.redirect=="string"?l.currentRoute.value.query.redirect:"/admin";await l.push(e.startsWith("/")?e:"/admin")}catch(e){i.value=e instanceof N?e.message:"登录失败",d.value=!0,window.setTimeout(()=>{d.value=!1},480)}finally{a.value=!1}}return(e,s)=>(f(),p("div",S,[s[10]||(s[10]=V('<div class="tops-login__atmosphere" aria-hidden="true"><div class="tops-login__grid"></div><div class="tops-login__glow tops-login__glow--a"></div><div class="tops-login__glow tops-login__glow--b"></div><div class="tops-login__horizon"></div></div>',1)),t("div",h,[s[6]||(s[6]=t("div",{class:"tops-login__mark"},"JH",-1)),s[7]||(s[7]=t("h1",{class:"tops-login__title"},"分子实验室三维可视化管理平台",-1)),s[8]||(s[8]=t("p",{class:"tops-login__tagline"},"Lab Intelligent Control",-1)),s[9]||(s[9]=t("p",{class:"tops-login__support"},"登录进入管理后台;大屏无需登录即可查看实时态势。",-1)),t("form",{class:A(["tops-login__form",{"is-shake":d.value}]),onSubmit:k(w,["prevent"])},[t("label",B,[s[3]||(s[3]=t("span",null,"用户名",-1)),v(t("input",{"onUpdate:modelValue":s[0]||(s[0]=n=>r.value=n),autocomplete:"username",required:""},null,512),[[_,r.value]])]),t("label",R,[s[4]||(s[4]=t("span",null,"密码",-1)),v(t("input",{"onUpdate:modelValue":s[1]||(s[1]=n=>u.value=n),type:"password",autocomplete:"current-password",required:""},null,512),[[_,u.value]])]),i.value?(f(),p("p",T,g(i.value),1)):C("",!0),t("button",{class:"tops-login__submit",type:"submit",disabled:a.value},[t("span",null,g(a.value?"登录中…":"进入后台"),1)],8,z),s[5]||(s[5]=t("p",{class:"tops-login__hint"},[m(" 演示账号 "),t("code",null,"admin / Admin@123"),m(" · "),t("code",null,"viewer / Viewer@123")],-1)),t("button",{class:"tops-login__dash",type:"button",onClick:s[2]||(s[2]=n=>q(l).push("/dashboard"))}," 无需登录,进入大屏 ")],34)])]))}});export{L as default};
import{d as b,u as y,c as p,a as V,b as t,w as k,n as A,e as v,v as _,t as g,f as C,g as m,h as q,i as x,r as o,o as f,A as N}from"./index-BssDqbym.js";const S={class:"tops-login"},h={class:"tops-login__stage"},B={class:"tops-login__field"},R={class:"tops-login__field"},T={key:0,class:"tops-login__error",role:"alert"},z=["disabled"],L=b({__name:"LoginView",setup(D){const c=y(),l=x(),r=o("admin"),u=o("Admin@123"),i=o(""),a=o(!1),d=o(!1);async function w(){i.value="",a.value=!0;try{await c.login(r.value.trim(),u.value);const e=typeof l.currentRoute.value.query.redirect=="string"?l.currentRoute.value.query.redirect:"/admin";await l.push(e.startsWith("/")?e:"/admin")}catch(e){i.value=e instanceof N?e.message:"登录失败",d.value=!0,window.setTimeout(()=>{d.value=!1},480)}finally{a.value=!1}}return(e,s)=>(f(),p("div",S,[s[10]||(s[10]=V('<div class="tops-login__atmosphere" aria-hidden="true"><div class="tops-login__grid"></div><div class="tops-login__glow tops-login__glow--a"></div><div class="tops-login__glow tops-login__glow--b"></div><div class="tops-login__horizon"></div></div>',1)),t("div",h,[s[6]||(s[6]=t("div",{class:"tops-login__mark"},"JH",-1)),s[7]||(s[7]=t("h1",{class:"tops-login__title"},"分子实验室三维可视化管理平台",-1)),s[8]||(s[8]=t("p",{class:"tops-login__tagline"},"Lab Intelligent Control",-1)),s[9]||(s[9]=t("p",{class:"tops-login__support"},"登录进入管理后台;大屏无需登录即可查看实时态势。",-1)),t("form",{class:A(["tops-login__form",{"is-shake":d.value}]),onSubmit:k(w,["prevent"])},[t("label",B,[s[3]||(s[3]=t("span",null,"用户名",-1)),v(t("input",{"onUpdate:modelValue":s[0]||(s[0]=n=>r.value=n),autocomplete:"username",required:""},null,512),[[_,r.value]])]),t("label",R,[s[4]||(s[4]=t("span",null,"密码",-1)),v(t("input",{"onUpdate:modelValue":s[1]||(s[1]=n=>u.value=n),type:"password",autocomplete:"current-password",required:""},null,512),[[_,u.value]])]),i.value?(f(),p("p",T,g(i.value),1)):C("",!0),t("button",{class:"tops-login__submit",type:"submit",disabled:a.value},[t("span",null,g(a.value?"登录中…":"进入后台"),1)],8,z),s[5]||(s[5]=t("p",{class:"tops-login__hint"},[m(" 演示账号 "),t("code",null,"admin / Admin@123"),m(" · "),t("code",null,"viewer / Viewer@123")],-1)),t("button",{class:"tops-login__dash",type:"button",onClick:s[2]||(s[2]=n=>q(l).push("/dashboard"))}," 无需登录,进入大屏 ")],34)])]))}});export{L as default};
.panel-box[data-v-59463572]{width:100%;height:100%;min-height:0;display:flex;flex-direction:column;background:#00122a6b;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid rgba(0,247,255,.12);box-shadow:0 8px 28px #00000047}.title-bar[data-v-59463572]{position:relative;height:32px;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0 12px 2px 10px}.title-bar.line[data-v-59463572]{height:28px;justify-content:flex-start;gap:8px;padding:0 4px 0 6px}.title-frame[data-v-59463572]{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;pointer-events:none;filter:drop-shadow(0 0 6px rgba(0,247,255,.18))}.title-main[data-v-59463572],.title-extra[data-v-59463572]{position:relative;z-index:1;display:flex;align-items:center}.title-main[data-v-59463572]{gap:8px}.title-icon[data-v-59463572]{width:10px;height:10px;border:1px solid #00f7ff;background:linear-gradient(#00f7ff,#00f7ff) center / 4px 4px no-repeat,#00284899;box-shadow:0 0 8px #00f7ffb3}.plus-icon[data-v-59463572]{width:12px;height:12px;flex-shrink:0;background:linear-gradient(#00f7ff,#00f7ff) center / 12px 2px no-repeat,linear-gradient(#00f7ff,#00f7ff) center / 2px 12px no-repeat;box-shadow:0 0 8px #00f7ffb3}.hairline[data-v-59463572]{flex:1;height:1px;margin-left:4px;background:linear-gradient(90deg,rgba(0,247,255,.7) 0%,rgba(0,247,255,.18) 42%,transparent 46%,transparent 52%,rgba(0,247,255,.2) 56%,rgba(0,247,255,.05) 100%)}.title-text[data-v-59463572]{font-family:var(--title-font);color:#f4feff;font-size:15px;font-weight:500;letter-spacing:var(--cjk-tracking, 1px);text-shadow:0 0 8px rgba(0,247,255,.35);white-space:nowrap}.title-extra[data-v-59463572]{gap:8px}.title-dots[data-v-59463572]{display:flex;gap:4px}.title-dots i[data-v-59463572]{width:6px;height:6px;background:#00f7ff;box-shadow:0 0 6px #00f7ff}.panel-body[data-v-59463572]{flex:1;min-height:0;padding:8px 10px 10px;overflow:hidden;display:flex;flex-direction:column}.panel-body.line[data-v-59463572]{padding:4px 8px 8px}.flip-digit[data-v-b06c8413]{position:relative;display:inline-block;width:24px;height:38px;margin:0 1.5px;perspective:180px;font-family:var(--font-num);font-weight:700;font-size:24px;line-height:38px;color:#e8fbff;vertical-align:middle}.flip-digit.punct[data-v-b06c8413]{width:12px;background:transparent;box-shadow:none}.flip-digit.punct .static[data-v-b06c8413],.flip-digit.punct .hinge[data-v-b06c8413]{background:transparent;border:none;box-shadow:none}.static[data-v-b06c8413],.hinge[data-v-b06c8413]{position:absolute;left:0;width:100%;height:50%;overflow:hidden;background:linear-gradient(180deg,#0d2a4a,#071828);border:1px solid rgba(0,247,255,.28);box-shadow:inset 0 0 8px #00000059}.static.top[data-v-b06c8413],.hinge.top[data-v-b06c8413]{top:0;border-radius:3px 3px 0 0;border-bottom-color:#00f7ff1f}.static.bottom[data-v-b06c8413],.hinge.bottom[data-v-b06c8413]{bottom:0;border-radius:0 0 3px 3px;border-top-color:#00000059}.static i[data-v-b06c8413],.hinge i[data-v-b06c8413]{position:absolute;left:0;width:100%;height:38px;display:flex;align-items:center;justify-content:center;font-style:normal}.static.top i[data-v-b06c8413],.hinge.top i[data-v-b06c8413]{top:0}.static.bottom i[data-v-b06c8413],.hinge.bottom i[data-v-b06c8413]{bottom:0}.hinge[data-v-b06c8413]{z-index:2;backface-visibility:hidden;transform-style:preserve-3d;pointer-events:none;opacity:0}.hinge.top[data-v-b06c8413]{transform-origin:bottom center}.hinge.bottom[data-v-b06c8413]{transform-origin:top center;transform:rotateX(90deg)}.hinge.top.active[data-v-b06c8413]{opacity:1;animation:flip-top-b06c8413 .5s ease-in forwards}.hinge.bottom.active[data-v-b06c8413]{opacity:1;animation:flip-bottom-b06c8413 .5s ease-out forwards}@keyframes flip-top-b06c8413{0%{transform:rotateX(0)}to{transform:rotateX(-90deg)}}@keyframes flip-bottom-b06c8413{0%{transform:rotateX(90deg)}to{transform:rotateX(0)}}.kpi-flip-board[data-v-ccc29753]{display:flex;gap:10px;width:100%;pointer-events:auto}.kpi-flip[data-v-ccc29753]{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:10px 12px 12px;border-radius:4px;border:1px solid rgba(0,247,255,.28);background:linear-gradient(180deg,#082444e0,#030e20c7);box-shadow:inset 0 1px #ffffff0f,0 8px 20px #00000047;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.kpi-head[data-v-ccc29753]{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;min-width:0}.kpi-texts[data-v-ccc29753]{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:2px}.kpi-label[data-v-ccc29753]{font-size:13px;font-weight:600;color:#9ad8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.kpi-hint[data-v-ccc29753]{font-size:10px;color:#9ed2e8b8;white-space:nowrap}.kpi-digits[data-v-ccc29753]{display:flex;align-items:center;justify-content:center;width:100%;min-height:36px}.kpi-flip[data-v-ccc29753] .kpi-ico{width:22px;height:22px;flex:0 0 auto}.tone-power[data-v-ccc29753]{border-color:#ffb44073}.tone-power .kpi-label[data-v-ccc29753]{color:#f0c878}.tone-water[data-v-ccc29753]{border-color:#38bdf873}.tone-water .kpi-label[data-v-ccc29753]{color:#7dd3fc}.tone-cool[data-v-ccc29753]{border-color:#67e8f96b}.tone-cool .kpi-label[data-v-ccc29753]{color:#a5f3fc}.tone-device[data-v-ccc29753]{border-color:#34d39973}.tone-device .kpi-label[data-v-ccc29753]{color:#6ee7b7}.tone-access[data-v-ccc29753]{border-color:#a78bfa73}.tone-access .kpi-label[data-v-ccc29753]{color:#c4b5fd}.overview-float[data-v-60f2b453]{position:relative;width:100%;height:100%;min-height:0;display:grid;grid-template-columns:var(--screen-side-w, clamp(280px, 24vw, 480px)) minmax(0,1fr) var( --screen-side-w, clamp(280px, 24vw, 480px) );grid-template-rows:auto 1fr;gap:10px 12px;pointer-events:none}.kpi-top[data-v-60f2b453]{grid-column:2;grid-row:1;min-width:0;z-index:2}.side[data-v-60f2b453]{min-width:0;min-height:0;height:100%;display:grid;gap:10px;pointer-events:auto}.left[data-v-60f2b453],.right[data-v-60f2b453]{grid-row:1 / -1;grid-template-rows:minmax(0,1.05fr) minmax(0,1fr) minmax(0,.95fr)}.left[data-v-60f2b453]{grid-column:1}.right[data-v-60f2b453]{grid-column:3}.center-pass[data-v-60f2b453]{grid-column:2;grid-row:2;pointer-events:none}.badge[data-v-60f2b453]{padding:1px 7px;border-radius:2px;font-size:10px;letter-spacing:.06em;background:#52c41a24;color:#7dff9a;border:1px solid rgba(82,196,26,.28)}.badge.amber[data-v-60f2b453]{background:#faad1424;color:#ffd666;border-color:#faad1459}.panel-stack[data-v-60f2b453]{flex:1;min-height:0;display:flex;flex-direction:column;gap:6px}.chart-main[data-v-60f2b453]{flex:1.2;min-height:0}.chart-sub[data-v-60f2b453]{flex:.9;min-height:0}.panel-split[data-v-60f2b453]{flex:1;min-height:0;display:grid;grid-template-columns:1fr 1fr;gap:6px}.panel-split--pies[data-v-60f2b453]{grid-template-rows:minmax(0,1fr)}.pie-col[data-v-60f2b453]{min-height:0;display:flex;flex-direction:column}.pie-cap[data-v-60f2b453]{text-align:center;font-size:10px;color:var(--panel-muted);margin-top:-2px}.digit-row[data-v-60f2b453]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;flex-shrink:0}.digit-card[data-v-60f2b453]{padding:6px 8px;border-radius:4px;background:#061c3873;border:1px solid rgba(0,247,255,.14);text-align:center}.digit-card.warn[data-v-60f2b453]{border-color:#faad1466}.d-label[data-v-60f2b453]{font-size:10px;color:var(--panel-muted)}.d-value[data-v-60f2b453]{margin-top:2px;font-size:15px;font-weight:600;font-family:var(--font-num);color:#fff;line-height:1.2}.d-value small[data-v-60f2b453]{font-size:10px;color:#52c41a;font-weight:500}.energy-digits[data-v-60f2b453]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;flex-shrink:0}.ed[data-v-60f2b453]{padding:8px;border-radius:4px;background:#061c3873;border:1px solid rgba(0,247,255,.14)}.ed span[data-v-60f2b453]{display:block;font-size:10px;color:var(--panel-muted)}.ed b[data-v-60f2b453]{display:block;margin-top:4px;font-size:16px;font-family:var(--font-num);color:#fff}.ed small[data-v-60f2b453]{font-size:10px;color:#c8e6f5a6;font-weight:500}.screen-err[data-v-60f2b453]{position:absolute;left:50%;bottom:12px;transform:translate(-50%);z-index:8;padding:4px 12px;border-radius:4px;background:#781414bf;color:#ffb0b0;font-size:12px;pointer-events:none}.side[data-v-60f2b453] .echart-wrap{flex:1;min-height:0}
.panel-box[data-v-c98402ab]{width:100%;height:100%;min-height:0;display:flex;flex-direction:column;background:#00122a6b;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid rgba(0,247,255,.12);box-shadow:0 8px 28px #00000047}.title-bar[data-v-c98402ab]{position:relative;height:32px;flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0 12px 2px 10px}.title-bar.line[data-v-c98402ab]{height:28px;justify-content:flex-start;gap:8px;padding:0 4px 0 6px}.title-frame[data-v-c98402ab]{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;pointer-events:none;filter:drop-shadow(0 0 6px rgba(0,247,255,.18))}.title-main[data-v-c98402ab],.title-extra[data-v-c98402ab]{position:relative;z-index:1;display:flex;align-items:center}.title-main[data-v-c98402ab]{gap:8px}.title-icon[data-v-c98402ab]{width:10px;height:10px;border:1px solid #00f7ff;background:linear-gradient(#00f7ff,#00f7ff) center / 4px 4px no-repeat,#00284899;box-shadow:0 0 8px #00f7ffb3}.plus-icon[data-v-c98402ab]{width:12px;height:12px;flex-shrink:0;background:linear-gradient(#00f7ff,#00f7ff) center / 12px 2px no-repeat,linear-gradient(#00f7ff,#00f7ff) center / 2px 12px no-repeat;box-shadow:0 0 8px #00f7ffb3}.hairline[data-v-c98402ab]{flex:1;height:1px;margin-left:4px;background:linear-gradient(90deg,rgba(0,247,255,.7) 0%,rgba(0,247,255,.18) 42%,transparent 46%,transparent 52%,rgba(0,247,255,.2) 56%,rgba(0,247,255,.05) 100%)}.title-text[data-v-c98402ab]{font-family:var(--title-font);color:#f4feff;font-size:15px;font-weight:400;letter-spacing:1px;text-shadow:0 0 8px rgba(0,247,255,.35);white-space:nowrap}.title-extra[data-v-c98402ab]{gap:8px}.title-dots[data-v-c98402ab]{display:flex;gap:4px}.title-dots i[data-v-c98402ab]{width:6px;height:6px;background:#00f7ff;box-shadow:0 0 6px #00f7ff}.panel-body[data-v-c98402ab]{flex:1;min-height:0;padding:8px 10px 10px;overflow:hidden;display:flex;flex-direction:column}.panel-body.line[data-v-c98402ab]{padding:4px 8px 8px}.flip-digit[data-v-b06c8413]{position:relative;display:inline-block;width:24px;height:38px;margin:0 1.5px;perspective:180px;font-family:var(--font-num);font-weight:700;font-size:24px;line-height:38px;color:#e8fbff;vertical-align:middle}.flip-digit.punct[data-v-b06c8413]{width:12px;background:transparent;box-shadow:none}.flip-digit.punct .static[data-v-b06c8413],.flip-digit.punct .hinge[data-v-b06c8413]{background:transparent;border:none;box-shadow:none}.static[data-v-b06c8413],.hinge[data-v-b06c8413]{position:absolute;left:0;width:100%;height:50%;overflow:hidden;background:linear-gradient(180deg,#0d2a4a,#071828);border:1px solid rgba(0,247,255,.28);box-shadow:inset 0 0 8px #00000059}.static.top[data-v-b06c8413],.hinge.top[data-v-b06c8413]{top:0;border-radius:3px 3px 0 0;border-bottom-color:#00f7ff1f}.static.bottom[data-v-b06c8413],.hinge.bottom[data-v-b06c8413]{bottom:0;border-radius:0 0 3px 3px;border-top-color:#00000059}.static i[data-v-b06c8413],.hinge i[data-v-b06c8413]{position:absolute;left:0;width:100%;height:38px;display:flex;align-items:center;justify-content:center;font-style:normal}.static.top i[data-v-b06c8413],.hinge.top i[data-v-b06c8413]{top:0}.static.bottom i[data-v-b06c8413],.hinge.bottom i[data-v-b06c8413]{bottom:0}.hinge[data-v-b06c8413]{z-index:2;backface-visibility:hidden;transform-style:preserve-3d;pointer-events:none;opacity:0}.hinge.top[data-v-b06c8413]{transform-origin:bottom center}.hinge.bottom[data-v-b06c8413]{transform-origin:top center;transform:rotateX(90deg)}.hinge.top.active[data-v-b06c8413]{opacity:1;animation:flip-top-b06c8413 .5s ease-in forwards}.hinge.bottom.active[data-v-b06c8413]{opacity:1;animation:flip-bottom-b06c8413 .5s ease-out forwards}@keyframes flip-top-b06c8413{0%{transform:rotateX(0)}to{transform:rotateX(-90deg)}}@keyframes flip-bottom-b06c8413{0%{transform:rotateX(90deg)}to{transform:rotateX(0)}}.kpi-flip-board[data-v-ccc29753]{display:flex;gap:10px;width:100%;pointer-events:auto}.kpi-flip[data-v-ccc29753]{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:10px 12px 12px;border-radius:4px;border:1px solid rgba(0,247,255,.28);background:linear-gradient(180deg,#082444e0,#030e20c7);box-shadow:inset 0 1px #ffffff0f,0 8px 20px #00000047;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px)}.kpi-head[data-v-ccc29753]{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;min-width:0}.kpi-texts[data-v-ccc29753]{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:2px}.kpi-label[data-v-ccc29753]{font-size:13px;font-weight:600;color:#9ad8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.kpi-hint[data-v-ccc29753]{font-size:10px;color:#9ed2e8b8;white-space:nowrap}.kpi-digits[data-v-ccc29753]{display:flex;align-items:center;justify-content:center;width:100%;min-height:36px}.kpi-flip[data-v-ccc29753] .kpi-ico{width:22px;height:22px;flex:0 0 auto}.tone-power[data-v-ccc29753]{border-color:#ffb44073}.tone-power .kpi-label[data-v-ccc29753]{color:#f0c878}.tone-water[data-v-ccc29753]{border-color:#38bdf873}.tone-water .kpi-label[data-v-ccc29753]{color:#7dd3fc}.tone-cool[data-v-ccc29753]{border-color:#67e8f96b}.tone-cool .kpi-label[data-v-ccc29753]{color:#a5f3fc}.tone-device[data-v-ccc29753]{border-color:#34d39973}.tone-device .kpi-label[data-v-ccc29753]{color:#6ee7b7}.tone-access[data-v-ccc29753]{border-color:#a78bfa73}.tone-access .kpi-label[data-v-ccc29753]{color:#c4b5fd}.overview-float[data-v-390066df]{position:relative;width:100%;height:100%;min-height:0;display:grid;grid-template-columns:var(--screen-side-w, clamp(280px, 24vw, 480px)) minmax(0,1fr) var( --screen-side-w, clamp(280px, 24vw, 480px) );grid-template-rows:auto 1fr;gap:10px 12px;pointer-events:none}.kpi-top[data-v-390066df]{grid-column:2;grid-row:1;min-width:0;z-index:2}.side[data-v-390066df]{min-width:0;min-height:0;height:100%;display:grid;gap:10px;pointer-events:auto}.left[data-v-390066df],.right[data-v-390066df]{grid-row:1 / -1;grid-template-rows:minmax(0,1.05fr) minmax(0,1fr) minmax(0,.95fr)}.left[data-v-390066df]{grid-column:1}.right[data-v-390066df]{grid-column:3}.center-pass[data-v-390066df]{grid-column:2;grid-row:2;pointer-events:none}.badge[data-v-390066df]{padding:1px 7px;border-radius:2px;font-size:10px;letter-spacing:.06em;background:#52c41a24;color:#7dff9a;border:1px solid rgba(82,196,26,.28)}.badge.amber[data-v-390066df]{background:#faad1424;color:#ffd666;border-color:#faad1459}.panel-stack[data-v-390066df]{flex:1;min-height:0;display:flex;flex-direction:column;gap:6px}.chart-main[data-v-390066df]{flex:1.2;min-height:0}.chart-sub[data-v-390066df]{flex:.9;min-height:0}.panel-split[data-v-390066df]{flex:1;min-height:0;display:grid;grid-template-columns:1fr 1fr;gap:6px}.panel-split--pies[data-v-390066df]{grid-template-rows:minmax(0,1fr)}.pie-col[data-v-390066df]{min-height:0;display:flex;flex-direction:column}.pie-cap[data-v-390066df]{text-align:center;font-size:10px;color:var(--panel-muted);margin-top:-2px}.digit-row[data-v-390066df]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;flex-shrink:0}.digit-card[data-v-390066df]{padding:6px 8px;border-radius:4px;background:#061c3873;border:1px solid rgba(0,247,255,.14)}.digit-card.warn[data-v-390066df]{border-color:#faad1466}.d-label[data-v-390066df]{font-size:10px;color:var(--panel-muted)}.d-value[data-v-390066df]{margin-top:2px;font-size:15px;font-weight:600;font-family:var(--font-num);color:#fff;line-height:1.2}.d-value small[data-v-390066df]{font-size:10px;color:#c8e6f5a6;font-weight:500}.energy-digits[data-v-390066df]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;flex-shrink:0}.ed[data-v-390066df]{padding:8px;border-radius:4px;background:#061c3873;border:1px solid rgba(0,247,255,.14)}.ed span[data-v-390066df]{display:block;font-size:10px;color:var(--panel-muted)}.ed b[data-v-390066df]{display:block;margin-top:4px;font-size:16px;font-family:var(--font-num);color:#fff}.ed small[data-v-390066df]{font-size:10px;color:#c8e6f5a6;font-weight:500}.screen-err[data-v-390066df]{position:absolute;left:50%;bottom:12px;transform:translate(-50%);z-index:8;padding:4px 12px;border-radius:4px;background:#781414bf;color:#ffb0b0;font-size:12px;pointer-events:none}.side[data-v-390066df] .echart-wrap{flex:1;min-height:0}
import{E as r,m as n}from"./historyChart-DTdcyadg.js";import{u as d}from"./useMetricHistory-Ck1zYYEr.js";import{u as x}from"./realtime-CI_KLy_N.js";import{d as y,c as a,b as e,D as u,h as o,F as b,z as S,B as p,o as i,t as l,y as P,_ as w}from"./index-Bs_x-_cK.js";const B={class:"page-fill pressure-page"},F={class:"panel",style:{flex:"1","min-height":"0",display:"flex","flex-direction":"column",gap:"10px",overflow:"hidden"}},L={class:"charts"},V={class:"chart-card"},k={class:"chart-card"},E={class:"table-wrap"},O={class:"table"},z=y({__name:"PressureView",setup(C){const c=x(),{series:m,loading:h}=d(()=>({source:"room",metric:"pressure"})),{series:v,loading:_}=d(()=>({source:"room",metric:"temp"})),g=p(()=>n(m.value,{unit:"Pa",maxSeries:14})),f=p(()=>n(v.value,{unit:"℃",maxSeries:14}));return(D,t)=>(i(),a("div",B,[e("section",F,[t[3]||(t[3]=e("div",{class:"panel-head"},[e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"房间压差集中监控")]),e("div",{class:"badge"},"实时 + 近24h")],-1)),e("div",L,[e("div",V,[t[0]||(t[0]=e("div",{class:"chart-title"},"压差历史 Pa",-1)),u(r,{option:g.value,loading:o(h),"empty-text":"压差历史积累中"},null,8,["option","loading"])]),e("div",k,[t[1]||(t[1]=e("div",{class:"chart-title"},"温度历史 ℃",-1)),u(r,{option:f.value,loading:o(_),"empty-text":"温度历史积累中"},null,8,["option","loading"])])]),e("div",E,[e("table",O,[t[2]||(t[2]=e("thead",null,[e("tr",null,[e("th",null,"房间"),e("th",null,"压差 (Pa)"),e("th",null,"温度 (℃)"),e("th",null,"湿度 (%)"),e("th",null,"告警")])],-1)),e("tbody",null,[(i(!0),a(b,null,S(o(c).snapshot.rooms,s=>(i(),a("tr",{key:s.id},[e("td",null,l(s.name),1),e("td",{style:P({color:s.alarms.length?"var(--red)":"var(--green)"})},l(s.pressure.toFixed(1)),5),e("td",null,l(s.temp.toFixed(1)),1),e("td",null,l(s.humidity),1),e("td",null,l(s.alarms.join("、")||"正常"),1)]))),128))])])])])]))}}),M=w(z,[["__scopeId","data-v-9d9108d2"]]);export{M as default};
.pressure-page[data-v-4f9dd116]{display:flex;min-height:0}.charts[data-v-4f9dd116]{display:grid;grid-template-columns:1fr 1fr;gap:10px;min-height:240px;flex:1}.chart-card[data-v-4f9dd116]{min-height:0;display:flex;flex-direction:column;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.chart-title[data-v-4f9dd116]{font-size:12px;color:var(--label);margin-bottom:4px}.chart-card[data-v-4f9dd116] .echart-wrap{flex:1;min-height:180px}.table-wrap[data-v-4f9dd116]{max-height:260px;overflow:auto}
.pressure-page[data-v-9d9108d2]{display:flex;min-height:0}.charts[data-v-9d9108d2]{display:grid;grid-template-columns:1fr 1fr;gap:10px;min-height:240px;flex:1}.chart-card[data-v-9d9108d2]{min-height:0;display:flex;flex-direction:column;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.chart-title[data-v-9d9108d2]{font-size:12px;color:var(--label);margin-bottom:4px}.chart-card[data-v-9d9108d2] .echart-wrap{flex:1;min-height:180px}.table-wrap[data-v-9d9108d2]{max-height:260px;overflow:auto}
import{d as f,j as b,c as l,b as e,t,h as s,H as n,F as S,x as E,s as c,E as H,o as i,_ as w}from"./index-BssDqbym.js";import{E as d,m as p}from"./historyChart-DJ1B6lTP.js";import{u as m}from"./useMetricHistory-CLFOMuUx.js";import{u as F}from"./realtime-lmP-Cubp.js";const L={class:"page-fill pressure-page"},P={class:"panel",style:{flex:"1","min-height":"0",display:"flex","flex-direction":"column",gap:"10px",overflow:"hidden"}},V={class:"panel-head"},k={class:"panel-title"},B={class:"badge"},O={class:"charts"},j={class:"chart-card"},C={class:"chart-title"},I={class:"chart-card"},N={class:"chart-title"},T={class:"table-wrap"},z={class:"table"},A=f({__name:"PressureView",setup(D){const{t:o}=b(),u=F(),{series:_,loading:h}=m(()=>({source:"room",metric:"pressure"})),{series:g,loading:v}=m(()=>({source:"room",metric:"temp"})),x=c(()=>p(_.value,{unit:"Pa",maxSeries:14})),y=c(()=>p(g.value,{unit:"℃",maxSeries:14}));return(M,r)=>(i(),l("div",L,[e("section",P,[e("div",V,[e("div",k,[r[0]||(r[0]=e("div",{class:"vbar"},null,-1)),e("h3",null,t(s(o)("pages.pressureTitle")),1)]),e("div",B,t(s(o)("common.realtime24h")),1)]),e("div",O,[e("div",j,[e("div",C,t(s(o)("pages.pressureHist")),1),n(d,{option:x.value,loading:s(h),"empty-text":s(o)("pages.pressureEmpty")},null,8,["option","loading","empty-text"])]),e("div",I,[e("div",N,t(s(o)("pages.tempHist")),1),n(d,{option:y.value,loading:s(v),"empty-text":s(o)("pages.tempEmpty")},null,8,["option","loading","empty-text"])])]),e("div",T,[e("table",z,[e("thead",null,[e("tr",null,[e("th",null,t(s(o)("common.room")),1),e("th",null,t(s(o)("pages.colPressure")),1),e("th",null,t(s(o)("pages.colTemp")),1),e("th",null,t(s(o)("pages.colHumidity")),1),e("th",null,t(s(o)("pages.colAlarm")),1)])]),e("tbody",null,[(i(!0),l(S,null,E(s(u).snapshot.rooms,a=>(i(),l("tr",{key:a.id},[e("td",null,t(a.name),1),e("td",{style:H({color:a.alarms.length?"var(--red)":"var(--green)"})},t(a.pressure.toFixed(1)),5),e("td",null,t(a.temp.toFixed(1)),1),e("td",null,t(a.humidity),1),e("td",null,t(a.alarms.join("、")||s(o)("common.normal")),1)]))),128))])])])])]))}}),K=w(A,[["__scopeId","data-v-4f9dd116"]]);export{K as default};
.mask[data-v-a9087e36]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:80;background:#000814b8;display:grid;place-items:center;padding:24px}.dialog[data-v-a9087e36]{width:min(1440px,98vw);height:min(860px,94vh);display:flex;flex-direction:column;background:#071426;border:1px solid rgba(0,247,255,.28);border-radius:8px;overflow:hidden;box-shadow:0 20px 60px #00000073}.head[data-v-a9087e36]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid rgba(0,247,255,.16)}.head h3[data-v-a9087e36]{margin:0;font-size:16px;color:#f4feff;letter-spacing:.06em}.hint[data-v-a9087e36]{margin:0;padding:8px 16px;font-size:12px;color:#7eb6d6;background:#00284859}.body[data-v-a9087e36]{flex:1;min-height:0;display:grid;grid-template-columns:280px 1fr}.icon-pane[data-v-a9087e36]{display:flex;flex-direction:column;min-height:0;border-right:1px solid rgba(0,247,255,.14);background:#001024a6}.icon-head[data-v-a9087e36]{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:10px 12px;font-size:12px;color:#f4feff;border-bottom:1px solid rgba(0,247,255,.12)}.icon-search[data-v-a9087e36]{width:120px;height:28px;border-radius:4px;border:1px solid rgba(0,247,255,.25);background:#00000059;color:#d7f4ff;padding:0 8px;font-size:12px}.icon-err[data-v-a9087e36]{margin:8px 12px 0;font-size:12px;color:#ffb0b0}.icon-grid[data-v-a9087e36]{flex:1;min-height:0;overflow:auto;padding:8px;display:grid;grid-template-columns:repeat(3,1fr);gap:8px;align-content:start}.icon-item[data-v-a9087e36]{display:flex;align-items:center;justify-content:center;min-height:88px;padding:10px 6px;border-radius:6px;border:1px solid rgba(0,247,255,.16);background:#081c3873;cursor:pointer}.icon-item img[data-v-a9087e36]{width:52px;height:52px;object-fit:contain}.icon-item[data-v-a9087e36]:hover{border-color:#00f7ff73}.icon-item.active[data-v-a9087e36]{border-color:#00f7ff;background:#0064a059;color:#fff;box-shadow:0 0 0 1px #00f7ff40 inset}.icon-current[data-v-a9087e36]{display:flex;align-items:center;gap:8px;padding:8px 12px;border-top:1px solid rgba(0,247,255,.12);font-size:11px;color:#7eb6d6}.icon-current img[data-v-a9087e36]{width:22px;height:22px;object-fit:contain}.icon-current b[data-v-a9087e36]{color:#f4feff;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-only-pane[data-v-a9087e36]{padding-bottom:12px}.text-only-tip[data-v-a9087e36]{margin:12px;font-size:12px;line-height:1.55;color:#7eb6d6}.text-only-sample[data-v-a9087e36]{margin:0 12px;padding:12px;border-radius:6px;border:1px solid rgba(0,247,255,.28);background:#00284873;color:#00f7ff;font-family:var(--digital-font, ui-monospace, monospace);font-size:14px;line-height:1.45;white-space:pre-wrap}.frame-wrap[data-v-a9087e36]{min-height:0;background:#000}.frame[data-v-a9087e36]{width:100%;height:100%;border:0;display:block}.foot[data-v-a9087e36]{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-top:1px solid rgba(0,247,255,.16)}.coords[data-v-a9087e36]{font-family:var(--digital-font);font-size:13px;color:var(--accent)}.coords.muted[data-v-a9087e36]{color:var(--panel-muted)}.actions[data-v-a9087e36]{display:flex;gap:8px}.admin-list-page[data-v-03b146a4]{min-height:0}.list-panel[data-v-03b146a4]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-03b146a4]{display:flex;align-items:center;gap:10px}.filter[data-v-03b146a4]{min-width:120px;border-radius:2px;border:1px solid var(--panel-border);background:#060c148c;color:#eaf4ff;padding:6px 10px}.desc[data-v-03b146a4]{margin:0 0 10px;font-size:12px;color:var(--panel-muted);line-height:1.5}.table-wrap[data-v-03b146a4]{flex:1;min-height:0;overflow:auto}.mono[data-v-03b146a4]{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:12px}.binds[data-v-03b146a4]{max-width:360px;font-size:12px;color:var(--panel-muted)}.ops[data-v-03b146a4]{display:flex;gap:6px;white-space:nowrap}.muted[data-v-03b146a4]{color:var(--panel-muted)}.msg.ok[data-v-03b146a4]{color:var(--ok);font-size:13px;margin-bottom:8px}.msg.err[data-v-03b146a4]{color:#ffb0b0;font-size:13px;margin:8px 0}.btn.danger[data-v-03b146a4]{color:#ffb0b0}.modal-mask[data-v-03b146a4]{position:fixed;top:0;right:0;bottom:0;left:0;background:#04080eb8;display:flex;align-items:center;justify-content:center;z-index:50}.modal[data-v-03b146a4]{width:min(720px,94vw);max-height:90vh;overflow:auto}.form-grid[data-v-03b146a4]{display:grid;grid-template-columns:1fr 1fr;gap:12px}.form-grid.roles[data-v-03b146a4]{grid-template-columns:1fr}.field label[data-v-03b146a4]{display:block;font-size:12px;color:var(--panel-muted);margin-bottom:4px}.field input[data-v-03b146a4],.field select[data-v-03b146a4]{width:100%;border-radius:2px;border:1px solid var(--panel-border);background:#060c148c;color:#eaf4ff;padding:8px 10px}.field.check[data-v-03b146a4]{display:flex;align-items:end}.field.check label[data-v-03b146a4]{display:flex;align-items:center;gap:8px;color:#eaf4ff}.sub[data-v-03b146a4]{margin:16px 0 8px;font-size:13px;color:#eaf4ff}.comm-hint[data-v-03b146a4]{margin:-4px 0 10px;font-size:12px;color:var(--panel-muted);line-height:1.45}.modal-actions[data-v-03b146a4]{margin-top:14px;display:flex;gap:10px}
.admin-list-page[data-v-64a5b5b8]{min-height:0}.list-panel[data-v-64a5b5b8]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-64a5b5b8]{display:flex;align-items:center;gap:10px}.table-wrap[data-v-64a5b5b8]{flex:1;min-height:0;overflow:auto}.muted[data-v-64a5b5b8]{color:var(--panel-muted)}.hint[data-v-64a5b5b8]{margin-top:12px;font-size:12px;color:var(--panel-muted)}.msg.ok[data-v-64a5b5b8]{color:var(--ok);font-size:13px;margin-bottom:8px}.msg.err[data-v-64a5b5b8]{color:#ffb0b0;font-size:13px;margin:8px 0}.desc[data-v-64a5b5b8]{margin:0 0 12px;font-size:12px;color:var(--panel-muted);line-height:1.6}.presets[data-v-64a5b5b8]{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}.preset[data-v-64a5b5b8]{border-radius:2px;border:1px solid var(--panel-border);background:#060c148c;color:var(--panel-muted);padding:6px 12px;font-size:12px;cursor:pointer;font-weight:600}.preset.active[data-v-64a5b5b8]{background:#3dbeff38;color:#eaf4ff;border-color:#3dbeff8c}.poll-form[data-v-64a5b5b8]{grid-template-columns:1fr 1fr}.title-form[data-v-64a5b5b8]{grid-template-columns:1fr}.modal-mask[data-v-64a5b5b8]{position:fixed;top:0;right:0;bottom:0;left:0;background:#04080eb8;display:flex;align-items:center;justify-content:center;z-index:50}.modal[data-v-64a5b5b8]{width:min(560px,92vw);max-height:90vh;overflow:auto}.modal-actions[data-v-64a5b5b8]{margin-top:14px;display:flex;gap:10px}
import{d as D,u as U,j as V,c as n,b as e,n as C,h,t as i,f as r,F as w,z as P,w as z,r as c,B as L,l as S,o,_ as R}from"./index-Bs_x-_cK.js";const j={class:"page-fill admin-list-page"},I={class:"panel list-panel"},G={class:"panel-head"},J={class:"head-actions"},O={key:0,class:"msg ok"},T={key:1,class:"msg err"},q={class:"table-wrap"},H={class:"table"},K={key:1,class:"muted"},Q={key:2,class:"hint"},W={class:"modal panel"},X={class:"presets"},Y=["onClick"],Z={class:"form-grid poll-form"},ee={class:"field"},le=["value"],te={class:"field"},se=["value"],ae={key:0,class:"msg err"},ne={class:"modal-actions"},oe=["disabled"],ue=D({__name:"SettingsView",setup(ie){const u=U(),A="rtengine",E="/rtengine/gis.html?scene=mvlpj2yt6379aa29",a=c(null),v=c(2e3),b=c(2),p=c(!1),y=c(""),d=c(""),_=c(!1),x=[{label:"1 秒",ms:1e3},{label:"2 秒",ms:2e3},{label:"5 秒",ms:5e3},{label:"10 秒",ms:1e4},{label:"30 秒",ms:3e4},{label:"1 分钟",ms:6e4}],N=L(()=>{var t,l,s,f,M;return[{key:"user",label:"当前用户",value:`${((t=u.user)==null?void 0:t.displayName)||"—"} (${((l=u.user)==null?void 0:l.username)||"—"})`},{key:"viewport",label:"三维视口模式",value:A},{key:"ue",label:"云渲染 / UE 地址",value:E},{key:"dataMode",label:"数据采集模式",value:((s=a.value)==null?void 0:s.dataMode)==="modbus"?"Modbus 实采":((f=a.value)==null?void 0:f.dataMode)==="gateway"?"网关实采":"Mock 模拟"},{key:"retention",label:"采集数据保留",value:`${((M=a.value)==null?void 0:M.collectRetentionDays)??"—"} 天`},{key:"poll",label:"数据采集频率",value:a.value?`${a.value.collectPollMs/1e3} 秒`:"—",editable:!0},{key:"access",label:"门禁桥接",value:"ACCESS_BRIDGE_URL 未配置时使用 Mock"}]});function m(t){v.value=t,b.value=+(t/1e3).toFixed(t%1e3===0?0:1)}function B(t){const l=Math.max(.5,Math.min(600,t||.5));b.value=l,v.value=Math.round(l*1e3)}function g(){u.isAdmin&&(a.value&&m(a.value.collectPollMs),d.value="",_.value=!0)}function k(){_.value=!1,d.value=""}async function $(){y.value="";try{const t=await S("/api/settings",{token:u.token});a.value=t,m(t.collectPollMs)}catch(t){d.value=t instanceof Error?t.message:"加载失败"}}async function F(){if(u.isAdmin){p.value=!0,d.value="";try{const t=await S("/api/settings/collect-poll",{method:"PUT",token:u.token,body:JSON.stringify({collectPollMs:v.value})});a.value=t,m(t.collectPollMs),y.value=`采集频率已更新为 ${b.value} 秒`,k()}catch(t){d.value=t instanceof Error?t.message:"保存失败"}finally{p.value=!1}}}return V($),(t,l)=>(o(),n("div",j,[e("section",I,[e("div",G,[l[2]||(l[2]=e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"系统设置")],-1)),e("div",J,[e("div",{class:C(["badge",h(u).isAdmin?"":"amber"])},i(h(u).isAdmin?"管理员":"只读"),3),h(u).isAdmin?(o(),n("button",{key:0,class:"btn",type:"button",onClick:g}," 编辑采集频率 ")):r("",!0)])]),y.value?(o(),n("p",O,i(y.value),1)):r("",!0),d.value&&!_.value?(o(),n("p",T,i(d.value),1)):r("",!0),e("div",q,[e("table",H,[l[3]||(l[3]=e("thead",null,[e("tr",null,[e("th",null,"配置项"),e("th",null,"当前值"),e("th",null,"操作")])],-1)),e("tbody",null,[(o(!0),n(w,null,P(N.value,s=>(o(),n("tr",{key:s.key},[e("td",null,i(s.label),1),e("td",null,i(s.value),1),e("td",null,[s.editable&&h(u).isAdmin?(o(),n("button",{key:0,class:"btn ghost",type:"button",onClick:g}," 编辑 ")):(o(),n("span",K,"—"))])]))),128))])])]),a.value?(o(),n("p",Q," 最近更新:"+i(a.value.updatedBy||"—")+" · "+i(new Date(a.value.updatedAt).toLocaleString("zh-CN",{hour12:!1})),1)):r("",!0)]),_.value?(o(),n("div",{key:0,class:"modal-mask",onClick:z(k,["self"])},[e("div",W,[e("div",{class:"panel-head"},[l[4]||(l[4]=e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"编辑采集频率")],-1)),e("button",{class:"btn ghost",type:"button",onClick:k},"关闭")]),l[7]||(l[7]=e("p",{class:"desc"}," 控制服务端轮询周期,保存后立即生效。允许范围 0.5–600 秒。 ",-1)),e("div",X,[(o(),n(w,null,P(x,s=>e("button",{key:s.ms,type:"button",class:C(["preset",{active:v.value===s.ms}]),onClick:f=>m(s.ms)},i(s.label),11,Y)),64))]),e("div",Z,[e("div",ee,[l[5]||(l[5]=e("label",null,"周期(秒)",-1)),e("input",{value:b.value,type:"number",min:"0.5",max:"600",step:"0.5",onChange:l[0]||(l[0]=s=>B(Number(s.target.value)))},null,40,le)]),e("div",te,[l[6]||(l[6]=e("label",null,"周期(毫秒)",-1)),e("input",{value:v.value,type:"number",min:"500",max:"600000",step:"100",onChange:l[1]||(l[1]=s=>m(Number(s.target.value)))},null,40,se)])]),d.value?(o(),n("p",ae,i(d.value),1)):r("",!0),e("div",ne,[e("button",{class:"btn",type:"button",disabled:p.value,onClick:F},i(p.value?"保存中…":"保存并生效"),9,oe),e("button",{class:"btn ghost",type:"button",onClick:k},"取消")])])])):r("",!0)]))}}),ce=R(ue,[["__scopeId","data-v-ed6d7751"]]);export{ce as default};
import{d as J,u as O,l as j,c as o,b as e,n as D,h as f,t as d,f as c,F as U,x as $,w as T,e as G,v as q,a1 as H,r,s as Q,p as E,o as i,_ as W}from"./index-BssDqbym.js";import{D as w,p as X}from"./useScreenTitle-DLVybyzQ.js";const Y={class:"page-fill admin-list-page"},Z={class:"panel list-panel"},ee={class:"panel-head"},te={class:"head-actions"},le={key:0,class:"msg ok"},se={key:1,class:"msg err"},ae={class:"table-wrap"},ne={class:"table"},oe=["onClick"],ie={key:1,class:"muted"},ue={key:2,class:"hint"},de={class:"modal panel"},ce={class:"form-grid title-form"},re={class:"field"},ve=["onKeydown"],pe={class:"hint"},be={key:0,class:"msg err"},me={class:"modal-actions"},ye=["disabled"],fe={class:"modal panel"},_e={class:"presets"},he=["onClick"],ke={class:"form-grid poll-form"},ge={class:"field"},Ce=["value"],Me={class:"field"},we=["value"],Te={key:0,class:"msg err"},Ee={class:"modal-actions"},Ae=["disabled"],Se=J({__name:"SettingsView",setup(xe){const u=O(),F="rtengine",B="/rtengine/gis.html?scene=mvlpj2yt6379aa29",n=r(null),b=r(2e3),_=r(2),p=r(w),v=r(!1),m=r(""),a=r(""),h=r(!1),k=r(!1),L=[{label:"1 秒",ms:1e3},{label:"2 秒",ms:2e3},{label:"5 秒",ms:5e3},{label:"10 秒",ms:1e4},{label:"30 秒",ms:3e4},{label:"1 分钟",ms:6e4}],V=Q(()=>{var l,t,s,M,P,N;return[{key:"user",label:"当前用户",value:`${((l=u.user)==null?void 0:l.displayName)||"—"} (${((t=u.user)==null?void 0:t.username)||"—"})`},{key:"screenTitle",label:"大屏标题",value:((s=n.value)==null?void 0:s.screenTitle)||w,editable:!0,edit:"title"},{key:"viewport",label:"三维视口模式",value:F},{key:"ue",label:"云渲染 / UE 地址",value:B},{key:"dataMode",label:"数据采集模式",value:((M=n.value)==null?void 0:M.dataMode)==="modbus"?"Modbus 实采":((P=n.value)==null?void 0:P.dataMode)==="gateway"?"网关实采":"Mock 模拟"},{key:"retention",label:"采集数据保留",value:`${((N=n.value)==null?void 0:N.collectRetentionDays)??"—"} 天`},{key:"poll",label:"数据采集频率",value:n.value?`${n.value.collectPollMs/1e3} 秒`:"—",editable:!0,edit:"poll"},{key:"access",label:"门禁桥接",value:"ACCESS_BRIDGE_URL 未配置时使用 Mock"}]});function y(l){b.value=l,_.value=+(l/1e3).toFixed(l%1e3===0?0:1)}function R(l){const t=Math.max(.5,Math.min(600,l||.5));_.value=t,b.value=Math.round(t*1e3)}function A(){u.isAdmin&&(n.value&&y(n.value.collectPollMs),a.value="",h.value=!0)}function g(){h.value=!1,a.value=""}function S(){var l;u.isAdmin&&(p.value=((l=n.value)==null?void 0:l.screenTitle)||w,a.value="",k.value=!0)}function C(){k.value=!1,a.value=""}function I(l){l==="title"?S():A()}async function K(){m.value="";try{const l=await E("/api/settings",{token:u.token});n.value=l,y(l.collectPollMs),p.value=l.screenTitle||w}catch(l){a.value=l instanceof Error?l.message:"加载失败"}}async function z(){if(u.isAdmin){v.value=!0,a.value="";try{const l=await E("/api/settings/collect-poll",{method:"PUT",token:u.token,body:JSON.stringify({collectPollMs:b.value})});n.value=l,y(l.collectPollMs),m.value=`采集频率已更新为 ${_.value} 秒`,g()}catch(l){a.value=l instanceof Error?l.message:"保存失败"}finally{v.value=!1}}}async function x(){if(!u.isAdmin)return;const l=p.value.trim();if(!l){a.value="大屏标题不能为空";return}if(l.length>64){a.value="大屏标题最多 64 个字符";return}v.value=!0,a.value="";try{const t=await E("/api/settings/screen-title",{method:"PUT",token:u.token,body:JSON.stringify({screenTitle:l})});n.value=t,p.value=t.screenTitle,X(t.screenTitle),m.value="大屏标题已更新",C()}catch(t){a.value=t instanceof Error?t.message:"保存失败"}finally{v.value=!1}}return j(K),(l,t)=>(i(),o("div",Y,[e("section",Z,[e("div",ee,[t[3]||(t[3]=e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"系统设置")],-1)),e("div",te,[e("div",{class:D(["badge",f(u).isAdmin?"":"amber"])},d(f(u).isAdmin?"管理员":"只读"),3),f(u).isAdmin?(i(),o("button",{key:0,class:"btn ghost",type:"button",onClick:S}," 编辑大屏标题 ")):c("",!0),f(u).isAdmin?(i(),o("button",{key:1,class:"btn",type:"button",onClick:A}," 编辑采集频率 ")):c("",!0)])]),m.value?(i(),o("p",le,d(m.value),1)):c("",!0),a.value&&!h.value&&!k.value?(i(),o("p",se,d(a.value),1)):c("",!0),e("div",ae,[e("table",ne,[t[4]||(t[4]=e("thead",null,[e("tr",null,[e("th",null,"配置项"),e("th",null,"当前值"),e("th",null,"操作")])],-1)),e("tbody",null,[(i(!0),o(U,null,$(V.value,s=>(i(),o("tr",{key:s.key},[e("td",null,d(s.label),1),e("td",null,d(s.value),1),e("td",null,[s.editable&&f(u).isAdmin?(i(),o("button",{key:0,class:"btn ghost",type:"button",onClick:M=>I(s.edit)}," 编辑 ",8,oe)):(i(),o("span",ie,"—"))])]))),128))])])]),n.value?(i(),o("p",ue," 最近更新:"+d(n.value.updatedBy||"—")+" · "+d(new Date(n.value.updatedAt).toLocaleString("zh-CN",{hour12:!1})),1)):c("",!0)]),k.value?(i(),o("div",{key:0,class:"modal-mask",onClick:T(C,["self"])},[e("div",de,[e("div",{class:"panel-head"},[t[5]||(t[5]=e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"编辑大屏标题")],-1)),e("button",{class:"btn ghost",type:"button",onClick:C},"关闭")]),t[7]||(t[7]=e("p",{class:"desc"}," 保存后大屏顶栏标题立即更新(其他终端约数秒内同步)。最多 64 个字符。 ",-1)),e("div",ce,[e("div",re,[t[6]||(t[6]=e("label",null,"标题文案",-1)),G(e("input",{"onUpdate:modelValue":t[0]||(t[0]=s=>p.value=s),type:"text",maxlength:"64",placeholder:"分子实验室三维可视化管理平台",onKeydown:H(T(x,["prevent"]),["enter"])},null,40,ve),[[q,p.value]])])]),e("p",pe,d(p.value.trim().length)+" / 64",1),a.value?(i(),o("p",be,d(a.value),1)):c("",!0),e("div",me,[e("button",{class:"btn",type:"button",disabled:v.value,onClick:x},d(v.value?"保存中…":"保存并生效"),9,ye),e("button",{class:"btn ghost",type:"button",onClick:C},"取消")])])])):c("",!0),h.value?(i(),o("div",{key:1,class:"modal-mask",onClick:T(g,["self"])},[e("div",fe,[e("div",{class:"panel-head"},[t[8]||(t[8]=e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"编辑采集频率")],-1)),e("button",{class:"btn ghost",type:"button",onClick:g},"关闭")]),t[11]||(t[11]=e("p",{class:"desc"}," 控制服务端轮询周期,保存后立即生效。允许范围 0.5–600 秒。 ",-1)),e("div",_e,[(i(),o(U,null,$(L,s=>e("button",{key:s.ms,type:"button",class:D(["preset",{active:b.value===s.ms}]),onClick:M=>y(s.ms)},d(s.label),11,he)),64))]),e("div",ke,[e("div",ge,[t[9]||(t[9]=e("label",null,"周期(秒)",-1)),e("input",{value:_.value,type:"number",min:"0.5",max:"600",step:"0.5",onChange:t[1]||(t[1]=s=>R(Number(s.target.value)))},null,40,Ce)]),e("div",Me,[t[10]||(t[10]=e("label",null,"周期(毫秒)",-1)),e("input",{value:b.value,type:"number",min:"500",max:"600000",step:"100",onChange:t[2]||(t[2]=s=>y(Number(s.target.value)))},null,40,we)])]),a.value?(i(),o("p",Te,d(a.value),1)):c("",!0),e("div",Ee,[e("button",{class:"btn",type:"button",disabled:v.value,onClick:z},d(v.value?"保存中…":"保存并生效"),9,Ae),e("button",{class:"btn ghost",type:"button",onClick:g},"取消")])])])):c("",!0)]))}}),De=W(Se,[["__scopeId","data-v-64a5b5b8"]]);export{De as default};
.admin-list-page[data-v-ed6d7751]{min-height:0}.list-panel[data-v-ed6d7751]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-ed6d7751]{display:flex;align-items:center;gap:10px}.table-wrap[data-v-ed6d7751]{flex:1;min-height:0;overflow:auto}.muted[data-v-ed6d7751]{color:var(--panel-muted)}.hint[data-v-ed6d7751]{margin-top:12px;font-size:12px;color:var(--panel-muted)}.msg.ok[data-v-ed6d7751]{color:var(--ok);font-size:13px;margin-bottom:8px}.msg.err[data-v-ed6d7751]{color:#ffb0b0;font-size:13px;margin:8px 0}.desc[data-v-ed6d7751]{margin:0 0 12px;font-size:12px;color:var(--panel-muted);line-height:1.6}.presets[data-v-ed6d7751]{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}.preset[data-v-ed6d7751]{border-radius:2px;border:1px solid var(--panel-border);background:#060c148c;color:var(--panel-muted);padding:6px 12px;font-size:12px;cursor:pointer;font-weight:600}.preset.active[data-v-ed6d7751]{background:#3dbeff38;color:#eaf4ff;border-color:#3dbeff8c}.poll-form[data-v-ed6d7751]{grid-template-columns:1fr 1fr}.modal-mask[data-v-ed6d7751]{position:fixed;top:0;right:0;bottom:0;left:0;background:#04080eb8;display:flex;align-items:center;justify-content:center;z-index:50}.modal[data-v-ed6d7751]{width:min(560px,92vw);max-height:90vh;overflow:auto}.modal-actions[data-v-ed6d7751]{margin-top:14px;display:flex;gap:10px}
import{d as c,j as u,c as l,b as s,t as a,s as o,O as i,o as p}from"./index-BssDqbym.js";const m={class:"page-fill"},_={class:"stub-box"},v=c({__name:"StubView",setup(d){const{t:e}=u(),t=i(),n=o(()=>t.name==="roam"?e("pages.roamTitle"):e("pages.stubTitle")),r=o(()=>t.name==="roam"?e("pages.roamDesc"):e("pages.stubDesc"));return(f,b)=>(p(),l("div",m,[s("div",_,[s("div",null,[s("h2",null,a(n.value),1),s("p",null,a(r.value),1)])])]))}});export{v as default};
import{d as c,c as l,b as e,t as s,B as o,o as i,K as u}from"./index-Bs_x-_cK.js";const r={class:"page-fill"},d={class:"stub-box"},v=c({__name:"StubView",setup(_){const t=u(),a=o(()=>String(t.meta.title||"功能页")),n=o(()=>String(t.meta.desc||"该模块骨架已就绪,待数据接口与交互深化。"));return(p,m)=>(i(),l("div",r,[e("div",d,[e("div",null,[e("h2",null,s(a.value),1),e("p",null,s(n.value),1)])])]))}});export{v as default};
import{d as S,u as $,l as M,c as i,b as e,t as o,f as r,F as U,x,w as L,e as g,v as _,P as A,r as u,R as D,p as k,o as d,_ as T}from"./index-BssDqbym.js";const B={class:"page-fill admin-list-page"},F={class:"panel list-panel"},O={class:"panel-head"},P={class:"panel-title"},J={key:0,class:"msg ok"},z={key:1,class:"msg err"},H={class:"table-wrap"},I={class:"table"},R={class:"ops"},j=["onClick"],q=["onClick"],G={key:0},K={class:"modal panel"},Q={class:"form-grid"},W={class:"field"},X={class:"field"},Y={class:"field"},Z={class:"field"},ee={key:0,class:"msg err"},te={class:"modal-actions"},se=["disabled"],le=S({__name:"UserManageView",setup(ae){const v=$(),c=u([]),a=u(""),p=u(""),m=u(!1),b=u(!1),l=D({username:"",displayName:"",password:"",role:"viewer"});function w(){l.username="",l.displayName="",l.password="",l.role="viewer",a.value=""}function h(){w(),m.value=!0}function f(){m.value=!1,a.value=""}async function y(){const n=await k("/api/users",{token:v.token});c.value=n.users}async function C(){a.value="",b.value=!0;try{await k("/api/users",{method:"POST",token:v.token,body:JSON.stringify(l)}),p.value=`已创建用户 ${l.username}`,f(),w(),await y()}catch(n){a.value=n instanceof Error?n.message:"创建失败"}finally{b.value=!1}}async function N(n){a.value="";try{await k(`/api/users/${n.id}`,{method:"PATCH",token:v.token,body:JSON.stringify({enabled:!n.enabled})}),await y()}catch(t){a.value=t instanceof Error?t.message:"更新失败"}}async function V(n){if(confirm(`确认删除用户 ${n.username}?`)){a.value="";try{await k(`/api/users/${n.id}`,{method:"DELETE",token:v.token}),p.value=`已删除 ${n.username}`,await y()}catch(t){a.value=t instanceof Error?t.message:"删除失败"}}}return M(y),(n,t)=>(d(),i("div",B,[e("section",F,[e("div",O,[e("div",P,[t[4]||(t[4]=e("div",{class:"vbar"},null,-1)),e("h3",null,"用户列表("+o(c.value.length)+")",1)]),e("div",{class:"head-actions"},[t[5]||(t[5]=e("div",{class:"badge"},"仅管理员",-1)),e("button",{class:"btn",type:"button",onClick:h},"新增用户")])]),p.value?(d(),i("p",J,o(p.value),1)):r("",!0),a.value&&!m.value?(d(),i("p",z,o(a.value),1)):r("",!0),e("div",H,[e("table",I,[t[7]||(t[7]=e("thead",null,[e("tr",null,[e("th",null,"用户名"),e("th",null,"显示名"),e("th",null,"角色"),e("th",null,"状态"),e("th",null,"最近登录"),e("th",null,"操作")])],-1)),e("tbody",null,[(d(!0),i(U,null,x(c.value,s=>(d(),i("tr",{key:s.id},[e("td",null,o(s.username),1),e("td",null,o(s.displayName),1),e("td",null,o(s.role==="admin"?"管理员":"只读"),1),e("td",null,o(s.enabled?"启用":"禁用"),1),e("td",null,o(s.lastLoginAt?new Date(s.lastLoginAt).toLocaleString("zh-CN"):"—"),1),e("td",R,[e("button",{class:"btn ghost",type:"button",onClick:E=>N(s)},o(s.enabled?"禁用":"启用"),9,j),e("button",{class:"btn danger",type:"button",onClick:E=>V(s)},"删除",8,q)])]))),128)),c.value.length?r("",!0):(d(),i("tr",G,[...t[6]||(t[6]=[e("td",{colspan:"6",class:"empty"},"暂无用户,点击右上角「新增用户」",-1)])]))])])])]),m.value?(d(),i("div",{key:0,class:"modal-mask",onClick:L(f,["self"])},[e("div",K,[e("div",{class:"panel-head"},[t[8]||(t[8]=e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"新增用户")],-1)),e("button",{class:"btn ghost",type:"button",onClick:f},"关闭")]),e("div",Q,[e("div",W,[t[9]||(t[9]=e("label",null,"用户名",-1)),g(e("input",{"onUpdate:modelValue":t[0]||(t[0]=s=>l.username=s),autocomplete:"off"},null,512),[[_,l.username]])]),e("div",X,[t[10]||(t[10]=e("label",null,"显示名",-1)),g(e("input",{"onUpdate:modelValue":t[1]||(t[1]=s=>l.displayName=s)},null,512),[[_,l.displayName]])]),e("div",Y,[t[11]||(t[11]=e("label",null,"密码",-1)),g(e("input",{"onUpdate:modelValue":t[2]||(t[2]=s=>l.password=s),type:"password"},null,512),[[_,l.password]])]),e("div",Z,[t[13]||(t[13]=e("label",null,"角色",-1)),g(e("select",{"onUpdate:modelValue":t[3]||(t[3]=s=>l.role=s)},[...t[12]||(t[12]=[e("option",{value:"viewer"},"只读 (viewer)",-1),e("option",{value:"admin"},"管理员 (admin)",-1)])],512),[[A,l.role]])])]),a.value?(d(),i("p",ee,o(a.value),1)):r("",!0),e("div",te,[e("button",{class:"btn",type:"button",disabled:b.value,onClick:C},o(b.value?"创建中…":"创建"),9,se),e("button",{class:"btn ghost",type:"button",onClick:f},"取消")])])])):r("",!0)]))}}),oe=T(le,[["__scopeId","data-v-b2e28b89"]]);export{oe as default};
import{d as S,u as $,j as L,c as i,b as e,t as o,f as r,F as M,z as U,w as x,e as g,v as _,L as A,r as u,N as D,l as k,o as d,_ as T}from"./index-Bs_x-_cK.js";const B={class:"page-fill admin-list-page"},F={class:"panel list-panel"},O={class:"panel-head"},z={class:"panel-title"},J={key:0,class:"msg ok"},P={key:1,class:"msg err"},j={class:"table-wrap"},H={class:"table"},I={class:"ops"},q=["onClick"],G=["onClick"],K={key:0},Q={class:"modal panel"},R={class:"form-grid"},W={class:"field"},X={class:"field"},Y={class:"field"},Z={class:"field"},ee={key:0,class:"msg err"},te={class:"modal-actions"},se=["disabled"],le=S({__name:"UserManageView",setup(ae){const v=$(),c=u([]),a=u(""),p=u(""),m=u(!1),b=u(!1),l=D({username:"",displayName:"",password:"",role:"viewer"});function w(){l.username="",l.displayName="",l.password="",l.role="viewer",a.value=""}function h(){w(),m.value=!0}function f(){m.value=!1,a.value=""}async function y(){const n=await k("/api/users",{token:v.token});c.value=n.users}async function C(){a.value="",b.value=!0;try{await k("/api/users",{method:"POST",token:v.token,body:JSON.stringify(l)}),p.value=`已创建用户 ${l.username}`,f(),w(),await y()}catch(n){a.value=n instanceof Error?n.message:"创建失败"}finally{b.value=!1}}async function N(n){a.value="";try{await k(`/api/users/${n.id}`,{method:"PATCH",token:v.token,body:JSON.stringify({enabled:!n.enabled})}),await y()}catch(t){a.value=t instanceof Error?t.message:"更新失败"}}async function V(n){if(confirm(`确认删除用户 ${n.username}?`)){a.value="";try{await k(`/api/users/${n.id}`,{method:"DELETE",token:v.token}),p.value=`已删除 ${n.username}`,await y()}catch(t){a.value=t instanceof Error?t.message:"删除失败"}}}return L(y),(n,t)=>(d(),i("div",B,[e("section",F,[e("div",O,[e("div",z,[t[4]||(t[4]=e("div",{class:"vbar"},null,-1)),e("h3",null,"用户列表("+o(c.value.length)+")",1)]),e("div",{class:"head-actions"},[t[5]||(t[5]=e("div",{class:"badge"},"仅管理员",-1)),e("button",{class:"btn",type:"button",onClick:h},"新增用户")])]),p.value?(d(),i("p",J,o(p.value),1)):r("",!0),a.value&&!m.value?(d(),i("p",P,o(a.value),1)):r("",!0),e("div",j,[e("table",H,[t[7]||(t[7]=e("thead",null,[e("tr",null,[e("th",null,"用户名"),e("th",null,"显示名"),e("th",null,"角色"),e("th",null,"状态"),e("th",null,"最近登录"),e("th",null,"操作")])],-1)),e("tbody",null,[(d(!0),i(M,null,U(c.value,s=>(d(),i("tr",{key:s.id},[e("td",null,o(s.username),1),e("td",null,o(s.displayName),1),e("td",null,o(s.role==="admin"?"管理员":"只读"),1),e("td",null,o(s.enabled?"启用":"禁用"),1),e("td",null,o(s.lastLoginAt?new Date(s.lastLoginAt).toLocaleString("zh-CN"):"—"),1),e("td",I,[e("button",{class:"btn ghost",type:"button",onClick:E=>N(s)},o(s.enabled?"禁用":"启用"),9,q),e("button",{class:"btn danger",type:"button",onClick:E=>V(s)},"删除",8,G)])]))),128)),c.value.length?r("",!0):(d(),i("tr",K,[...t[6]||(t[6]=[e("td",{colspan:"6",class:"empty"},"暂无用户,点击右上角「新增用户」",-1)])]))])])])]),m.value?(d(),i("div",{key:0,class:"modal-mask",onClick:x(f,["self"])},[e("div",Q,[e("div",{class:"panel-head"},[t[8]||(t[8]=e("div",{class:"panel-title"},[e("div",{class:"vbar"}),e("h3",null,"新增用户")],-1)),e("button",{class:"btn ghost",type:"button",onClick:f},"关闭")]),e("div",R,[e("div",W,[t[9]||(t[9]=e("label",null,"用户名",-1)),g(e("input",{"onUpdate:modelValue":t[0]||(t[0]=s=>l.username=s),autocomplete:"off"},null,512),[[_,l.username]])]),e("div",X,[t[10]||(t[10]=e("label",null,"显示名",-1)),g(e("input",{"onUpdate:modelValue":t[1]||(t[1]=s=>l.displayName=s)},null,512),[[_,l.displayName]])]),e("div",Y,[t[11]||(t[11]=e("label",null,"密码",-1)),g(e("input",{"onUpdate:modelValue":t[2]||(t[2]=s=>l.password=s),type:"password"},null,512),[[_,l.password]])]),e("div",Z,[t[13]||(t[13]=e("label",null,"角色",-1)),g(e("select",{"onUpdate:modelValue":t[3]||(t[3]=s=>l.role=s)},[...t[12]||(t[12]=[e("option",{value:"viewer"},"只读 (viewer)",-1),e("option",{value:"admin"},"管理员 (admin)",-1)])],512),[[A,l.role]])])]),a.value?(d(),i("p",ee,o(a.value),1)):r("",!0),e("div",te,[e("button",{class:"btn",type:"button",disabled:b.value,onClick:C},o(b.value?"创建中…":"创建"),9,se),e("button",{class:"btn ghost",type:"button",onClick:f},"取消")])])])):r("",!0)]))}}),oe=T(le,[["__scopeId","data-v-b2e28b89"]]);export{oe as default};
.hist[data-v-12002b74]{min-height:200px;margin-bottom:10px;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.hist[data-v-12002b74] .echart-wrap{height:180px}.vav-page[data-v-12002b74]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.table-wrap[data-v-12002b74]{flex:1;min-height:0;overflow:auto}.vav-table[data-v-12002b74]{width:auto;max-width:100%;table-layout:auto;border-collapse:collapse;font-size:12px}.vav-table[data-v-12002b74] th,.vav-table th[data-v-12002b74],.vav-table td[data-v-12002b74]{padding:6px 10px;white-space:nowrap;width:1%}.vav-table .name[data-v-12002b74]{width:auto;max-width:160px;overflow:hidden;text-overflow:ellipsis}.vav-table .ops[data-v-12002b74]{display:flex;gap:6px;width:1%}.vav-table[data-v-12002b74] .btn.sm,.vav-table .btn.sm[data-v-12002b74]{padding:4px 10px;font-size:11px;line-height:1.2}.foot[data-v-12002b74]{margin-top:12px;flex:0 0 auto;font-size:12px;color:var(--label);line-height:1.7}
.hist[data-v-07c9907f]{min-height:200px;margin-bottom:10px;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.hist[data-v-07c9907f] .echart-wrap{height:180px}.vav-page[data-v-07c9907f]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.table-wrap[data-v-07c9907f]{flex:1;min-height:0;overflow:auto}.vav-table[data-v-07c9907f]{width:auto;max-width:100%;table-layout:auto;border-collapse:collapse;font-size:12px}.vav-table[data-v-07c9907f] th,.vav-table th[data-v-07c9907f],.vav-table td[data-v-07c9907f]{padding:6px 10px;white-space:nowrap;width:1%}.vav-table .name[data-v-07c9907f]{width:auto;max-width:160px;overflow:hidden;text-overflow:ellipsis}.vav-table .ops[data-v-07c9907f]{display:flex;gap:6px;width:1%}.vav-table[data-v-07c9907f] .btn.sm,.vav-table .btn.sm[data-v-07c9907f]{padding:4px 10px;font-size:11px;line-height:1.2}.foot[data-v-07c9907f]{margin-top:12px;flex:0 0 auto;font-size:12px;color:var(--label);line-height:1.7}
import{d as w,j as A,u as V,c,b as e,t as s,h as a,H as k,F as C,x as E,s as d,p as F,o as m,_ as O}from"./index-BssDqbym.js";import{u as S}from"./realtime-lmP-Cubp.js";import{E as H,m as M}from"./historyChart-DJ1B6lTP.js";import{u}from"./useMetricHistory-CLFOMuUx.js";const N={class:"page-fill"},B={class:"panel vav-page"},L={class:"panel-head"},T={class:"panel-title"},D={class:"badge"},I={class:"hist"},R={class:"table-wrap"},$={class:"table vav-table"},j={class:"name"},J={class:"ops"},P=["disabled","onClick"],q=["disabled","onClick"],z={class:"foot"},G=w({__name:"VavView",setup(K){const{t}=A(),l=V(),p=S(),{series:v,loading:h}=u(()=>({source:"vav",metric:"airflow"})),{series:_,loading:g}=u(()=>({source:"vav",metric:"face_velocity"})),f=d(()=>M([...v.value.map(o=>({...o,name:t("pages.seriesAirflow",{name:o.name||o.id})})),..._.value.map(o=>({...o,name:t("pages.seriesFaceVel",{name:o.name||o.id})}))],{maxSeries:8})),b=d(()=>h.value||g.value);async function r(o,i){l.isAdmin&&(await F("/api/realtime/write",{method:"POST",token:l.token,body:JSON.stringify({target:o,action:i?"start":"stop"})}),alert(t(i?"pages.vavCmdOn":"pages.vavCmdOff")))}function x(o){return o.maxExhaust?t("metrics.maxExhaust"):o.minAirflow?t("metrics.minAirflow"):t("pages.vavModeNormal")}return(o,i)=>(m(),c("div",N,[e("section",B,[e("div",L,[e("div",T,[i[0]||(i[0]=e("div",{class:"vbar"},null,-1)),e("h3",null,s(a(t)("pages.vavTitle")),1)]),e("div",D,s(a(l).isAdmin?a(t)("common.controllable"):a(t)("common.readonly"))+" · "+s(a(t)("common.last24h")),1)]),e("div",I,[k(H,{option:f.value,loading:b.value,"empty-text":a(t)("pages.vavEmpty")},null,8,["option","loading","empty-text"])]),e("div",R,[e("table",$,[e("thead",null,[e("tr",null,[e("th",null,s(a(t)("common.device")),1),e("th",null,s(a(t)("pages.vavRunState")),1),e("th",null,s(a(t)("pages.vavFaceVel")),1),e("th",null,s(a(t)("pages.vavDoorH")),1),e("th",null,s(a(t)("pages.vavAirflow")),1),e("th",null,s(a(t)("pages.vavValve")),1),e("th",null,s(a(t)("pages.vavMaxEx")),1),e("th",null,s(a(t)("pages.vavMinAf")),1),e("th",null,s(a(t)("pages.vavExMode")),1),e("th",null,s(a(t)("pages.vavAlarm")),1),e("th",null,s(a(t)("common.action")),1)])]),e("tbody",null,[(m(!0),c(C,null,E(a(p).snapshot.vav,n=>(m(),c("tr",{key:n.id},[e("td",j,s(n.name),1),e("td",null,s(n.running?a(t)("screen.running"):a(t)("screen.stopped")),1),e("td",null,s(n.faceVelocity.toFixed(3))+" m/s",1),e("td",null,s(n.sashHeight)+" mm",1),e("td",null,s(n.airflow)+" m³/h",1),e("td",null,s(n.valveOpen)+" %",1),e("td",null,s(n.maxExhaust?a(t)("common.yes"):a(t)("common.no")),1),e("td",null,s(n.minAirflow?a(t)("common.yes"):a(t)("common.no")),1),e("td",null,s(x(n)),1),e("td",null,s(n.alarmCode||a(t)("common.none")),1),e("td",J,[e("button",{class:"btn sm",disabled:!a(l).isAdmin,onClick:y=>r(n.id,!0)},s(a(t)("common.start")),9,P),e("button",{class:"btn ghost sm",disabled:!a(l).isAdmin,onClick:y=>r(n.id,!1)},s(a(t)("common.stop")),9,q)])]))),128))])])]),e("p",z,s(a(t)("pages.vavFoot")),1)])]))}}),Y=O(G,[["__scopeId","data-v-12002b74"]]);export{Y as default};
import{d as V,u as k,c as i,b as t,t as l,h as o,D as y,F as w,z as A,B as u,o as d,l as C,_ as S}from"./index-Bs_x-_cK.js";import{u as E}from"./realtime-CI_KLy_N.js";import{E as O,m as B}from"./historyChart-DTdcyadg.js";import{u as c}from"./useMetricHistory-Ck1zYYEr.js";const F={class:"page-fill"},$={class:"panel vav-page"},H={class:"panel-head"},L={class:"badge"},M={class:"hist"},N={class:"table-wrap"},D={class:"table vav-table"},T={class:"name"},z={class:"ops"},I=["disabled","onClick"],J=["disabled","onClick"],P=V({__name:"VavView",setup(R){const n=k(),m=E(),{series:h,loading:p}=c(()=>({source:"vav",metric:"airflow"})),{series:_,loading:f}=c(()=>({source:"vav",metric:"face_velocity"})),v=u(()=>B([...h.value.map(e=>({...e,name:`${e.name||e.id}·风量`})),..._.value.map(e=>({...e,name:`${e.name||e.id}·面风速`}))],{maxSeries:8})),b=u(()=>p.value||f.value);async function r(e,a){n.isAdmin&&(await C("/api/realtime/write",{method:"POST",token:n.token,body:JSON.stringify({target:e,action:a?"start":"stop"})}),alert(a?"开机指令已下发(Mock)":"关机指令已下发(Mock)"))}function g(e){return e.maxExhaust?"最大排风":e.minAirflow?"最小风量":"常态"}return(e,a)=>(d(),i("div",F,[t("section",$,[t("div",H,[a[0]||(a[0]=t("div",{class:"panel-title"},[t("div",{class:"vbar"}),t("h3",null,"通风柜 VAV 集中监控")],-1)),t("div",L,l(o(n).isAdmin?"可控制":"只读")+" · 近24h曲线",1)]),t("div",M,[y(O,{option:v.value,loading:b.value,"empty-text":"VAV 历史积累中"},null,8,["option","loading"])]),t("div",N,[t("table",D,[a[1]||(a[1]=t("thead",null,[t("tr",null,[t("th",null,"设备"),t("th",null,"开停状态"),t("th",null,"实际面风速"),t("th",null,"实际门高"),t("th",null,"实际风量"),t("th",null,"阀开度"),t("th",null,"最大排风"),t("th",null,"最小风量"),t("th",null,"排风模式"),t("th",null,"报警代码"),t("th",null,"操作")])],-1)),t("tbody",null,[(d(!0),i(w,null,A(o(m).snapshot.vav,s=>(d(),i("tr",{key:s.id},[t("td",T,l(s.name),1),t("td",null,l(s.running?"运行":"停止"),1),t("td",null,l(s.faceVelocity.toFixed(3))+" m/s",1),t("td",null,l(s.sashHeight)+" mm",1),t("td",null,l(s.airflow)+" m³/h",1),t("td",null,l(s.valveOpen)+" %",1),t("td",null,l(s.maxExhaust?"是":"否"),1),t("td",null,l(s.minAirflow?"是":"否"),1),t("td",null,l(g(s)),1),t("td",null,l(s.alarmCode||"无"),1),t("td",z,[t("button",{class:"btn sm",disabled:!o(n).isAdmin,onClick:x=>r(s.id,!0)},"开机",8,I),t("button",{class:"btn ghost sm",disabled:!o(n).isAdmin,onClick:x=>r(s.id,!1)},"关机",8,J)])]))),128))])])]),a[2]||(a[2]=t("p",{class:"foot"}," 点位来源:文库制备1/2通风柜(开停、面风速×0.001、门高、风量、最大/最小风量、报警代码)。阀开度按报价清单监控项展示。 ",-1))])]))}}),Q=S(P,[["__scopeId","data-v-07c9907f"]]);export{Q as default};
.video-page[data-v-36429526]{gap:12px}.head-right[data-v-36429526]{display:flex;align-items:center;gap:8px}.video-tabs[data-v-36429526]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-36429526]{border-radius:6px;border:1px solid rgba(62,200,255,.22);background:#122a4e66;color:var(--muted);padding:6px 14px;font-size:12px;cursor:pointer}.tab.active[data-v-36429526]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-36429526]{flex:1}.msg[data-v-36429526]{font-size:12px;color:var(--cyan-lt)}.hint[data-v-36429526]{font-size:12px;color:var(--label);line-height:1.6;margin-bottom:12px}.form-grid.three[data-v-36429526]{grid-template-columns:repeat(3,1fr);max-width:1100px}.row-actions[data-v-36429526]{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:14px}.switch-field .switch[data-v-36429526]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.list-panel[data-v-36429526],.config-panel[data-v-36429526]{flex:1;min-height:0;display:flex;flex-direction:column}.table-wrap[data-v-36429526]{flex:1;min-height:0;overflow:auto}.ops[data-v-36429526]{display:flex;flex-wrap:wrap;gap:6px}.empty[data-v-36429526]{text-align:center;color:var(--label);padding:24px!important}.st[data-v-36429526]{display:inline-block;padding:1px 8px;border-radius:4px;font-size:11px}.st.online[data-v-36429526]{background:#2fd6a126;color:var(--green)}.st.offline[data-v-36429526]{background:#8eb4d41f;color:var(--muted)}.st.alarm[data-v-36429526]{background:#f0b4292e;color:var(--amber)}.preview-layout[data-v-36429526]{flex:1;min-height:0;display:grid;grid-template-columns:1.6fr 1fr;gap:12px}.webrtc-stage[data-v-36429526]{display:flex;flex-direction:column;min-height:360px;padding:0;overflow:hidden}.webrtc-stage[data-v-36429526] .webrtc-player{flex:1;min-height:320px;border-radius:0}.cam-meta.bar[data-v-36429526]{flex:0 0 auto;padding:8px 12px;font-size:12px;color:var(--muted);border-top:1px solid rgba(62,200,255,.15);background:#00000040}.preview-main[data-v-36429526],.preview-side[data-v-36429526]{min-height:0;display:flex;flex-direction:column}.stage[data-v-36429526]{position:relative;flex:1;min-height:280px;border-radius:8px;overflow:hidden;border:1px solid rgba(62,200,255,.28);background:radial-gradient(ellipse at 30% 20%,rgba(58,140,220,.22),transparent 55%),linear-gradient(160deg,#0a1a30,#07111f 55%,#040a14)}.stage.alarm[data-v-36429526]{background:radial-gradient(ellipse at 40% 30%,rgba(240,180,41,.18),transparent 50%),linear-gradient(160deg,#1a1520,#0c1018)}.stage.offline[data-v-36429526]{filter:saturate(.55)}.scan[data-v-36429526]{pointer-events:none;position:absolute;top:0;right:0;bottom:0;left:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(122,216,255,.03) 2px,rgba(122,216,255,.03) 4px)}.live-tag[data-v-36429526]{position:absolute;left:12px;top:12px;z-index:2;display:flex;align-items:center;gap:6px;font-size:11px;color:#fff;background:#04101fa6;padding:3px 8px;border-radius:4px}.live-tag i[data-v-36429526]{width:7px;height:7px;border-radius:50%;background:#ff5a5a;box-shadow:0 0 #ff5a5a99;animation:pulse-36429526 1.4s ease infinite}@keyframes pulse-36429526{70%{box-shadow:0 0 0 6px #ff5a5a00}to{box-shadow:0 0 #ff5a5a00}}.stage-body[data-v-36429526]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:24px;z-index:1}.cam-title[data-v-36429526]{font-size:22px;font-weight:600;color:var(--title)}.cam-sub[data-v-36429526]{margin-top:8px;font-size:13px;color:var(--cyan-lt)}.cam-meta[data-v-36429526]{margin-top:10px;font-size:12px;color:var(--label);font-family:var(--font-num)}.cam-note[data-v-36429526]{margin-top:16px;max-width:420px;font-size:12px;color:var(--muted);line-height:1.7}.thumb-grid[data-v-36429526]{flex:1;min-height:0;overflow:auto;display:grid;grid-template-columns:1fr 1fr;gap:8px;align-content:start}.thumb[data-v-36429526]{text-align:left;cursor:pointer;border-radius:8px;padding:10px 12px;border:1px solid rgba(62,200,255,.2);background:#0e244459;color:inherit}.thumb[data-v-36429526]:hover{border-color:#3ec8ff73}.thumb.active[data-v-36429526]{border-color:#3ac8ffb3;background:#1a6fff38;box-shadow:inset 0 0 0 1px #7ad8ff26}.thumb-top[data-v-36429526]{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.ch[data-v-36429526]{font-size:11px;color:var(--cyan-lt);font-family:var(--font-num)}.thumb-name[data-v-36429526]{font-size:13px;font-weight:600;color:#fff}.thumb-loc[data-v-36429526]{margin-top:4px;font-size:11px;color:var(--label)}
.webrtc-player[data-v-a83be64f]{position:relative;width:100%;height:100%;min-height:280px;background:#050a14;border-radius:8px;overflow:hidden}.video[data-v-a83be64f]{width:100%;height:100%;object-fit:contain;background:#000}.overlay[data-v-a83be64f]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#c9d7f0;font-size:14px;background:#050a14b8;text-align:center;padding:16px}.sub[data-v-a83be64f]{font-size:12px;color:#8fa3c4;max-width:80%;line-height:1.5;word-break:break-all}.btn[data-v-a83be64f]{border:1px solid rgba(61,139,253,.45);background:#3d8bfd33;color:#fff;border-radius:6px;padding:6px 14px;cursor:pointer}.live-tag[data-v-a83be64f]{position:absolute;top:12px;left:12px;display:flex;align-items:center;gap:6px;font-size:11px;padding:4px 8px;border-radius:4px;background:#0000008c;color:#9dffc0}.live-tag i[data-v-a83be64f]{width:7px;height:7px;border-radius:50%;background:#3dd68c;box-shadow:0 0 8px #3dd68c}.video-page[data-v-b1a1fb2b]{gap:12px}.head-right[data-v-b1a1fb2b]{display:flex;align-items:center;gap:8px}.video-tabs[data-v-b1a1fb2b]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-b1a1fb2b]{border-radius:6px;border:1px solid rgba(62,200,255,.22);background:#122a4e66;color:var(--muted);padding:6px 14px;font-size:12px;cursor:pointer}.tab.active[data-v-b1a1fb2b]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-b1a1fb2b]{flex:1}.msg[data-v-b1a1fb2b]{font-size:12px;color:var(--cyan-lt)}.hint[data-v-b1a1fb2b]{font-size:12px;color:var(--label);line-height:1.6;margin-bottom:12px}.form-grid.three[data-v-b1a1fb2b]{grid-template-columns:repeat(3,1fr);max-width:1100px}.row-actions[data-v-b1a1fb2b]{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:14px}.switch-field .switch[data-v-b1a1fb2b]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.list-panel[data-v-b1a1fb2b],.config-panel[data-v-b1a1fb2b]{flex:1;min-height:0;display:flex;flex-direction:column}.table-wrap[data-v-b1a1fb2b]{flex:1;min-height:0;overflow:auto}.ops[data-v-b1a1fb2b]{display:flex;flex-wrap:wrap;gap:6px}.empty[data-v-b1a1fb2b]{text-align:center;color:var(--label);padding:24px!important}.st[data-v-b1a1fb2b]{display:inline-block;padding:1px 8px;border-radius:4px;font-size:11px}.st.online[data-v-b1a1fb2b]{background:#2fd6a126;color:var(--green)}.st.offline[data-v-b1a1fb2b]{background:#8eb4d41f;color:var(--muted)}.st.alarm[data-v-b1a1fb2b]{background:#f0b4292e;color:var(--amber)}.preview-layout[data-v-b1a1fb2b]{flex:1;min-height:0;display:grid;grid-template-columns:1.6fr 1fr;gap:12px}.webrtc-stage[data-v-b1a1fb2b]{display:flex;flex-direction:column;min-height:360px;padding:0;overflow:hidden}.webrtc-stage[data-v-b1a1fb2b] .webrtc-player{flex:1;min-height:320px;border-radius:0}.cam-meta.bar[data-v-b1a1fb2b]{flex:0 0 auto;padding:8px 12px;font-size:12px;color:var(--muted);border-top:1px solid rgba(62,200,255,.15);background:#00000040}.preview-main[data-v-b1a1fb2b],.preview-side[data-v-b1a1fb2b]{min-height:0;display:flex;flex-direction:column}.stage[data-v-b1a1fb2b]{position:relative;flex:1;min-height:280px;border-radius:8px;overflow:hidden;border:1px solid rgba(62,200,255,.28);background:radial-gradient(ellipse at 30% 20%,rgba(58,140,220,.22),transparent 55%),linear-gradient(160deg,#0a1a30,#07111f 55%,#040a14)}.stage.alarm[data-v-b1a1fb2b]{background:radial-gradient(ellipse at 40% 30%,rgba(240,180,41,.18),transparent 50%),linear-gradient(160deg,#1a1520,#0c1018)}.stage.offline[data-v-b1a1fb2b]{filter:saturate(.55)}.scan[data-v-b1a1fb2b]{pointer-events:none;position:absolute;top:0;right:0;bottom:0;left:0;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(122,216,255,.03) 2px,rgba(122,216,255,.03) 4px)}.live-tag[data-v-b1a1fb2b]{position:absolute;left:12px;top:12px;z-index:2;display:flex;align-items:center;gap:6px;font-size:11px;color:#fff;background:#04101fa6;padding:3px 8px;border-radius:4px}.live-tag i[data-v-b1a1fb2b]{width:7px;height:7px;border-radius:50%;background:#ff5a5a;box-shadow:0 0 #ff5a5a99;animation:pulse-b1a1fb2b 1.4s ease infinite}@keyframes pulse-b1a1fb2b{70%{box-shadow:0 0 0 6px #ff5a5a00}to{box-shadow:0 0 #ff5a5a00}}.stage-body[data-v-b1a1fb2b]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:24px;z-index:1}.cam-title[data-v-b1a1fb2b]{font-size:22px;font-weight:600;color:var(--title)}.cam-sub[data-v-b1a1fb2b]{margin-top:8px;font-size:13px;color:var(--cyan-lt)}.cam-meta[data-v-b1a1fb2b]{margin-top:10px;font-size:12px;color:var(--label);font-family:var(--font-num)}.cam-note[data-v-b1a1fb2b]{margin-top:16px;max-width:420px;font-size:12px;color:var(--muted);line-height:1.7}.thumb-grid[data-v-b1a1fb2b]{flex:1;min-height:0;overflow:auto;display:grid;grid-template-columns:1fr 1fr;gap:8px;align-content:start}.thumb[data-v-b1a1fb2b]{text-align:left;cursor:pointer;border-radius:8px;padding:10px 12px;border:1px solid rgba(62,200,255,.2);background:#0e244459;color:inherit}.thumb[data-v-b1a1fb2b]:hover{border-color:#3ec8ff73}.thumb.active[data-v-b1a1fb2b]{border-color:#3ac8ffb3;background:#1a6fff38;box-shadow:inset 0 0 0 1px #7ad8ff26}.thumb-top[data-v-b1a1fb2b]{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.ch[data-v-b1a1fb2b]{font-size:11px;color:var(--cyan-lt);font-family:var(--font-num)}.thumb-name[data-v-b1a1fb2b]{font-size:13px;font-weight:600;color:#fff}.thumb-loc[data-v-b1a1fb2b]{margin-top:4px;font-size:11px;color:var(--label)}
import{d as S,u as E,q as P,s as W,c as s,n as I,b as i,F as w,g as k,t as x,r as g,o as r,_ as B}from"./index-Bs_x-_cK.js";const D={key:0,class:"overlay"},O={class:"sub"},A={key:1,class:"live-tag"},L=S({__name:"WebRtcPlayer",props:{deviceId:{},whepPath:{}},setup(_,{expose:C}){const c=_,f=E(),n=g(null),a=g("idle"),l=g("");let e=null,d=null;async function u(){try{d&&f.token&&await fetch(d,{method:"DELETE",headers:{Authorization:`Bearer ${f.token}`}}).catch(()=>{})}finally{d=null,e==null||e.close(),e=null,n.value&&(n.value.srcObject=null),a.value="idle"}}async function h(){var o;if(await u(),!!c.deviceId){a.value="connecting",l.value="",e=new RTCPeerConnection({iceServers:[{urls:"stun:stun.l.google.com:19302"}]}),e.addTransceiver("video",{direction:"recvonly"}),e.addTransceiver("audio",{direction:"recvonly"}),e.ontrack=t=>{n.value&&(n.value.srcObject=t.streams[0]||new MediaStream([t.track]),n.value.play().catch(()=>{}),a.value="playing")},e.onconnectionstatechange=()=>{e&&(e.connectionState==="failed"||e.connectionState==="disconnected")&&(a.value="error",l.value="WebRTC 连接中断")};try{const t=await e.createOffer();await e.setLocalDescription(t),await R(e);const m=c.whepPath||`/api/video/devices/${encodeURIComponent(c.deviceId)}/whep`,p=await fetch(m,{method:"POST",headers:{"Content-Type":"application/sdp",Accept:"application/sdp",Authorization:`Bearer ${f.token}`},body:((o=e.localDescription)==null?void 0:o.sdp)||t.sdp||""}),v=p.headers.get("Location");v&&(d=(v.startsWith("http"),v));const y=await p.text();if(!p.ok){let b=y||`WHEP HTTP ${p.status}`;try{const T=JSON.parse(y);T.message&&(b=T.message)}catch{}throw new Error(b)}await e.setRemoteDescription({type:"answer",sdp:y})}catch(t){a.value="error",l.value=t instanceof Error?t.message:"WebRTC 启动失败",await u(),a.value="error"}}}function R(o){return o.iceGatheringState==="complete"?Promise.resolve():new Promise(t=>{const m=window.setTimeout(()=>t(),2e3);o.onicegatheringstatechange=()=>{o.iceGatheringState==="complete"&&(window.clearTimeout(m),t())}})}return P(()=>c.deviceId,()=>{h()},{immediate:!0}),W(()=>{u()}),C({restart:h,stop:u}),(o,t)=>(r(),s("div",{class:I(["webrtc-player",a.value])},[i("video",{ref_key:"videoEl",ref:n,class:"video",autoplay:"",playsinline:"",muted:""},null,512),a.value!=="playing"?(r(),s("div",D,[a.value==="connecting"?(r(),s(w,{key:0},[k("正在建立 WebRTC…")],64)):a.value==="error"?(r(),s(w,{key:1},[t[0]||(t[0]=i("div",null,"WebRTC 未就绪",-1)),i("div",O,x(l.value),1),i("button",{type:"button",class:"btn",onClick:h},"重试")],64)):(r(),s(w,{key:2},[k("空闲")],64))])):(r(),s("div",A,[...t[1]||(t[1]=[i("i",null,null,-1),i("span",null,"WebRTC · LIVE",-1)])]))],2))}}),j=B(L,[["__scopeId","data-v-a83be64f"]]);export{j as W};
.webrtc-player[data-v-a83be64f]{position:relative;width:100%;height:100%;min-height:280px;background:#050a14;border-radius:8px;overflow:hidden}.video[data-v-a83be64f]{width:100%;height:100%;object-fit:contain;background:#000}.overlay[data-v-a83be64f]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;color:#c9d7f0;font-size:14px;background:#050a14b8;text-align:center;padding:16px}.sub[data-v-a83be64f]{font-size:12px;color:#8fa3c4;max-width:80%;line-height:1.5;word-break:break-all}.btn[data-v-a83be64f]{border:1px solid rgba(61,139,253,.45);background:#3d8bfd33;color:#fff;border-radius:6px;padding:6px 14px;cursor:pointer}.live-tag[data-v-a83be64f]{position:absolute;top:12px;left:12px;display:flex;align-items:center;gap:6px;font-size:11px;padding:4px 8px;border-radius:4px;background:#0000008c;color:#9dffc0}.live-tag i[data-v-a83be64f]{width:7px;height:7px;border-radius:50%;background:#3dd68c;box-shadow:0 0 8px #3dd68c}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
import{p as y,r as c,B as u}from"./index-Bs_x-_cK.js";const g={ts:0,rooms:[],hvac:{systemRunning:!1,supplyFanHz:0,exhaustPressure:0,filterRemainPct:0,mode:"auto",supplyTemp:0,supplyHumidity:0,supplyPressure:0,freshAirTemp:0,alarms:[]},vav:[],fridges:[],neLights:[],metering:{powerKwh:0,waterM3:0,coolingKwh:0,powerTrendPct:0,waterTrendPct:0,coolingTrendPct:0},access:{doors:[],todayPassCount:0,recentEvents:[]},online:{devicesOnline:0,devicesTotal:0,gatewayOk:!1},pointAlarms:[]},T=y("realtime",()=>{const t=c(g),n=c(!1),r=c("sample-1");let e=null,a;const m=u(()=>{if(Array.isArray(t.value.pointAlarms))return t.value.pointAlarms.length;const o=t.value.rooms.reduce((s,h)=>s+h.alarms.length,0),l=t.value.fridges.filter(s=>s.temp>-19).length;return o+l+t.value.hvac.alarms.length}),p=u(()=>t.value.rooms.find(o=>o.id===r.value)||t.value.rooms[0]||{id:"-",name:"-",temp:0,humidity:0,pressure:0,alarms:[]});function d(o){r.value=o}function f(o){t.value=o}function i(){const o=`${location.protocol==="https:"?"wss":"ws"}://${location.host}/ws`;e==null||e.close(),e=new WebSocket(o),e.onopen=()=>{n.value=!0},e.onmessage=l=>{try{const s=JSON.parse(l.data);s.type==="snapshot"&&(t.value=s.data)}catch{}},e.onclose=()=>{n.value=!1,window.clearTimeout(a),a=window.setTimeout(i,2e3)},e.onerror=()=>e==null?void 0:e.close()}function v(){window.clearTimeout(a),e==null||e.close(),e=null}return{snapshot:t,connected:n,selectedRoomId:r,alarmCount:m,primaryRoom:p,selectRoom:d,setSnapshot:f,connect:i,disconnect:v}});export{T as u};
import{B as y,r as c,s as u}from"./index-BssDqbym.js";const g={ts:0,rooms:[],hvac:{systemRunning:!1,supplyFanHz:0,exhaustPressure:0,filterRemainPct:0,mode:"auto",supplyTemp:0,supplyHumidity:0,supplyPressure:0,freshAirTemp:0,alarms:[]},vav:[],fridges:[],neLights:[],metering:{powerKwh:0,waterM3:0,coolingKwh:0,powerTrendPct:0,waterTrendPct:0,coolingTrendPct:0},access:{doors:[],todayPassCount:0,recentEvents:[]},online:{devicesOnline:0,devicesTotal:0,gatewayOk:!1},pointAlarms:[]},T=y("realtime",()=>{const t=c(g),n=c(!1),r=c("sample-1");let e=null,a;const m=u(()=>{if(Array.isArray(t.value.pointAlarms))return t.value.pointAlarms.length;const o=t.value.rooms.reduce((s,h)=>s+h.alarms.length,0),l=t.value.fridges.filter(s=>s.temp>-19).length;return o+l+t.value.hvac.alarms.length}),p=u(()=>t.value.rooms.find(o=>o.id===r.value)||t.value.rooms[0]||{id:"-",name:"-",temp:0,humidity:0,pressure:0,alarms:[]});function d(o){r.value=o}function f(o){t.value=o}function i(){const o=`${location.protocol==="https:"?"wss":"ws"}://${location.host}/ws`;e==null||e.close(),e=new WebSocket(o),e.onopen=()=>{n.value=!0},e.onmessage=l=>{try{const s=JSON.parse(l.data);s.type==="snapshot"&&(t.value=s.data)}catch{}},e.onclose=()=>{n.value=!1,window.clearTimeout(a),a=window.setTimeout(i,2e3)},e.onerror=()=>e==null?void 0:e.close()}function v(){window.clearTimeout(a),e==null||e.close(),e=null}return{snapshot:t,connected:n,selectedRoomId:r,alarmCount:m,primaryRoom:p,selectRoom:d,setSnapshot:f,connect:i,disconnect:v}});export{T as u};
import{y as o}from"./index-BssDqbym.js";function e(t,n){return String(o.global.t(t,n))}function l(t){if(t==null||t==="")return"—";const n=String(o.global.locale.value),r=n==="zh-CN"?"zh-CN":n==="zh-TW"?"zh-TW":n==="pt"?"pt-PT":"en-US";return new Date(t).toLocaleString(r,{hour12:!1})}export{l as f,e as t};
import{j as w,k as $,l as d,m as g,r as u}from"./index-BssDqbym.js";function n(t){return t<10?`0${t}`:`${t}`}function k(){const{tm:t,locale:l}=w(),a=u("--:--:--"),s=u("");let r;function o(){const e=new Date;a.value=`${n(e.getHours())}:${n(e.getMinutes())}:${n(e.getSeconds())}`;const c=t("weekdays"),i=Array.isArray(c)&&c[e.getDay()]||"";s.value=`${e.getFullYear()}-${n(e.getMonth()+1)}-${n(e.getDate())} ${i}`}return $(l,()=>o()),d(()=>{o(),r=window.setInterval(o,1e3)}),g(()=>window.clearInterval(r)),{time:a,date:s}}export{k as u};
import{j as s,k as c,r as u}from"./index-Bs_x-_cK.js";const i=["星期日","星期一","星期二","星期三","星期四","星期五","星期六"];function n(t){return t<10?`0${t}`:`${t}`}function $(){const t=u("--:--:--"),o=u("");let a;function r(){const e=new Date;t.value=`${n(e.getHours())}:${n(e.getMinutes())}:${n(e.getSeconds())}`,o.value=`${e.getFullYear()}-${n(e.getMonth()+1)}-${n(e.getDate())} ${i[e.getDay()]}`}return s(()=>{r(),a=window.setInterval(r,1e3)}),c(()=>window.clearInterval(a)),{time:t,date:o}}export{$ as u};
import{l as f,C as m,k as v,p as d,r as n}from"./index-BssDqbym.js";function p(i,u=6e4){const c=n([]),s=n(!1),a=n("");let o;async function r(){const e=i();if(!(!e.source||!e.metric)){s.value=!0,a.value="";try{const t=new URLSearchParams({source:e.source,metric:e.metric,hours:String(e.hours??24),bucketMinutes:String(e.bucketMinutes??60)}),l=await d(`/api/realtime/history?${t}`);c.value=l.series||[]}catch(t){a.value=t instanceof Error?t.message:"历史加载失败"}finally{s.value=!1}}}return f(()=>{r(),o=window.setInterval(()=>void r(),u)}),m(()=>{o&&window.clearInterval(o)}),v(i,()=>void r(),{deep:!0}),{series:c,loading:s,error:a,refresh:r}}export{p as u};
import{j as f,s as m,q as v,l as d,r as n}from"./index-Bs_x-_cK.js";function w(i,u=6e4){const c=n([]),t=n(!1),a=n("");let o;async function r(){const e=i();if(!(!e.source||!e.metric)){t.value=!0,a.value="";try{const s=new URLSearchParams({source:e.source,metric:e.metric,hours:String(e.hours??24),bucketMinutes:String(e.bucketMinutes??15)}),l=await d(`/api/realtime/history?${s}`);c.value=l.series||[]}catch(s){a.value=s instanceof Error?s.message:"历史加载失败"}finally{t.value=!1}}}return f(()=>{r(),o=window.setInterval(()=>void r(),u)}),m(()=>{o&&window.clearInterval(o)}),v(i,()=>void r(),{deep:!0}),{series:c,loading:t,error:a,refresh:r}}export{w as u};
import{s as H,r as J}from"./index-Bs_x-_cK.js";const Y="scene=mvlpj2yt6379aa29",b="/rtengine";function w(){return"/rtengine/gis.html?scene=mvlpj2yt6379aa29".trim()||"/rtengine/gis.html?scene=mvlpj2yt6379aa29".trim()||`${b}/gis.html?${Y}`}function B(r=w()){try{return new URL(r,typeof window<"u"?window.location.href:"http://localhost").origin}catch{return typeof window<"u"?window.location.origin:"http://localhost"}}function E(r=w()){try{const o=new URL(r,typeof window<"u"?window.location.href:"http://localhost");return o.pathname.startsWith(`${b}/`)||o.pathname===b?b:""}catch{return b}}function q(r,o=w()){const u=String(r||"").trim().replace(/\\/g,"/").split("/").pop()||"";if(!u)return"";const s=E(o);return`${B(o)}${s}/api/icons/${encodeURIComponent(u)}`}async function Q(r=w()){const o=E(r),u=await fetch(`${B(r)}${o}/api/icons`);if(!u.ok)throw new Error(`图标列表加载失败 (${u.status})`);const s=await u.json();return(Array.isArray(s)?s:s.files||[]).filter(f=>typeof f=="string"&&f.toLowerCase().endsWith(".png"))}async function te(r,o=w()){const u=r!=null&&r.length?r:await Q(o);return await Promise.all(u.map(s=>new Promise(a=>{const f=new Image;f.onload=()=>a(),f.onerror=()=>a(),f.src=q(s,o)}))),u}function $(r,o,u){if(!r)return;let s="*";try{s=new URL(o,window.location.href).origin}catch{}try{r.postMessage(u,s)}catch{try{r.postMessage(u,"*")}catch{}}}function X(r){if(!r||typeof r!="object")return null;const o=r,u=[o,o.data,o.location,o.coord,o.coordinates,o.metaData];if(typeof o.metaData=="string")try{u.push(JSON.parse(o.metaData))}catch{}for(const s of u){if(!s||typeof s!="object")continue;const a=s,f=Number(a.x??a.lng??a.longitude??a.lon),g=Number(a.y??a.lat??a.latitude),k=Number(a.z??a.height??a.alt??a.altitude??0);if(Number.isFinite(f)&&Number.isFinite(g))return{x:f,y:g,z:Number.isFinite(k)?k:0}}return null}function K(r){if(!r||typeof r!="object")return[];const o=r,u=[o];if(o.data&&typeof o.data=="object"&&u.push(o.data),o.metaData&&typeof o.metaData=="object"&&u.push(o.metaData),typeof o.metaData=="string")try{u.push(JSON.parse(o.metaData))}catch{}return o.params&&typeof o.params=="object"&&u.push(o.params),u}function Z(r){const o={id:null,name:null,type:null,tag0:null,tag1:null,tag2:null,icon:null,eventId:null},u=K(r);if(!u.length)return o;const s=(...M)=>{for(const y of u)for(const I of M){const p=y[I];if(p!=null&&String(p).trim())return String(p).trim()}return null},a=s("id","markerId","objectId"),f=s("name","text","title","label"),g=s("text","title","label","name");return{id:a,name:f&&a&&f===a&&g&&g!==a?g:f,type:s("type","objectType"),tag0:s("tag0","Tag0"),tag1:s("tag1","Tag1"),tag2:s("tag2","Tag2"),icon:s("icon","url","Icon"),eventId:s("eventId")}}function ne(r){const o=J(!1);function u(){var e;return((e=r.iframeRef.value)==null?void 0:e.contentWindow)??null}function s(){try{const e=u();return e?(e.location.href,e.$gisObj??null):null}catch{return null}}function a(e,t){const n=s(),i=n==null?void 0:n[e];if(typeof i=="function")try{if(e==="flyToBookmark"||e==="flyToViewpoint"||e==="flyToMarker"||e==="flyToHome"){const c=typeof t=="string"||typeof t=="number"?t:t&&typeof t=="object"?t.id??t.name??t.bookmarkName??t:t;i.call(n,c,t);return}i.call(n,t??{});return}catch{}$(u(),r.cloudUrl,{type:e,data:t??{},messageId:`web-${Date.now()}-${Math.random().toString(36).slice(2,8)}`})}const f=[];let g=!1;async function k(){if(g)return;const e=s();if(!e||typeof e.registerBookmarks!="function")return;let t="";try{t=new URL(r.cloudUrl,window.location.href).searchParams.get("scene")||""}catch{}if(!t)return;const n=[`/rtengine/designer/data/scenes/${encodeURIComponent(t)}.json`,`/designer/data/scenes/${encodeURIComponent(t)}.json`,`/designer/api/embed/scenes/${encodeURIComponent(t)}`];for(const i of n)try{const c=await fetch(i);if(!c.ok)continue;const l=await c.json();if(!Array.isArray(l.bookmarks)||!l.bookmarks.length)continue;e.registerBookmarks(l.bookmarks),e._bookmarkFlyReady=!0,g=!0;return}catch{}}function M(e,t){const n=String(e||"").trim();if(!n)return;const i=()=>{const c=s();if(!c||typeof c.flyToBookmark!="function")return!1;try{const l=t?c.flyToBookmark(n,t):c.flyToBookmark(n);return l&&typeof l=="object"&&l.queued&&(f.includes(n)||f.push(n)),!0}catch{return!1}};i()||(f.includes(n)||f.push(n),a("flyToBookmark",n),k().then(()=>{i()}),window.setTimeout(()=>i(),600),window.setTimeout(()=>i(),2e3))}function y(){const e=f.splice(0,f.length);for(const t of e)M(t)}function I(e){a("setMapPickMode",{state:e,enable:e})}function p(e){const t=(e.icon||"").trim();return{id:e.id,name:e.name,text:e.text??e.name,showText:e.showText!==!1,type:e.type||"marker",x:e.x,y:e.y,z:e.z,scale:e.scale??1,scalex:e.scale??1,scaley:e.scale??1,scalez:e.scale??1,tag0:e.tag0||"camera",tag1:e.tag1||e.id,...t?{icon:t,url:t}:{}}}function F(e){a("addMarker",p(e))}function A(e){a("addMarkers",e.map(t=>p(t)))}function U(e){a("removeMarker",{id:e})}function D(){a("clearMarkers",{})}function L(){const e=s(),t=e==null?void 0:e.markers,n=[];return!t||typeof t.forEach!="function"||t.forEach(i=>{if(!i||typeof i!="object")return;const c=i,l=String(c.id||"");l&&n.push({id:l,name:String(c.name||c.text||""),tag0:String(c.tag0||""),icon:String(c.icon||c.url||"")})}),n}function z(e,t){const n=s();if(n&&typeof n.setPoiVisible=="function")try{n.setPoiVisible(e,!!t);return}catch{}a("setPoiVisible",{id:e,visible:t})}function P(e,t){const n=s(),i=n==null?void 0:n[e];if(typeof i=="function")try{i.call(n,t,void 0,void 0);return}catch{}$(u(),r.cloudUrl,{type:e,data:{tag0:t},messageId:`web-${Date.now()}-${Math.random().toString(36).slice(2,8)}`})}function O(e){P("showPOIs",e)}function V(e){P("hidePOIs",e)}function _(e){const t=p(e);a("updateMarker",t),a("addMarker",t)}let T=!1;function W(){T||(T=!0,a("setMarkerVisible",{visible:!0}),a("showPOIs",{visible:!0}))}let N=!1;function j(){var e;if(N){f.length&&y();return}N=!0,o.value=!0,(e=r.onReady)==null||e.call(r),k().then(()=>y()),window.setTimeout(()=>{k().then(()=>y())},800),window.setTimeout(()=>y(),2500)}function v(e){var l;const t=e.data;if(!t||typeof t!="object")return;const n=String(t.type||""),i=t.data??t,c=i&&typeof i=="object"?i:null;if(!N&&c&&(c.bookmarksReady===!0||Array.isArray(c.bookmarks)&&c.bookmarks.length>0||typeof c.bookmarkCount=="number"&&c.bookmarkCount>0)&&j(),n==="sceneEmbedLoaded"||n==="sceneEmbedProgress"||n==="ueConnected"||n==="peerConnected"||n==="psConnected"||n==="streamPlaying"||n==="bookmarksReady"||n==="sceneBookmarks"){if(n==="sceneEmbedProgress"){const m=Number(c==null?void 0:c.percent);Number.isFinite(m)&&m>=100&&j();return}j();return}if(n==="mouseClick"||n==="mouseClickCallback"||n==="modelClick"||n==="modelClickCallback"||n==="selectMarker"||i&&typeof i=="object"&&i.eventId==="mouseClick"){const m=Z(i);(l=r.onMouseClick)==null||l.call(r,{point:X(i),markerId:m.id||m.tag1||m.tag0,marker:m,raw:i})}}window.addEventListener("message",v),H(()=>window.removeEventListener("message",v));function G(){const e=new Map,t=s();if(!t)return e;const n=[];try{typeof t.getBookmarks=="function"&&n.push(t.getBookmarks())}catch{}for(const i of["bookmarks","_bookmarks","bookmarkList","views"]){const c=t[i];c&&n.push(c)}for(const i of n){const c=Array.isArray(i)?i:i&&typeof i=="object"&&Array.isArray(i.bookmarks)?i.bookmarks:[];for(const l of c){if(!l||typeof l!="object")continue;const m=l,R=String(m.name??m.id??m.title??"").trim();if(!R)continue;const C=[m];for(const d of["camera","position","pos","location","coord","coordinates","view"]){const h=m[d];h&&typeof h=="object"&&C.push(h)}for(const d of C){const h=Number(d.x??d.lng??d.longitude??d.lon),x=Number(d.y??d.lat??d.latitude),S=Number(d.z??d.height??d.alt??d.altitude??0);if(Number.isFinite(h)&&Number.isFinite(x)){e.set(R,{x:h,y:x,z:Number.isFinite(S)?S:0});break}}}}return e}return{ready:o,send:a,flyToBookmark:M,setPickMode:I,addMarker:F,addMarkers:A,removeMarker:U,clearMarkers:D,setMarkerVisible:z,listMarkers:L,showPois:O,hidePois:V,updateMarker:_,showScenePresets:W,markReady:j,flushPendingFlies:y,readBookmarkPositions:G,ensureBookmarks:k}}export{w as d,Q as f,te as p,q as r,ne as u};
import{l as u,m as f,p as d,r as p}from"./index-BssDqbym.js";const l="分子实验室三维可视化管理平台",T="jh-lab-screen-title",a=p(l);let s,n=null,i=0;async function r(){try{const t=await d("/api/settings"),e=String(t.screenTitle||"").trim();e&&(a.value=e)}catch{}}function c(){n||typeof BroadcastChannel>"u"||(n=new BroadcastChannel(T),n.onmessage=t=>{var o;const e=String(((o=t.data)==null?void 0:o.screenTitle)||"").trim();e&&(a.value=e)})}function h(t){const e=String(t||"").trim()||l;a.value=e;try{c(),n==null||n.postMessage({screenTitle:e})}catch{}}function v(t){return u(()=>{i+=1,c(),r(),s||(s=window.setInterval(()=>{r()},5e3))}),f(()=>{i=Math.max(0,i-1),i===0&&s&&(window.clearInterval(s),s=void 0)}),{title:a,refresh:r}}export{l as D,h as p,v as u};
......@@ -11,8 +11,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Rajdhani:wght@500;600;700&family=Share+Tech+Mono&display=swap" rel="stylesheet" />
<script type="module" src="https://unpkg.com/@google/model-viewer/dist/model-viewer.min.js"></script>
<script type="module" crossorigin src="/assets/index-Bs_x-_cK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Yzi0d50E.css">
<script type="module" crossorigin src="/assets/index-BssDqbym.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Crx1aGdr.css">
</head>
<body>
<div id="app"></div>
......
......@@ -121,15 +121,14 @@ export async function queryFridgeTempSeries(opts?: {
}): Promise<FridgeTempSeries[]> {
if (!isPgEnabled()) return []
const hours = Math.min(168, Math.max(1, opts?.hours ?? 24))
const bucketMinutes = Math.min(60, Math.max(5, opts?.bucketMinutes ?? 15))
const bucketMinutes = Math.min(60, Math.max(1, opts?.bucketMinutes ?? 60))
try {
const series = await queryTsSeries({
source: 'fridge',
metric: 'temp',
hours,
// 24h 内用 1min 桶,曲线更密
bucketMinutes: hours <= 24 ? Math.min(bucketMinutes, 1) || 1 : bucketMinutes,
bucketMinutes,
})
if (series.length) {
const fridges = await listDevices({ category: 'fridge' })
......
......@@ -362,7 +362,7 @@ export function resolveTsTimeRange(opts: {
}
if (toMs - fromMs > maxMs) {
throw new Error(`查询跨度不能超过 ${maxHistorySpanDays()} 天`)
throw new Error(查询跨度不能超过 天)
}
if (toMs - fromMs < 60_000) {
throw new Error('查询跨度至少 1 分钟')
......@@ -393,7 +393,29 @@ function mapRollupRows(
return [...map.values()]
}
/** 优先读预聚合;无数据时回退原始点桶聚合。支持绝对 from/to 或相对 hours。 */
/** 将更密的点抽成整点小时(UTC 整点 = 中国本地 :00) */
function downsampleToHourly(series: TsSeries[]): TsSeries[] {
const HOUR = 3_600_000
return series.map((s) => {
const byHour = new Map<number, { t: number; sum: number; n: number }>()
for (const p of s.points) {
const key = Math.floor(p.t / HOUR) * HOUR
const hit = byHour.get(key)
if (hit) {
hit.sum += p.v
hit.n += 1
} else {
byHour.set(key, { t: key, sum: p.v, n: 1 })
}
}
const points = [...byHour.values()]
.sort((a, b) => a.t - b.t)
.map((x) => ({ t: x.t, v: x.sum / x.n }))
return { ...s, points }
})
}
/** 优先读预聚合;60min 无桶时从 15/1min 抽整点;支持绝对 from/to 或相对 hours */
export async function queryTsSeries(opts: {
source: string
metric: string
......@@ -406,15 +428,15 @@ export async function queryTsSeries(opts: {
if (!isPgEnabled()) return []
const range = resolveTsTimeRange(opts)
const hours = range.hours
// 短窗口优先 1min 桶(更密);长窗口用请求粒度或 15min
const wantBucket = Math.min(60, Math.max(1, opts.bucketMinutes ?? 15))
const bucket = hours <= 24 && wantBucket <= 5 ? 1 : wantBucket === 1 ? 1 : wantBucket
const wantBucket = Math.min(60, Math.max(1, opts.bucketMinutes ?? 60))
const hourly = wantBucket >= 60
const bucket = hourly ? 60 : hours <= 24 && wantBucket <= 5 ? 1 : wantBucket === 1 ? 1 : wantBucket
const params: unknown[] = [opts.source, opts.metric, range.from, range.to, bucket]
let deviceFilter = ''
if (opts.deviceIds?.length) {
params.push(opts.deviceIds)
deviceFilter = ` AND device_id = ANY($${params.length}::text[])`
deviceFilter = AND device_id = ANY({params.length}::text[])
}
let { rows: rollupRows } = await query<{
......@@ -422,15 +444,43 @@ export async function queryTsSeries(opts: {
bucket: Date
avg_eng: string | number
}>(
`SELECT device_id, bucket, avg_eng
SELECT device_id, bucket, avg_eng
FROM ts_rollups
WHERE source = $1 AND metric = $2 AND bucket_minutes = $5
AND bucket >= $3 AND bucket <= $4
${deviceFilter}
ORDER BY device_id, bucket`,
WHERE source = AND metric = AND bucket_minutes =
AND bucket >= AND bucket <=
ORDER BY device_id, bucket,
params,
)
// 60min 无预聚合:用 15min(再不行 1min)抽整点
if (!rollupRows.length && hourly) {
for (const fb of [15, 1]) {
const fbParams: unknown[] = [opts.source, opts.metric, range.from, range.to, fb]
let fbFilter = ''
if (opts.deviceIds?.length) {
fbParams.push(opts.deviceIds)
fbFilter = AND device_id = ANY({fbParams.length}::text[])
}
const fallback = await query<{
device_id: string
bucket: Date
avg_eng: string | number
}>(
SELECT device_id, bucket, avg_eng
FROM ts_rollups
WHERE source = AND metric = AND bucket_minutes =
AND bucket >= AND bucket <=
ORDER BY device_id, bucket,
fbParams,
)
if (fallback.rows.length) {
return downsampleToHourly(mapRollupRows(fallback.rows))
}
}
}
// 1min 尚无数据时回退 15min
if (!rollupRows.length && bucket === 1) {
const fallback = await query<{
......@@ -438,18 +488,21 @@ export async function queryTsSeries(opts: {
bucket: Date
avg_eng: string | number
}>(
`SELECT device_id, bucket, avg_eng
SELECT device_id, bucket, avg_eng
FROM ts_rollups
WHERE source = $1 AND metric = $2 AND bucket_minutes = 15
AND bucket >= $3 AND bucket <= $4
${deviceFilter}
ORDER BY device_id, bucket`,
WHERE source = AND metric = AND bucket_minutes = 15
AND bucket >= AND bucket <=
ORDER BY device_id, bucket,
params,
)
rollupRows = fallback.rows
}
if (rollupRows.length) return mapRollupRows(rollupRows)
if (rollupRows.length) {
const series = mapRollupRows(rollupRows)
return hourly ? downsampleToHourly(series) : series
}
// 回退:原始点按桶聚合
const { rows } = await query<{
......@@ -457,20 +510,21 @@ export async function queryTsSeries(opts: {
bucket: Date
avg_eng: string | number
}>(
`SELECT
SELECT
device_id,
to_timestamp(floor(extract(epoch FROM ts) / ($5 * 60)) * ($5 * 60)) AS bucket,
to_timestamp(floor(extract(epoch FROM ts) / ( * 60)) * ( * 60)) AS bucket,
AVG(eng) AS avg_eng
FROM ts_samples
WHERE source = $1 AND metric = $2
AND ts >= $3 AND ts <= $4
${deviceFilter}
WHERE source = AND metric =
AND ts >= AND ts <=
GROUP BY device_id, bucket
ORDER BY device_id, bucket`,
ORDER BY device_id, bucket,
params,
)
return mapRollupRows(rows)
const series = mapRollupRows(rows)
return hourly ? downsampleToHourly(series) : series
}
export type TsExportRow = {
......@@ -503,7 +557,7 @@ export async function queryTsExportRows(opts: {
let deviceFilter = ''
if (opts.deviceIds?.length) {
params.push(opts.deviceIds)
deviceFilter = ` AND device_id = ANY($${params.length}::text[])`
deviceFilter = AND device_id = ANY({params.length}::text[])
}
const { rows: raw } = await query<{
......@@ -514,13 +568,13 @@ export async function queryTsExportRows(opts: {
eng: string | number
raw: string | number | null
}>(
`SELECT ts, device_id, source, metric, eng, raw
SELECT ts, device_id, source, metric, eng, raw
FROM ts_samples
WHERE source = $1 AND metric = $2
AND ts >= $3 AND ts <= $4
${deviceFilter}
WHERE source = AND metric =
AND ts >= AND ts <=
ORDER BY device_id, ts
LIMIT $5`,
LIMIT ,
params,
)
......@@ -548,7 +602,7 @@ export async function queryTsExportRows(opts: {
let rollDeviceFilter = ''
if (opts.deviceIds?.length) {
rollParams.push(opts.deviceIds)
rollDeviceFilter = ` AND device_id = ANY($${rollParams.length}::text[])`
rollDeviceFilter = AND device_id = ANY({rollParams.length}::text[])
}
rollParams.push(EXPORT_ROW_LIMIT + 1)
const limitIdx = rollParams.length
......@@ -559,13 +613,13 @@ export async function queryTsExportRows(opts: {
metric: string
avg_eng: string | number
}>(
`SELECT bucket, device_id, source, metric, avg_eng
SELECT bucket, device_id, source, metric, avg_eng
FROM ts_rollups
WHERE source = $1 AND metric = $2 AND bucket_minutes = $5
AND bucket >= $3 AND bucket <= $4
${rollDeviceFilter}
WHERE source = AND metric = AND bucket_minutes =
AND bucket >= AND bucket <=
ORDER BY device_id, bucket
LIMIT $${limitIdx}`,
LIMIT {limitIdx},
rollParams,
)
if (!roll.length) continue
......@@ -684,7 +738,7 @@ export async function queryNamedTsSeries(
opts?: { hours?: number; bucketMinutes?: number },
): Promise<Record<string, TsSeries[]>> {
const hours = opts?.hours ?? 24
const bucketMinutes = opts?.bucketMinutes ?? 15
const bucketMinutes = opts?.bucketMinutes ?? 60
const out: Record<string, TsSeries[]> = {}
await Promise.all(
queries.map(async (q) => {
......
......@@ -13,7 +13,7 @@ import type {
import { NE_LIGHT_DEFS } from './neLights.js'
/** 与前端 PROTOCOL_ROOMS / 场景书签对齐的稳定房间 id */
const ROOM_ID_BY_NAME: Record<string, string> = {
export const ROOM_ID_BY_NAME: Record<string, string> = {
收样咨询室: 'recv',
生信分析报告室: 'report',
试剂准备间: 'reagent',
......@@ -30,6 +30,13 @@ const ROOM_ID_BY_NAME: Record<string, string> = {
测序区缓冲: 'seq-buf',
}
/** 时序 deviceId(如 lib-1-buf)→ 中文房间名,供曲线图例/提示使用 */
export const ROOM_NAME_BY_ID: Record<string, string> = Object.fromEntries(
Object.entries(ROOM_ID_BY_NAME)
.filter(([name]) => name !== 'NGS走廊')
.map(([name, id]) => [id, name]),
)
function stableRoomId(d: Device): string {
if (ROOM_ID_BY_NAME[d.name]) return ROOM_ID_BY_NAME[d.name]
for (const [name, id] of Object.entries(ROOM_ID_BY_NAME)) {
......
......@@ -9,6 +9,7 @@ import {
type TsSeries,
} from '../db/timeseries.js'
import { buildExcelXml, formatExportTs } from './excelXml.js'
import { ROOM_NAME_BY_ID } from './fromDevices.js'
const METRIC_LABEL: Record<string, string> = {
temp: '温度',
......@@ -45,6 +46,8 @@ export function sourceLabel(source: string) {
async function deviceNameMap() {
const devices = await listDevices()
const map = new Map<string, string>()
// 房间时序 id(lib-1 / sample-1 …)优先挂中文名,避免图例显示英文编号
for (const [id, name] of Object.entries(ROOM_NAME_BY_ID)) map.set(id, name)
for (const d of devices) map.set(d.id, d.name)
map.set('PAU201', 'PAU-201 机组')
map.set('lab', '实验室汇总')
......@@ -89,7 +92,7 @@ export async function getHistorySeries(opts: {
series: TsSeries[]
}> {
const range = resolveTsTimeRange({ hours: opts.hours, from: opts.from, to: opts.to })
const bucketMinutes = opts.bucketMinutes ?? 15
const bucketMinutes = opts.bucketMinutes ?? 60
const raw = await queryTsSeries({
source: opts.source,
metric: opts.metric,
......
......@@ -118,7 +118,7 @@ export async function buildScreenDashboard(): Promise<ScreenDashboard> {
listDevices(),
listPoints(),
listGateways(),
queryFridgeTempSeries({ hours: 24, bucketMinutes: 15 }).catch(() => [] as FridgeTempSeries[]),
queryFridgeTempSeries({ hours: 24, bucketMinutes: 60 }).catch(() => [] as FridgeTempSeries[]),
resolveGatewayBase()
? fetchGatewayHealth().catch(() => ({ ok: false as boolean }))
: Promise.resolve({ ok: false as boolean }),
......@@ -140,7 +140,7 @@ export async function buildScreenDashboard(): Promise<ScreenDashboard> {
{ key: 'meteringWater', source: 'metering', metric: 'water_m3' },
{ key: 'meteringCooling', source: 'metering', metric: 'cooling_kwh' },
],
{ hours: 24, bucketMinutes: 1 },
{ hours: 24, bucketMinutes: 60 },
).catch(() => ({}) as Record<string, TsSeries[]>),
])
......
......@@ -45,7 +45,7 @@ realtimeRouter.get('/history', async (req, res) => {
const hours = req.query.hours != null && req.query.hours !== '' ? Number(req.query.hours) : undefined
const from = req.query.from ? String(req.query.from) : undefined
const to = req.query.to ? String(req.query.to) : undefined
const bucketMinutes = Number(req.query.bucketMinutes) || 15
const bucketMinutes = Number(req.query.bucketMinutes) || 60
const deviceId = req.query.deviceId ? String(req.query.deviceId) : undefined
try {
const data = await getHistorySeries({ source, metric, hours, from, to, bucketMinutes, deviceId })
......@@ -98,7 +98,7 @@ realtimeRouter.get('/history/export', async (req, res) => {
/** 各冰箱近 24h 温度曲线(采集落库聚合) */
realtimeRouter.get('/fridges/temp-history', async (req, res) => {
const hours = Number(req.query.hours) || 24
const bucketMinutes = Number(req.query.bucketMinutes) || 15
const bucketMinutes = Number(req.query.bucketMinutes) || 60
try {
const series = await queryFridgeTempSeries({ hours, bucketMinutes })
res.json({ hours, bucketMinutes, series })
......
......@@ -40,7 +40,7 @@ async function loadHistory() {
loading.value = true
error.value = ''
try {
const res = await api<{ series: HistSeries[] }>('/api/realtime/fridges/temp-history?hours=24&bucketMinutes=15')
const res = await api<{ series: HistSeries[] }>('/api/realtime/fridges/temp-history?hours=24&bucketMinutes=60')
const next: Record<string, HistSeries> = {}
for (const s of res.series || []) next[s.id] = s
seriesMap.value = next
......
......@@ -15,7 +15,6 @@ import AppIcon from '@/components/icons/AppIcon.vue'
import MarkerNearDialog from '@/components/scene/MarkerNearDialog.vue'
import { api } from '@/api/http'
import {
MARKER_LEADER_HEIGHT_M,
preloadRtengineIcons,
useRtengineBridge,
type SceneMarkerHit,
......@@ -377,29 +376,11 @@ async function onLightControl(on: boolean) {
}
}
function cameraMarkerList() {
return cameras.value
.filter((d) => d.enabled && d.scenePoint)
.map((d) => ({
id: d.id,
name: d.name,
text: d.name,
x: d.scenePoint!.x,
y: d.scenePoint!.y,
z: d.scenePoint!.z,
icon: d.scenePoint!.icon || 'cctv-blue.png',
tag0: 'camera',
tag1: d.id,
leader: true,
leaderHeight: MARKER_LEADER_HEIGHT_M,
leaderThickness: 2,
leaderColor: '#00f7ff',
}))
}
/** 引擎就绪后:预加载图标 → 显示 POI → 摄像头 Marker(只同步一次) */
/** 场景书签「全景总览」= 模型默认初始视角 */
const OVERVIEW_BOOKMARK = '全景总览'
/** 引擎就绪后:预加载图标 → 按分类同步设施 POI */
/** 底栏「全景总览」飞到场景书签「主视图」 */
const OVERVIEW_BOOKMARK = '主视图'
/** 引擎就绪默认视角(与底栏主视图可不同) */
const DEFAULT_BOOKMARK = '全景总览'
async function onEngineReady() {
if (!useCloudFrame.value || engineReadyDone || engineBootstrapping) return
......@@ -412,8 +393,8 @@ async function onEngineReady() {
syncCameraMarkers(true)
applyCameraVisibility()
await syncModePois()
// 视角飞到「全景总览」默认相机,图标仍只留压差
flyToViewpoint(OVERVIEW_BOOKMARK)
// 就绪后飞默认相机;底栏「全景总览」另飞「主视图」
flyToViewpoint(DEFAULT_BOOKMARK)
// 引擎稍晚才 hydrate marker 列表,再补两次压差过滤
window.setTimeout(() => {
if (!markersSynced) syncCameraMarkers(true)
......@@ -431,27 +412,28 @@ async function onEngineReady() {
}
}
function cameraIconsVisible() {
return activeMode.value === 'home' || activeMode.value === 'camera'
}
/** 监控图标已改由设施 sensors(useScreenModePois)按分类显示,视频台账点仅用于点击预览匹配,不再向场景打点 */
function applyCameraVisibility() {
const show = cameraIconsVisible()
for (const d of cameras.value) {
if (!d.scenePoint) continue
bridge.setMarkerVisible(d.id, show)
bridge.setMarkerVisible(d.id, false)
}
}
function syncCameraMarkers(force = false) {
if (!useCloudFrame.value) return
if (markersSynced && !force) return
const list = cameraMarkerList()
if (!list.length) return
// 批量一次写入即可(逐条 + 批量会触发多次场景刷新)
bridge.addMarkers(list)
// 卸掉历史视频台账 Marker,避免与设施摄像头重复
for (const d of cameras.value) {
if (!d.scenePoint) continue
try {
bridge.setMarkerVisible(d.id, false)
bridge.removeMarker(d.id)
} catch {
/* ignore */
}
}
markersSynced = true
applyCameraVisibility()
}
function normalizeCamKey(v: unknown): string {
......@@ -698,8 +680,8 @@ function onSelectArea(areaId: string) {
}
function onSelectOverview() {
// 只飞到主视图书签,不改点位显隐(保持当前分类图标)
activeAreaId.value = OVERVIEW_AREA_ID
screenMode.setMode('home')
if (useCloudFrame.value) flyToViewpoint(OVERVIEW_BOOKMARK)
}
......
......@@ -131,7 +131,7 @@ function previewMarker(): RtengineMarker | null {
z: p.z,
tag0: 'pick-preview',
tag1: PREVIEW_ID,
scale: 1,
scale: 0.5,
icon: textOnly ? '' : (selectedIcon.value || '').trim(),
leader: true,
leaderHeight: MARKER_LEADER_HEIGHT_M,
......
......@@ -21,7 +21,7 @@ export function useMetricHistory(
source: q.source,
metric: q.metric,
hours: String(q.hours ?? 24),
bucketMinutes: String(q.bucketMinutes ?? 15),
bucketMinutes: String(q.bucketMinutes ?? 60),
})
const res = await api<{ series: HistorySeries[] }>(`/api/realtime/history?${params}`)
series.value = res.series || []
......
......@@ -122,8 +122,16 @@ const FACILITY_ICON_FALLBACK: Record<string, string> = {
access: 'access-control-blue.png',
}
function toSensorMarker(s: SensorSnapshot): RtengineMarker {
/** 引擎常见监控图标;cctv.png 若不存在则回落 cctv-blue */
function resolveFacilityIcon(s: SensorSnapshot): string {
const fallback = FACILITY_ICON_FALLBACK[s.category] || MODE_MARKER_ICON.pressure
const raw = (s.icon || '').trim()
if (!raw) return fallback
if (s.category === 'camera' && raw === 'cctv.png') return 'cctv-blue.png'
return raw
}
function toSensorMarker(s: SensorSnapshot): RtengineMarker {
const tag0 =
s.category in DEVICE_TAG
? DEVICE_TAG[s.category as keyof typeof DEVICE_TAG]
......@@ -143,7 +151,7 @@ function toSensorMarker(s: SensorSnapshot): RtengineMarker {
tag1: s.id,
scale: MODE_MARKER_SCALE,
// 压差:只显示温湿度压差文字,不挂图标
...(textOnly ? { icon: '' } : { icon: (s.icon || '').trim() || fallback }),
...(textOnly ? { icon: '' } : { icon: resolveFacilityIcon(s) }),
...MARKER_LEADER,
}
}
......@@ -172,15 +180,15 @@ function markersForMode(mode: ScreenMode, snap: RealtimeSnapshot): RtengineMarke
if (mode === 'vav') {
return sensorsOf(snap, 'vav').map(toSensorMarker)
}
// 监控点由 SceneViewport 视频台账打点,避免与设施重复
// 监控与门禁一致:走设施快照(含三维坐标/图标)
if (mode === 'camera') {
return []
return sensorsOf(snap, 'camera').map(toSensorMarker)
}
if (mode === 'access') {
return sensorsOf(snap, 'access').map(toSensorMarker)
}
if (mode === 'home') {
return sensorsOf(snap, 'pressure', 'lightN', 'lightE', 'fridge', 'vav', 'access').map(
return sensorsOf(snap, 'pressure', 'lightN', 'lightE', 'fridge', 'vav', 'access', 'camera').map(
toSensorMarker,
)
}
......@@ -228,7 +236,7 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
/** 当前分类应显示的 tag0 集合 */
function tagsForMode(current: ScreenMode): Set<string> {
if (current === 'home') {
return new Set(ALL_DEVICE_TAGS.filter((t) => t !== 'camera'))
return new Set(ALL_DEVICE_TAGS)
}
if (current === 'e066') return new Set()
if (current in DEVICE_TAG) {
......@@ -241,7 +249,7 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
return [...showTags].sort().join(',')
}
/** 引擎预设 / CAD 点:按分类显隐(摄像头留给 SceneViewport) */
/** 引擎预设 / CAD 点:按分类显隐(含监控 tag) */
function applyCadVisibility(current: ScreenMode, force = false) {
const showTags = tagsForMode(current)
const sig = tagVisSig(showTags)
......@@ -254,7 +262,6 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
// 仅分类切换时才对引擎做 tag 级 show/hide(整屏刷新很重)
if (force || !modeSame || !tagsSame) {
for (const tag of ALL_DEVICE_TAGS) {
if (tag === 'camera') continue
if (showTags.has(tag)) bridge.showPois(tag)
else bridge.hidePois(tag)
}
......@@ -263,9 +270,9 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
const live = bridge.listMarkers?.() || []
for (const marker of live) {
if (marker.id.startsWith(PREFIX)) continue
if (marker.id.startsWith('cam:') || marker.tag0 === 'camera') continue
if (marker.id.startsWith(SENSOR_PREFIX)) continue
const cat = categoryOfMarker(marker)
if (!cat || cat === 'camera') continue
if (!cat) continue
const show = current === 'home' || showTags.has(cat) || showTags.has(marker.tag0)
bridge.setMarkerVisible(marker.id, show)
}
......@@ -354,7 +361,6 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
lastCadMode = ''
lastTagVisSig = ''
for (const tag of ALL_DEVICE_TAGS) {
if (tag === 'camera') continue
bridge.hidePois(tag)
}
} else {
......
......@@ -31,7 +31,7 @@ export const DEVICE_TAG: Record<DeviceCategory, string> = {
access: 'access',
}
export const MODE_MARKER_SCALE = 1
export const MODE_MARKER_SCALE = 0.5
export const SCREEN_MODES: { id: ScreenMode; label: string }[] = [
{ id: 'home', label: '全景总览' },
......
......@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} OK',
deviceLedger: 'Device ledger',
envEmpty: 'Room T/H pending',
roomHumidityEmpty: 'Room humidity history accumulating',
roomTempEmpty: 'Room temp history accumulating',
coldTitle: 'Cold chain · 24h',
coldEmpty: 'Fridge temp history accumulating',
......
......@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} OK',
deviceLedger: 'Inventário',
envEmpty: 'T/H das salas pendente',
roomHumidityEmpty: 'Histórico de humidade a acumular',
roomTempEmpty: 'Histórico de temp. a acumular',
coldTitle: 'Cadeia de frio · 24h',
coldEmpty: 'Histórico de frigoríficos a acumular',
......
......@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} 合格',
deviceLedger: '设备台账',
envEmpty: '房间温湿度待采集',
roomHumidityEmpty: '房间湿度历史积累中',
roomTempEmpty: '房间温度历史积累中',
coldTitle: '冷链监测 · 24h',
coldEmpty: '冰箱温度历史积累中',
......
......@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} 合格',
deviceLedger: '設備台帳',
envEmpty: '房間溫濕度待採集',
roomHumidityEmpty: '房間濕度歷史累積中',
roomTempEmpty: '房間溫度歷史累積中',
coldTitle: '冷鏈監測 · 24h',
coldEmpty: '冰箱溫度歷史累積中',
......
......@@ -22,7 +22,10 @@ const chartMuted = 'rgba(158,200,220,0.35)'
function formatTick(t: number) {
const d = new Date(t)
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
const hh = String(d.getHours()).padStart(2, '0')
const mm = d.getMinutes()
// 整点只显示小时,避免轴标签过密
return mm === 0 ? `${hh}:00` : `${hh}:${String(mm).padStart(2, '0')}`
}
/** 多设备历史折线(统一时间轴) */
......
......@@ -21,10 +21,10 @@ async function load() {
try {
const [tempRes, humidityRes] = await Promise.all([
api<{ series: typeof series.value }>(
'/api/realtime/history?source=fridge&metric=temp&hours=24&bucketMinutes=15',
'/api/realtime/history?source=fridge&metric=temp&hours=24&bucketMinutes=60',
),
api<{ series: typeof humidity.value }>(
'/api/realtime/history?source=fridge&metric=humidity&hours=24&bucketMinutes=15',
'/api/realtime/history?source=fridge&metric=humidity&hours=24&bucketMinutes=60',
),
])
series.value = tempRes.series || []
......
......@@ -7,7 +7,7 @@ import EChart, { type ECOption } from '@/components/charts/EChart.vue'
import { useScreenDashboard } from '@/composables/useScreenDashboard'
import { useRealtimeStore } from '@/stores/realtime'
import { multiLineOption, toHistorySeries } from '@/utils/historyChart'
import { compactTooltipBase, pieTooltip, roundNum } from '@/utils/chartTooltip'
import { pieTooltip } from '@/utils/chartTooltip'
const { t, te } = useI18n()
const realtime = useRealtimeStore()
......@@ -66,7 +66,6 @@ const kpiItems = computed<KpiFlipItem[]>(() => {
})
const chartText = '#9ec8dc'
const chartMuted = 'rgba(158,200,220,0.35)'
function statusLabel(key: string | undefined, fallback: string) {
if (key && te(`status.${key}`)) return t(`status.${key}`)
......@@ -89,75 +88,13 @@ function pauLabel(key: string | undefined, fallback: string) {
return fallback
}
/** 左1:环境气泡图 温度×湿度,大小=|压差| */
const envBubbleOption = computed<ECOption>(() => {
const rows = (data.value?.envBubbles || []).filter(
(b) => b.temp !== null && b.humidity !== null,
)
return {
backgroundColor: 'transparent',
animation: false,
animationDurationUpdate: 0,
grid: { left: 36, right: 12, top: 28, bottom: 28 },
tooltip: {
...compactTooltipBase,
trigger: 'item',
formatter: (p: unknown) => {
const x = p as {
data?: { name?: string; value?: number[]; pressure?: number | null }
}
const d = x.data
if (!d?.value) return ''
return (
`<div style="font-size:11px;color:#e8fbff;font-weight:600">${d.name ?? ''}</div>` +
`<div style="font-size:10px;margin-top:2px">${t('overview.tipTemp', {
temp: roundNum(d.value[0]),
humidity: roundNum(d.value[1]),
})}</div>` +
`<div style="font-size:10px">${t('overview.tipPressure', {
pressure: roundNum(d.pressure),
})}</div>`
)
},
},
xAxis: {
name: '℃',
nameTextStyle: { color: chartMuted, fontSize: 10 },
axisLabel: { color: chartText, fontSize: 10 },
splitLine: { lineStyle: { color: 'rgba(0,247,255,0.08)' } },
axisLine: { lineStyle: { color: chartMuted } },
},
yAxis: {
name: '%RH',
nameTextStyle: { color: chartMuted, fontSize: 10 },
axisLabel: { color: chartText, fontSize: 10 },
splitLine: { lineStyle: { color: 'rgba(0,247,255,0.08)' } },
axisLine: { lineStyle: { color: chartMuted } },
},
series: [
{
type: 'scatter',
symbolSize: (val: number[]) =>
Math.max(10, Math.min(36, Math.abs(Number(val[2]) || 0) * 1.2 + 10)),
data: rows.map((b) => ({
value: [
Math.round(b.temp! * 10) / 10,
Math.round(b.humidity! * 10) / 10,
Math.round(Math.abs(b.pressure ?? 0) * 10) / 10,
],
name: b.name,
pressure: b.pressure == null ? null : Math.round(b.pressure * 10) / 10,
itemStyle: {
color: b.ok ? 'rgba(82,196,26,0.85)' : 'rgba(250,173,20,0.9)',
},
})),
},
],
}
})
const curves = computed(() => data.value?.curves)
/** 环境总览上图:仅湿度历史曲线 */
const roomHumidityHistoryOption = computed<ECOption>(() =>
multiLineOption(curves.value?.roomHumidity || [], { unit: '%RH', maxSeries: 10 }),
)
const roomTempHistoryOption = computed<ECOption>(() =>
multiLineOption(curves.value?.roomTemp || [], { unit: '℃', maxSeries: 10 }),
)
......@@ -305,7 +242,11 @@ const energyDigits = computed(() => digits.value)
</span>
</template>
<div class="panel-stack">
<EChart class="chart-main" :option="envBubbleOption" :empty-text="t('overview.envEmpty')" />
<EChart
class="chart-main"
:option="roomHumidityHistoryOption"
:empty-text="t('overview.roomHumidityEmpty')"
/>
<EChart
class="chart-sub"
:option="roomTempHistoryOption"
......@@ -532,6 +473,7 @@ const energyDigits = computed(() => digits.value)
border-radius: 4px;
background: rgba(6, 28, 56, 0.45);
border: 1px solid rgba(0, 247, 255, 0.14);
text-align: center;
}
.digit-card.warn {
border-color: rgba(250, 173, 20, 0.4);
......@@ -550,7 +492,7 @@ const energyDigits = computed(() => digits.value)
}
.d-value small {
font-size: 10px;
color: rgba(200, 230, 245, 0.65);
color: #52c41a;
font-weight: 500;
}
.energy-digits {
......
......@@ -147,6 +147,29 @@ function pickPreferredIcons(s: Sensor | null) {
return SENSOR_PREFERRED_ICONS[s.category] || Object.values(FACILITY_DEFAULT_ICON)
}
/** 摄像头/门禁通信参数(对齐交底表:IP通道地址、协议、管理端口、用户名、密码) */
type CommForm = {
ip: string
protocol: string
port: string
username: string
password: string
}
function emptyComm(): CommForm {
return { ip: '', protocol: '海康威视', port: '8000', username: 'admin', password: '' }
}
function needsComm(category: string) {
return category === 'camera' || category === 'access'
}
function metaStr(meta: Record<string, unknown> | undefined, key: string, fallback = '') {
const v = meta?.[key]
if (v == null) return fallback
return String(v)
}
const form = reactive({
code: '',
name: '',
......@@ -165,6 +188,7 @@ const form = reactive({
airflow: '',
alarmCode: '',
} as Record<string, string>,
comm: emptyComm(),
})
function matchKeyword(parts: Array<string | number | null | undefined>, q: string) {
......@@ -249,6 +273,7 @@ function openCreate() {
airflow: '',
alarmCode: '',
}
form.comm = emptyComm()
error.value = ''
showEdit.value = true
}
......@@ -276,10 +301,37 @@ function openEdit(s: Sensor) {
for (const b of s.points || []) {
form.roles[b.role] = b.pointId
}
const meta = s.meta || {}
form.comm = {
ip: metaStr(meta, 'ip'),
protocol: metaStr(meta, 'protocol', '海康威视'),
port: metaStr(meta, 'port', '8000'),
username: metaStr(meta, 'username', 'admin'),
password: metaStr(meta, 'password'),
}
error.value = ''
showEdit.value = true
}
function buildMetaPayload(existing?: Record<string, unknown>) {
const meta: Record<string, unknown> = { ...(existing || {}) }
if (!needsComm(form.category)) {
delete meta.ip
delete meta.protocol
delete meta.port
delete meta.username
delete meta.password
return meta
}
meta.ip = form.comm.ip.trim()
meta.protocol = form.comm.protocol.trim() || '海康威视'
const portNum = Number(form.comm.port)
meta.port = Number.isFinite(portNum) && portNum > 0 ? portNum : 8000
meta.username = form.comm.username.trim()
meta.password = form.comm.password
return meta
}
function closeEdit() {
showEdit.value = false
error.value = ''
......@@ -303,6 +355,9 @@ async function save() {
saving.value = true
error.value = ''
message.value = ''
const current = editingId.value
? sensors.value.find((s) => s.id === editingId.value)
: undefined
const body = {
code,
name,
......@@ -312,6 +367,7 @@ async function save() {
sceneY: Number(form.sceneY),
sceneZ: Number(form.sceneZ),
points: buildPointsPayload(),
meta: buildMetaPayload(current?.meta),
}
try {
if (editingId.value) {
......@@ -411,7 +467,7 @@ onMounted(load)
</div>
<p class="desc">
设施含压差、照明 N/E 电、冰箱、通风柜、摄像头、门禁(后四类与交底表对齐)。摄像头/门禁无采集点绑定,可通过「三维打点」调整坐标与图标。
设施含压差、照明 N/E 电、冰箱、通风柜、摄像头、门禁(后四类与交底表对齐)。摄像头/门禁可编辑通信参数(IP、协议、管理端口、用户名、密码),并通过「三维打点」调整坐标与图标。
</p>
<div class="filters">
......@@ -449,7 +505,7 @@ onMounted(load)
<th>分类</th>
<th>启用</th>
<th>坐标</th>
<th>绑定点位</th>
<th>绑定点位 / 通信</th>
<th>操作</th>
</tr>
</thead>
......@@ -471,7 +527,14 @@ onMounted(load)
<td class="mono">
{{ s.sceneX.toFixed(6) }}, {{ s.sceneY.toFixed(6) }}, {{ s.sceneZ }}
</td>
<td class="binds">{{ bindsSummary(s) }}</td>
<td class="binds">
<template v-if="needsComm(s.category)">
{{ metaStr(s.meta, 'ip') || '未配置 IP' }}
<span v-if="metaStr(s.meta, 'port')"> · :{{ metaStr(s.meta, 'port') }}</span>
<span v-if="metaStr(s.meta, 'protocol')"> · {{ metaStr(s.meta, 'protocol') }}</span>
</template>
<template v-else>{{ bindsSummary(s) }}</template>
</td>
<td class="ops">
<button
v-if="auth.isAdmin"
......@@ -556,18 +619,58 @@ onMounted(load)
</div>
</div>
<h4 class="sub">点位绑定</h4>
<div class="form-grid roles">
<div v-for="role in rolesForCategory(form.category)" :key="role" class="field">
<label>{{ ROLE_LABEL[role] || role }}</label>
<select v-model="form.roles[role]">
<option value="">— 未选择 —</option>
<option v-for="p in pointOptions" :key="p.id" :value="p.id">
{{ pointLabel(p) }}
</option>
</select>
<template v-if="needsComm(form.category)">
<h4 class="sub">通信参数</h4>
<p class="comm-hint">参考交底表:IP 通道地址、协议、管理端口、用户名、密码。</p>
<div class="form-grid">
<div class="field">
<label>IP 通道地址</label>
<input v-model="form.comm.ip" type="text" placeholder="192.168.10.150" autocomplete="off" />
</div>
<div class="field">
<label>协议</label>
<input v-model="form.comm.protocol" type="text" placeholder="海康威视" list="comm-protocol-list" />
<datalist id="comm-protocol-list">
<option value="海康威视" />
<option value="sdk" />
<option value="rtsp" />
<option value="isapi" />
</datalist>
</div>
<div class="field">
<label>管理端口</label>
<input v-model="form.comm.port" type="text" placeholder="8000" inputmode="numeric" />
</div>
<div class="field">
<label>用户名</label>
<input v-model="form.comm.username" type="text" placeholder="admin" autocomplete="username" />
</div>
<div class="field">
<label>密码</label>
<input
v-model="form.comm.password"
type="password"
placeholder="设备登录密码"
autocomplete="new-password"
/>
</div>
</div>
</div>
</template>
<template v-else>
<h4 class="sub">点位绑定</h4>
<div class="form-grid roles">
<div v-for="role in rolesForCategory(form.category)" :key="role" class="field">
<label>{{ ROLE_LABEL[role] || role }}</label>
<select v-model="form.roles[role]">
<option value="">— 未选择 —</option>
<option v-for="p in pointOptions" :key="p.id" :value="p.id">
{{ pointLabel(p) }}
</option>
</select>
</div>
</div>
</template>
<p v-if="error" class="msg err">{{ error }}</p>
<div class="modal-actions">
......@@ -731,6 +834,12 @@ onMounted(load)
font-size: 13px;
color: #eaf4ff;
}
.comm-hint {
margin: -4px 0 10px;
font-size: 12px;
color: var(--panel-muted);
line-height: 1.45;
}
.modal-actions {
margin-top: 14px;
display: flex;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment