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 @@ ...@@ -11,8 +11,8 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <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" /> <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" 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> <script type="module" crossorigin src="/assets/index-BssDqbym.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Yzi0d50E.css"> <link rel="stylesheet" crossorigin href="/assets/index-Crx1aGdr.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
......
...@@ -121,15 +121,14 @@ export async function queryFridgeTempSeries(opts?: { ...@@ -121,15 +121,14 @@ export async function queryFridgeTempSeries(opts?: {
}): Promise<FridgeTempSeries[]> { }): Promise<FridgeTempSeries[]> {
if (!isPgEnabled()) return [] if (!isPgEnabled()) return []
const hours = Math.min(168, Math.max(1, opts?.hours ?? 24)) 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 { try {
const series = await queryTsSeries({ const series = await queryTsSeries({
source: 'fridge', source: 'fridge',
metric: 'temp', metric: 'temp',
hours, hours,
// 24h 内用 1min 桶,曲线更密 bucketMinutes,
bucketMinutes: hours <= 24 ? Math.min(bucketMinutes, 1) || 1 : bucketMinutes,
}) })
if (series.length) { if (series.length) {
const fridges = await listDevices({ category: 'fridge' }) const fridges = await listDevices({ category: 'fridge' })
......
...@@ -362,7 +362,7 @@ export function resolveTsTimeRange(opts: { ...@@ -362,7 +362,7 @@ export function resolveTsTimeRange(opts: {
} }
if (toMs - fromMs > maxMs) { if (toMs - fromMs > maxMs) {
throw new Error(`查询跨度不能超过 ${maxHistorySpanDays()} 天`) throw new Error(查询跨度不能超过 天)
} }
if (toMs - fromMs < 60_000) { if (toMs - fromMs < 60_000) {
throw new Error('查询跨度至少 1 分钟') throw new Error('查询跨度至少 1 分钟')
...@@ -393,7 +393,29 @@ function mapRollupRows( ...@@ -393,7 +393,29 @@ function mapRollupRows(
return [...map.values()] 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: { export async function queryTsSeries(opts: {
source: string source: string
metric: string metric: string
...@@ -406,15 +428,15 @@ export async function queryTsSeries(opts: { ...@@ -406,15 +428,15 @@ export async function queryTsSeries(opts: {
if (!isPgEnabled()) return [] if (!isPgEnabled()) return []
const range = resolveTsTimeRange(opts) const range = resolveTsTimeRange(opts)
const hours = range.hours const hours = range.hours
// 短窗口优先 1min 桶(更密);长窗口用请求粒度或 15min const wantBucket = Math.min(60, Math.max(1, opts.bucketMinutes ?? 60))
const wantBucket = Math.min(60, Math.max(1, opts.bucketMinutes ?? 15)) const hourly = wantBucket >= 60
const bucket = hours <= 24 && wantBucket <= 5 ? 1 : wantBucket === 1 ? 1 : wantBucket 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] const params: unknown[] = [opts.source, opts.metric, range.from, range.to, bucket]
let deviceFilter = '' let deviceFilter = ''
if (opts.deviceIds?.length) { if (opts.deviceIds?.length) {
params.push(opts.deviceIds) 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<{ let { rows: rollupRows } = await query<{
...@@ -422,15 +444,43 @@ export async function queryTsSeries(opts: { ...@@ -422,15 +444,43 @@ export async function queryTsSeries(opts: {
bucket: Date bucket: Date
avg_eng: string | number avg_eng: string | number
}>( }>(
`SELECT device_id, bucket, avg_eng SELECT device_id, bucket, avg_eng
FROM ts_rollups FROM ts_rollups
WHERE source = $1 AND metric = $2 AND bucket_minutes = $5 WHERE source = AND metric = AND bucket_minutes =
AND bucket >= $3 AND bucket <= $4 AND bucket >= AND bucket <=
${deviceFilter}
ORDER BY device_id, bucket`, ORDER BY device_id, bucket,
params, 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 // 1min 尚无数据时回退 15min
if (!rollupRows.length && bucket === 1) { if (!rollupRows.length && bucket === 1) {
const fallback = await query<{ const fallback = await query<{
...@@ -438,18 +488,21 @@ export async function queryTsSeries(opts: { ...@@ -438,18 +488,21 @@ export async function queryTsSeries(opts: {
bucket: Date bucket: Date
avg_eng: string | number avg_eng: string | number
}>( }>(
`SELECT device_id, bucket, avg_eng SELECT device_id, bucket, avg_eng
FROM ts_rollups FROM ts_rollups
WHERE source = $1 AND metric = $2 AND bucket_minutes = 15 WHERE source = AND metric = AND bucket_minutes = 15
AND bucket >= $3 AND bucket <= $4 AND bucket >= AND bucket <=
${deviceFilter}
ORDER BY device_id, bucket`, ORDER BY device_id, bucket,
params, params,
) )
rollupRows = fallback.rows 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<{ const { rows } = await query<{
...@@ -457,20 +510,21 @@ export async function queryTsSeries(opts: { ...@@ -457,20 +510,21 @@ export async function queryTsSeries(opts: {
bucket: Date bucket: Date
avg_eng: string | number avg_eng: string | number
}>( }>(
`SELECT SELECT
device_id, 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 AVG(eng) AS avg_eng
FROM ts_samples FROM ts_samples
WHERE source = $1 AND metric = $2 WHERE source = AND metric =
AND ts >= $3 AND ts <= $4 AND ts >= AND ts <=
${deviceFilter}
GROUP BY device_id, bucket GROUP BY device_id, bucket
ORDER BY device_id, bucket`, ORDER BY device_id, bucket,
params, params,
) )
return mapRollupRows(rows) const series = mapRollupRows(rows)
return hourly ? downsampleToHourly(series) : series
} }
export type TsExportRow = { export type TsExportRow = {
...@@ -503,7 +557,7 @@ export async function queryTsExportRows(opts: { ...@@ -503,7 +557,7 @@ export async function queryTsExportRows(opts: {
let deviceFilter = '' let deviceFilter = ''
if (opts.deviceIds?.length) { if (opts.deviceIds?.length) {
params.push(opts.deviceIds) 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<{ const { rows: raw } = await query<{
...@@ -514,13 +568,13 @@ export async function queryTsExportRows(opts: { ...@@ -514,13 +568,13 @@ export async function queryTsExportRows(opts: {
eng: string | number eng: string | number
raw: string | number | null raw: string | number | null
}>( }>(
`SELECT ts, device_id, source, metric, eng, raw SELECT ts, device_id, source, metric, eng, raw
FROM ts_samples FROM ts_samples
WHERE source = $1 AND metric = $2 WHERE source = AND metric =
AND ts >= $3 AND ts <= $4 AND ts >= AND ts <=
${deviceFilter}
ORDER BY device_id, ts ORDER BY device_id, ts
LIMIT $5`, LIMIT ,
params, params,
) )
...@@ -548,7 +602,7 @@ export async function queryTsExportRows(opts: { ...@@ -548,7 +602,7 @@ export async function queryTsExportRows(opts: {
let rollDeviceFilter = '' let rollDeviceFilter = ''
if (opts.deviceIds?.length) { if (opts.deviceIds?.length) {
rollParams.push(opts.deviceIds) 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) rollParams.push(EXPORT_ROW_LIMIT + 1)
const limitIdx = rollParams.length const limitIdx = rollParams.length
...@@ -559,13 +613,13 @@ export async function queryTsExportRows(opts: { ...@@ -559,13 +613,13 @@ export async function queryTsExportRows(opts: {
metric: string metric: string
avg_eng: string | number avg_eng: string | number
}>( }>(
`SELECT bucket, device_id, source, metric, avg_eng SELECT bucket, device_id, source, metric, avg_eng
FROM ts_rollups FROM ts_rollups
WHERE source = $1 AND metric = $2 AND bucket_minutes = $5 WHERE source = AND metric = AND bucket_minutes =
AND bucket >= $3 AND bucket <= $4 AND bucket >= AND bucket <=
${rollDeviceFilter}
ORDER BY device_id, bucket ORDER BY device_id, bucket
LIMIT $${limitIdx}`, LIMIT {limitIdx},
rollParams, rollParams,
) )
if (!roll.length) continue if (!roll.length) continue
...@@ -684,7 +738,7 @@ export async function queryNamedTsSeries( ...@@ -684,7 +738,7 @@ export async function queryNamedTsSeries(
opts?: { hours?: number; bucketMinutes?: number }, opts?: { hours?: number; bucketMinutes?: number },
): Promise<Record<string, TsSeries[]>> { ): Promise<Record<string, TsSeries[]>> {
const hours = opts?.hours ?? 24 const hours = opts?.hours ?? 24
const bucketMinutes = opts?.bucketMinutes ?? 15 const bucketMinutes = opts?.bucketMinutes ?? 60
const out: Record<string, TsSeries[]> = {} const out: Record<string, TsSeries[]> = {}
await Promise.all( await Promise.all(
queries.map(async (q) => { queries.map(async (q) => {
......
...@@ -13,7 +13,7 @@ import type { ...@@ -13,7 +13,7 @@ import type {
import { NE_LIGHT_DEFS } from './neLights.js' import { NE_LIGHT_DEFS } from './neLights.js'
/** 与前端 PROTOCOL_ROOMS / 场景书签对齐的稳定房间 id */ /** 与前端 PROTOCOL_ROOMS / 场景书签对齐的稳定房间 id */
const ROOM_ID_BY_NAME: Record<string, string> = { export const ROOM_ID_BY_NAME: Record<string, string> = {
收样咨询室: 'recv', 收样咨询室: 'recv',
生信分析报告室: 'report', 生信分析报告室: 'report',
试剂准备间: 'reagent', 试剂准备间: 'reagent',
...@@ -30,6 +30,13 @@ const ROOM_ID_BY_NAME: Record<string, string> = { ...@@ -30,6 +30,13 @@ const ROOM_ID_BY_NAME: Record<string, string> = {
测序区缓冲: 'seq-buf', 测序区缓冲: '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 { function stableRoomId(d: Device): string {
if (ROOM_ID_BY_NAME[d.name]) return ROOM_ID_BY_NAME[d.name] 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)) { for (const [name, id] of Object.entries(ROOM_ID_BY_NAME)) {
......
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
type TsSeries, type TsSeries,
} from '../db/timeseries.js' } from '../db/timeseries.js'
import { buildExcelXml, formatExportTs } from './excelXml.js' import { buildExcelXml, formatExportTs } from './excelXml.js'
import { ROOM_NAME_BY_ID } from './fromDevices.js'
const METRIC_LABEL: Record<string, string> = { const METRIC_LABEL: Record<string, string> = {
temp: '温度', temp: '温度',
...@@ -45,6 +46,8 @@ export function sourceLabel(source: string) { ...@@ -45,6 +46,8 @@ export function sourceLabel(source: string) {
async function deviceNameMap() { async function deviceNameMap() {
const devices = await listDevices() const devices = await listDevices()
const map = new Map<string, string>() 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) for (const d of devices) map.set(d.id, d.name)
map.set('PAU201', 'PAU-201 机组') map.set('PAU201', 'PAU-201 机组')
map.set('lab', '实验室汇总') map.set('lab', '实验室汇总')
...@@ -89,7 +92,7 @@ export async function getHistorySeries(opts: { ...@@ -89,7 +92,7 @@ export async function getHistorySeries(opts: {
series: TsSeries[] series: TsSeries[]
}> { }> {
const range = resolveTsTimeRange({ hours: opts.hours, from: opts.from, to: opts.to }) 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({ const raw = await queryTsSeries({
source: opts.source, source: opts.source,
metric: opts.metric, metric: opts.metric,
......
...@@ -118,7 +118,7 @@ export async function buildScreenDashboard(): Promise<ScreenDashboard> { ...@@ -118,7 +118,7 @@ export async function buildScreenDashboard(): Promise<ScreenDashboard> {
listDevices(), listDevices(),
listPoints(), listPoints(),
listGateways(), listGateways(),
queryFridgeTempSeries({ hours: 24, bucketMinutes: 15 }).catch(() => [] as FridgeTempSeries[]), queryFridgeTempSeries({ hours: 24, bucketMinutes: 60 }).catch(() => [] as FridgeTempSeries[]),
resolveGatewayBase() resolveGatewayBase()
? fetchGatewayHealth().catch(() => ({ ok: false as boolean })) ? fetchGatewayHealth().catch(() => ({ ok: false as boolean }))
: Promise.resolve({ ok: false as boolean }), : Promise.resolve({ ok: false as boolean }),
...@@ -140,7 +140,7 @@ export async function buildScreenDashboard(): Promise<ScreenDashboard> { ...@@ -140,7 +140,7 @@ export async function buildScreenDashboard(): Promise<ScreenDashboard> {
{ key: 'meteringWater', source: 'metering', metric: 'water_m3' }, { key: 'meteringWater', source: 'metering', metric: 'water_m3' },
{ key: 'meteringCooling', source: 'metering', metric: 'cooling_kwh' }, { key: 'meteringCooling', source: 'metering', metric: 'cooling_kwh' },
], ],
{ hours: 24, bucketMinutes: 1 }, { hours: 24, bucketMinutes: 60 },
).catch(() => ({}) as Record<string, TsSeries[]>), ).catch(() => ({}) as Record<string, TsSeries[]>),
]) ])
......
...@@ -45,7 +45,7 @@ realtimeRouter.get('/history', async (req, res) => { ...@@ -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 hours = req.query.hours != null && req.query.hours !== '' ? Number(req.query.hours) : undefined
const from = req.query.from ? String(req.query.from) : undefined const from = req.query.from ? String(req.query.from) : undefined
const to = req.query.to ? String(req.query.to) : 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 const deviceId = req.query.deviceId ? String(req.query.deviceId) : undefined
try { try {
const data = await getHistorySeries({ source, metric, hours, from, to, bucketMinutes, deviceId }) const data = await getHistorySeries({ source, metric, hours, from, to, bucketMinutes, deviceId })
...@@ -98,7 +98,7 @@ realtimeRouter.get('/history/export', async (req, res) => { ...@@ -98,7 +98,7 @@ realtimeRouter.get('/history/export', async (req, res) => {
/** 各冰箱近 24h 温度曲线(采集落库聚合) */ /** 各冰箱近 24h 温度曲线(采集落库聚合) */
realtimeRouter.get('/fridges/temp-history', async (req, res) => { realtimeRouter.get('/fridges/temp-history', async (req, res) => {
const hours = Number(req.query.hours) || 24 const hours = Number(req.query.hours) || 24
const bucketMinutes = Number(req.query.bucketMinutes) || 15 const bucketMinutes = Number(req.query.bucketMinutes) || 60
try { try {
const series = await queryFridgeTempSeries({ hours, bucketMinutes }) const series = await queryFridgeTempSeries({ hours, bucketMinutes })
res.json({ hours, bucketMinutes, series }) res.json({ hours, bucketMinutes, series })
......
...@@ -40,7 +40,7 @@ async function loadHistory() { ...@@ -40,7 +40,7 @@ async function loadHistory() {
loading.value = true loading.value = true
error.value = '' error.value = ''
try { 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> = {} const next: Record<string, HistSeries> = {}
for (const s of res.series || []) next[s.id] = s for (const s of res.series || []) next[s.id] = s
seriesMap.value = next seriesMap.value = next
......
...@@ -15,7 +15,6 @@ import AppIcon from '@/components/icons/AppIcon.vue' ...@@ -15,7 +15,6 @@ import AppIcon from '@/components/icons/AppIcon.vue'
import MarkerNearDialog from '@/components/scene/MarkerNearDialog.vue' import MarkerNearDialog from '@/components/scene/MarkerNearDialog.vue'
import { api } from '@/api/http' import { api } from '@/api/http'
import { import {
MARKER_LEADER_HEIGHT_M,
preloadRtengineIcons, preloadRtengineIcons,
useRtengineBridge, useRtengineBridge,
type SceneMarkerHit, type SceneMarkerHit,
...@@ -377,29 +376,11 @@ async function onLightControl(on: boolean) { ...@@ -377,29 +376,11 @@ async function onLightControl(on: boolean) {
} }
} }
function cameraMarkerList() { /** 引擎就绪后:预加载图标 → 按分类同步设施 POI */
return cameras.value /** 底栏「全景总览」飞到场景书签「主视图」 */
.filter((d) => d.enabled && d.scenePoint) const OVERVIEW_BOOKMARK = '主视图'
.map((d) => ({ /** 引擎就绪默认视角(与底栏主视图可不同) */
id: d.id, const DEFAULT_BOOKMARK = '全景总览'
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 = '全景总览'
async function onEngineReady() { async function onEngineReady() {
if (!useCloudFrame.value || engineReadyDone || engineBootstrapping) return if (!useCloudFrame.value || engineReadyDone || engineBootstrapping) return
...@@ -412,8 +393,8 @@ async function onEngineReady() { ...@@ -412,8 +393,8 @@ async function onEngineReady() {
syncCameraMarkers(true) syncCameraMarkers(true)
applyCameraVisibility() applyCameraVisibility()
await syncModePois() await syncModePois()
// 视角飞到「全景总览」默认相机,图标仍只留压差 // 就绪后飞默认相机;底栏「全景总览」另飞「主视图」
flyToViewpoint(OVERVIEW_BOOKMARK) flyToViewpoint(DEFAULT_BOOKMARK)
// 引擎稍晚才 hydrate marker 列表,再补两次压差过滤 // 引擎稍晚才 hydrate marker 列表,再补两次压差过滤
window.setTimeout(() => { window.setTimeout(() => {
if (!markersSynced) syncCameraMarkers(true) if (!markersSynced) syncCameraMarkers(true)
...@@ -431,27 +412,28 @@ async function onEngineReady() { ...@@ -431,27 +412,28 @@ async function onEngineReady() {
} }
} }
function cameraIconsVisible() { /** 监控图标已改由设施 sensors(useScreenModePois)按分类显示,视频台账点仅用于点击预览匹配,不再向场景打点 */
return activeMode.value === 'home' || activeMode.value === 'camera'
}
function applyCameraVisibility() { function applyCameraVisibility() {
const show = cameraIconsVisible()
for (const d of cameras.value) { for (const d of cameras.value) {
if (!d.scenePoint) continue if (!d.scenePoint) continue
bridge.setMarkerVisible(d.id, show) bridge.setMarkerVisible(d.id, false)
} }
} }
function syncCameraMarkers(force = false) { function syncCameraMarkers(force = false) {
if (!useCloudFrame.value) return if (!useCloudFrame.value) return
if (markersSynced && !force) return if (markersSynced && !force) return
const list = cameraMarkerList() // 卸掉历史视频台账 Marker,避免与设施摄像头重复
if (!list.length) return for (const d of cameras.value) {
// 批量一次写入即可(逐条 + 批量会触发多次场景刷新) if (!d.scenePoint) continue
bridge.addMarkers(list) try {
bridge.setMarkerVisible(d.id, false)
bridge.removeMarker(d.id)
} catch {
/* ignore */
}
}
markersSynced = true markersSynced = true
applyCameraVisibility()
} }
function normalizeCamKey(v: unknown): string { function normalizeCamKey(v: unknown): string {
...@@ -698,8 +680,8 @@ function onSelectArea(areaId: string) { ...@@ -698,8 +680,8 @@ function onSelectArea(areaId: string) {
} }
function onSelectOverview() { function onSelectOverview() {
// 只飞到主视图书签,不改点位显隐(保持当前分类图标)
activeAreaId.value = OVERVIEW_AREA_ID activeAreaId.value = OVERVIEW_AREA_ID
screenMode.setMode('home')
if (useCloudFrame.value) flyToViewpoint(OVERVIEW_BOOKMARK) if (useCloudFrame.value) flyToViewpoint(OVERVIEW_BOOKMARK)
} }
......
...@@ -131,7 +131,7 @@ function previewMarker(): RtengineMarker | null { ...@@ -131,7 +131,7 @@ function previewMarker(): RtengineMarker | null {
z: p.z, z: p.z,
tag0: 'pick-preview', tag0: 'pick-preview',
tag1: PREVIEW_ID, tag1: PREVIEW_ID,
scale: 1, scale: 0.5,
icon: textOnly ? '' : (selectedIcon.value || '').trim(), icon: textOnly ? '' : (selectedIcon.value || '').trim(),
leader: true, leader: true,
leaderHeight: MARKER_LEADER_HEIGHT_M, leaderHeight: MARKER_LEADER_HEIGHT_M,
......
...@@ -21,7 +21,7 @@ export function useMetricHistory( ...@@ -21,7 +21,7 @@ export function useMetricHistory(
source: q.source, source: q.source,
metric: q.metric, metric: q.metric,
hours: String(q.hours ?? 24), 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}`) const res = await api<{ series: HistorySeries[] }>(`/api/realtime/history?${params}`)
series.value = res.series || [] series.value = res.series || []
......
...@@ -122,8 +122,16 @@ const FACILITY_ICON_FALLBACK: Record<string, string> = { ...@@ -122,8 +122,16 @@ const FACILITY_ICON_FALLBACK: Record<string, string> = {
access: 'access-control-blue.png', 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 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 = const tag0 =
s.category in DEVICE_TAG s.category in DEVICE_TAG
? DEVICE_TAG[s.category as keyof typeof DEVICE_TAG] ? DEVICE_TAG[s.category as keyof typeof DEVICE_TAG]
...@@ -143,7 +151,7 @@ function toSensorMarker(s: SensorSnapshot): RtengineMarker { ...@@ -143,7 +151,7 @@ function toSensorMarker(s: SensorSnapshot): RtengineMarker {
tag1: s.id, tag1: s.id,
scale: MODE_MARKER_SCALE, scale: MODE_MARKER_SCALE,
// 压差:只显示温湿度压差文字,不挂图标 // 压差:只显示温湿度压差文字,不挂图标
...(textOnly ? { icon: '' } : { icon: (s.icon || '').trim() || fallback }), ...(textOnly ? { icon: '' } : { icon: resolveFacilityIcon(s) }),
...MARKER_LEADER, ...MARKER_LEADER,
} }
} }
...@@ -172,15 +180,15 @@ function markersForMode(mode: ScreenMode, snap: RealtimeSnapshot): RtengineMarke ...@@ -172,15 +180,15 @@ function markersForMode(mode: ScreenMode, snap: RealtimeSnapshot): RtengineMarke
if (mode === 'vav') { if (mode === 'vav') {
return sensorsOf(snap, 'vav').map(toSensorMarker) return sensorsOf(snap, 'vav').map(toSensorMarker)
} }
// 监控点由 SceneViewport 视频台账打点,避免与设施重复 // 监控与门禁一致:走设施快照(含三维坐标/图标)
if (mode === 'camera') { if (mode === 'camera') {
return [] return sensorsOf(snap, 'camera').map(toSensorMarker)
} }
if (mode === 'access') { if (mode === 'access') {
return sensorsOf(snap, 'access').map(toSensorMarker) return sensorsOf(snap, 'access').map(toSensorMarker)
} }
if (mode === 'home') { 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, toSensorMarker,
) )
} }
...@@ -228,7 +236,7 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl ...@@ -228,7 +236,7 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
/** 当前分类应显示的 tag0 集合 */ /** 当前分类应显示的 tag0 集合 */
function tagsForMode(current: ScreenMode): Set<string> { function tagsForMode(current: ScreenMode): Set<string> {
if (current === 'home') { 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 === 'e066') return new Set()
if (current in DEVICE_TAG) { if (current in DEVICE_TAG) {
...@@ -241,7 +249,7 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl ...@@ -241,7 +249,7 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
return [...showTags].sort().join(',') return [...showTags].sort().join(',')
} }
/** 引擎预设 / CAD 点:按分类显隐(摄像头留给 SceneViewport) */ /** 引擎预设 / CAD 点:按分类显隐(含监控 tag) */
function applyCadVisibility(current: ScreenMode, force = false) { function applyCadVisibility(current: ScreenMode, force = false) {
const showTags = tagsForMode(current) const showTags = tagsForMode(current)
const sig = tagVisSig(showTags) const sig = tagVisSig(showTags)
...@@ -254,7 +262,6 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl ...@@ -254,7 +262,6 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
// 仅分类切换时才对引擎做 tag 级 show/hide(整屏刷新很重) // 仅分类切换时才对引擎做 tag 级 show/hide(整屏刷新很重)
if (force || !modeSame || !tagsSame) { if (force || !modeSame || !tagsSame) {
for (const tag of ALL_DEVICE_TAGS) { for (const tag of ALL_DEVICE_TAGS) {
if (tag === 'camera') continue
if (showTags.has(tag)) bridge.showPois(tag) if (showTags.has(tag)) bridge.showPois(tag)
else bridge.hidePois(tag) else bridge.hidePois(tag)
} }
...@@ -263,9 +270,9 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl ...@@ -263,9 +270,9 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
const live = bridge.listMarkers?.() || [] const live = bridge.listMarkers?.() || []
for (const marker of live) { for (const marker of live) {
if (marker.id.startsWith(PREFIX)) continue 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) const cat = categoryOfMarker(marker)
if (!cat || cat === 'camera') continue if (!cat) continue
const show = current === 'home' || showTags.has(cat) || showTags.has(marker.tag0) const show = current === 'home' || showTags.has(cat) || showTags.has(marker.tag0)
bridge.setMarkerVisible(marker.id, show) bridge.setMarkerVisible(marker.id, show)
} }
...@@ -354,7 +361,6 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl ...@@ -354,7 +361,6 @@ export function useScreenModePois(bridge: BridgeLike, enabled: Ref<boolean>, _cl
lastCadMode = '' lastCadMode = ''
lastTagVisSig = '' lastTagVisSig = ''
for (const tag of ALL_DEVICE_TAGS) { for (const tag of ALL_DEVICE_TAGS) {
if (tag === 'camera') continue
bridge.hidePois(tag) bridge.hidePois(tag)
} }
} else { } else {
......
...@@ -31,7 +31,7 @@ export const DEVICE_TAG: Record<DeviceCategory, string> = { ...@@ -31,7 +31,7 @@ export const DEVICE_TAG: Record<DeviceCategory, string> = {
access: 'access', access: 'access',
} }
export const MODE_MARKER_SCALE = 1 export const MODE_MARKER_SCALE = 0.5
export const SCREEN_MODES: { id: ScreenMode; label: string }[] = [ export const SCREEN_MODES: { id: ScreenMode; label: string }[] = [
{ id: 'home', label: '全景总览' }, { id: 'home', label: '全景总览' },
......
...@@ -96,6 +96,7 @@ export default { ...@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} OK', envQualified: '{ok}/{total} OK',
deviceLedger: 'Device ledger', deviceLedger: 'Device ledger',
envEmpty: 'Room T/H pending', envEmpty: 'Room T/H pending',
roomHumidityEmpty: 'Room humidity history accumulating',
roomTempEmpty: 'Room temp history accumulating', roomTempEmpty: 'Room temp history accumulating',
coldTitle: 'Cold chain · 24h', coldTitle: 'Cold chain · 24h',
coldEmpty: 'Fridge temp history accumulating', coldEmpty: 'Fridge temp history accumulating',
......
...@@ -96,6 +96,7 @@ export default { ...@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} OK', envQualified: '{ok}/{total} OK',
deviceLedger: 'Inventário', deviceLedger: 'Inventário',
envEmpty: 'T/H das salas pendente', envEmpty: 'T/H das salas pendente',
roomHumidityEmpty: 'Histórico de humidade a acumular',
roomTempEmpty: 'Histórico de temp. a acumular', roomTempEmpty: 'Histórico de temp. a acumular',
coldTitle: 'Cadeia de frio · 24h', coldTitle: 'Cadeia de frio · 24h',
coldEmpty: 'Histórico de frigoríficos a acumular', coldEmpty: 'Histórico de frigoríficos a acumular',
......
...@@ -96,6 +96,7 @@ export default { ...@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} 合格', envQualified: '{ok}/{total} 合格',
deviceLedger: '设备台账', deviceLedger: '设备台账',
envEmpty: '房间温湿度待采集', envEmpty: '房间温湿度待采集',
roomHumidityEmpty: '房间湿度历史积累中',
roomTempEmpty: '房间温度历史积累中', roomTempEmpty: '房间温度历史积累中',
coldTitle: '冷链监测 · 24h', coldTitle: '冷链监测 · 24h',
coldEmpty: '冰箱温度历史积累中', coldEmpty: '冰箱温度历史积累中',
......
...@@ -96,6 +96,7 @@ export default { ...@@ -96,6 +96,7 @@ export default {
envQualified: '{ok}/{total} 合格', envQualified: '{ok}/{total} 合格',
deviceLedger: '設備台帳', deviceLedger: '設備台帳',
envEmpty: '房間溫濕度待採集', envEmpty: '房間溫濕度待採集',
roomHumidityEmpty: '房間濕度歷史累積中',
roomTempEmpty: '房間溫度歷史累積中', roomTempEmpty: '房間溫度歷史累積中',
coldTitle: '冷鏈監測 · 24h', coldTitle: '冷鏈監測 · 24h',
coldEmpty: '冰箱溫度歷史累積中', coldEmpty: '冰箱溫度歷史累積中',
......
...@@ -22,7 +22,10 @@ const chartMuted = 'rgba(158,200,220,0.35)' ...@@ -22,7 +22,10 @@ const chartMuted = 'rgba(158,200,220,0.35)'
function formatTick(t: number) { function formatTick(t: number) {
const d = new Date(t) 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() { ...@@ -21,10 +21,10 @@ async function load() {
try { try {
const [tempRes, humidityRes] = await Promise.all([ const [tempRes, humidityRes] = await Promise.all([
api<{ series: typeof series.value }>( 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<{ 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 || [] series.value = tempRes.series || []
......
...@@ -7,7 +7,7 @@ import EChart, { type ECOption } from '@/components/charts/EChart.vue' ...@@ -7,7 +7,7 @@ import EChart, { type ECOption } from '@/components/charts/EChart.vue'
import { useScreenDashboard } from '@/composables/useScreenDashboard' import { useScreenDashboard } from '@/composables/useScreenDashboard'
import { useRealtimeStore } from '@/stores/realtime' import { useRealtimeStore } from '@/stores/realtime'
import { multiLineOption, toHistorySeries } from '@/utils/historyChart' import { multiLineOption, toHistorySeries } from '@/utils/historyChart'
import { compactTooltipBase, pieTooltip, roundNum } from '@/utils/chartTooltip' import { pieTooltip } from '@/utils/chartTooltip'
const { t, te } = useI18n() const { t, te } = useI18n()
const realtime = useRealtimeStore() const realtime = useRealtimeStore()
...@@ -66,7 +66,6 @@ const kpiItems = computed<KpiFlipItem[]>(() => { ...@@ -66,7 +66,6 @@ const kpiItems = computed<KpiFlipItem[]>(() => {
}) })
const chartText = '#9ec8dc' const chartText = '#9ec8dc'
const chartMuted = 'rgba(158,200,220,0.35)'
function statusLabel(key: string | undefined, fallback: string) { function statusLabel(key: string | undefined, fallback: string) {
if (key && te(`status.${key}`)) return t(`status.${key}`) if (key && te(`status.${key}`)) return t(`status.${key}`)
...@@ -89,75 +88,13 @@ function pauLabel(key: string | undefined, fallback: string) { ...@@ -89,75 +88,13 @@ function pauLabel(key: string | undefined, fallback: string) {
return fallback 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 curves = computed(() => data.value?.curves)
/** 环境总览上图:仅湿度历史曲线 */
const roomHumidityHistoryOption = computed<ECOption>(() =>
multiLineOption(curves.value?.roomHumidity || [], { unit: '%RH', maxSeries: 10 }),
)
const roomTempHistoryOption = computed<ECOption>(() => const roomTempHistoryOption = computed<ECOption>(() =>
multiLineOption(curves.value?.roomTemp || [], { unit: '℃', maxSeries: 10 }), multiLineOption(curves.value?.roomTemp || [], { unit: '℃', maxSeries: 10 }),
) )
...@@ -305,7 +242,11 @@ const energyDigits = computed(() => digits.value) ...@@ -305,7 +242,11 @@ const energyDigits = computed(() => digits.value)
</span> </span>
</template> </template>
<div class="panel-stack"> <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 <EChart
class="chart-sub" class="chart-sub"
:option="roomTempHistoryOption" :option="roomTempHistoryOption"
...@@ -532,6 +473,7 @@ const energyDigits = computed(() => digits.value) ...@@ -532,6 +473,7 @@ const energyDigits = computed(() => digits.value)
border-radius: 4px; border-radius: 4px;
background: rgba(6, 28, 56, 0.45); background: rgba(6, 28, 56, 0.45);
border: 1px solid rgba(0, 247, 255, 0.14); border: 1px solid rgba(0, 247, 255, 0.14);
text-align: center;
} }
.digit-card.warn { .digit-card.warn {
border-color: rgba(250, 173, 20, 0.4); border-color: rgba(250, 173, 20, 0.4);
...@@ -550,7 +492,7 @@ const energyDigits = computed(() => digits.value) ...@@ -550,7 +492,7 @@ const energyDigits = computed(() => digits.value)
} }
.d-value small { .d-value small {
font-size: 10px; font-size: 10px;
color: rgba(200, 230, 245, 0.65); color: #52c41a;
font-weight: 500; font-weight: 500;
} }
.energy-digits { .energy-digits {
......
...@@ -147,6 +147,29 @@ function pickPreferredIcons(s: Sensor | null) { ...@@ -147,6 +147,29 @@ function pickPreferredIcons(s: Sensor | null) {
return SENSOR_PREFERRED_ICONS[s.category] || Object.values(FACILITY_DEFAULT_ICON) 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({ const form = reactive({
code: '', code: '',
name: '', name: '',
...@@ -165,6 +188,7 @@ const form = reactive({ ...@@ -165,6 +188,7 @@ const form = reactive({
airflow: '', airflow: '',
alarmCode: '', alarmCode: '',
} as Record<string, string>, } as Record<string, string>,
comm: emptyComm(),
}) })
function matchKeyword(parts: Array<string | number | null | undefined>, q: string) { function matchKeyword(parts: Array<string | number | null | undefined>, q: string) {
...@@ -249,6 +273,7 @@ function openCreate() { ...@@ -249,6 +273,7 @@ function openCreate() {
airflow: '', airflow: '',
alarmCode: '', alarmCode: '',
} }
form.comm = emptyComm()
error.value = '' error.value = ''
showEdit.value = true showEdit.value = true
} }
...@@ -276,10 +301,37 @@ function openEdit(s: Sensor) { ...@@ -276,10 +301,37 @@ function openEdit(s: Sensor) {
for (const b of s.points || []) { for (const b of s.points || []) {
form.roles[b.role] = b.pointId 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 = '' error.value = ''
showEdit.value = true 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() { function closeEdit() {
showEdit.value = false showEdit.value = false
error.value = '' error.value = ''
...@@ -303,6 +355,9 @@ async function save() { ...@@ -303,6 +355,9 @@ async function save() {
saving.value = true saving.value = true
error.value = '' error.value = ''
message.value = '' message.value = ''
const current = editingId.value
? sensors.value.find((s) => s.id === editingId.value)
: undefined
const body = { const body = {
code, code,
name, name,
...@@ -312,6 +367,7 @@ async function save() { ...@@ -312,6 +367,7 @@ async function save() {
sceneY: Number(form.sceneY), sceneY: Number(form.sceneY),
sceneZ: Number(form.sceneZ), sceneZ: Number(form.sceneZ),
points: buildPointsPayload(), points: buildPointsPayload(),
meta: buildMetaPayload(current?.meta),
} }
try { try {
if (editingId.value) { if (editingId.value) {
...@@ -411,7 +467,7 @@ onMounted(load) ...@@ -411,7 +467,7 @@ onMounted(load)
</div> </div>
<p class="desc"> <p class="desc">
设施含压差、照明 N/E 电、冰箱、通风柜、摄像头、门禁(后四类与交底表对齐)。摄像头/门禁无采集点绑定,可通过「三维打点」调整坐标与图标。 设施含压差、照明 N/E 电、冰箱、通风柜、摄像头、门禁(后四类与交底表对齐)。摄像头/门禁可编辑通信参数(IP、协议、管理端口、用户名、密码),并通过「三维打点」调整坐标与图标。
</p> </p>
<div class="filters"> <div class="filters">
...@@ -449,7 +505,7 @@ onMounted(load) ...@@ -449,7 +505,7 @@ onMounted(load)
<th>分类</th> <th>分类</th>
<th>启用</th> <th>启用</th>
<th>坐标</th> <th>坐标</th>
<th>绑定点位</th> <th>绑定点位 / 通信</th>
<th>操作</th> <th>操作</th>
</tr> </tr>
</thead> </thead>
...@@ -471,7 +527,14 @@ onMounted(load) ...@@ -471,7 +527,14 @@ onMounted(load)
<td class="mono"> <td class="mono">
{{ s.sceneX.toFixed(6) }}, {{ s.sceneY.toFixed(6) }}, {{ s.sceneZ }} {{ s.sceneX.toFixed(6) }}, {{ s.sceneY.toFixed(6) }}, {{ s.sceneZ }}
</td> </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"> <td class="ops">
<button <button
v-if="auth.isAdmin" v-if="auth.isAdmin"
...@@ -556,6 +619,45 @@ onMounted(load) ...@@ -556,6 +619,45 @@ onMounted(load)
</div> </div>
</div> </div>
<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>
</template>
<template v-else>
<h4 class="sub">点位绑定</h4> <h4 class="sub">点位绑定</h4>
<div class="form-grid roles"> <div class="form-grid roles">
<div v-for="role in rolesForCategory(form.category)" :key="role" class="field"> <div v-for="role in rolesForCategory(form.category)" :key="role" class="field">
...@@ -568,6 +670,7 @@ onMounted(load) ...@@ -568,6 +670,7 @@ onMounted(load)
</select> </select>
</div> </div>
</div> </div>
</template>
<p v-if="error" class="msg err">{{ error }}</p> <p v-if="error" class="msg err">{{ error }}</p>
<div class="modal-actions"> <div class="modal-actions">
...@@ -731,6 +834,12 @@ onMounted(load) ...@@ -731,6 +834,12 @@ onMounted(load)
font-size: 13px; font-size: 13px;
color: #eaf4ff; color: #eaf4ff;
} }
.comm-hint {
margin: -4px 0 10px;
font-size: 12px;
color: var(--panel-muted);
line-height: 1.45;
}
.modal-actions { .modal-actions {
margin-top: 14px; margin-top: 14px;
display: flex; 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