Commit eff3fd97 by 久林

feat: 大屏默认压差过滤、全景底栏与 CAD 点位工具

默认只显示压差图标并飞到全景总览;全景按钮移到底栏测序区前;计量计费改到管理端;补充 CAD 重打点与 110 部署脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
parent 2b579ac5
...@@ -60,7 +60,7 @@ VITE_WS_URL= ...@@ -60,7 +60,7 @@ VITE_WS_URL=
# 三维视口:glb | ue-stream | rtengine(RTEngineHD 云渲染 iframe) # 三维视口:glb | ue-stream | rtengine(RTEngineHD 云渲染 iframe)
VITE_VIEWPORT_MODE=rtengine VITE_VIEWPORT_MODE=rtengine
# 推荐同域反代(postMessage 同域);开发/Nginx 已代理 /rtengine + /peer-stream + /designer # 推荐同域反代(postMessage 同域);开发/Nginx 已代理 /rtengine + /peer-stream + /designer
VITE_RTENGINE_URL=/rtengine/gis.html?scene=mvg8x4b453b45f38 VITE_RTENGINE_URL=/rtengine/gis.html?scene=mvlpj2yt6379aa29
VITE_UE_STREAM_URL=/rtengine/gis.html?scene=mvg8x4b453b45f38 VITE_UE_STREAM_URL=/rtengine/gis.html?scene=mvlpj2yt6379aa29
# 反代上游引擎地址 # 反代上游引擎地址(110 本机推流用 127.0.0.1)
VITE_RTENGINE_PROXY_TARGET=http://192.168.32.121:6088 VITE_RTENGINE_PROXY_TARGET=http://127.0.0.1:6088
"""构建前端并推送到 110,确保 nginx / 场景可用。"""
import json
import subprocess
import sys
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def run(cmd: list[str], cwd: Path | None = None) -> None:
print("$", " ".join(cmd))
r = subprocess.run(cmd, cwd=str(cwd or ROOT), check=False)
if r.returncode != 0:
raise SystemExit(r.returncode)
# 1) build (Windows: npx.cmd)
run(["npx.cmd", "vite", "build"], cwd=ROOT / "web")
# 2) push web
run([sys.executable, str(ROOT / "deploy" / "_push_web_110.py")])
# 3) ensure scene still has overview bookmark + unified marker height
scene = ROOT / "deploy" / "_scene_check.json"
if scene.exists():
d = json.loads(scene.read_text(encoding="utf-8"))
hs = {
round((c.get("params") or {}).get("height", 0), 3)
for c in d.get("components") or []
if c.get("type") == "marker"
}
names = [b.get("name") for b in d.get("bookmarks") or []]
print("local scene marker heights", sorted(hs))
print("has 全景总览", "全景总览" in names)
run([sys.executable, str(ROOT / "deploy" / "_put_scene.py")])
# 4) nginx up
run([sys.executable, str(ROOT / "deploy" / "_start_nginx_keep.py")])
# 5) verify
for u in [
"http://192.168.32.110/",
"http://192.168.32.110/dashboard/overview",
"http://192.168.32.110/admin/metering",
"http://192.168.32.110/rtengine/designer/data/scenes/mvlpj2yt6379aa29.json",
]:
try:
r = urllib.request.urlopen(u, timeout=12)
print("OK", r.status, u, "len", len(r.read(200)))
except Exception as e:
print("FAIL", u, e)
# confirm remote index chunk
html = urllib.request.urlopen("http://192.168.32.110/", timeout=12).read().decode("utf-8", "replace")
print("index snippet:", [line.strip() for line in html.splitlines() if "assets/index-" in line])
print("DEPLOY OK")
"""注册计划任务:每分钟检查 nginx,挂了就拉起。"""
import paramiko
keeper = r"""
$ErrorActionPreference='SilentlyContinue'
$ngx='D:\apps\jh-lab\platform\nginx'
$listen = Get-NetTCPConnection -LocalPort 80 -State Listen -EA SilentlyContinue
$procs = Get-Process nginx -EA SilentlyContinue
if ($listen -and $procs) { exit 0 }
# stop docker web if reclaiming 80
docker stop jh-lab-web 2>$null | Out-Null
Get-Process nginx -EA SilentlyContinue | Stop-Process -Force
Start-Sleep 1
# free port
Get-NetTCPConnection -LocalPort 80 -State Listen -EA SilentlyContinue | ForEach-Object {
Stop-Process -Id $_.OwningProcess -Force -EA SilentlyContinue
}
Start-Sleep 1
Start-Process -FilePath "$ngx\nginx.exe" -WorkingDirectory $ngx -ArgumentList '-p',"$ngx/",'-c','conf/nginx.conf' -WindowStyle Hidden
"""
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.32.110", username="admin", password="123", timeout=20)
sftp = c.open_sftp()
with sftp.file(r"C:\Users\admin\nginx_keepalive.ps1", "w") as f:
f.write(keeper)
cmds = [
r'schtasks /Create /F /TN JhNginxKeepalive /TR "powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\admin\nginx_keepalive.ps1" /SC MINUTE /MO 1 /RL HIGHEST',
r"schtasks /Run /TN JhNginxKeepalive",
r"schtasks /Query /TN JhNginxKeepalive /V /FO LIST",
]
for cmd in cmds:
_i, o, e = c.exec_command(cmd, timeout=30)
print(o.read().decode("utf-8", "replace")[:800])
err = e.read().decode("utf-8", "replace")
if err.strip():
print("[err]", err[:400])
sftp.close()
c.close()
"""Sync local web dist to 110 native web and reload nginx."""
import os
import time
from pathlib import Path
import paramiko
ROOT = Path(__file__).resolve().parents[1]
LOCAL = ROOT / "web" / "dist"
REMOTE = r"D:\apps\jh-lab\platform\web"
HOST = "192.168.32.110"
# also copy into deploy/prod/native/web for next full package
NATIVE = ROOT / "deploy" / "prod" / "native" / "web"
def copy_tree(src: Path, dst: Path) -> None:
if dst.exists():
import shutil
if (dst / "assets").exists():
shutil.rmtree(dst / "assets")
dst.mkdir(parents=True, exist_ok=True)
import shutil
for item in src.iterdir():
target = dst / item.name
if item.is_dir():
if target.exists():
shutil.rmtree(target)
shutil.copytree(item, target)
else:
shutil.copy2(item, target)
copy_tree(LOCAL, NATIVE)
print("native web synced")
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(HOST, username="admin", password="123", timeout=20)
sftp = c.open_sftp()
def ensure_dir(remote: str) -> None:
parts = remote.replace("/", "\\").split("\\")
cur = parts[0] + "\\"
for p in parts[1:]:
if not p:
continue
cur = cur.rstrip("\\") + "\\" + p
try:
sftp.stat(cur)
except OSError:
try:
sftp.mkdir(cur)
except OSError:
pass
def put_dir(local: Path, remote: str) -> None:
ensure_dir(remote)
for root, dirs, files in os.walk(local):
rel = os.path.relpath(root, local)
rdir = remote if rel == "." else remote + "\\" + rel.replace("/", "\\")
ensure_dir(rdir)
for name in files:
lp = os.path.join(root, name)
rp = rdir + "\\" + name
sftp.put(lp, rp)
# clear remote assets then upload
_i, o, e = c.exec_command(
r'cmd /c if exist D:\apps\jh-lab\platform\web\assets rmdir /s /q D:\apps\jh-lab\platform\web\assets',
timeout=30,
)
o.read()
e.read()
put_dir(LOCAL, REMOTE)
sftp.close()
print("uploaded web")
# reload via full path; if master gone, start fresh
_i, o, e = c.exec_command(
r'powershell -NoProfile -Command "'
r"$ngx='D:\apps\jh-lab\platform\nginx'; "
r"if (Get-Process nginx -EA SilentlyContinue) { "
r"& \"$ngx\nginx.exe\" -s reload -p \"$ngx/\" -c conf/nginx.conf "
r"} else { "
r"Start-Process -FilePath \"$ngx\nginx.exe\" -WorkingDirectory $ngx "
r"-ArgumentList '-p',\"$ngx/\",'-c','conf/nginx.conf' -WindowStyle Hidden "
r"}; Start-Sleep 1; "
r"try { (Invoke-WebRequest http://127.0.0.1/ -UseBasicParsing -TimeoutSec 8).StatusCode } "
r"catch { $_.Exception.Message }"
r'"',
timeout=40,
)
print("WEB", o.read().decode("utf-8", "replace"), e.read().decode("utf-8", "replace")[:500])
c.close()
print("DONE")
import paramiko
local = r"C:\Users\jiuli\Desktop\卓邦\镜湖医院\dashboard\deploy\_scene_check.json"
remote = r"C:\Users\admin\AppData\Roaming\rtenginehd-electron-editor\designer-data\scenes\mvlpj2yt6379aa29.json"
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.32.110", username="admin", password="123", timeout=20)
sftp = c.open_sftp()
sftp.put(local, remote)
st = sftp.stat(remote)
print("size", st.st_size)
sftp.close()
c.close()
"""把当前三维画面写入场景默认 camera 和书签「全景总览」。不改其它书签。"""
import json
import uuid
from datetime import datetime, timezone
from pathlib import Path
# 2026-10-25 用户调好画面后,$gisObj.getViewGeo() 抓取
VIEW = {
"x": 133.54296233615662,
"y": 22.19714852691828,
"z": 67.00246906239317,
"heading": -32.127238704530676,
"pitch": -53.551698938215054,
"roll": 0,
"duration": 1.2,
"lng": 133.54296233615662,
"lat": 22.19714852691828,
"height": 67.00246906239317,
"longitude": 133.54296233615662,
"latitude": 22.19714852691828,
"yaw": -32.127238704530676,
}
ROOT = Path(__file__).resolve().parents[1]
PATHS = [ROOT / "deploy" / "_scene_check.json", ROOT / "deploy" / "_scene.json"]
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
def apply(path: Path) -> None:
data = json.loads(path.read_text(encoding="utf-8"))
data["camera"] = dict(VIEW)
bookmarks = list(data.get("bookmarks") or [])
found = False
for b in bookmarks:
if isinstance(b, dict) and b.get("name") == "全景总览":
b["camera"] = dict(VIEW)
b["updatedAt"] = now
found = True
break
if not found:
bookmarks.append(
{
"id": str(uuid.uuid4()),
"name": "全景总览",
"camera": dict(VIEW),
"createdAt": now,
}
)
data["bookmarks"] = bookmarks
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(path.name, "全景总览", round(VIEW["height"], 2), round(VIEW["yaw"], 2), round(VIEW["pitch"], 2))
for p in PATHS:
apply(p)
"""启动 nginx 并确认 10 秒内仍在监听 :80。"""
import time
import paramiko
ps = r"""
$ErrorActionPreference='Continue'
$ngx='D:\apps\jh-lab\platform\nginx'
docker stop jh-lab-web 2>$null | Out-Null
# only kill nginx, not other holders if none
$ngxProcs = @(Get-Process nginx -EA SilentlyContinue)
if ($ngxProcs.Count -gt 0) {
$ngxProcs | Stop-Process -Force
Start-Sleep 1
}
# if :80 still held, report
$hold = Get-NetTCPConnection -LocalPort 80 -State Listen -EA SilentlyContinue
if ($hold) {
Write-Host 'port80 still held:'
$hold | Format-Table LocalAddress,OwningProcess -AutoSize | Out-String | Write-Host
exit 2
}
& "$ngx\nginx.exe" -t -p "$ngx/" -c conf/nginx.conf
if ($LASTEXITCODE -ne 0) { exit 3 }
Start-Process -FilePath "$ngx\nginx.exe" -WorkingDirectory $ngx -ArgumentList '-p',"$ngx/",'-c','conf/nginx.conf' -WindowStyle Hidden
Start-Sleep 2
$ok = $false
for ($i=0; $i -lt 5; $i++) {
$l = Get-NetTCPConnection -LocalPort 80 -State Listen -EA SilentlyContinue
$p = @(Get-Process nginx -EA SilentlyContinue)
Write-Host ("t=$i listen80=" + @($l).Count + " nginx=" + $p.Count)
if (@($l).Count -gt 0 -and $p.Count -gt 0) { $ok = $true; break }
Start-Sleep 1
}
if (-not $ok) {
if (Test-Path "$ngx\logs\error.log") { Get-Content "$ngx\logs\error.log" -Tail 20 }
exit 4
}
try {
$r = Invoke-WebRequest 'http://127.0.0.1/dashboard/overview' -UseBasicParsing -TimeoutSec 8
Write-Host ("overview " + $r.StatusCode)
} catch { Write-Host ("overview FAIL " + $_.Exception.Message); exit 5 }
Write-Host READY
"""
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.32.110", username="admin", password="123", timeout=20)
sftp = c.open_sftp()
path = r"C:\Users\admin\start_nginx_keep.ps1"
with sftp.file(path, "w") as f:
f.write(ps)
_i, o, e = c.exec_command(
f'powershell -NoProfile -ExecutionPolicy Bypass -File "{path}"',
timeout=60,
)
print(o.read().decode("utf-8", "replace"))
print(e.read().decode("utf-8", "replace")[:1000])
sftp.close()
c.close()
"""统一全部 marker 高度到模型顶部附近。"""
import json
from pathlib import Path
TARGET_H = 55.5 # 当前最高约 55.86,取齐顶附近
path = Path("deploy/_scene_check.json")
data = json.loads(path.read_text(encoding="utf-8"))
n = 0
for m in data.get("components") or []:
if not isinstance(m, dict) or m.get("type") != "marker":
continue
p = m.setdefault("params", {})
p["height"] = TARGET_H
n += 1
path.write_text(json.dumps(data, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print(f"set {n} markers height={TARGET_H}")
import json
import re
import urllib.request
for u in [
"http://192.168.32.110/",
"http://192.168.32.110/dashboard/overview",
"http://192.168.32.110/admin/metering",
"http://192.168.32.110/rtengine/designer/data/scenes/mvlpj2yt6379aa29.json",
]:
try:
r = urllib.request.urlopen(u, timeout=12)
body = r.read()
print(r.status, u, "len", len(body))
if u.endswith(".json"):
d = json.loads(body.decode("utf-8"))
hs = {
round((c.get("params") or {}).get("height", 0), 3)
for c in d.get("components") or []
if c.get("type") == "marker"
}
print(" marker heights", sorted(hs))
print(
" 全景总览",
any(b.get("name") == "全景总览" for b in d.get("bookmarks") or []),
)
except Exception as e:
print("FAIL", u, e)
html = urllib.request.urlopen("http://192.168.32.110/", timeout=12).read().decode(
"utf-8", "replace"
)
m = re.search(r"assets/index-[^\"' ]+", html)
print("chunk", m.group(0) if m else None)
import json
import urllib.request
import paramiko
REMOTE = r"C:\Users\admin\AppData\Roaming\rtenginehd-electron-editor\designer-data\scenes\mvlpj2yt6379aa29.json"
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.32.110", username="admin", password="123", timeout=20)
sftp = c.open_sftp()
with sftp.file(REMOTE, "r") as f:
d = json.loads(f.read().decode("utf-8"))
print(
"disk camera",
round(d["camera"]["height"], 2),
round(d["camera"]["yaw"], 2),
round(d["camera"]["pitch"], 2),
)
names = [b.get("name") for b in d.get("bookmarks") or []]
print("disk bookmarks", names)
for b in d.get("bookmarks") or []:
if b.get("name") == "全景总览":
cam = b["camera"]
print(
"全景总览",
round(cam["height"], 2),
round(cam["yaw"], 2),
round(cam["pitch"], 2),
)
_i, o, e = c.exec_command(
r'powershell -NoProfile -Command "Get-Content D:\apps\jh-lab\platform\web\index.html"',
timeout=20,
)
print(o.read().decode("utf-8", "replace"))
sftp.close()
c.close()
# LAN
for u in [
"http://192.168.32.110/",
"http://192.168.32.110/dashboard/overview",
"http://192.168.32.110/rtengine/designer/data/scenes/mvlpj2yt6379aa29.json",
]:
r = urllib.request.urlopen(u, timeout=12)
body = r.read()
print(r.status, u, "len", len(body))
if u.endswith(".json"):
jd = json.loads(body.decode("utf-8"))
print(
"http camera H",
round(jd["camera"]["height"], 2),
"has 全景总览",
any(b.get("name") == "全景总览" for b in jd.get("bookmarks") or []),
)
import paramiko
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect("192.168.32.110", username="admin", password="123", timeout=20)
sftp = c.open_sftp()
with sftp.file(r"D:\apps\jh-lab\platform\web\index.html", "r") as f:
html = f.read().decode("utf-8", "replace")
print("index", [x.strip() for x in html.splitlines() if "assets/index-" in x])
for name in sftp.listdir(r"D:\apps\jh-lab\platform\web\assets"):
if not (name.startswith("ScreenLayout-") and name.endswith(".js")):
continue
with sftp.file(rf"D:\apps\jh-lab\platform\web\assets\{name}", "r") as f:
t = f.read().decode("utf-8", "replace")
print(
name,
"setMode pressure",
t.count('setMode("pressure")') + t.count("setMode('pressure')"),
"hide Devices branch",
"Devices" in t,
"showScenePresets count",
t.count("showScenePresets"),
)
ps = r"""
$ngx='D:\apps\jh-lab\platform\nginx'
$l=@(Get-NetTCPConnection -LocalPort 80 -State Listen -EA SilentlyContinue)
$p=@(Get-Process nginx -EA SilentlyContinue)
if ($l.Count -eq 0) {
Start-Process -FilePath "$ngx\nginx.exe" -WorkingDirectory $ngx -ArgumentList '-p',"$ngx/",'-c','conf/nginx.conf' -WindowStyle Hidden
Start-Sleep 2
}
try { Write-Host ('OK ' + (Invoke-WebRequest http://127.0.0.1/dashboard/overview -UseBasicParsing -TimeoutSec 5).StatusCode) }
catch { Write-Host ('FAIL ' + $_.Exception.Message) }
Get-NetTCPConnection -LocalPort 80 -State Listen -EA SilentlyContinue | Format-Table LocalAddress,OwningProcess -AutoSize | Out-String | Write-Host
"""
with sftp.file(r"C:\Users\admin\vpd.ps1", "w") as f:
f.write(ps)
_i, o, e = c.exec_command(
r"powershell -NoProfile -ExecutionPolicy Bypass -File C:\Users\admin\vpd.ps1",
timeout=30,
)
print(o.read().decode("utf-8", "replace"))
sftp.close()
c.close()
"""Print bookmark look-at points and corridor / sequencing / library-2 markers."""
import json
import math
from pathlib import Path
d = json.loads(Path("deploy/_scene_check.json").read_text(encoding="utf-8"))
o = d["origin"]
R = 6378137.0
def en(lng, lat):
e = (lng - o["longitude"]) * math.cos(math.radians(o["latitude"])) * math.pi / 180 * R
n = (lat - o["latitude"]) * math.pi / 180 * R
return e, n
print("BOOKMARKS")
for b in d["bookmarks"]:
c = b["camera"]
e, n = en(c["longitude"], c["latitude"])
yaw = math.radians(c.get("yaw") or 0)
pitch = math.radians(c.get("pitch") or -45)
z = c.get("height") or 56
dz = z - 52.0
horiz = dz / math.tan(abs(pitch)) if abs(pitch) > 0.05 else 0
de = math.sin(yaw) * horiz
dn = math.cos(yaw) * horiz
print(
f"{b['name']:12} eye E{e:+6.1f} N{n:+6.1f} z{z:5.1f} "
f"yaw{c.get('yaw'):7.1f} pitch{c.get('pitch'):6.1f} "
f"look E{e+de:+6.1f} N{n+dn:+6.1f}"
)
print("\nMARKERS of interest")
keys = ("测序", "文库制备区2", "文库制备2", "文库2", "走廊")
for c in d["components"]:
if c.get("type") != "marker":
continue
name = c["name"]
if not any(k in name for k in keys):
continue
e, n = en(c["params"]["lng"], c["params"]["lat"])
print(f"{name:24} {c['params'].get('tag0'):8} E{e:+6.2f} N{n:+6.2f}")
$ErrorActionPreference = 'Stop'
$dwg = 'C:\Users\jiuli\Desktop\卓邦\镜湖医院\dashboard\deploy\cad\lab_plan.dwg'
$dxf = 'C:\Users\jiuli\Desktop\卓邦\镜湖医院\dashboard\deploy\cad\lab_plan.dxf'
if (Test-Path $dxf) { Remove-Item $dxf -Force }
$acad = $null
try {
$acad = [Runtime.InteropServices.Marshal]::GetActiveObject('AutoCAD.Application')
} catch {
$acad = New-Object -ComObject AutoCAD.Application.21
}
$acad.Visible = $false
$doc = $acad.Documents.Open($dwg, $false)
# R2010 DXF
$doc.SaveAs($dxf, 13)
$doc.Close($false)
Write-Host "saved $dxf"
Get-Item $dxf | Format-List FullName,Length
"""提取照明平面 / 监控门禁平面 / 自控平面上的设备点与房间名。"""
from __future__ import annotations
import json
from collections import Counter, defaultdict
from pathlib import Path
import ezdxf
DXF = Path(r"C:\temp\jhcad\lab_plan.dxf")
OUT = Path(__file__).resolve().parent
doc = ezdxf.readfile(str(DXF))
msp = doc.modelspace()
SHEETS = {
"lighting_NE": dict(xmin=100000, xmax=145000, ymin=-480000, ymax=-445000),
"monitor_access": dict(xmin=290000, xmax=360000, ymin=-480000, ymax=-445000),
"autocl_room": dict(xmin=380000, xmax=430000, ymin=-850000, ymax=-800000),
"fridge_comm": dict(xmin=450000, xmax=500000, ymin=-850000, ymax=-790000),
}
def in_box(x, y, b):
return b["xmin"] <= x <= b["xmax"] and b["ymin"] <= y <= b["ymax"]
def collect(box):
texts = []
inserts = []
circles = []
for e in msp.query("TEXT MTEXT"):
try:
if e.dxftype() == "TEXT":
t = (e.dxf.text or "").strip()
x, y, *_ = e.dxf.insert
h = float(e.dxf.height or 0)
else:
t = (e.text or "").replace("\\P", " ").strip()
x, y, *_ = e.dxf.insert
h = float(getattr(e.dxf, "char_height", 0) or 0)
if not in_box(x, y, box):
continue
texts.append({"text": t[:100], "x": float(x), "y": float(y), "h": h, "layer": e.dxf.layer})
except Exception:
pass
for e in msp.query("INSERT"):
try:
x, y, *_ = e.dxf.insert
if not in_box(x, y, box):
continue
inserts.append(
{
"name": e.dxf.name,
"x": float(x),
"y": float(y),
"layer": e.dxf.layer,
"sx": float(getattr(e.dxf, "xscale", 1) or 1),
"sy": float(getattr(e.dxf, "yscale", 1) or 1),
"rot": float(getattr(e.dxf, "rotation", 0) or 0),
}
)
except Exception:
pass
for e in msp.query("CIRCLE"):
try:
x, y, *_ = e.dxf.center
if not in_box(x, y, box):
continue
circles.append(
{
"x": float(x),
"y": float(y),
"r": float(e.dxf.radius),
"layer": e.dxf.layer,
}
)
except Exception:
pass
return texts, inserts, circles
result = {}
for name, box in SHEETS.items():
texts, inserts, circles = collect(box)
rooms = [
t
for t in texts
if any(k in t["text"] for k in ("测序", "文库", "样本", "咨询", "准备", "报告", "PCR", "缓冲", "走廊", "收样", "生信"))
and len(t["text"]) < 30
]
print(f"\n==== {name} ====")
print("box", box)
print("texts", len(texts), "inserts", len(inserts), "circles", len(circles))
print("insert layers", Counter(i["layer"] for i in inserts).most_common())
print("insert names", Counter(i["name"] for i in inserts).most_common(12))
print("ROOM TEXTS:")
for t in sorted(rooms, key=lambda a: (-a["y"], a["x"])):
print(f" ({t['x']:.0f},{t['y']:.0f}) [{t['layer']}] {t['text']!r}")
# print lighting / security inserts
for layer in ("EQUIP-照明", "EQUIP-安防", "EQUIP-楼控", "EQUIP-通讯", "EQUIP-箱柜"):
items = [i for i in inserts if i["layer"] == layer]
if not items:
continue
print(f" {layer} ({len(items)}):")
for i in sorted(items, key=lambda a: (-a["y"], a["x"])):
print(f" ({i['x']:.0f},{i['y']:.0f}) {i['name']}")
result[name] = {
"box": box,
"rooms": rooms,
"inserts": inserts,
"circles": circles,
"texts": [t for t in texts if len(t["text"]) < 40][:500],
}
(OUT / "_key_sheets.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print("\nwrote", OUT / "_key_sheets.json")
"""聚焦主平面区域,提取照明/安防/压差相关块与文字坐标。"""
from __future__ import annotations
import json
import math
from collections import Counter, defaultdict
from pathlib import Path
import ezdxf
DXF = Path(r"C:\temp\jhcad\lab_plan.dxf")
OUT = Path(__file__).resolve().parent
doc = ezdxf.readfile(str(DXF))
msp = doc.modelspace()
# 主平面区:注释层房间名附近
PLAN = dict(xmin=295000, xmax=325000, ymin=-555000, ymax=-505000)
ROOM_LABELS = {}
for e in msp.query("TEXT MTEXT"):
try:
if e.dxftype() == "TEXT":
content = (e.dxf.text or "").strip()
x, y, *_ = e.dxf.insert
else:
content = (e.text or "").replace("\\P", " ").strip()
x, y, *_ = e.dxf.insert
if not (PLAN["xmin"] <= x <= PLAN["xmax"] and PLAN["ymin"] <= y <= PLAN["ymax"]):
continue
if e.dxf.layer in ("注释", "RD-标注", "N-TEXT", "文字") or True:
for key in (
"测序区",
"文库制备区2",
"文库制备区1",
"样本制备区2",
"样本制备区1",
"咨询室",
"准备区",
"报告室",
"PCR走廊",
"缓冲",
):
if key in content and len(content) < 20:
ROOM_LABELS.setdefault(content, []).append((float(x), float(y), e.dxf.layer))
except Exception:
pass
print("ROOM LABELS in plan bbox:")
for k, pts in sorted(ROOM_LABELS.items()):
print(f" {k}: {pts[:5]}")
def in_plan(x, y):
return PLAN["xmin"] <= x <= PLAN["xmax"] and PLAN["ymin"] <= y <= PLAN["ymax"]
# inserts in plan by layer
by_layer = defaultdict(list)
for e in msp.query("INSERT"):
try:
x, y, *_ = e.dxf.insert
if not in_plan(x, y):
continue
by_layer[e.dxf.layer].append(
{"name": e.dxf.name, "x": float(x), "y": float(y), "layer": e.dxf.layer}
)
except Exception:
continue
print("\nINSERT layers in plan:")
for layer, items in sorted(by_layer.items(), key=lambda kv: -len(kv[1])):
print(f" {layer}: {len(items)} top={Counter(i['name'] for i in items).most_common(5)}")
# circles might be outlets/lights
circles = []
for e in msp.query("CIRCLE"):
try:
x, y, *_ = e.dxf.center
if not in_plan(x, y):
continue
circles.append(
{
"x": float(x),
"y": float(y),
"r": float(e.dxf.radius),
"layer": e.dxf.layer,
}
)
except Exception:
continue
print("\nCIRCLE layers:", Counter(c["layer"] for c in circles).most_common(15))
print("CIRCLE radius top:", Counter(round(c["r"], 1) for c in circles).most_common(15))
# texts in plan that look like device tags
dev_txt = []
for e in msp.query("TEXT MTEXT"):
try:
if e.dxftype() == "TEXT":
content = (e.dxf.text or "").strip()
x, y, *_ = e.dxf.insert
h = float(e.dxf.height or 0)
else:
content = (e.text or "").replace("\\P", " ").strip()
x, y, *_ = e.dxf.insert
h = float(getattr(e.dxf, "char_height", 0) or 0)
if not in_plan(x, y):
continue
if any(
k in content
for k in (
"监控",
"摄像",
"门禁",
"压差",
"冰箱",
"通风",
"照明",
"E电",
"N电",
"灯",
"摄像头",
)
):
dev_txt.append(
{
"text": content[:80],
"x": float(x),
"y": float(y),
"h": h,
"layer": e.dxf.layer,
}
)
except Exception:
pass
print("\nDEVICE TEXTS in plan:", len(dev_txt))
for t in sorted(dev_txt, key=lambda a: (a["y"], a["x"])):
print(f" [{t['layer']}] ({t['x']:.0f},{t['y']:.0f}) {t['text']!r}")
# dump lighting inserts with coords
lights = by_layer.get("EQUIP-照明", [])
print("\nEQUIP-照明 inserts:", len(lights))
for i in sorted(lights, key=lambda a: (a["y"], a["x"])):
print(f" ({i['x']:.0f},{i['y']:.0f}) {i['name']}")
sec = by_layer.get("EQUIP-安防", [])
print("\nEQUIP-安防 inserts:", len(sec))
for i in sorted(sec, key=lambda a: (a["y"], a["x"])):
print(f" ({i['x']:.0f},{i['y']:.0f}) {i['name']}")
# also look at larger plan bbox for 咨询/准备/报告
PLAN2 = dict(xmin=290000, xmax=330000, ymin=-560000, ymax=-500000)
labels2 = []
for e in msp.query("TEXT"):
try:
content = (e.dxf.text or "").strip()
x, y, *_ = e.dxf.insert
if not (PLAN2["xmin"] <= x <= PLAN2["xmax"] and PLAN2["ymin"] <= y <= PLAN2["ymax"]):
continue
if any(k in content for k in ("咨询", "准备", "报告", "收样", "PCR", "缓冲", "测序", "文库", "样本")):
if len(content) < 25:
labels2.append((content, float(x), float(y), e.dxf.layer))
except Exception:
pass
print("\nALL room-ish TEXT in PLAN2:")
for t in sorted(labels2, key=lambda a: (-a[2], a[1])):
print(f" [{t[3]}] ({t[1]:.0f},{t[2]:.0f}) {t[0]!r}")
out = {
"plan": PLAN,
"room_labels": {k: v for k, v in ROOM_LABELS.items()},
"by_layer": {k: v for k, v in by_layer.items()},
"circles": circles,
"dev_txt": dev_txt,
}
(OUT / "_plan_devices.json").write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
print("wrote", OUT / "_plan_devices.json")
"""定位各专业平面图图纸范围与设备插入点。"""
from __future__ import annotations
import json
from collections import Counter, defaultdict
from pathlib import Path
import ezdxf
DXF = Path(r"C:\temp\jhcad\lab_plan.dxf")
OUT = Path(__file__).resolve().parent
doc = ezdxf.readfile(str(DXF))
msp = doc.modelspace()
# titles
titles = []
for e in msp.query("TEXT MTEXT"):
try:
if e.dxftype() == "TEXT":
t = (e.dxf.text or "").strip()
x, y, *_ = e.dxf.insert
else:
t = (e.text or "").replace("\\P", " ").strip()
x, y, *_ = e.dxf.insert
if any(
k in t
for k in (
"平面图",
"系统图",
"照明",
"自控",
"数字孪生",
"安防",
"弱电",
"监控",
"门禁",
)
):
if len(t) < 60:
titles.append((t, float(x), float(y), e.dxf.layer))
except Exception:
pass
print("TITLES:")
for t in sorted(titles, key=lambda a: (-a[2], a[1])):
print(f" [{t[3]}] ({t[1]:.0f},{t[2]:.0f}) {t[0]!r}")
# EQUIP-照明 / EQUIP-安防 / EQUIP-楼控 extents
for layer in ("EQUIP-照明", "EQUIP-安防", "EQUIP-楼控", "EQUIP-通讯", "EQUIP-箱柜", "WIRE-照明"):
pts = []
for e in msp.query("INSERT"):
if e.dxf.layer != layer:
continue
x, y, *_ = e.dxf.insert
pts.append((float(x), float(y), e.dxf.name))
if not pts:
print(f"\n{layer}: 0")
continue
xs = [p[0] for p in pts]
ys = [p[1] for p in pts]
print(
f"\n{layer}: n={len(pts)} X={min(xs):.0f}..{max(xs):.0f} Y={min(ys):.0f}..{max(ys):.0f}"
)
print(" names", Counter(p[2] for p in pts).most_common(10))
# cluster EQUIP-照明 into sheets by rough grid
lights = []
for e in msp.query("INSERT"):
if e.dxf.layer != "EQUIP-照明":
continue
x, y, *_ = e.dxf.insert
lights.append((float(x), float(y), e.dxf.name))
# bucket by 50k grid
buckets = defaultdict(list)
for x, y, name in lights:
buckets[(int(x // 50000), int(y // 50000))].append((x, y, name))
print("\nEQUIP-照明 grid buckets:")
for k, v in sorted(buckets.items(), key=lambda kv: -len(kv[1]))[:20]:
xs = [p[0] for p in v]
ys = [p[1] for p in v]
print(
f" cell {k}: n={len(v)} X={min(xs):.0f}..{max(xs):.0f} Y={min(ys):.0f}..{max(ys):.0f}"
)
# same for 安防
secs = []
for e in msp.query("INSERT"):
if e.dxf.layer != "EQUIP-安防":
continue
x, y, *_ = e.dxf.insert
secs.append((float(x), float(y), e.dxf.name))
buckets = defaultdict(list)
for x, y, name in secs:
buckets[(int(x // 50000), int(y // 50000))].append((x, y, name))
print("\nEQUIP-安防 grid buckets:")
for k, v in sorted(buckets.items(), key=lambda kv: -len(kv[1]))[:20]:
xs = [p[0] for p in v]
ys = [p[1] for p in v]
print(
f" cell {k}: n={len(v)} X={min(xs):.0f}..{max(xs):.0f} Y={min(ys):.0f}..{max(ys):.0f}"
)
# room labels near lighting sheet title at ~138565,-467194
print("\nRoom labels near lighting title (~140k,-470k):")
for e in msp.query("TEXT"):
t = (e.dxf.text or "").strip()
x, y, *_ = e.dxf.insert
if 100000 <= x <= 220000 and -520000 <= y <= -430000:
if any(k in t for k in ("测序", "文库", "样本", "咨询", "准备", "报告", "PCR", "缓冲", "走廊")):
print(f" ({x:.0f},{y:.0f}) [{e.dxf.layer}] {t!r}")
"""
从 N电照明平面图提取墙线估算房间,并从监控平面提取摄像头/门禁。
再与场景压差锚点做仿射对齐,输出可写回场景的点位表。
"""
from __future__ import annotations
import json
import math
from collections import defaultdict
from pathlib import Path
import ezdxf
DXF = Path(r"C:\temp\jhcad\lab_plan.dxf")
SCENE = Path(__file__).resolve().parents[1] / "_scene_check.json"
OUT = Path(__file__).resolve().parent
doc = ezdxf.readfile(str(DXF))
msp = doc.modelspace()
# N电照明平面(左侧)
LN = dict(xmin=104000, xmax=118500, ymin=-471000, ymax=-445500)
# 监控平面(与照明同比例,坐标不同)
MON = dict(xmin=300000, xmax=325000, ymin=-475000, ymax=-445000)
def in_box(x, y, b):
return b["xmin"] <= x <= b["xmax"] and b["ymin"] <= y <= b["ymax"]
# --- wall segments in LN ---
segs = []
for e in msp.query("LWPOLYLINE LINE"):
try:
layer = e.dxf.layer
if layer not in ("WALL", "墙", "WALL_LINE", "0"):
# still take WALL primarily
if "WALL" not in layer.upper() and layer not in ("DOOR",):
continue
if e.dxftype() == "LINE":
x1, y1, *_ = e.dxf.start
x2, y2, *_ = e.dxf.end
if in_box(x1, y1, LN) or in_box(x2, y2, LN):
segs.append(((float(x1), float(y1)), (float(x2), float(y2)), layer))
else:
pts = [(float(p[0]), float(p[1])) for p in e.get_points("xy")]
for a, b in zip(pts, pts[1:]):
if in_box(a[0], a[1], LN) or in_box(b[0], b[1], LN):
segs.append((a, b, layer))
if e.closed and len(pts) > 2:
segs.append((pts[-1], pts[0], layer))
except Exception:
pass
print("wall segs in LN", len(segs), "layers", Counter := __import__("collections").Counter(s[2] for s in segs))
print(Counter.most_common(10))
# lights N (left sheet only)
lights_n = []
lights_e = []
for e in msp.query("INSERT"):
if e.dxf.layer != "EQUIP-照明":
continue
x, y, *_ = e.dxf.insert
x, y = float(x), float(y)
name = e.dxf.name
if in_box(x, y, LN):
lights_n.append({"x": x, "y": y, "block": name, "kind": "N" if "00000073" in name or "01962" in name else ("E" if "02603" in name or "01963" in name else "?")})
if 118500 <= x <= 132000 and LN["ymin"] <= y <= LN["ymax"]:
lights_e.append({"x": x, "y": y, "block": name})
print("lights N", len(lights_n), "E", len(lights_e))
print("N blocks", __import__("collections").Counter(l["block"] for l in lights_n).most_common())
# Heuristic: on N sheet, 00000073 and 01962 = N电灯具, 02603 = E电应急(同图叠画)
# Separate by block family
n_lamps = [l for l in lights_n if "00000073" in l["block"] or l["block"].endswith("01962")]
e_lamps = [l for l in lights_n if "02603" in l["block"] or "01963" in l["block"]]
print("n_lamps", len(n_lamps), "e_lamps", len(e_lamps))
# cameras / access on monitor sheet
cams = []
doors = []
for e in msp.query("INSERT"):
x, y, *_ = e.dxf.insert
x, y = float(x), float(y)
if not in_box(x, y, MON):
continue
name = e.dxf.name
if e.dxf.layer != "EQUIP-安防":
continue
if "02353" in name:
cams.append({"x": x, "y": y, "block": name})
elif "02474" in name:
doors.append({"x": x, "y": y, "block": name})
print("cams", len(cams), "doors", len(doors))
for c in sorted(cams, key=lambda a: (-a["y"], a["x"])):
print(f" cam ({c['x']:.0f},{c['y']:.0f})")
for d in sorted(doors, key=lambda a: (-a["y"], a["x"])):
print(f" door ({d['x']:.0f},{d['y']:.0f})")
# Estimate floor footprint from lights N
all_pts = n_lamps + e_lamps
xs = [p["x"] for p in all_pts]
ys = [p["y"] for p in all_pts]
print(f"light footprint X {min(xs):.0f}..{max(xs):.0f} Y {min(ys):.0f}..{max(ys):.0f}")
print(f"size {(max(xs)-min(xs))/1000:.2f}m x {(max(ys)-min(ys))/1000:.2f}m (if units=mm)")
# Load scene pressure anchors as room centers (geo)
scene = json.loads(SCENE.read_text(encoding="utf-8"))
origin = scene["origin"]
R = 6378137.0
def en(lng, lat):
dE = (lng - origin["longitude"]) * math.cos(math.radians(origin["latitude"])) * math.pi / 180 * R
dN = (lat - origin["latitude"]) * math.pi / 180 * R
return dE, dN
def from_en(e, n):
lng = origin["longitude"] + e / (math.cos(math.radians(origin["latitude"])) * math.pi / 180 * R)
lat = origin["latitude"] + n / (math.pi / 180 * R)
return lng, lat
anchors = {}
for c in scene["components"]:
if c.get("type") != "marker":
continue
name = c.get("name") or ""
if not name.startswith("压差-"):
continue
p = c["params"]
key = name.replace("压差-", "")
e, n = en(p["lng"], p["lat"])
anchors[key] = {"lng": p["lng"], "lat": p["lat"], "e": e, "n": n, "h": p.get("height", 53.5)}
print("\nscene pressure anchors:")
for k, v in anchors.items():
print(f" {k}: E{v['e']:+.1f} N{v['n']:+.1f}")
# Map monitor sheet → lighting sheet by proportional transform using extents of security vs lights
# First get cam footprint
cxs = [c["x"] for c in cams]
cys = [c["y"] for c in cams]
print(f"cam footprint X {min(cxs):.0f}..{max(cxs):.0f} Y {min(cys):.0f}..{max(cys):.0f}")
# Save intermediate
out = {
"lights_n": n_lamps,
"lights_e": e_lamps,
"cams": cams,
"doors": doors,
"anchors": anchors,
"origin": origin,
"light_bbox": [min(xs), min(ys), max(xs), max(ys)],
"cam_bbox": [min(cxs), min(cys), max(cxs), max(cys)],
}
(OUT / "_devices_raw.json").write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
print("wrote", OUT / "_devices_raw.json")
"""解析电气自控布线 DXF:文本/块/图层分布,定位房间与设备。"""
from __future__ import annotations
import json
import math
import re
from collections import Counter, defaultdict
from pathlib import Path
import ezdxf
DXF = Path(r"C:\temp\jhcad\lab_plan.dxf")
OUT = Path(__file__).resolve().parent
doc = ezdxf.readfile(str(DXF))
msp = doc.modelspace()
print("dxf version", doc.dxfversion)
print("layers", len(doc.layers))
print("blocks", len(doc.blocks))
layer_names = [l.dxf.name for l in doc.layers]
print("sample layers:", layer_names[:40])
# entities by type
types = Counter(e.dxftype() for e in msp)
print("entity types", types.most_common(20))
# collect TEXT / MTEXT
texts = []
for e in msp.query("TEXT MTEXT"):
try:
if e.dxftype() == "TEXT":
content = e.dxf.text
x, y, *_ = e.dxf.insert
h = float(getattr(e.dxf, "height", 0) or 0)
else:
content = e.text
x, y, *_ = e.dxf.insert
h = float(getattr(e.dxf, "char_height", 0) or 0)
content = (content or "").replace("\\P", "\n").strip()
if not content:
continue
texts.append(
{
"text": content,
"x": float(x),
"y": float(y),
"h": h,
"layer": e.dxf.layer,
"type": e.dxftype(),
}
)
except Exception:
continue
print("texts", len(texts))
# Chinese room keywords
ROOM_KEYS = [
"测序",
"文库",
"样本",
"咨询",
"准备",
"报告",
"PCR",
"缓冲",
"走廊",
"气闸",
"洗消",
"更衣",
]
room_texts = [t for t in texts if any(k in t["text"] for k in ROOM_KEYS)]
print("room-like texts", len(room_texts))
for t in sorted(room_texts, key=lambda a: (-a["y"], a["x"]))[:80]:
print(f" [{t['layer']}] ({t['x']:.1f},{t['y']:.1f}) h={t['h']:.1f} {t['text'][:60]!r}")
# device-ish keywords
DEV_KEYS = [
"摄像头",
"监控",
"摄像机",
"门禁",
"读卡",
"冰箱",
"通风柜",
"排风柜",
"照明",
"灯",
"压差",
"温湿度",
"CCTV",
"IPC",
"NVR",
"应急",
"普通照明",
"配电",
]
dev_texts = [t for t in texts if any(k in t["text"].upper() or k in t["text"] for k in DEV_KEYS)]
print("device-like texts", len(dev_texts))
for t in sorted(dev_texts, key=lambda a: a["text"])[:100]:
print(f" [{t['layer']}] ({t['x']:.1f},{t['y']:.1f}) {t['text'][:80]!r}")
# INSERT blocks
inserts = []
for e in msp.query("INSERT"):
try:
name = e.dxf.name
x, y, *_ = e.dxf.insert
inserts.append({"name": name, "x": float(x), "y": float(y), "layer": e.dxf.layer})
except Exception:
continue
print("inserts", len(inserts))
print("block name top", Counter(i["name"] for i in inserts).most_common(40))
print("insert layers top", Counter(i["layer"] for i in inserts).most_common(30))
# extents
xs = [t["x"] for t in texts] + [i["x"] for i in inserts]
ys = [t["y"] for t in texts] + [i["y"] for i in inserts]
if xs:
print(f"extent X {min(xs):.2f}..{max(xs):.2f} Y {min(ys):.2f}..{max(ys):.2f}")
print(f"span {max(xs)-min(xs):.2f} x {max(ys)-min(ys):.2f}")
# dimensions
dims = []
for e in msp.query("DIMENSION"):
try:
dims.append(
{
"layer": e.dxf.layer,
"text": getattr(e.dxf, "text", None),
"defpoint": tuple(getattr(e.dxf, "defpoint", (0, 0, 0)))[:2],
}
)
except Exception:
pass
print("dimensions", len(dims))
out = {
"texts": texts,
"room_texts": room_texts,
"dev_texts": dev_texts,
"inserts": inserts[:5000],
"insert_name_counts": Counter(i["name"] for i in inserts).most_common(80),
"layers": layer_names,
"types": types.most_common(),
}
(OUT / "_dxf_extract.json").write_text(
json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8"
)
print("wrote", OUT / "_dxf_extract.json")
"""
保留场景原有 marker 名称/ID,按类型把每个点吸附到 CAD 对应设备点
(摄像头/门禁 ← 监控平面;照明 ← N/E 照明平面)。
压差锚点保持;冰箱/通风柜在房间内按 CAD 相对侧偏微调。
"""
from __future__ import annotations
import json
import math
from pathlib import Path
SCENE = Path(__file__).resolve().parents[1] / "_scene_check.json"
RAW = Path(__file__).resolve().parent / "_devices_raw.json"
KEY = Path(__file__).resolve().parent / "_key_sheets.json"
scene = json.loads(SCENE.read_text(encoding="utf-8"))
raw = json.loads(RAW.read_text(encoding="utf-8"))
key = json.loads(KEY.read_text(encoding="utf-8"))
origin = scene["origin"]
R = 6378137.0
HEIGHT = 53.5
def en(lng, lat):
dE = (lng - origin["longitude"]) * math.cos(math.radians(origin["latitude"])) * math.pi / 180 * R
dN = (lat - origin["latitude"]) * math.pi / 180 * R
return dE, dN
def from_en(e, n):
lng = origin["longitude"] + e / (math.cos(math.radians(origin["latitude"])) * math.pi / 180 * R)
lat = origin["latitude"] + n / (math.pi / 180 * R)
return lng, lat
# pressure anchors
anchors = {}
for c in scene["components"]:
if c.get("type") == "marker" and str(c.get("name", "")).startswith("压差-"):
p = c["params"]
anchors[c["name"].replace("压差-", "")] = en(p["lng"], p["lat"])
aes = [v[0] for v in anchors.values()]
ans = [v[1] for v in anchors.values()]
geo = (min(aes), min(ans), max(aes), max(ans))
def make_mapper(cad_pts):
xs = [p[0] for p in cad_pts]
ys = [p[1] for p in cad_pts]
x0, x1 = min(xs), max(xs)
y0, y1 = min(ys), max(ys)
e0, n0, e1, n1 = geo
pe, pn = (e1 - e0) * 0.06, (n1 - n0) * 0.06
e0p, e1p, n0p, n1p = e0 - pe, e1 + pe, n0 - pn, n1 + pn
def map_xy(x, y):
tx = (x - x0) / (x1 - x0) if x1 != x0 else 0.5
ty = (y - y0) / (y1 - y0) if y1 != y0 else 0.5
return e0p + tx * (e1p - e0p), n0p + ty * (n1p - n0p)
return map_xy
cams_cad = [(c["x"], c["y"]) for c in raw["cams"]]
doors_cad = [(d["x"], d["y"]) for d in raw["doors"]]
map_mon = make_mapper(cams_cad + doors_cad)
# lighting from key sheet left (N) and right (E) — E sheet shifted
lit = key["lighting_NE"]["inserts"]
n_pts = []
e_pts = []
for i in lit:
if i["layer"] != "EQUIP-照明":
continue
x, y = i["x"], i["y"]
# left column ~104k-118.5k = N sheet;right ~118.5k-132k = E sheet
if x < 118500:
# block heuristic
if "02603" in i["name"] or "01963" in i["name"]:
e_pts.append((x, y))
else:
n_pts.append((x, y))
else:
e_pts.append((x - 14000.0, y))
map_lit = make_mapper(n_pts + e_pts if (n_pts or e_pts) else cams_cad)
cad_n = [map_lit(x, y) for x, y in n_pts]
cad_e = [map_lit(x, y) for x, y in e_pts]
cad_cam = [map_mon(x, y) for x, y in cams_cad]
cad_door = [map_mon(x, y) for x, y in doors_cad]
print(f"cad targets cam={len(cad_cam)} door={len(cad_door)} lightN={len(cad_n)} lightE={len(cad_e)}")
def snap(points_en, targets):
"""Greedy nearest: each scene point → unique CAD target."""
used = set()
mapping = {}
# sort scene points by isolation? assign farthest first for stability
order = sorted(range(len(points_en)), key=lambda i: -min(
(points_en[i][0] - t[0]) ** 2 + (points_en[i][1] - t[1]) ** 2 for t in targets
) if targets else 0)
for i in order:
pe, pn = points_en[i]
best_j, best_d = None, 1e18
for j, (te, tn) in enumerate(targets):
if j in used:
continue
d = (pe - te) ** 2 + (pn - tn) ** 2
if d < best_d:
best_d, best_j = d, j
if best_j is None:
mapping[i] = (pe, pn, None)
continue
used.add(best_j)
te, tn = targets[best_j]
mapping[i] = (te, tn, math.sqrt(best_d))
return mapping
def markers(prefix=None, tag0=None):
out = []
for c in scene["components"]:
if c.get("type") != "marker":
continue
name = c.get("name") or ""
if prefix and not name.startswith(prefix):
continue
if tag0 and (c.get("params") or {}).get("tag0") != tag0:
continue
out.append(c)
return out
def apply_snap(group, targets, label):
pts = [en(c["params"]["lng"], c["params"]["lat"]) for c in group]
if not targets:
print(f"{label}: no CAD targets, skip")
return
mp = snap(pts, targets)
for i, c in enumerate(group):
e, n, dist = mp[i]
lng, lat = from_en(e, n)
c["params"]["lng"] = round(lng, 8)
c["params"]["lat"] = round(lat, 8)
c["params"]["height"] = HEIGHT
c["params"]["scale"] = 0.5
print(f" {c['name']:22} → E{e:+.1f} N{n:+.1f} (moved {dist:.1f}m)" if dist is not None else f" {c['name']}")
print("cameras")
apply_snap(markers(prefix="监控-"), cad_cam, "cam")
print("doors")
apply_snap(markers(prefix="门禁-"), cad_door, "door")
print("lightN")
apply_snap(markers(tag0="lightN"), cad_n, "lightN")
print("lightE")
apply_snap(markers(tag0="lightE"), cad_e, "lightE")
# pressure / fridge / vav: unify height; fridge/vav slight room-side offset from CAD-ish layout
for c in markers(tag0="pressure"):
c["params"]["height"] = HEIGHT
c["params"]["scale"] = 0.5
# fridge: place near room anchor with side bias from name
fridge_bias = {
"冰箱-测序区": ("测序区", -0.9, -0.4),
"冰箱-文库2": ("文库制备区2", -0.9, 0.5),
"冰箱-文库1": ("文库制备区1", -0.9, 0.5),
"冰箱-样本1": ("样本制备区1", -0.9, 0.4),
"冰箱-咨询室1": ("咨询室", 0.7, -0.4),
"冰箱-咨询室2": ("咨询室", 0.7, 0.4),
"冰箱-准备区1": ("准备区", -0.5, 0.7),
"冰箱-准备区2": ("准备区", 0.5, 0.7),
}
print("fridge/vav")
for c in markers(tag0="fridge"):
name = c["name"]
if name in fridge_bias:
room, de, dn = fridge_bias[name]
ae, an = anchors[room]
lng, lat = from_en(ae + de, an + dn)
c["params"]["lng"] = round(lng, 8)
c["params"]["lat"] = round(lat, 8)
c["params"]["height"] = HEIGHT
c["params"]["scale"] = 0.5
print(f" {name}")
for c in markers(tag0="vav"):
room = "文库制备区2" if "文库2" in c["name"] else "文库制备区1"
ae, an = anchors[room]
lng, lat = from_en(ae + 1.0, an + 0.2)
c["params"]["lng"] = round(lng, 8)
c["params"]["lat"] = round(lat, 8)
c["params"]["height"] = HEIGHT
c["params"]["scale"] = 0.5
print(f" {c['name']}")
# drop extra CAD-only door if added earlier
scene["components"] = [
c
for c in scene["components"]
if not (c.get("type") == "marker" and str(c.get("name", "")).startswith("门禁-CAD"))
]
SCENE.write_text(json.dumps(scene, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print("saved", SCENE)
# summary
hs = {
round((c.get("params") or {}).get("height", 0), 2)
for c in scene["components"]
if c.get("type") == "marker"
}
print("marker count", sum(1 for c in scene["components"] if c.get("type") == "marker"), "heights", hs)
"""Move 测序区 / 文库制备区2 / out-of-model corridor markers back onto the model.
Room targets are the floor points the saved bookmarks look at (yaw: 0=north,
positive yaw toward west). Only lng/lat change.
"""
from __future__ import annotations
import json
import math
from pathlib import Path
SCENE = Path(__file__).resolve().parents[1] / "_scene_check.json"
scene = json.loads(SCENE.read_text(encoding="utf-8"))
origin = scene["origin"]
R = 6378137.0
def en(lng, lat):
e = (lng - origin["longitude"]) * math.cos(math.radians(origin["latitude"])) * math.pi / 180 * R
n = (lat - origin["latitude"]) * math.pi / 180 * R
return e, n
def from_en(e, n):
lng = origin["longitude"] + e / (math.cos(math.radians(origin["latitude"])) * math.pi / 180 * R)
lat = origin["latitude"] + n / (math.pi / 180 * R)
return lng, lat
def is_lib2(name: str) -> bool:
return "文库制备区2" in name or "文库制备2" in name or "文库2" in name
def is_seq(name: str) -> bool:
return "测序" in name
# Floor points in front of the room bookmarks (inside the visible rooms).
SEQ_TARGET = (-12.9, 11.0)
LIB2_TARGET = (-14.8, 9.0)
# Corridor spine inside the model, south → north.
CORRIDOR = {
"门禁-后走廊": (-13.5, 1.0),
"监控-后走廊": (-13.1, 2.2),
"应急照明-走廊": (-13.6, 3.6),
"压差-PCR走廊": (-13.3, 5.0),
"普通照明-走廊": (-13.7, 6.5),
}
def markers():
return [c for c in scene["components"] if c.get("type") == "marker"]
def centroid(pred):
pts = [en(c["params"]["lng"], c["params"]["lat"]) for c in markers() if pred(c["name"])]
if not pts:
raise SystemExit("empty group")
return sum(p[0] for p in pts) / len(pts), sum(p[1] for p in pts) / len(pts)
def shift_group(pred, target, label):
ce, cn = centroid(pred)
de, dn = target[0] - ce, target[1] - cn
print(f"{label} centroid E{ce:+.2f} N{cn:+.2f} shift dE{de:+.2f} dN{dn:+.2f}")
for c in markers():
if not pred(c["name"]):
continue
e, n = en(c["params"]["lng"], c["params"]["lat"])
lng, lat = from_en(e + de, n + dn)
c["params"]["lng"] = round(lng, 8)
c["params"]["lat"] = round(lat, 8)
print(f" {c['name']:22} E{e+de:+.2f} N{n+dn:+.2f}")
shift_group(is_seq, SEQ_TARGET, "测序")
shift_group(is_lib2, LIB2_TARGET, "文库2")
print("corridor")
for c in markers():
name = c["name"]
if name not in CORRIDOR:
continue
e, n = CORRIDOR[name]
lng, lat = from_en(e, n)
c["params"]["lng"] = round(lng, 8)
c["params"]["lat"] = round(lat, 8)
print(f" {name:22} E{e:+.2f} N{n:+.2f}")
SCENE.write_text(json.dumps(scene, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
print("saved", SCENE)
...@@ -27,7 +27,7 @@ http { ...@@ -27,7 +27,7 @@ http {
proxy_read_timeout 86400; proxy_read_timeout 86400;
} }
location /rtengine/ { location /rtengine/ {
proxy_pass http://192.168.32.121:6088/; proxy_pass http://127.0.0.1:6088/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $proxy_host; proxy_set_header Host $proxy_host;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
...@@ -36,7 +36,7 @@ http { ...@@ -36,7 +36,7 @@ http {
proxy_buffering off; proxy_buffering off;
} }
location /peer-stream { location /peer-stream {
proxy_pass http://192.168.32.121:6088/peer-stream; proxy_pass http://127.0.0.1:6088/peer-stream;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
...@@ -45,7 +45,7 @@ http { ...@@ -45,7 +45,7 @@ http {
proxy_buffering off; proxy_buffering off;
} }
location /designer/ { location /designer/ {
proxy_pass http://192.168.32.121:6088/designer/; proxy_pass http://127.0.0.1:6088/designer/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $proxy_host; proxy_set_header Host $proxy_host;
} }
......
...@@ -27,7 +27,7 @@ http { ...@@ -27,7 +27,7 @@ http {
proxy_read_timeout 86400; proxy_read_timeout 86400;
} }
location /rtengine/ { location /rtengine/ {
proxy_pass http://192.168.32.121:6088/; proxy_pass http://127.0.0.1:6088/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $proxy_host; proxy_set_header Host $proxy_host;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
...@@ -36,7 +36,7 @@ http { ...@@ -36,7 +36,7 @@ http {
proxy_buffering off; proxy_buffering off;
} }
location /peer-stream { location /peer-stream {
proxy_pass http://192.168.32.121:6088/peer-stream; proxy_pass http://127.0.0.1:6088/peer-stream;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
...@@ -45,7 +45,7 @@ http { ...@@ -45,7 +45,7 @@ http {
proxy_buffering off; proxy_buffering off;
} }
location /designer/ { location /designer/ {
proxy_pass http://192.168.32.121:6088/designer/; proxy_pass http://127.0.0.1:6088/designer/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $proxy_host; proxy_set_header Host $proxy_host;
} }
......
import{d as f,u as h,c as n,b as t,t as e,h as i,F as u,B as c,o,l as b}from"./index-BRA9HJ0O.js";import{u as v}from"./realtime-BbiS_VD3.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"},z=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{z 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 w,u as z,c as i,b as s,a as B,t as e,h as r,F as h,B as m,z as N,y as R,i as S,o as a,s as x,C as E,n as I,E as v,I as O}from"./index-BRA9HJ0O.js";import{u as V}from"./useClock-FtQkLZqk.js";const D={class:"tops-root"},H={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"},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=w({__name:"AdminLayout",setup(Z){const n=z(),c=S(),d=O(),{time:b}=V(),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"],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=R(()=>[{to:"/admin/devices",label:"设备管理",hint:"点位 / 网关",tone:"accent",icon:"devices"},{to:"/admin/cameras",label:"摄像头管理",hint:"列表 / 三维打点",tone:"accent",icon:"cameras"},{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 y(){n.logout(),c.push("/login")}function u(){c.push("/dashboard")}return(l,t)=>{var p,_;const f=v("RouterLink"),C=v("RouterView");return a(),i("div",D,[s("header",H,[s("div",{class:"tops-brand",onClick:u},[...t[0]||(t[0]=[B('<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:y},"退出")])]),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(),x(f,{key:o.to,to:o.to,class:I(["tops-menu__item",[`tops-tone--${o.tone}`,{"is-on":M(o.to)}]])},{default:E(()=>[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};
.icon[data-v-682d4798]{display:block;flex:0 0 auto}.logo[data-v-682d4798]{width:34px;height:34px}.p-icon[data-v-682d4798]{width:18px;height:18px}.m-icon[data-v-682d4798]{width:20px;height:20px}.v-icon[data-v-682d4798]{width:15px;height:15px}.f-icon[data-v-682d4798]{width:14px;height:14px}.a-icon[data-v-682d4798]{width:18px;height:18px}.corner[data-v-682d4798]{width:22px;height:22px}.pin[data-v-682d4798]{width:16px;height:16px}.k-icon[data-v-682d4798]{width:40px;height:40px}
.icon[data-v-efd0f58c]{display:block;flex:0 0 auto}.logo[data-v-efd0f58c]{width:34px;height:34px}.p-icon[data-v-efd0f58c]{width:18px;height:18px}.m-icon[data-v-efd0f58c]{width:20px;height:20px}.v-icon[data-v-efd0f58c]{width:15px;height:15px}.f-icon[data-v-efd0f58c]{width:14px;height:14px}.a-icon[data-v-efd0f58c]{width:18px;height:18px}.corner[data-v-efd0f58c]{width:22px;height:22px}.pin[data-v-efd0f58c]{width:16px;height:16px}.k-icon[data-v-efd0f58c]{width:40px;height:40px}
.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)}
.mask[data-v-980a3158]{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-980a3158]{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-980a3158]{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-980a3158]{margin:0;font-size:16px;color:#f4feff;letter-spacing:.06em}.hint[data-v-980a3158]{margin:0;padding:8px 16px;font-size:12px;color:#7eb6d6;background:#00284859}.body[data-v-980a3158]{flex:1;min-height:0;display:grid;grid-template-columns:280px 1fr}.icon-pane[data-v-980a3158]{display:flex;flex-direction:column;min-height:0;border-right:1px solid rgba(0,247,255,.14);background:#001024a6}.icon-head[data-v-980a3158]{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-980a3158]{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-980a3158]{margin:8px 12px 0;font-size:12px;color:#ffb0b0}.icon-grid[data-v-980a3158]{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-980a3158]{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-980a3158]{width:36px;height:36px;object-fit:contain}.icon-item span[data-v-980a3158]{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.icon-item[data-v-980a3158]:hover{border-color:#00f7ff73}.icon-item.active[data-v-980a3158]{border-color:#00f7ff;background:#0064a059;color:#fff;box-shadow:0 0 0 1px #00f7ff40 inset}.icon-current[data-v-980a3158]{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-980a3158]{width:22px;height:22px;object-fit:contain}.icon-current b[data-v-980a3158]{color:#f4feff;font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.frame-wrap[data-v-980a3158]{min-height:0;background:#000}.frame[data-v-980a3158]{width:100%;height:100%;border:0;display:block}.foot[data-v-980a3158]{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-980a3158]{font-family:var(--digital-font);font-size:13px;color:var(--accent)}.coords.muted[data-v-980a3158]{color:var(--panel-muted)}.actions[data-v-980a3158]{display:flex;gap:8px}.mask[data-v-edc21f1c]{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-edc21f1c]{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-edc21f1c]{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-edc21f1c]{margin:0;font-size:16px;color:#f4feff}.body[data-v-edc21f1c]{padding:12px;min-height:360px;background:#000}.empty[data-v-edc21f1c]{min-height:320px;display:grid;place-items:center;color:#b9d6f08c;font-size:13px}.btn[data-v-edc21f1c]{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-edc21f1c]:hover{border-color:#00f7ff8c}.cameras-page[data-v-29152d20]{min-height:0}.list-panel[data-v-29152d20]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-29152d20]{display:flex;gap:8px}.create-row[data-v-29152d20]{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end;margin-bottom:12px}.create-row .field[data-v-29152d20]{min-width:120px}.table-wrap[data-v-29152d20]{flex:1;min-height:0;overflow:auto}.ops[data-v-29152d20]{white-space:nowrap}.ops .btn[data-v-29152d20]{margin-right:4px}.mono[data-v-29152d20]{font-family:var(--digital-font);font-size:12px}.st[data-v-29152d20]{display:inline-block;padding:1px 6px;border-radius:2px;font-size:11px;margin-right:4px}.st.online[data-v-29152d20]{background:#52c41a26;color:#7dff9a}.st.offline[data-v-29152d20]{background:#8ea3b826;color:#9bb0c4}.st.alarm[data-v-29152d20]{background:#ff4d4f26;color:#ffb0b0}.st.off[data-v-29152d20]{background:#faad141f;color:#ffd666}.empty[data-v-29152d20]{text-align:center;color:var(--panel-muted);padding:24px}.msg.ok[data-v-29152d20]{color:#7dcea0}.msg.err[data-v-29152d20]{color:#ffb0b0}.tip[data-v-29152d20]{margin-top:10px;font-size:12px;color:var(--panel-muted)}
.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,q as k,c as o,b as t,z as p,F as B,B as S,h as C,r as n,y as v,o as r,t as l,x as E,l as h,_ as V}from"./index-BRA9HJ0O.js";import{E as y,m as f}from"./historyChart-ytIpUdNw.js";import{u as F}from"./realtime-BbiS_VD3.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"},L={class:"chart-card"},N={class:"table"},R=b({__name:"ColdView",setup(j){const _=F(),d=n([]),u=n([]),a=n(!1);let i;const g=v(()=>f(d.value,{unit:"℃",maxSeries:12})),x=v(()=>f(u.value,{unit:"%",maxSeries:12}));async function c(){a.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{a.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:a.value,"empty-text":"温度历史积累中"},null,8,["option","loading"])]),t("div",L,[e[1]||(e[1]=t("div",{class:"chart-title"},"湿度 %RH",-1)),p(y,{option:x.value,loading:a.value,"empty-text":"湿度历史积累中"},null,8,["option","loading"])])]),t("table",N,[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,l(s.name),1),t("td",{style:E({color:s.temp>-19?"var(--amber)":"var(--green)"})},l(s.temp.toFixed(1))+" ℃ ",5),t("td",null,l(s.humidity)+" %",1),t("td",null,l(s.temp>-19?"超限":"正常"),1)]))),128))])])])]))}}),P=V(R,[["__scopeId","data-v-00aa9c1c"]]);export{P as default};
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-00aa9c1c]{display:flex;min-height:0}.charts[data-v-00aa9c1c]{display:grid;grid-template-columns:1fr 1fr;gap:10px;min-height:260px;flex:1}.chart-card[data-v-00aa9c1c]{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-00aa9c1c]{font-size:12px;color:var(--label);margin-bottom:4px}.chart-card[data-v-00aa9c1c] .echart-wrap{flex:1;min-height:200px}
.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}
.devices-page[data-v-4d7cc5d2]{min-height:0}.list-panel[data-v-4d7cc5d2]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden;gap:10px}.panel-title[data-v-4d7cc5d2]{gap:10px}.head-actions[data-v-4d7cc5d2]{display:flex;flex-wrap:wrap;align-items:center;gap:8px;justify-content:flex-end}.tab-row[data-v-4d7cc5d2],.filters[data-v-4d7cc5d2]{display:flex;flex-wrap:wrap;gap:10px;align-items:flex-end}.field[data-v-4d7cc5d2]{display:flex;flex-direction:column;gap:4px;min-width:140px}.field label[data-v-4d7cc5d2]{font-size:12px;opacity:.75}.field input[data-v-4d7cc5d2],.field select[data-v-4d7cc5d2],.csv-area[data-v-4d7cc5d2]{background:#060c14a6;box-shadow:0 0 0 1px #3dbeff47 inset;border:none;color:inherit;border-radius:2px;padding:6px 8px}.csv-area[data-v-4d7cc5d2]{width:100%;min-height:160px;margin-top:4px;font-family:var(--digital-font),ui-monospace,monospace;font-size:12px}.filter-hint[data-v-4d7cc5d2]{font-size:12px;color:var(--panel-muted);margin-bottom:6px}.check[data-v-4d7cc5d2]{display:flex;align-items:center;gap:6px;font-size:13px;margin-bottom:4px}.table-wrap[data-v-4d7cc5d2]{flex:1;min-height:0;overflow:auto}.msg.ok[data-v-4d7cc5d2]{color:var(--ok);font-size:13px}.msg.err[data-v-4d7cc5d2]{color:#ffb0b0;font-size:13px}.hint[data-v-4d7cc5d2]{font-size:12px;opacity:.7;margin-top:10px}.mono[data-v-4d7cc5d2]{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-4d7cc5d2]{display:flex;gap:6px}.row-click[data-v-4d7cc5d2]{cursor:pointer}.row-click:hover td[data-v-4d7cc5d2]{background:#3dbeff1f}.device-meta[data-v-4d7cc5d2]{display:flex;flex-wrap:wrap;gap:10px 16px;margin-bottom:12px}.device-meta span[data-v-4d7cc5d2]{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-4d7cc5d2]{font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--panel-muted)}.device-meta b[data-v-4d7cc5d2]{font-size:12px;font-weight:600;color:var(--panel-title)}.detail-table-wrap[data-v-4d7cc5d2]{max-height:min(68vh,720px);overflow:auto}.eng[data-v-4d7cc5d2]{font-family:var(--digital-font),ui-monospace,monospace;font-size:14px;font-weight:700;color:var(--accent)}.eng.muted[data-v-4d7cc5d2]{color:var(--panel-muted);font-weight:600}.ts[data-v-4d7cc5d2]{font-size:11px;color:var(--panel-muted)}.empty[data-v-4d7cc5d2]{text-align:center;color:var(--panel-muted);padding:28px 10px!important}.gw-status[data-v-4d7cc5d2]{margin-bottom:8px}.modal-mask[data-v-4d7cc5d2]{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-4d7cc5d2]{width:min(640px,92vw);max-height:90vh;overflow:auto}.modal-lg[data-v-4d7cc5d2]{width:min(860px,96vw)}.modal-xl[data-v-4d7cc5d2]{width:min(1280px,98vw);max-height:92vh}.modal-actions[data-v-4d7cc5d2]{margin-top:14px;display:flex;gap:10px}.form-grid[data-v-4d7cc5d2]{display:grid;grid-template-columns:1fr 1fr;gap:10px}.write-cell[data-v-4d7cc5d2]{display:inline-flex;align-items:center;gap:6px;min-width:140px;white-space:nowrap}.ops[data-v-4d7cc5d2]{white-space:nowrap}.ops .btn[data-v-4d7cc5d2]{margin-right:4px}.write-input[data-v-4d7cc5d2]{width:88px;padding:4px 6px;border-radius:6px;border:1px solid rgba(255,255,255,.12);background:#00000040;color:inherit}.bool-on[data-v-4d7cc5d2],.bool-off[data-v-4d7cc5d2]{min-width:44px;padding:4px 10px}.muted[data-v-4d7cc5d2]{color:var(--panel-muted)}.msg.ok[data-v-4d7cc5d2]{color:#7dcea0}
.history-page[data-v-4ad3bfa1]{display:flex;min-height:0}.history-panel[data-v-4ad3bfa1]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.filters[data-v-4ad3bfa1]{display:flex;flex-wrap:wrap;gap:10px 14px;align-items:end;padding:8px 4px 12px}.filters label[data-v-4ad3bfa1]{display:flex;flex-direction:column;gap:4px;font-size:11px;color:var(--label, #8eb0c4)}.filters select[data-v-4ad3bfa1]{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-4ad3bfa1]{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-4ad3bfa1]:disabled{opacity:.5;cursor:default}.chart-box[data-v-4ad3bfa1]{flex:1;min-height:280px;border:1px solid rgba(0,247,255,.15);border-radius:6px;background:#04102066;padding:6px}.table-wrap[data-v-4ad3bfa1]{margin-top:10px;max-height:220px;overflow:auto}.muted[data-v-4ad3bfa1]{text-align:center;color:var(--label)}.err[data-v-4ad3bfa1]{color:#ffb0b0;font-size:12px;margin-bottom:8px}
import{d as $,p as M,j as D,c as u,b as t,t as s,g,e as y,L as h,F as k,B as V,f as S,z as H,r,y as w,l as F,o as i,_ as O}from"./index-BRA9HJ0O.js";import{E as P,m as j}from"./historyChart-ytIpUdNw.js";const q={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(()=>j(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",q,[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,[H(P,{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=O(te,[["__scopeId","data-v-4ad3bfa1"]]);export{oe as default}; 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};
.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}
.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};
import{d as g,u as x,c as A,b as s,t as i,h as t,z as P,n as S,e as k,v as w,r as H,y as r,l as T,o as V,_ as C}from"./index-BRA9HJ0O.js";import{u as E}from"./realtime-BbiS_VD3.js";import{E as F,m as O}from"./historyChart-ytIpUdNw.js";import{u as d}from"./useMetricHistory-DAj0vDDt.js";const z={class:"page-fill"},B={class:"panel"},M={class:"panel-head"},N={class:"badge"},D={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=E(),n=H(180),{series:u,loading:v}=d(()=>({source:"hvac",metric:"supply_temp"})),{series:c,loading:p}=d(()=>({source:"hvac",metric:"supply_pressure"})),{series:m,loading:_}=d(()=>({source:"hvac",metric:"exhaust_pressure"})),h=r(()=>O([...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:n.value})}),alert("送风压力设定已下发(Mock)"))}return(o,e)=>(V(),A("div",z,[s("section",B,[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",D,[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(F,{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=>n.value=f),type:"number",disabled:!t(a).isAdmin},null,8,ts),[[w,n.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=C(as,[["__scopeId","data-v-310ea492"]]);export{vs as default};
.hist[data-v-310ea492]{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-310ea492]{font-size:12px;color:var(--label);margin-bottom:4px}.hist[data-v-310ea492] .echart-wrap{height:200px}
.light-page[data-v-1aa8fc55]{gap:12px}.light-tabs[data-v-1aa8fc55]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-1aa8fc55]{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-1aa8fc55]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-1aa8fc55]{flex:1}.msg[data-v-1aa8fc55]{font-size:12px;color:var(--cyan-lt)}.hint[data-v-1aa8fc55]{font-size:12px;color:var(--label);line-height:1.6;margin-bottom:12px}.hint.inline[data-v-1aa8fc55]{margin-bottom:0}.foot-hint[data-v-1aa8fc55]{margin-top:10px;font-size:12px;color:var(--label)}.form-grid.three[data-v-1aa8fc55]{grid-template-columns:repeat(3,1fr);max-width:1100px}.row-actions[data-v-1aa8fc55]{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:14px}.head-right[data-v-1aa8fc55]{display:flex;align-items:center;gap:8px}.switch-field .switch[data-v-1aa8fc55]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.list-panel[data-v-1aa8fc55],.config-panel[data-v-1aa8fc55]{flex:1;min-height:0;display:flex;flex-direction:column}.table-wrap[data-v-1aa8fc55]{flex:1;min-height:0;overflow:auto}.ops[data-v-1aa8fc55]{display:flex;flex-wrap:wrap;gap:6px}.empty[data-v-1aa8fc55]{text-align:center;color:var(--label);padding:24px!important}.muted[data-v-1aa8fc55]{color:var(--label);font-size:11px}.st[data-v-1aa8fc55],.pwr[data-v-1aa8fc55]{display:inline-block;padding:1px 8px;border-radius:4px;font-size:11px}.st.online[data-v-1aa8fc55]{background:#2fd6a126;color:var(--green)}.st.offline[data-v-1aa8fc55]{background:#8eb4d41f;color:var(--muted)}.st.fault[data-v-1aa8fc55]{background:#f0b4292e;color:var(--amber)}.pwr.on[data-v-1aa8fc55]{background:#fbbf2433;color:#fbbf24}.pwr.off[data-v-1aa8fc55]{background:#8eb4d41f;color:var(--muted)}.switch-btn.is-on[data-v-1aa8fc55]{border-color:#fbbf248c;background:#fbbf2438;color:#ffe08a}.switch-btn.is-on[data-v-1aa8fc55]:hover:not(:disabled){background:#fbbf2461}.switch-btn.is-off[data-v-1aa8fc55]{border-color:#3ac8ff73;background:#1a6fff47;color:#eaf9ff}.switch-btn.is-off[data-v-1aa8fc55]: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-BRA9HJ0O.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-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};
.line-chart[data-v-7577b21a]{position:relative;width:100%;min-height:140px}.svg[data-v-7577b21a]{width:100%;height:100%;display:block;overflow:visible}.axis[data-v-7577b21a]{fill:#7fa6c9e6;font-size:11px;font-family:var(--font-num)}.tip[data-v-7577b21a]{position:absolute;top:4px;transform:translate(-50%);display:flex;flex-direction:column;gap:2px;padding:6px 10px;border-radius:6px;background:#06142aeb;border:1px solid rgba(62,200,255,.35);pointer-events:none;white-space:nowrap;z-index:2}.tip b[data-v-7577b21a]{font-size:11px;color:#fff}.tip span[data-v-7577b21a]{font-size:12px;color:var(--cyan-lt);font-family:var(--font-num)}.billing-page[data-v-75284264]{gap:12px}.billing-tabs[data-v-75284264]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-75284264]{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-75284264]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-75284264]{flex:1}.msg[data-v-75284264]{font-size:12px;color:var(--cyan-lt)}.metric-grid[data-v-75284264]{display:grid;gap:12px;flex:0 0 auto}.metric-grid.three[data-v-75284264]{grid-template-columns:repeat(3,1fr)}.metric-grid.four[data-v-75284264]{grid-template-columns:repeat(4,1fr)}.metric-card[data-v-75284264]{padding:16px 18px}.metric-card .m-label[data-v-75284264]{font-size:12px;color:var(--label);margin-bottom:8px}.metric-card .m-value[data-v-75284264]{font-size:28px;font-weight:700;font-family:var(--font-num);color:#fff;line-height:1.1}.metric-card .m-sub[data-v-75284264]{margin-top:8px;font-size:11px;color:var(--muted)}.tone-amber .m-value[data-v-75284264]{color:#ffcf5c}.tone-cyan .m-value[data-v-75284264]{color:var(--cyan-lt)}.tone-green .m-value[data-v-75284264]{color:var(--green)}.tone-violet .m-value[data-v-75284264]{color:#c4b5fd}.usage-charts[data-v-75284264]{flex:0 0 auto;display:grid;grid-template-columns:repeat(3,1fr);gap:12px;align-items:start}.usage-charts .chart-panel[data-v-75284264]{height:fit-content;padding-bottom:10px}.chart-panel[data-v-75284264]{min-height:0;display:flex;flex-direction:column}.month-trend[data-v-75284264]{flex:0 0 auto}.split[data-v-75284264]{flex:1;min-height:0;display:grid;grid-template-columns:1fr 1.2fr;gap:12px}.split .panel[data-v-75284264]{min-height:0;display:flex;flex-direction:column}.kind-list[data-v-75284264]{display:flex;flex-direction:column;gap:14px}.kind-row[data-v-75284264]{display:grid;grid-template-columns:120px 1fr 140px;gap:12px;align-items:center}.kind-name[data-v-75284264]{display:block;font-size:13px;font-weight:600;color:#fff}.kind-usage[data-v-75284264]{font-size:11px;color:var(--label)}.kind-bar[data-v-75284264]{height:10px;border-radius:5px;background:#ffffff0f;overflow:hidden}.kind-bar i[data-v-75284264]{display:block;height:100%;border-radius:5px}.kind-row.power .kind-bar i[data-v-75284264]{background:linear-gradient(90deg,#d97706,#fbbf24)}.kind-row.water .kind-bar i[data-v-75284264]{background:linear-gradient(90deg,#0284c7,#38bdf8)}.kind-row.cooling .kind-bar i[data-v-75284264]{background:linear-gradient(90deg,#0891b2,#67e8f9)}.kind-cost[data-v-75284264]{text-align:right;font-family:var(--font-num);font-size:13px;color:#fff}.kind-cost em[data-v-75284264]{font-style:normal;color:var(--label);margin-left:6px;font-size:11px}.tariff-panel[data-v-75284264]{flex:1;min-height:0;overflow:auto}.tariff-grid[data-v-75284264]{grid-template-columns:repeat(3,1fr);max-width:960px}.switch-field .switch[data-v-75284264]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.tariff-actions[data-v-75284264]{display:flex;align-items:center;gap:14px;margin-top:18px;margin-bottom:12px}.hint[data-v-75284264]{font-size:12px;color:var(--label)}.report-panel[data-v-75284264]{flex:1;min-height:0;display:flex;flex-direction:column}.report-tools[data-v-75284264]{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--label)}.report-tools input[type=date][data-v-75284264]{margin-left:4px;background:#051a3db3;border:1px solid rgba(62,200,255,.3);color:#fff;border-radius:6px;padding:4px 8px}.report-summary[data-v-75284264]{display:flex;flex-wrap:wrap;gap:12px 18px;font-size:12px;color:var(--label);padding:8px 0 12px;border-bottom:1px solid rgba(62,200,255,.12);margin-bottom:8px}.report-summary b[data-v-75284264]{color:var(--cyan-lt);font-family:var(--font-num)}.table-wrap[data-v-75284264]{flex:1;min-height:0;overflow:auto}.table-wrap[data-v-75284264] thead th{position:sticky;top:0;z-index:2;background:#0a1a30;box-shadow:0 1px #3ec8ff38}.loading[data-v-75284264]{padding:24px;color:var(--label);font-size:13px}
.line-chart[data-v-a7e520cd]{position:relative;width:100%;min-height:140px}.svg[data-v-a7e520cd]{width:100%;height:100%;display:block;overflow:visible}.axis[data-v-a7e520cd]{fill:#7fa6c9e6;font-size:11px;font-family:var(--font-num)}.tip[data-v-a7e520cd]{position:absolute;top:4px;transform:translate(-50%);display:flex;flex-direction:column;gap:2px;padding:6px 10px;border-radius:6px;background:#06142aeb;border:1px solid rgba(62,200,255,.35);pointer-events:none;white-space:nowrap;z-index:2}.tip b[data-v-a7e520cd]{font-size:11px;color:#fff}.tip span[data-v-a7e520cd]{font-size:12px;color:var(--cyan-lt);font-family:var(--font-num)}.admin-billing[data-v-a9526d06]{height:100%;min-height:0;overflow:auto;display:flex;flex-direction:column;padding:4px 2px 12px;box-sizing:border-box}.billing-page[data-v-a9526d06]{gap:12px}.billing-tabs[data-v-a9526d06]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-a9526d06]{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-a9526d06]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-a9526d06]{flex:1}.msg[data-v-a9526d06]{font-size:12px;color:var(--cyan-lt)}.metric-grid[data-v-a9526d06]{display:grid;gap:12px;flex:0 0 auto}.metric-grid.three[data-v-a9526d06]{grid-template-columns:repeat(3,1fr)}.metric-grid.four[data-v-a9526d06]{grid-template-columns:repeat(4,1fr)}.metric-card[data-v-a9526d06]{padding:16px 18px}.metric-card .m-label[data-v-a9526d06]{font-size:12px;color:var(--label);margin-bottom:8px}.metric-card .m-value[data-v-a9526d06]{font-size:28px;font-weight:700;font-family:var(--font-num);color:#fff;line-height:1.1}.metric-card .m-sub[data-v-a9526d06]{margin-top:8px;font-size:11px;color:var(--muted)}.tone-amber .m-value[data-v-a9526d06]{color:#ffcf5c}.tone-cyan .m-value[data-v-a9526d06]{color:var(--cyan-lt)}.tone-green .m-value[data-v-a9526d06]{color:var(--green)}.tone-violet .m-value[data-v-a9526d06]{color:#c4b5fd}.usage-charts[data-v-a9526d06]{flex:0 0 auto;display:grid;grid-template-columns:repeat(3,1fr);gap:12px;align-items:start}.usage-charts .chart-panel[data-v-a9526d06]{height:fit-content;padding-bottom:10px}.chart-panel[data-v-a9526d06]{min-height:0;display:flex;flex-direction:column}.month-trend[data-v-a9526d06]{flex:0 0 auto}.split[data-v-a9526d06]{flex:1;min-height:0;display:grid;grid-template-columns:1fr 1.2fr;gap:12px}.split .panel[data-v-a9526d06]{min-height:0;display:flex;flex-direction:column}.kind-list[data-v-a9526d06]{display:flex;flex-direction:column;gap:14px}.kind-row[data-v-a9526d06]{display:grid;grid-template-columns:120px 1fr 140px;gap:12px;align-items:center}.kind-name[data-v-a9526d06]{display:block;font-size:13px;font-weight:600;color:#fff}.kind-usage[data-v-a9526d06]{font-size:11px;color:var(--label)}.kind-bar[data-v-a9526d06]{height:10px;border-radius:5px;background:#ffffff0f;overflow:hidden}.kind-bar i[data-v-a9526d06]{display:block;height:100%;border-radius:5px}.kind-row.power .kind-bar i[data-v-a9526d06]{background:linear-gradient(90deg,#d97706,#fbbf24)}.kind-row.water .kind-bar i[data-v-a9526d06]{background:linear-gradient(90deg,#0284c7,#38bdf8)}.kind-row.cooling .kind-bar i[data-v-a9526d06]{background:linear-gradient(90deg,#0891b2,#67e8f9)}.kind-cost[data-v-a9526d06]{text-align:right;font-family:var(--font-num);font-size:13px;color:#fff}.kind-cost em[data-v-a9526d06]{font-style:normal;color:var(--label);margin-left:6px;font-size:11px}.tariff-panel[data-v-a9526d06]{flex:1;min-height:0;overflow:auto}.tariff-grid[data-v-a9526d06]{grid-template-columns:repeat(3,1fr);max-width:960px}.switch-field .switch[data-v-a9526d06]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.tariff-actions[data-v-a9526d06]{display:flex;align-items:center;gap:14px;margin-top:18px;margin-bottom:12px}.hint[data-v-a9526d06]{font-size:12px;color:var(--label)}.report-panel[data-v-a9526d06]{flex:1;min-height:0;display:flex;flex-direction:column}.report-tools[data-v-a9526d06]{display:flex;align-items:center;gap:10px;font-size:12px;color:var(--label)}.report-tools input[type=date][data-v-a9526d06]{margin-left:4px;background:#051a3db3;border:1px solid rgba(62,200,255,.3);color:#fff;border-radius:6px;padding:4px 8px}.report-summary[data-v-a9526d06]{display:flex;flex-wrap:wrap;gap:12px 18px;font-size:12px;color:var(--label);padding:8px 0 12px;border-bottom:1px solid rgba(62,200,255,.12);margin-bottom:8px}.report-summary b[data-v-a9526d06]{color:var(--cyan-lt);font-family:var(--font-num)}.table-wrap[data-v-a9526d06]{flex:1;min-height:0;overflow:auto}.table-wrap[data-v-a9526d06] thead th{position:sticky;top:0;z-index:2;background:#0a1a30;box-shadow:0 1px #3ec8ff38}.loading[data-v-a9526d06]{padding:24px;color:var(--label);font-size:13px}
.panel-box[data-v-cff92950]{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-cff92950]{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-cff92950]{height:28px;justify-content:flex-start;gap:8px;padding:0 4px 0 6px}.title-frame[data-v-cff92950]{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-cff92950],.title-extra[data-v-cff92950]{position:relative;z-index:1;display:flex;align-items:center}.title-main[data-v-cff92950]{gap:8px}.title-icon[data-v-cff92950]{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-cff92950]{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-cff92950]{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-cff92950]{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-cff92950]{gap:8px}.title-dots[data-v-cff92950]{display:flex;gap:4px}.title-dots i[data-v-cff92950]{width:6px;height:6px;background:#00f7ff;box-shadow:0 0 6px #00f7ff}.panel-body[data-v-cff92950]{flex:1;min-height:0;padding:8px 10px 10px;overflow:hidden;display:flex;flex-direction:column}.panel-body.line[data-v-cff92950]{padding:4px 8px 8px}.flip-digit[data-v-56730cea]{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-56730cea]{width:12px;background:transparent;box-shadow:none}.flip-digit.punct .static[data-v-56730cea],.flip-digit.punct .hinge[data-v-56730cea]{background:transparent;border:none;box-shadow:none}.static[data-v-56730cea],.hinge[data-v-56730cea]{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-56730cea],.hinge.top[data-v-56730cea]{top:0;border-radius:3px 3px 0 0;border-bottom-color:#00f7ff1f}.static.bottom[data-v-56730cea],.hinge.bottom[data-v-56730cea]{bottom:0;border-radius:0 0 3px 3px;border-top-color:#00000059}.static i[data-v-56730cea],.hinge i[data-v-56730cea]{position:absolute;left:0;width:100%;height:38px;display:flex;align-items:center;justify-content:center;font-style:normal}.static.top i[data-v-56730cea],.hinge.top i[data-v-56730cea]{top:0}.static.bottom i[data-v-56730cea],.hinge.bottom i[data-v-56730cea]{bottom:0}.hinge[data-v-56730cea]{z-index:2;backface-visibility:hidden;transform-style:preserve-3d;pointer-events:none;opacity:0}.hinge.top[data-v-56730cea]{transform-origin:bottom center}.hinge.bottom[data-v-56730cea]{transform-origin:top center;transform:rotateX(90deg)}.hinge.top.active[data-v-56730cea]{opacity:1;animation:flip-top-56730cea .5s ease-in forwards}.hinge.bottom.active[data-v-56730cea]{opacity:1;animation:flip-bottom-56730cea .5s ease-out forwards}@keyframes flip-top-56730cea{0%{transform:rotateX(0)}to{transform:rotateX(-90deg)}}@keyframes flip-bottom-56730cea{0%{transform:rotateX(90deg)}to{transform:rotateX(0)}}.kpi-flip-board[data-v-9e22c451]{display:flex;gap:10px;width:100%;pointer-events:auto}.kpi-flip[data-v-9e22c451]{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-9e22c451]{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;min-width:0}.kpi-texts[data-v-9e22c451]{min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:2px}.kpi-label[data-v-9e22c451]{font-size:13px;font-weight:600;color:#9ad8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:100%}.kpi-hint[data-v-9e22c451]{font-size:10px;color:#9ed2e8b8;white-space:nowrap}.kpi-digits[data-v-9e22c451]{display:flex;align-items:center;justify-content:center;width:100%;min-height:36px}.kpi-flip[data-v-9e22c451] .kpi-ico{width:22px;height:22px;flex:0 0 auto}.tone-power[data-v-9e22c451]{border-color:#ffb44073}.tone-power .kpi-label[data-v-9e22c451]{color:#f0c878}.tone-water[data-v-9e22c451]{border-color:#38bdf873}.tone-water .kpi-label[data-v-9e22c451]{color:#7dd3fc}.tone-cool[data-v-9e22c451]{border-color:#67e8f96b}.tone-cool .kpi-label[data-v-9e22c451]{color:#a5f3fc}.tone-device[data-v-9e22c451]{border-color:#34d39973}.tone-device .kpi-label[data-v-9e22c451]{color:#6ee7b7}.tone-access[data-v-9e22c451]{border-color:#a78bfa73}.tone-access .kpi-label[data-v-9e22c451]{color:#c4b5fd}.overview-float[data-v-5b50b0a3]{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-5b50b0a3]{grid-column:2;grid-row:1;min-width:0;z-index:2}.side[data-v-5b50b0a3]{min-width:0;min-height:0;height:100%;display:grid;gap:10px;pointer-events:auto}.left[data-v-5b50b0a3],.right[data-v-5b50b0a3]{grid-row:1 / -1;grid-template-rows:minmax(0,1.05fr) minmax(0,1fr) minmax(0,.95fr)}.left[data-v-5b50b0a3]{grid-column:1}.right[data-v-5b50b0a3]{grid-column:3}.center-pass[data-v-5b50b0a3]{grid-column:2;grid-row:2;pointer-events:none}.badge[data-v-5b50b0a3]{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-5b50b0a3]{background:#faad1424;color:#ffd666;border-color:#faad1459}.panel-stack[data-v-5b50b0a3]{flex:1;min-height:0;display:flex;flex-direction:column;gap:6px}.chart-main[data-v-5b50b0a3]{flex:1.2;min-height:0}.chart-sub[data-v-5b50b0a3]{flex:.9;min-height:0}.panel-split[data-v-5b50b0a3]{flex:1;min-height:0;display:grid;grid-template-columns:1fr 1fr;gap:6px}.panel-split--pies[data-v-5b50b0a3]{grid-template-rows:minmax(0,1fr)}.pie-col[data-v-5b50b0a3]{min-height:0;display:flex;flex-direction:column}.pie-cap[data-v-5b50b0a3]{text-align:center;font-size:10px;color:var(--panel-muted);margin-top:-2px}.digit-row[data-v-5b50b0a3]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;flex-shrink:0}.digit-card[data-v-5b50b0a3]{padding:6px 8px;border-radius:4px;background:#061c3873;border:1px solid rgba(0,247,255,.14)}.digit-card.warn[data-v-5b50b0a3]{border-color:#faad1466}.d-label[data-v-5b50b0a3]{font-size:10px;color:var(--panel-muted)}.d-value[data-v-5b50b0a3]{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-5b50b0a3]{font-size:10px;color:#c8e6f5a6;font-weight:500}.energy-digits[data-v-5b50b0a3]{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;flex-shrink:0}.ed[data-v-5b50b0a3]{padding:8px;border-radius:4px;background:#061c3873;border:1px solid rgba(0,247,255,.14)}.ed span[data-v-5b50b0a3]{display:block;font-size:10px;color:var(--panel-muted)}.ed b[data-v-5b50b0a3]{display:block;margin-top:4px;font-size:16px;font-family:var(--font-num);color:#fff}.ed small[data-v-5b50b0a3]{font-size:10px;color:#c8e6f5a6;font-weight:500}.screen-err[data-v-5b50b0a3]{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-5b50b0a3] .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-ytIpUdNw.js";import{u as d}from"./useMetricHistory-DAj0vDDt.js";import{u as x}from"./realtime-BbiS_VD3.js";import{d as y,c as a,b as e,z as u,h as o,F as b,B as S,y as p,o as i,t as l,x as P,_ as w}from"./index-BRA9HJ0O.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(N,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-e6c5cb40"]]);export{M as default}; 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-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}
.pressure-page[data-v-e6c5cb40]{display:flex;min-height:0}.charts[data-v-e6c5cb40]{display:grid;grid-template-columns:1fr 1fr;gap:10px;min-height:240px;flex:1}.chart-card[data-v-e6c5cb40]{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-e6c5cb40]{font-size:12px;color:var(--label);margin-bottom:4px}.chart-card[data-v-e6c5cb40] .echart-wrap{flex:1;min-height:180px}.table-wrap[data-v-e6c5cb40]{max-height:260px;overflow:auto}
.admin-list-page[data-v-b6ca3592]{min-height:0}.list-panel[data-v-b6ca3592]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-b6ca3592]{display:flex;align-items:center;gap:10px}.table-wrap[data-v-b6ca3592]{flex:1;min-height:0;overflow:auto}.muted[data-v-b6ca3592]{color:var(--panel-muted)}.hint[data-v-b6ca3592]{margin-top:12px;font-size:12px;color:var(--panel-muted)}.msg.ok[data-v-b6ca3592]{color:var(--ok);font-size:13px;margin-bottom:8px}.msg.err[data-v-b6ca3592]{color:#ffb0b0;font-size:13px;margin:8px 0}.desc[data-v-b6ca3592]{margin:0 0 12px;font-size:12px;color:var(--panel-muted);line-height:1.6}.presets[data-v-b6ca3592]{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}.preset[data-v-b6ca3592]{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-b6ca3592]{background:#3dbeff38;color:#eaf4ff;border-color:#3dbeff8c}.poll-form[data-v-b6ca3592]{grid-template-columns:1fr 1fr}.modal-mask[data-v-b6ca3592]{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-b6ca3592]{width:min(560px,92vw);max-height:90vh;overflow:auto}.modal-actions[data-v-b6ca3592]{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,B as P,w as L,r as d,y as R,l as S,o,_ as z}from"./index-BRA9HJ0O.js";const I={class:"page-fill admin-list-page"},j={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=mvg8x4b453b45f38",a=d(null),v=d(2e3),m=d(2),p=d(!1),y=d(""),c=d(""),_=d(!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=R(()=>{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 b(t){v.value=t,m.value=+(t/1e3).toFixed(t%1e3===0?0:1)}function B(t){const l=Math.max(.5,Math.min(600,t||.5));m.value=l,v.value=Math.round(l*1e3)}function g(){u.isAdmin&&(a.value&&b(a.value.collectPollMs),c.value="",_.value=!0)}function k(){_.value=!1,c.value=""}async function $(){y.value="";try{const t=await S("/api/settings",{token:u.token});a.value=t,b(t.collectPollMs)}catch(t){c.value=t instanceof Error?t.message:"加载失败"}}async function F(){if(u.isAdmin){p.value=!0,c.value="";try{const t=await S("/api/settings/collect-poll",{method:"PUT",token:u.token,body:JSON.stringify({collectPollMs:v.value})});a.value=t,b(t.collectPollMs),y.value=`采集频率已更新为 ${m.value} 秒`,k()}catch(t){c.value=t instanceof Error?t.message:"保存失败"}finally{p.value=!1}}}return V($),(t,l)=>(o(),n("div",I,[e("section",j,[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),c.value&&!_.value?(o(),n("p",T,i(c.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:L(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=>b(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:m.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=>b(Number(s.target.value)))},null,40,se)])]),c.value?(o(),n("p",ae,i(c.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)]))}}),de=z(ue,[["__scopeId","data-v-b6ca3592"]]);export{de as default}; 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};
.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,c as l,b as e,t as s,y as o,o as i,I as u}from"./index-BRA9HJ0O.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 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};
.admin-list-page[data-v-edd81bd7]{min-height:0}.list-panel[data-v-edd81bd7]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-edd81bd7]{display:flex;align-items:center;gap:10px}.table-wrap[data-v-edd81bd7]{flex:1;min-height:0;overflow:auto}.ops[data-v-edd81bd7]{display:flex;gap:6px}.empty[data-v-edd81bd7]{text-align:center;color:var(--panel-muted);padding:28px 10px!important}.msg.ok[data-v-edd81bd7]{color:var(--ok);font-size:13px;margin-bottom:8px}.msg.err[data-v-edd81bd7]{color:#ffb0b0;font-size:13px;margin:8px 0}.modal-mask[data-v-edd81bd7]{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-edd81bd7]{width:min(560px,92vw);max-height:90vh;overflow:auto}.modal-actions[data-v-edd81bd7]{margin-top:14px;display:flex;gap:10px}
import{d as S,u as $,j as L,c as i,b as e,t as o,f as r,F as M,B as U,w as x,e as g,v as _,L as A,r as u,K as B,l as k,o as d,_ as D}from"./index-BRA9HJ0O.js";const T={class:"page-fill admin-list-page"},F={class:"panel list-panel"},O={class:"panel-head"},J={class:"panel-title"},P={key:0,class:"msg ok"},j={key:1,class:"msg err"},z={class:"table-wrap"},H={class:"table"},I={class:"ops"},K=["onClick"],q=["onClick"],G={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=B({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",T,[e("section",F,[e("div",O,[e("div",J,[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",P,o(p.value),1)):r("",!0),a.value&&!m.value?(d(),i("p",j,o(a.value),1)):r("",!0),e("div",z,[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,K),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: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=D(le,[["__scopeId","data-v-edd81bd7"]]);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};
.admin-list-page[data-v-b2e28b89]{min-height:0}.list-panel[data-v-b2e28b89]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.head-actions[data-v-b2e28b89]{display:flex;align-items:center;gap:10px}.table-wrap[data-v-b2e28b89]{flex:1;min-height:0;overflow:auto}.ops[data-v-b2e28b89]{display:flex;gap:6px}.empty[data-v-b2e28b89]{text-align:center;color:var(--panel-muted);padding:28px 10px!important}.msg.ok[data-v-b2e28b89]{color:var(--ok);font-size:13px;margin-bottom:8px}.msg.err[data-v-b2e28b89]{color:#ffb0b0;font-size:13px;margin:8px 0}.modal-mask[data-v-b2e28b89]{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-b2e28b89]{width:min(560px,92vw);max-height:90vh;overflow:auto}.modal-actions[data-v-b2e28b89]{margin-top:14px;display:flex;gap:10px}
.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 y,u as V,c as i,b as t,t as l,h as o,z as k,F as w,B as A,y as u,o as d,l as C,_ as S}from"./index-BRA9HJ0O.js";import{u as E}from"./realtime-BbiS_VD3.js";import{E as O,m as B}from"./historyChart-ytIpUdNw.js";import{u as c}from"./useMetricHistory-DAj0vDDt.js";const F={class:"page-fill"},$={class:"panel vav-page"},H={class:"panel-head"},L={class:"badge"},M={class:"hist"},N={class:"table-wrap"},T={class:"table vav-table"},z={class:"name"},D={class:"ops"},I=["disabled","onClick"],J=["disabled","onClick"],P=y({__name:"VavView",setup(R){const n=V(),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,[k(O,{option:v.value,loading:b.value,"empty-text":"VAV 历史积累中"},null,8,["option","loading"])]),t("div",N,[t("table",T,[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",z,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",D,[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-0beef163"]]);export{Q 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};
.hist[data-v-0beef163]{min-height:200px;margin-bottom:10px;border:1px solid rgba(0,247,255,.16);border-radius:6px;padding:8px;background:#04102059}.hist[data-v-0beef163] .echart-wrap{height:180px}.vav-page[data-v-0beef163]{flex:1;min-height:0;display:flex;flex-direction:column;overflow:hidden}.table-wrap[data-v-0beef163]{flex:1;min-height:0;overflow:auto}.vav-table[data-v-0beef163]{width:auto;max-width:100%;table-layout:auto;border-collapse:collapse;font-size:12px}.vav-table[data-v-0beef163] th,.vav-table th[data-v-0beef163],.vav-table td[data-v-0beef163]{padding:6px 10px;white-space:nowrap;width:1%}.vav-table .name[data-v-0beef163]{width:auto;max-width:160px;overflow:hidden;text-overflow:ellipsis}.vav-table .ops[data-v-0beef163]{display:flex;gap:6px;width:1%}.vav-table[data-v-0beef163] .btn.sm,.vav-table .btn.sm[data-v-0beef163]{padding:4px 10px;font-size:11px;line-height:1.2}.foot[data-v-0beef163]{margin-top:12px;flex:0 0 auto;font-size:12px;color:var(--label);line-height:1.7}
.video-page[data-v-088a7476]{gap:12px}.head-right[data-v-088a7476]{display:flex;align-items:center;gap:8px}.video-tabs[data-v-088a7476]{display:flex;align-items:center;gap:8px;flex:0 0 auto}.tab[data-v-088a7476]{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-088a7476]{background:linear-gradient(90deg,#1a6fff,#0aa6e5);color:#fff;border-color:transparent;font-weight:600}.tab-spacer[data-v-088a7476]{flex:1}.msg[data-v-088a7476]{font-size:12px;color:var(--cyan-lt)}.hint[data-v-088a7476]{font-size:12px;color:var(--label);line-height:1.6;margin-bottom:12px}.form-grid.three[data-v-088a7476]{grid-template-columns:repeat(3,1fr);max-width:1100px}.row-actions[data-v-088a7476]{display:flex;align-items:center;flex-wrap:wrap;gap:12px;margin-top:14px}.switch-field .switch[data-v-088a7476]{display:flex;align-items:center;gap:8px;height:36px;color:var(--body);font-size:13px;cursor:pointer}.list-panel[data-v-088a7476],.config-panel[data-v-088a7476]{flex:1;min-height:0;display:flex;flex-direction:column}.table-wrap[data-v-088a7476]{flex:1;min-height:0;overflow:auto}.ops[data-v-088a7476]{display:flex;flex-wrap:wrap;gap:6px}.empty[data-v-088a7476]{text-align:center;color:var(--label);padding:24px!important}.st[data-v-088a7476]{display:inline-block;padding:1px 8px;border-radius:4px;font-size:11px}.st.online[data-v-088a7476]{background:#2fd6a126;color:var(--green)}.st.offline[data-v-088a7476]{background:#8eb4d41f;color:var(--muted)}.st.alarm[data-v-088a7476]{background:#f0b4292e;color:var(--amber)}.preview-layout[data-v-088a7476]{flex:1;min-height:0;display:grid;grid-template-columns:1.6fr 1fr;gap:12px}.webrtc-stage[data-v-088a7476]{display:flex;flex-direction:column;min-height:360px;padding:0;overflow:hidden}.webrtc-stage[data-v-088a7476] .webrtc-player{flex:1;min-height:320px;border-radius:0}.cam-meta.bar[data-v-088a7476]{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-088a7476],.preview-side[data-v-088a7476]{min-height:0;display:flex;flex-direction:column}.stage[data-v-088a7476]{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-088a7476]{background:radial-gradient(ellipse at 40% 30%,rgba(240,180,41,.18),transparent 50%),linear-gradient(160deg,#1a1520,#0c1018)}.stage.offline[data-v-088a7476]{filter:saturate(.55)}.scan[data-v-088a7476]{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-088a7476]{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-088a7476]{width:7px;height:7px;border-radius:50%;background:#ff5a5a;box-shadow:0 0 #ff5a5a99;animation:pulse-088a7476 1.4s ease infinite}@keyframes pulse-088a7476{70%{box-shadow:0 0 0 6px #ff5a5a00}to{box-shadow:0 0 #ff5a5a00}}.stage-body[data-v-088a7476]{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-088a7476]{font-size:22px;font-weight:600;color:var(--title)}.cam-sub[data-v-088a7476]{margin-top:8px;font-size:13px;color:var(--cyan-lt)}.cam-meta[data-v-088a7476]{margin-top:10px;font-size:12px;color:var(--label);font-family:var(--font-num)}.cam-note[data-v-088a7476]{margin-top:16px;max-width:420px;font-size:12px;color:var(--muted);line-height:1.7}.thumb-grid[data-v-088a7476]{flex:1;min-height:0;overflow:auto;display:grid;grid-template-columns:1fr 1fr;gap:8px;align-content:start}.thumb[data-v-088a7476]{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-088a7476]:hover{border-color:#3ec8ff73}.thumb.active[data-v-088a7476]{border-color:#3ac8ffb3;background:#1a6fff38;box-shadow:inset 0 0 0 1px #7ad8ff26}.thumb-top[data-v-088a7476]{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px}.ch[data-v-088a7476]{font-size:11px;color:var(--cyan-lt);font-family:var(--font-num)}.thumb-name[data-v-088a7476]{font-size:13px;font-weight:600;color:#fff}.thumb-loc[data-v-088a7476]{margin-top:4px;font-size:11px;color:var(--label)}
.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)}
import{d as S,u as E,p as P,q 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-BRA9HJ0O.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=_,h=E(),n=g(null),a=g("idle"),l=g("");let e=null,d=null;async function u(){try{d&&h.token&&await fetch(d,{method:"DELETE",headers:{Authorization:`Bearer ${h.token}`}}).catch(()=>{})}finally{d=null,e==null||e.close(),e=null,n.value&&(n.value.srcObject=null),a.value="idle"}}async function f(){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 ${h.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,()=>{f()},{immediate:!0}),W(()=>{u()}),C({restart:f,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:f},"重试")],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-7c02508e"]]);export{j as W}; 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-7c02508e]{position:relative;width:100%;height:100%;min-height:280px;background:#050a14;border-radius:8px;overflow:hidden}.video[data-v-7c02508e]{width:100%;height:100%;object-fit:contain;background:#000}.overlay[data-v-7c02508e]{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-7c02508e]{font-size:12px;color:#8fa3c4;max-width:80%;line-height:1.5;word-break:break-all}.btn[data-v-7c02508e]{border:1px solid rgba(61,139,253,.45);background:#3d8bfd33;color:#fff;border-radius:6px;padding:6px 14px;cursor:pointer}.live-tag[data-v-7c02508e]{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-7c02508e]{width:7px;height:7px;border-radius:50%;background:#3dd68c;box-shadow:0 0 8px #3dd68c}
.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}
.echart-wrap[data-v-5cb78322]{position:relative;min-width:0;min-height:0}.echart[data-v-5cb78322]{width:100%;height:100%}.echart-empty[data-v-5cb78322]{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--label, #7a9bb0);border:1px dashed rgba(0,247,255,.18);border-radius:4px;background:#04122459;pointer-events:none} .echart-wrap[data-v-7230e6da]{position:relative;min-width:0;min-height:0}.echart[data-v-7230e6da]{width:100%;height:100%}.echart-empty[data-v-7230e6da]{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;align-items:center;justify-content:center;font-size:12px;color:var(--label, #7a9bb0);border:1px dashed rgba(0,247,255,.18);border-radius:4px;background:#04122459;pointer-events:none}
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{M as y,r as c,y as u}from"./index-BRA9HJ0O.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:[],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{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{j as s,k as c,r as u}from"./index-BRA9HJ0O.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{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{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{j as f,q as m,p as v,l as d,r as n}from"./index-BRA9HJ0O.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??15)}),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{q as $,r as B}from"./index-BRA9HJ0O.js";const S="scene=mvg8x4b453b45f38",p="/rtengine";function h(){return"/rtengine/gis.html?scene=mvg8x4b453b45f38".trim()||"/rtengine/gis.html?scene=mvg8x4b453b45f38".trim()||`${p}/gis.html?${S}`}function v(n=h()){try{return new URL(n,typeof window<"u"?window.location.href:"http://localhost").origin}catch{return typeof window<"u"?window.location.origin:"http://localhost"}}function j(n=h()){try{const t=new URL(n,typeof window<"u"?window.location.href:"http://localhost");return t.pathname.startsWith(`${p}/`)||t.pathname===p?p:""}catch{return p}}function D(n,t=h()){const s=String(n||"").trim().replace(/\\/g,"/").split("/").pop()||"";if(!s)return"";const a=j(t);return`${v(t)}${a}/api/icons/${encodeURIComponent(s)}`}async function F(n=h()){const t=j(n),s=await fetch(`${v(n)}${t}/api/icons`);if(!s.ok)throw new Error(`图标列表加载失败 (${s.status})`);const a=await s.json();return(Array.isArray(a)?a:a.files||[]).filter(c=>typeof c=="string"&&c.toLowerCase().endsWith(".png"))}async function _(n,t=h()){const s=n!=null&&n.length?n:await F(t);return await Promise.all(s.map(a=>new Promise(i=>{const c=new Image;c.onload=()=>i(),c.onerror=()=>i(),c.src=D(a,t)}))),s}function U(n,t,s){if(!n)return;let a="*";try{a=new URL(t,window.location.href).origin}catch{}try{n.postMessage(s,a)}catch{try{n.postMessage(s,"*")}catch{}}}function L(n){if(!n||typeof n!="object")return null;const t=n,s=[t,t.data,t.location,t.coord,t.coordinates,t.metaData];if(typeof t.metaData=="string")try{s.push(JSON.parse(t.metaData))}catch{}for(const a of s){if(!a||typeof a!="object")continue;const i=a,c=Number(i.x??i.lng??i.longitude??i.lon),m=Number(i.y??i.lat??i.latitude),d=Number(i.z??i.height??i.alt??i.altitude??0);if(Number.isFinite(c)&&Number.isFinite(m))return{x:c,y:m,z:Number.isFinite(d)?d:0}}return null}function A(n){if(!n||typeof n!="object")return[];const t=n,s=[t];if(t.data&&typeof t.data=="object"&&s.push(t.data),t.metaData&&typeof t.metaData=="object"&&s.push(t.metaData),typeof t.metaData=="string")try{s.push(JSON.parse(t.metaData))}catch{}return t.params&&typeof t.params=="object"&&s.push(t.params),s}function O(n){const t={id:null,name:null,type:null,tag0:null,tag1:null,tag2:null,icon:null,eventId:null},s=A(n);if(!s.length)return t;const a=(...b)=>{for(const g of s)for(const T of b){const y=g[T];if(y!=null&&String(y).trim())return String(y).trim()}return null},i=a("id","markerId","objectId"),c=a("name","text","title","label"),m=a("text","title","label","name");return{id:i,name:c&&i&&c===i&&m&&m!==i?m:c,type:a("type","objectType"),tag0:a("tag0","Tag0"),tag1:a("tag1","Tag1"),tag2:a("tag2","Tag2"),icon:a("icon","url","Icon"),eventId:a("eventId")}}function W(n){const t=B(!1);function s(){var e;return((e=n.iframeRef.value)==null?void 0:e.contentWindow)??null}function a(){try{const e=s();return e?(e.location.href,e.$gisObj??null):null}catch{return null}}function i(e,o){const r=a(),u=r==null?void 0:r[e];if(typeof u=="function")try{if(e==="flyToBookmark"||e==="flyToViewpoint"||e==="flyToMarker"||e==="flyToHome"){const l=typeof o=="string"||typeof o=="number"?o:o&&typeof o=="object"?o.id??o.name??o.bookmarkName??o:o;u.call(r,l,o);return}u.call(r,o??{});return}catch{}U(s(),n.cloudUrl,{type:e,data:o??{},messageId:`web-${Date.now()}-${Math.random().toString(36).slice(2,8)}`})}const c=[];let m=!1;async function d(){if(m)return;const e=a();if(!e||typeof e.registerBookmarks!="function")return;let o="";try{o=new URL(n.cloudUrl,window.location.href).searchParams.get("scene")||""}catch{}if(!o)return;const r=[`/designer/data/scenes/${encodeURIComponent(o)}.json`,`/designer/api/embed/scenes/${encodeURIComponent(o)}`];for(const u of r)try{const l=await fetch(u);if(!l.ok)continue;const f=await l.json();if(!Array.isArray(f.bookmarks)||!f.bookmarks.length)continue;e.registerBookmarks(f.bookmarks),e._bookmarkFlyReady=!0,m=!0;return}catch{}}function b(e,o){const r=String(e||"").trim();if(!r)return;const u=()=>{const l=a();if(!l||typeof l.flyToBookmark!="function")return!1;try{const f=o?l.flyToBookmark(r,o):l.flyToBookmark(r);return f&&typeof f=="object"&&f.queued&&(c.includes(r)||c.push(r)),!0}catch{return!1}};u()||(c.includes(r)||c.push(r),i("flyToBookmark",r),d().then(()=>{u()}),window.setTimeout(()=>u(),600),window.setTimeout(()=>u(),2e3))}function g(){const e=c.splice(0,c.length);for(const o of e)b(o)}function T(e){i("setMapPickMode",{state:e,enable:e})}function y(e){const o=(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,...o?{icon:o,url:o}:{}}}function x(e){i("addMarker",y(e))}function C(e){i("addMarkers",e.map(o=>y(o)))}function N(e){i("removeMarker",{id:e})}function P(){i("clearMarkers",{})}let I=!1;function E(){I||(I=!0,i("setMarkerVisible",{visible:!0}),i("showPOIs",{visible:!0}))}let M=!1;function w(){var e;if(M){c.length&&g();return}M=!0,t.value=!0,(e=n.onReady)==null||e.call(n),d().then(()=>g()),window.setTimeout(()=>{d().then(()=>g())},800),window.setTimeout(()=>g(),2500)}function R(e){var f;const o=e.data;if(!o||typeof o!="object")return;const r=String(o.type||""),u=o.data??o,l=u&&typeof u=="object"?u:null;if(!M&&l&&(l.bookmarksReady===!0||Array.isArray(l.bookmarks)&&l.bookmarks.length>0||typeof l.bookmarkCount=="number"&&l.bookmarkCount>0)&&w(),r==="sceneEmbedLoaded"||r==="sceneEmbedProgress"||r==="ueConnected"||r==="peerConnected"||r==="psConnected"||r==="streamPlaying"||r==="bookmarksReady"||r==="sceneBookmarks"){if(r==="sceneEmbedProgress"){const k=Number(l==null?void 0:l.percent);Number.isFinite(k)&&k>=100&&w();return}w();return}if(r==="mouseClick"||r==="mouseClickCallback"||r==="modelClick"||r==="modelClickCallback"||r==="selectMarker"||u&&typeof u=="object"&&u.eventId==="mouseClick"){const k=O(u);(f=n.onMouseClick)==null||f.call(n,{point:L(u),markerId:k.id||k.tag1||k.tag0,marker:k,raw:u})}}return window.addEventListener("message",R),$(()=>window.removeEventListener("message",R)),{ready:t,send:i,flyToBookmark:b,setPickMode:T,addMarker:x,addMarkers:C,removeMarker:N,clearMarkers:P,showScenePresets:E,markReady:w,flushPendingFlies:g}}export{h as d,F as f,_ as p,D as r,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};
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
<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-BRA9HJ0O.js"></script> <script type="module" crossorigin src="/assets/index-Bs_x-_cK.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Yzi0d50E.css"> <link rel="stylesheet" crossorigin href="/assets/index-Yzi0d50E.css">
</head> </head>
<body> <body>
......
...@@ -1978,7 +1978,7 @@ ...@@ -1978,7 +1978,7 @@
}, },
"node_modules/echarts": { "node_modules/echarts": {
"version": "6.1.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.1.0.tgz",
"integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
...@@ -4126,7 +4126,7 @@ ...@@ -4126,7 +4126,7 @@
}, },
"node_modules/vue-echarts": { "node_modules/vue-echarts": {
"version": "8.3.2", "version": "8.3.2",
"resolved": "https://registry.npmjs.org/vue-echarts/-/vue-echarts-8.3.2.tgz", "resolved": "https://registry.npmmirror.com/vue-echarts/-/vue-echarts-8.3.2.tgz",
"integrity": "sha512-FbJD49OZZZv1I8aclHQ+gcQxU+Y9nNWQMuod1sewE7/YjGQUJAaeVcf6WrSfQDBRPke+WoVsUtdRFIqPOsRrnw==", "integrity": "sha512-FbJD49OZZZv1I8aclHQ+gcQxU+Y9nNWQMuod1sewE7/YjGQUJAaeVcf6WrSfQDBRPke+WoVsUtdRFIqPOsRrnw==",
"license": "MIT", "license": "MIT",
"peerDependencies": { "peerDependencies": {
......
"""从 devices.json 导出模拟器所需的 机组01/机组02/房间 CSV。"""
from __future__ import annotations
import csv
import json
import shutil
from pathlib import Path
ROOT = Path(__file__).resolve().parent
DEVICES = (
ROOT.parent.parent
/ "deploy"
/ "prod"
/ "native"
/ "server-run"
/ "data"
/ "devices.json"
)
OUT = ROOT / "protocol"
GW_FOLDER = {
"mb-102": ("机组01", "mb-102.csv"),
"mb-103": ("机组02", "mb-103.csv"),
"mb-104": ("房间", "mb-104.csv"),
}
def main() -> None:
data = json.loads(DEVICES.read_text(encoding="utf-8"))
if OUT.exists():
shutil.rmtree(OUT)
OUT.mkdir(parents=True)
for gw, (folder, fname) in GW_FOLDER.items():
rows = []
seen: set[int] = set()
for p in data.get("points") or []:
if p.get("gatewayId") != gw:
continue
addr = p.get("modbusAddress")
if addr is None:
continue
a = int(addr)
if a in seen:
continue
seen.add(a)
rows.append(
{
"Name": p.get("name") or "",
"Modbus Address": a,
"Data Type": p.get("dataType") or "Short",
"Scale": p.get("scale") if p.get("scale") is not None else 0,
"ItemID": p.get("itemId") or p.get("id") or "",
}
)
rows.sort(key=lambda r: r["Modbus Address"])
d = OUT / folder
d.mkdir(parents=True, exist_ok=True)
path = d / fname
with path.open("w", encoding="utf-8-sig", newline="") as f:
w = csv.DictWriter(
f, fieldnames=["Name", "Modbus Address", "Data Type", "Scale", "ItemID"]
)
w.writeheader()
w.writerows(rows)
print(f"[ok] {folder}/{fname}: {len(rows)} pts")
print(f"[done] {OUT}")
if __name__ == "__main__":
main()
Name,Modbus Address,Data Type,Scale,ItemID
Name,Modbus Address,Data Type,Scale,ItemID
线电压UAB,1,Float,0,RTU.COM1.PAU201_DB.线电压UAB
线电压UBC,3,Float,0,RTU.COM1.PAU201_DB.线电压UBC
线电压UCA,5,Float,0,RTU.COM1.PAU201_DB.线电压UCA
相电压UA,7,Float,0,RTU.COM1.PAU201_DB.相电压UA
相电压UB,9,Float,0,RTU.COM1.PAU201_DB.相电压UB
相电压UC,11,Float,0,RTU.COM1.PAU201_DB.相电压UC
电流IA,13,Float,0,RTU.COM1.PAU201_DB.电流IA
电流IB,15,Float,0,RTU.COM1.PAU201_DB.电流IB
电流IC,17,Float,0,RTU.COM1.PAU201_DB.电流IC
总有功功率,19,Float,0,RTU.COM1.PAU201_DB.总有功功率
总无功功率,21,Float,0,RTU.COM1.PAU201_DB.总无功功率
总功率因素,23,Float,0,RTU.COM1.PAU201_DB.总功率因素
正向有功总电能,25,Float,1,RTU.COM1.PAU201_DB.正向有功总电能
相电压UA,51,Word,0,RTU.COM2.2ATN01_DB.相电压UA
相电压UB,52,Word,0,RTU.COM2.2ATN01_DB.相电压UB
相电压UC,53,Word,0,RTU.COM2.2ATN01_DB.相电压UC
线电压UAB,54,Word,0,RTU.COM2.2ATN01_DB.线电压UAB
线电压UBC,55,Word,0,RTU.COM2.2ATN01_DB.线电压UBC
线电压UCA,56,Word,0,RTU.COM2.2ATN01_DB.线电压UCA
电流IA,57,Word,0,RTU.COM2.2ATN01_DB.电流IA
电流IB,58,Word,0,RTU.COM2.2ATN01_DB.电流IB
电流IC,59,Word,0,RTU.COM2.2ATN01_DB.电流IC
总有功功率,60,Word,0,RTU.COM2.2ATN01_DB.总有功功率
总无功功率,61,Word,0,RTU.COM2.2ATN01_DB.总无功功率
总功率因素,62,Word,0,RTU.COM2.2ATN01_DB.总功率因素
用电量,63,Float,1,RTU.COM2.2ATN01_DB.用电量
相电压UA,71,Word,0,RTU.COM2.2ATE01_DB.相电压UA
相电压UB,72,Word,0,RTU.COM2.2ATE01_DB.相电压UB
相电压UC,73,Word,0,RTU.COM2.2ATE01_DB.相电压UC
线电压UAB,74,Word,0,RTU.COM2.2ATE01_DB.线电压UAB
线电压UBC,75,Word,0,RTU.COM2.2ATE01_DB.线电压UBC
线电压UCA,76,Word,0,RTU.COM2.2ATE01_DB.线电压UCA
电流IA,77,Word,0,RTU.COM2.2ATE01_DB.电流IA
电流IB,78,Word,0,RTU.COM2.2ATE01_DB.电流IB
电流IC,79,Word,0,RTU.COM2.2ATE01_DB.电流IC
总有功功率,80,Word,0,RTU.COM2.2ATE01_DB.总有功功率
总无功功率,81,Word,0,RTU.COM2.2ATE01_DB.总无功功率
总功率因素,82,Word,0,RTU.COM2.2ATE01_DB.总功率因素
用电量,83,Float,1,RTU.COM2.2ATE01_DB.用电量
流速,151,Float,0,RTU.COM3.能量计_DN80.流速
瞬时流量,153,Float,0,RTU.COM3.能量计_DN80.瞬时流量
正累积流量,155,Double,0,RTU.COM3.能量计_DN80.正累积流量
今日净累积流量,159,Double,0,RTU.COM3.能量计_DN80.今日净累积流量
瞬时热量,163,Float,0,RTU.COM3.能量计_DN80.瞬时热量
正累积热量,165,Double,0,RTU.COM3.能量计_DN80.正累积热量
流速,171,Float,0,RTU.COM3.能量计_DN40.流速
瞬时流量,173,Float,0,RTU.COM3.能量计_DN40.瞬时流量
正累积流量,175,Double,0,RTU.COM3.能量计_DN40.正累积流量
今日净累积流量,179,Double,0,RTU.COM3.能量计_DN40.今日净累积流量
瞬时热量,183,Float,0,RTU.COM3.能量计_DN40.瞬时热量
正累积热量,185,Double,0,RTU.COM3.能量计_DN40.正累积热量
流速,191,Float,0,RTU.COM3.流量计(报告室).流速
瞬时流量,193,Float,0,RTU.COM3.流量计(报告室).瞬时流量
正累积流量,195,Double,0,RTU.COM3.流量计(报告室).正累积流量
今日净累积流量,199,Double,0,RTU.COM3.流量计(报告室).今日净累积流量
流速,211,Float,0,RTU.COM3.流量计(测序区).流速
瞬时流量,213,Float,0,RTU.COM3.流量计(测序区).瞬时流量
正累积流量,215,Double,0,RTU.COM3.流量计(测序区).正累积流量
今日净累积流量,219,Double,0,RTU.COM3.流量计(测序区).今日净累积流量
开停状态,231,Word,0,RTU.COM4.文库制备2通风柜.开停状态
最大排风状态,232,Word,0,RTU.COM4.文库制备2通风柜.最大排风状态
最小风量状态,233,Word,0,RTU.COM4.文库制备2通风柜.最小风量状态
实际面风速,234,Word,0,RTU.COM4.文库制备2通风柜.实际面风速
实际门高,235,Word,0,RTU.COM4.文库制备2通风柜.实际门高
实际风量,236,Word,0,RTU.COM4.文库制备2通风柜.实际风量
开机,237,Word,0,RTU.COM4.文库制备2通风柜.开机
关机,238,Word,0,RTU.COM4.文库制备2通风柜.关机
报警代码,239,Word,0,RTU.COM4.文库制备2通风柜.报警代码
开停状态,241,Word,0,RTU.COM4.文库制备1通风柜.开停状态
最大排风状态,242,Word,0,RTU.COM4.文库制备1通风柜.最大排风状态
最小风量状态,243,Word,0,RTU.COM4.文库制备1通风柜.最小风量状态
实际面风速,244,Word,0,RTU.COM4.文库制备1通风柜.实际面风速
实际门高,245,Word,0,RTU.COM4.文库制备1通风柜.实际门高
实际风量,246,Word,0,RTU.COM4.文库制备1通风柜.实际风量
开机,247,Word,0,RTU.COM4.文库制备1通风柜.开机
关机,248,Word,0,RTU.COM4.文库制备1通风柜.关机
报警代码,249,Word,0,RTU.COM4.文库制备1通风柜.报警代码
@echo off
cd /d "c:\Users\jiuli\Desktop\??\????\dashboard\tools\modbus_sim"
start "" /MIN "C:\Users\jiuli\AppData\Local\Programs\Python\Python313\python.exe" -u simulate.py --csv-root protocol --host 0.0.0.0 --port-102 1502 --port-103 1503 --port-104 1504
...@@ -25,9 +25,9 @@ server { ...@@ -25,9 +25,9 @@ server {
} }
# RTEngineHD:同域 iframe,便于 postMessage;媒体仍由 ICE 直连引擎机 # RTEngineHD:同域 iframe,便于 postMessage;媒体仍由 ICE 直连引擎机
# 上游默认现场 121:6088(可按部署改) # 原生 nginx:127.0.0.1:6088;Docker 容器内需改为 host.docker.internal:6088
location /rtengine/ { location /rtengine/ {
proxy_pass http://192.168.32.121:6088/; proxy_pass http://127.0.0.1:6088/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $proxy_host; proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
...@@ -46,7 +46,8 @@ server { ...@@ -46,7 +46,8 @@ server {
# 引擎 WebSocket 信令(dte.js 使用绝对路径 /peer-stream) # 引擎 WebSocket 信令(dte.js 使用绝对路径 /peer-stream)
location /peer-stream { location /peer-stream {
proxy_pass http://192.168.32.121:6088/peer-stream; # Docker 时同样改为 host.docker.internal:6088
proxy_pass http://127.0.0.1:6088/peer-stream;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
...@@ -60,7 +61,7 @@ server { ...@@ -60,7 +61,7 @@ server {
# 场景书签 / embed API(gis.js 绝对路径 /designer/...) # 场景书签 / embed API(gis.js 绝对路径 /designer/...)
location /designer/ { location /designer/ {
proxy_pass http://192.168.32.121:6088/designer/; proxy_pass http://127.0.0.1:6088/designer/;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Host $proxy_host; proxy_set_header Host $proxy_host;
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
......
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