This commit is contained in:
+63
-155
@@ -99,7 +99,7 @@
|
||||
.top-actions .select { width: auto; min-width: 126px; }
|
||||
.btn {
|
||||
min-height: 40px; border: 1px solid var(--line); border-radius: 9px; padding: 8px 13px; background: var(--surface-2); color: var(--text);
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-weight: 700; transition: border-color .2s ease, background .2s ease, opacity .2s ease;
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 8px; font-weight: 700; text-decoration: none; transition: border-color .2s ease, background .2s ease, opacity .2s ease;
|
||||
}
|
||||
.btn:hover { border-color: var(--primary); }
|
||||
.btn-primary { background: var(--primary-strong); border-color: var(--primary-strong); color: #fff; }
|
||||
@@ -141,7 +141,7 @@
|
||||
.search .icon { position: absolute; left: 11px; top: 11px; color: var(--subtle); pointer-events: none; }
|
||||
.search input { padding-left: 38px; }
|
||||
.table-wrap { overflow-x: auto; }
|
||||
table { width: 100%; border-collapse: collapse; min-width: 820px; }
|
||||
table { width: 100%; border-collapse: collapse; min-width: 960px; }
|
||||
th, td { padding: 12px 14px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: middle; }
|
||||
th { color: var(--muted); background: var(--surface-2); font-size: 12px; font-weight: 700; }
|
||||
td { font-variant-numeric: tabular-nums; }
|
||||
@@ -157,7 +157,17 @@
|
||||
.scene { position: absolute; inset: 0; width: 100%; height: 100%; }
|
||||
.camera-meta { padding: 12px 14px; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.camera-meta small { color: var(--muted); }
|
||||
.zone-layout { display: grid; grid-template-columns: 230px minmax(420px, 1fr) 310px; gap: 12px; min-height: calc(100dvh - 162px); }
|
||||
.breadcrumb { margin-bottom: 10px; display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 12px; }
|
||||
.breadcrumb button { min-height: 32px; padding: 4px 8px; border: 0; border-radius: 7px; background: transparent; color: var(--primary); font-weight: 700; }
|
||||
.breadcrumb button:hover { background: var(--primary-soft); }
|
||||
.detail-tabs { margin-bottom: 12px; padding: 5px; display: flex; gap: 5px; overflow-x: auto; border: 1px solid var(--line); border-radius: 11px; background: var(--surface); }
|
||||
.detail-tabs button { min-height: 40px; padding: 7px 13px; border: 0; border-radius: 8px; background: transparent; color: var(--muted); white-space: nowrap; }
|
||||
.detail-tabs button.active { background: var(--primary-soft); color: var(--text); box-shadow: inset 0 -2px var(--primary); }
|
||||
.detail-panel[hidden] { display: none; }
|
||||
.context-strip { margin-bottom: 12px; padding: 11px 13px; display: flex; align-items: center; justify-content: space-between; gap: 12px; border: 1px solid rgba(167,139,250,.38); border-radius: 10px; background: rgba(167,139,250,.1); }
|
||||
.context-strip[hidden] { display: none; }
|
||||
.context-strip p { margin: 0; color: var(--muted); }
|
||||
.zone-layout { display: grid; grid-template-columns: minmax(420px, 1fr) 320px; gap: 12px; min-height: calc(100dvh - 260px); }
|
||||
.zone-camera-list, .zone-settings, .zone-workspace { min-width: 0; }
|
||||
.compact-list { display: grid; gap: 6px; padding: 10px; }
|
||||
.compact-list button { width: 100%; min-height: 54px; border: 1px solid transparent; border-radius: 9px; background: transparent; color: var(--text); display: flex; align-items: center; gap: 10px; padding: 8px; text-align: left; }
|
||||
@@ -188,6 +198,10 @@
|
||||
.coordinate-box h3 { margin: 0 0 4px; font-size: 13px; }
|
||||
.coordinate-box p { margin: 0 0 10px; color: var(--muted); font-size: 12px; }
|
||||
.coordinate-row { display: grid; grid-template-columns: 1fr 1fr auto; gap: 6px; }
|
||||
.linked-rules { margin-top: 12px; padding: 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface-2); }
|
||||
.linked-rules h3 { margin: 0 0 4px; font-size: 13px; }
|
||||
.linked-rules p { margin: 0 0 10px; color: var(--muted); font-size: 12px; }
|
||||
.linked-rules ul { margin: 0 0 11px; padding-left: 18px; color: var(--muted); }
|
||||
.settings-actions { position: sticky; bottom: 0; margin: 14px -16px -16px; padding: 12px 16px; display: flex; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--line); background: var(--surface); }
|
||||
.drop-zone { padding: 28px; border: 1px dashed var(--line); border-radius: 12px; text-align: center; background: var(--surface-2); }
|
||||
.drop-zone .icon { width: 28px; height: 28px; color: var(--primary); }
|
||||
@@ -223,8 +237,8 @@
|
||||
.brand { justify-content: center; padding-inline: 0; }
|
||||
.nav button { justify-content: center; padding: 0; }
|
||||
.nav .count { position: absolute; margin: -24px 0 0 25px; }
|
||||
.zone-layout { grid-template-columns: 190px minmax(400px, 1fr); }
|
||||
.zone-settings { grid-column: 1/-1; }
|
||||
.zone-layout { grid-template-columns: 1fr; }
|
||||
.zone-settings { grid-column: auto; }
|
||||
.settings-actions { position: static; margin-bottom: -16px; }
|
||||
}
|
||||
@media (max-width: 820px) {
|
||||
@@ -237,142 +251,14 @@
|
||||
.content { padding: 14px 12px; }
|
||||
.stats, .system-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.split, .zone-layout { grid-template-columns: 1fr; }
|
||||
.editor-stage { min-height: 0; }
|
||||
.context-strip, .linked-zone { display: grid; }
|
||||
.zone-camera-list { order: 2; }
|
||||
.zone-workspace { order: 1; }
|
||||
.zone-settings { order: 3; }
|
||||
.camera-grid { grid-template-columns: 1fr; }
|
||||
.mobile-nav { position: fixed; z-index: 50; left: 8px; right: 8px; bottom: 8px; display: grid; grid-template-columns: repeat(5, 1fr); padding: 5px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface); box-shadow: var(--shadow); }
|
||||
.mobile-nav button { min-height: 48px; border: 0; border-radius: 9px; background: transparent; color: var(--muted); display: grid; place-items: center; font-size: 11px; }
|
||||
.mobile-nav button.active { color: var(--primary); background: var(--primary-soft); }
|
||||
}
|
||||
@media (max-width: 560px) {
|
||||
.stats, .system-grid { grid-template-columns: 1fr; }
|
||||
.page-head { display: grid; }
|
||||
.page-actions { width: 100%; }
|
||||
.page-actions .btn { flex: 1; }
|
||||
.form-grid { grid-template-columns: 1fr; }
|
||||
.form-field.full { grid-column: auto; }
|
||||
.editor-stage { min-height: 230px; }
|
||||
.coordinate-row { grid-template-columns: 1fr 1fr; }
|
||||
.coordinate-row .btn { grid-column: 1/-1; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; scroll-behavior: auto !important; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<svg aria-hidden="true" width="0" height="0" style="position:absolute">
|
||||
<symbol id="i-grid" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></symbol>
|
||||
<symbol id="i-video" viewBox="0 0 24 24"><rect x="3" y="5" width="14" height="14" rx="2"/><path d="m17 10 4-2v8l-4-2z"/></symbol>
|
||||
<symbol id="i-camera" viewBox="0 0 24 24"><path d="M5 7h3l1.4-2h5.2L16 7h3a2 2 0 0 1 2 2v9H3V9a2 2 0 0 1 2-2Z"/><circle cx="12" cy="12.5" r="3"/></symbol>
|
||||
<symbol id="i-zone" viewBox="0 0 24 24"><path d="m5 5 14 2-2 12-12-3Z"/><circle cx="5" cy="5" r="1.5"/><circle cx="19" cy="7" r="1.5"/><circle cx="17" cy="19" r="1.5"/><circle cx="5" cy="16" r="1.5"/></symbol>
|
||||
<symbol id="i-upload" viewBox="0 0 24 24"><path d="M12 16V4m0 0L7 9m5-5 5 5"/><path d="M4 15v5h16v-5"/></symbol>
|
||||
<symbol id="i-pulse" viewBox="0 0 24 24"><path d="M3 12h4l2-6 4 12 2-6h6"/></symbol>
|
||||
<symbol id="i-search" viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m20 20-4-4"/></symbol>
|
||||
<symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol>
|
||||
<symbol id="i-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></symbol>
|
||||
<symbol id="i-bell" viewBox="0 0 24 24"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 7-3 9h18c0-2-3-2-3-9"/><path d="M10 21h4"/></symbol>
|
||||
<symbol id="i-chevron" viewBox="0 0 24 24"><path d="m9 18 6-6-6-6"/></symbol>
|
||||
<symbol id="i-pointer" viewBox="0 0 24 24"><path d="m5 3 14 8-6 2-3 6Z"/></symbol>
|
||||
<symbol id="i-line" viewBox="0 0 24 24"><path d="m4 18 16-12"/><circle cx="4" cy="18" r="2"/><circle cx="20" cy="6" r="2"/></symbol>
|
||||
<symbol id="i-undo" viewBox="0 0 24 24"><path d="M9 7 4 12l5 5"/><path d="M5 12h8a6 6 0 0 1 6 6"/></symbol>
|
||||
<symbol id="i-trash" viewBox="0 0 24 24"><path d="M4 7h16M9 7V4h6v3m-9 0 1 14h10l1-14"/></symbol>
|
||||
<symbol id="i-alert" viewBox="0 0 24 24"><path d="M12 3 2 20h20Z"/><path d="M12 9v4m0 3v.01"/></symbol>
|
||||
<symbol id="i-check" viewBox="0 0 24 24"><path d="m5 12 4 4L19 6"/></symbol>
|
||||
<symbol id="i-refresh" viewBox="0 0 24 24"><path d="M20 6v5h-5M4 18v-5h5"/><path d="M18 9a7 7 0 0 0-12-2M6 15a7 7 0 0 0 12 2"/></symbol>
|
||||
<symbol id="i-more" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></symbol>
|
||||
<symbol id="i-wifi" viewBox="0 0 24 24"><path d="M4 9a12 12 0 0 1 16 0M7 13a8 8 0 0 1 10 0m-7 4a3 3 0 0 1 4 0M12 20v.01"/></symbol>
|
||||
</svg>
|
||||
|
||||
<div class="prototype-banner">PROTOTYPE - 仅供枚举交互,非实现依据</div>
|
||||
<div class="app">
|
||||
<aside class="sidebar" aria-label="Sense 主导航">
|
||||
<div class="brand"><div class="brand-mark">Y</div><div class="brand-copy"><strong>YoVision</strong><small>Sense · Edge Console</small></div></div>
|
||||
<div class="nav-label">Workspace</div>
|
||||
<nav class="nav">
|
||||
<button data-view="overview"><svg class="icon"><use href="#i-grid"/></svg><span>运行总览</span></button>
|
||||
<button data-view="monitor"><svg class="icon"><use href="#i-video"/></svg><span>实时监控</span><span class="count">4</span></button>
|
||||
<button data-view="devices"><svg class="icon"><use href="#i-camera"/></svg><span>摄像头</span><span class="count">16</span></button>
|
||||
<button data-view="zones" aria-current="page"><svg class="icon"><use href="#i-zone"/></svg><span>区域规则</span></button>
|
||||
<button data-view="onboarding"><svg class="icon"><use href="#i-upload"/></svg><span>接入任务</span><span class="count">1</span></button>
|
||||
<button data-view="system"><svg class="icon"><use href="#i-pulse"/></svg><span>系统状态</span></button>
|
||||
</nav>
|
||||
<div class="sidebar-foot"><div class="online-row"><span class="dot"></span><strong>边缘节点在线</strong></div><div class="copy">sense-edge-01<br><span class="mono">v0.1-prototype</span></div></div>
|
||||
</aside>
|
||||
|
||||
<main class="main" id="main-content" tabindex="-1">
|
||||
<header class="topbar">
|
||||
<div class="site-select"><div><strong>青藤寄宿学校</strong><span> / 主校区 · 16 / 16 路</span></div><svg class="icon"><use href="#i-chevron"/></svg></div>
|
||||
<div class="top-actions">
|
||||
<select class="select" id="stateSelect" aria-label="原型状态演示">
|
||||
<option value="normal">正常状态</option><option value="loading">加载中</option><option value="empty">空数据</option><option value="error">连接失败</option>
|
||||
</select>
|
||||
<button class="btn icon-btn" id="themeToggle" aria-label="切换明暗主题" title="切换明暗主题"><svg class="icon"><use href="#i-sun"/></svg></button>
|
||||
<button class="btn icon-btn" aria-label="查看运维通知" title="查看运维通知"><svg class="icon"><use href="#i-bell"/></svg></button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<section class="state-layer" id="stateLayer" aria-live="polite"></section>
|
||||
|
||||
<section class="view" data-page="overview">
|
||||
<div class="page-head"><div><h1>运行总览</h1><p>先判断是否影响业务,再进入设备或分片定位原因。</p></div><div class="page-actions"><button class="btn"><svg class="icon"><use href="#i-refresh"/></svg>立即对账</button></div></div>
|
||||
<div class="grid stats">
|
||||
<article class="card stat"><div class="stat-top"><span>在线设备</span><svg class="icon"><use href="#i-camera"/></svg></div><strong>15 / 16</strong><span class="trend">93.8% · 1 路重连中</span></article>
|
||||
<article class="card stat"><div class="stat-top"><span>活跃媒体流</span><svg class="icon"><use href="#i-video"/></svg></div><strong>15</strong><span class="trend">主/子码流均衡</span></article>
|
||||
<article class="card stat"><div class="stat-top"><span>待收敛项</span><svg class="icon"><use href="#i-refresh"/></svg></div><strong>2</strong><span class="muted">配置 1 · 时间漂移 1</span></article>
|
||||
<article class="card stat"><div class="stat-top"><span>今日设备告警</span><svg class="icon"><use href="#i-alert"/></svg></div><strong>3</strong><span class="muted">与业务预警分开</span></article>
|
||||
</div>
|
||||
<div class="grid split">
|
||||
<article class="card"><div class="card-head"><h2>分片健康</h2><span class="badge success">全部可用</span></div><div class="card-body health-list">
|
||||
<div class="health-row"><span>media-01</span><strong>8 / 32</strong><div class="meter"><i style="width:25%"></i></div></div>
|
||||
<div class="health-row"><span>media-02</span><strong>8 / 32</strong><div class="meter"><i style="width:25%"></i></div></div>
|
||||
<div class="health-row"><span>推理绑定</span><strong>16 / 16</strong><div class="meter"><i style="width:100%;background:var(--violet)"></i></div></div>
|
||||
</div></article>
|
||||
<article class="card"><div class="card-head"><h2>需要处理</h2><span class="badge warning">2 项</span></div><div class="card-body">
|
||||
<p><strong>北围墙 03</strong><br><span class="muted">断线 02:14 · 正在第 4 次重连</span></p>
|
||||
<p><strong>实验楼入口 02</strong><br><span class="muted">时间漂移 +3.8s · 建议重新校时</span></p>
|
||||
</div></article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="view" data-page="monitor">
|
||||
<div class="page-head"><div><h1>实时监控</h1><p>默认只并发预览 4 路子码流;切页会释放上一页播放器。</p></div><div class="page-actions"><span class="badge info">第 1 / 4 页</span><button class="btn">布局 2 × 2</button></div></div>
|
||||
<div class="grid camera-grid" id="cameraGrid"></div>
|
||||
</section>
|
||||
|
||||
<section class="view" data-page="devices">
|
||||
<div class="page-head"><div><h1>摄像头</h1><p>期望态与实际态分开展示;连接地址和凭据始终脱敏。</p></div><div class="page-actions"><button class="btn">扫描设备</button><button class="btn btn-primary" id="openCameraDialog"><svg class="icon"><use href="#i-plus"/></svg>添加摄像头</button></div></div>
|
||||
<article class="card">
|
||||
<div class="card-head"><div class="toolbar"><div class="search"><svg class="icon"><use href="#i-search"/></svg><input class="field" aria-label="搜索摄像头" placeholder="名称、序列号或点位"></div><select class="select" aria-label="设备状态筛选"><option>全部状态</option><option>异常</option><option>重连中</option></select></div><span class="muted">已用 16 / 128</span></div>
|
||||
<div class="table-wrap"><table><thead><tr><th><input type="checkbox" aria-label="选择全部设备"></th><th>设备</th><th>期望态</th><th>实际态</th><th>最后在线</th><th>时间漂移</th><th>媒体分片</th><th>操作</th></tr></thead><tbody id="deviceRows"></tbody></table></div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="view active" data-page="zones">
|
||||
<div class="page-head"><div><h1>区域与警戒线</h1><p>在子码流快照上定义规则空间;保存草稿不会立即影响正式策略。</p></div><div class="page-actions"><span class="badge warning" id="draftBadge">草稿未发布</span><button class="btn">版本记录</button><button class="btn btn-primary" id="publishRule">发布规则</button></div></div>
|
||||
<div class="zone-layout">
|
||||
<aside class="card zone-camera-list" aria-label="摄像头选择"><div class="card-head"><h2>点位</h2><span class="badge">16</span></div><div style="padding:10px"><div class="search"><svg class="icon"><use href="#i-search"/></svg><input class="field" aria-label="搜索点位" placeholder="搜索点位"></div></div><div class="compact-list">
|
||||
<button class="selected"><span class="mini-thumb"></span><span><strong>北围墙 01</strong><small>在线 · 2 条规则</small></span></button>
|
||||
<button><span class="mini-thumb"></span><span><strong>实验楼入口</strong><small>在线 · 1 条规则</small></span></button>
|
||||
<button><span class="mini-thumb"></span><span><strong>宿舍东通道</strong><small>在线 · 3 条规则</small></span></button>
|
||||
<button><span class="mini-thumb"></span><span><strong>配电房门口</strong><small>在线 · 1 条规则</small></span></button>
|
||||
</div></aside>
|
||||
|
||||
<section class="card zone-workspace" aria-label="区域绘制工作区">
|
||||
<div class="editor-tools" role="toolbar" aria-label="区域绘制工具">
|
||||
<button class="tool-btn" data-tool="pointer" aria-pressed="false" title="选择对象"><svg class="icon"><use href="#i-pointer"/></svg><span>选择</span></button>
|
||||
<button class="tool-btn active" data-tool="polygon" aria-pressed="true" title="绘制多边形"><svg class="icon"><use href="#i-zone"/></svg><span>多边形</span></button>
|
||||
<button class="tool-btn" data-tool="line" aria-pressed="false" title="绘制方向警戒线"><svg class="icon"><use href="#i-line"/></svg><span>警戒线</span></button>
|
||||
<span class="tool-divider"></span>
|
||||
<button class="tool-btn" id="undoPoint" title="撤销最后一个点"><svg class="icon"><use href="#i-undo"/></svg><span>撤销</span></button>
|
||||
<button class="tool-btn" id="clearPoints" title="清空当前草稿"><svg class="icon"><use href="#i-trash"/></svg><span>清空</span></button>
|
||||
</div>
|
||||
<div class="editor-stage">
|
||||
<svg id="zoneCanvas" viewBox="0 0 960 540" role="img" aria-label="北围墙 01 子码流快照及区域叠加;可点击添加多边形顶点">
|
||||
<defs><linearGradient id="sky" x1="0" y1="0" x2="0" y2="1"><stop stop-color="#10283b"/><stop offset="1" stop-color="#07111d"/></linearGradient><pattern id="scan" width="8" height="8" patternUnits="userSpaceOnUse"><path d="M0 8h8" stroke="#ffffff" stroke-opacity=".035"/></pattern></defs>
|
||||
<rect width="960" height="540" fill="url(#sky)"/><path d="M0 330 160 250 330 315 510 235 690 310 820 225 960 282V540H0Z" fill="#13283a"/><rect y="410" width="960" height="130" fill="#101a22"/><path d="M0 414h960M0 472h960" stroke="#5c7489" stroke-opacity=".45"/><path d="M170 300v240m230-245v245m250-250v250m190-280v280" stroke="#6f879b" stroke-width="4"/><path d="M0 345h960" stroke="#7890a4" stroke-width="5"/><rect width="960" height="540" fill="url(#scan)"/>
|
||||
.mobile-nav button { min-h…3676 tokens truncated…>
|
||||
<polygon points="105,365 285,310 535,335 505,466 135,472" fill="#38bdf8" fill-opacity=".18" stroke="#38bdf8" stroke-width="4" stroke-dasharray="10 7"/>
|
||||
<g fill="#07111d" stroke="#7dd3fc" stroke-width="4"><circle cx="105" cy="365" r="7"/><circle cx="285" cy="310" r="7"/><circle cx="535" cy="335" r="7"/><circle cx="505" cy="466" r="7"/><circle cx="135" cy="472" r="7"/></g>
|
||||
<line x1="650" y1="340" x2="855" y2="430" stroke="#fbbf24" stroke-width="5"/><path d="m760 382 30-2-18 24" fill="none" stroke="#fbbf24" stroke-width="5"/><g id="draftLayer"></g>
|
||||
@@ -383,22 +269,23 @@
|
||||
<div class="editor-status"><span id="pointStatus">当前草稿:0 个点</span><span>缩放 100% · 网格吸附关闭</span></div>
|
||||
</section>
|
||||
|
||||
<aside class="card zone-settings"><div class="card-head"><h2>规则属性</h2><button class="btn btn-quiet icon-btn" aria-label="更多规则操作"><svg class="icon"><use href="#i-more"/></svg></button></div><div class="card-body">
|
||||
<aside class="card zone-settings"><div class="card-head"><h2>检测区域属性</h2><button class="btn btn-quiet icon-btn" aria-label="更多区域操作"><svg class="icon"><use href="#i-more"/></svg></button></div><div class="card-body">
|
||||
<div class="form-grid">
|
||||
<div class="form-field full"><label for="zoneName">名称</label><input id="zoneName" class="field" value="北围墙危险区域"></div>
|
||||
<div class="form-field"><label for="zoneType">空间类型</label><select id="zoneType" class="select"><option>禁入区域</option><option>方向警戒线</option><option>关注区域</option></select></div>
|
||||
<div class="form-field"><label for="zoneColor">叠加颜色</label><select id="zoneColor" class="select"><option>蓝色</option><option>琥珀色</option><option>红色</option></select></div>
|
||||
<div class="form-field full"><label for="zoneRule">绑定规则</label><select id="zoneRule" class="select"><option>非授权人员进入</option><option>翻越围墙</option><option>区域内聚集</option></select></div>
|
||||
<div class="form-field"><label for="schedule">生效时段</label><select id="schedule" class="select"><option>全天</option><option>夜间 22:00–06:00</option><option>自定义课表</option></select></div>
|
||||
<div class="form-field"><label for="duration">持续时间</label><select id="duration" class="select"><option>立即</option><option>超过 3 秒</option><option>超过 10 秒</option></select></div>
|
||||
<div class="form-field full"><div class="switch-row"><span><strong>试运行</strong><br><small class="muted">命中只记录,不触发正式预警</small></span><label class="switch"><input id="dryRun" type="checkbox" checked aria-label="启用试运行"><span></span></label></div></div>
|
||||
</div>
|
||||
<div class="coordinate-box"><h3>键盘坐标编辑</h3><p>坐标范围 0–100%,为鼠标绘制提供等效操作。</p><div class="coordinate-row"><input class="field" id="coordX" type="number" min="0" max="100" value="50" aria-label="横坐标百分比"><input class="field" id="coordY" type="number" min="0" max="100" value="50" aria-label="纵坐标百分比"><button class="btn" id="addCoordinate">添加点</button></div><ol id="pointList" class="muted" aria-live="polite"><li>尚未添加草稿点</li></ol></div>
|
||||
<div class="settings-actions"><button class="btn" id="discardDraft">放弃</button><button class="btn" id="saveDraft">保存草稿</button></div>
|
||||
<div class="linked-rules"><h3>关联的告警规则</h3><p>规则由 Bell 管理;Sense 只提供摄像头快照和版本化空间坐标。</p><ul><li>非授权人员进入 · 正式生效</li><li>区域内聚集 · 试运行</li></ul><a class="btn" id="configureRules" href="../bell/index.html?view=rules&camera=CAM-S2-001&zone=ZONE-007">在 Bell 配置规则</a></div>
|
||||
<div class="settings-actions"><button class="btn" id="discardDraft">放弃修改</button><button class="btn btn-primary" id="saveDraft">保存区域版本</button></div>
|
||||
</div></aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="detail-panel" data-detail-panel="health" hidden><div class="grid system-grid"><article class="card system-tile"><h3>媒体流</h3><dl><dt>实际态</dt><dd><span class="badge success">在线</span></dd><dt>分片</dt><dd>media-01</dd><dt>重连</dt><dd>0</dd></dl></article><article class="card system-tile"><h3>设备时间</h3><dl><dt>漂移</dt><dd>+0.2s</dd><dt>最近校时</dt><dd>5m 前</dd></dl></article><article class="card system-tile"><h3>区域校准</h3><dl><dt>坐标版本</dt><dd>v7</dd><dt>画面参数</dt><dd>匹配</dd></dl></article></div></section>
|
||||
<section class="detail-panel" data-detail-panel="history" hidden><article class="card"><div class="card-head"><h2>操作记录</h2><span class="badge info">只读</span></div><div class="table-wrap"><table><thead><tr><th>时间</th><th>操作</th><th>对象</th><th>执行人</th><th>结果</th></tr></thead><tbody><tr><td>22:52</td><td>保存检测区域</td><td>ZONE-007 · v7</td><td>实施工程师</td><td><span class="badge success">成功</span></td></tr><tr><td>22:41</td><td>校验子码流参数</td><td>704 × 576</td><td>系统</td><td><span class="badge success">匹配</span></td></tr></tbody></table></div></article></section>
|
||||
</section>
|
||||
|
||||
<section class="view" data-page="onboarding">
|
||||
<div class="page-head"><div><h1>接入任务</h1><p>默认 16 路也支持批量导入;128 路时不依赖逐路手工操作。</p></div><div class="page-actions"><button class="btn">下载 CSV 模板</button></div></div>
|
||||
<div class="grid split">
|
||||
@@ -420,7 +307,7 @@
|
||||
</div>
|
||||
|
||||
<nav class="mobile-nav" aria-label="移动端主导航">
|
||||
<button data-view="overview">总览</button><button data-view="monitor">监控</button><button data-view="devices">设备</button><button class="active" data-view="zones">区域</button><button data-view="system">系统</button>
|
||||
<button data-view="overview">总览</button><button data-view="monitor">监控</button><button class="active" data-view="devices">设备</button><button data-view="onboarding">接入</button><button data-view="system">系统</button>
|
||||
</nav>
|
||||
|
||||
<dialog id="cameraDialog" aria-labelledby="cameraDialogTitle">
|
||||
@@ -448,24 +335,39 @@
|
||||
function notify(message) { toast.textContent = message; toast.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => toast.classList.remove('show'), 3600); }
|
||||
|
||||
const deviceData = [
|
||||
['北围墙 01','CAM-S2-001','启用','在线','刚刚','+0.2s','media-01','success'],
|
||||
['北围墙 03','CAM-S2-003','启用','重连中','2 分钟前','—','media-01','warning'],
|
||||
['实验楼入口 02','CAM-S2-006','启用','在线','12 秒前','+3.8s','media-02','warning'],
|
||||
['宿舍东通道','CAM-S2-011','启用','在线','4 秒前','+0.1s','media-02','success'],
|
||||
['配电房门口','CAM-S2-015','停用','已停止','1 天前','—','未绑定','info']
|
||||
['北围墙 01','CAM-S2-001','启用','在线','刚刚','+0.2s','media-01','success','2 个区域','success'],
|
||||
['北围墙 03','CAM-S2-003','启用','重连中','2 分钟前','—','media-01','warning','待校准','warning'],
|
||||
['实验楼入口 02','CAM-S2-006','启用','在线','12 秒前','+3.8s','media-02','warning','1 个区域','success'],
|
||||
['宿舍东通道','CAM-S2-011','启用','在线','4 秒前','+0.1s','media-02','success','3 个区域','success'],
|
||||
['配电房门口','CAM-S2-015','停用','已停止','1 天前','—','未绑定','info','未配置','info']
|
||||
];
|
||||
$('#deviceRows').innerHTML = deviceData.map(d => `<tr><td><input type="checkbox" aria-label="选择 ${d[0]}"></td><td><div class="device-name"><span class="camera-icon"><svg class="icon"><use href="#i-camera"/></svg></span><span><strong>${d[0]}</strong><small>${d[1]}</small></span></div></td><td>${d[2]}</td><td><span class="badge ${d[7]}">${d[3]}</span></td><td>${d[4]}</td><td>${d[5]}</td><td>${d[6]}</td><td><button class="btn btn-quiet icon-btn" aria-label="${d[0]} 更多操作"><svg class="icon"><use href="#i-more"/></svg></button></td></tr>`).join('');
|
||||
$('#deviceRows').innerHTML = deviceData.map(d => `<tr><td><input type="checkbox" aria-label="选择 ${d[0]}"></td><td><div class="device-name"><span class="camera-icon"><svg class="icon"><use href="#i-camera"/></svg></span><span><strong>${d[0]}</strong><small>${d[1]}</small></span></div></td><td>${d[2]}</td><td><span class="badge ${d[7]}">${d[3]}</span></td><td>${d[4]}</td><td>${d[5]}</td><td>${d[6]}</td><td><span class="badge ${d[9]}">${d[8]}</span></td><td><button class="btn" data-open-camera="${d[1]}">查看详情</button></td></tr>`).join('');
|
||||
|
||||
const cameraNames = ['北围墙 01','实验楼入口 02','宿舍东通道','配电房门口'];
|
||||
$('#cameraGrid').innerHTML = cameraNames.map((name, i) => `<article class="card camera-card"><div class="camera-stage"><svg class="scene" viewBox="0 0 640 360" role="img" aria-label="${name} 模拟子码流画面"><rect width="640" height="360" fill="#07111d"/><path d="M0 ${250-i*12} 140 180 310 230 460 150 640 210V360H0Z" fill="#14283a"/><path d="M0 290h640M100 210v150m210-165v165m220-190v190" stroke="#647b8e" stroke-width="4"/><rect width="640" height="360" fill="none" stroke="#ffffff" stroke-opacity=".08"/><text x="18" y="28" fill="#d7e7f3" font-size="16">${name} · 子码流</text><circle cx="598" cy="27" r="5" fill="#34d399"/></svg></div><div class="camera-meta"><span><strong>${name}</strong><small>704 × 576 · 5 FPS</small></span><span class="badge success">在线</span></div></article>`).join('');
|
||||
|
||||
function switchView(name) {
|
||||
$$('.view').forEach(v => v.classList.toggle('active', v.dataset.page === name));
|
||||
$$('[data-view]').forEach(b => { const current = b.dataset.view === name; b.toggleAttribute('aria-current', current); b.classList.toggle('active', current); });
|
||||
const navName = name === 'device-detail' ? 'devices' : name;
|
||||
$$('[data-view]').forEach(b => { const current = b.dataset.view === navName; b.toggleAttribute('aria-current', current); b.classList.toggle('active', current); });
|
||||
$('#stateSelect').value = 'normal'; renderState('normal');
|
||||
$('#main-content').focus({preventScroll:true});
|
||||
}
|
||||
$$('[data-view]').forEach(b => b.addEventListener('click', () => switchView(b.dataset.view)));
|
||||
function openDeviceDetail(serial = 'CAM-S2-001') {
|
||||
const device = deviceData.find(d => d[1] === serial) || deviceData[0];
|
||||
$('#deviceDetailName').textContent = device[0]; $('#deviceBreadcrumb').textContent = device[0]; $('#deviceDetailSerial').textContent = device[1];
|
||||
const bellHref = `../bell/index.html?view=rules&camera=${encodeURIComponent(device[1])}&zone=ZONE-007`;
|
||||
$('#configureRules').href = bellHref; $('#returnToBell').href = bellHref;
|
||||
switchView('device-detail');
|
||||
}
|
||||
$$('[data-open-camera]').forEach(b => b.addEventListener('click', () => openDeviceDetail(b.dataset.openCamera)));
|
||||
$('#backToDevices').addEventListener('click', () => switchView('devices'));
|
||||
$$('.detail-tabs button').forEach(button => button.addEventListener('click', () => {
|
||||
const name = button.dataset.detailTab;
|
||||
$$('.detail-tabs button').forEach(b => { const on = b === button; b.classList.toggle('active', on); b.setAttribute('aria-selected', on); });
|
||||
$$('[data-detail-panel]').forEach(panel => panel.hidden = panel.dataset.detailPanel !== name);
|
||||
}));
|
||||
|
||||
function renderState(state) {
|
||||
const layer = $('#stateLayer'); const views = $$('.view');
|
||||
@@ -488,7 +390,8 @@
|
||||
draftLayer.innerHTML = shape + points.map((p,i) => `<circle cx="${p.x}" cy="${p.y}" r="8" fill="#08111f" stroke="#c4b5fd" stroke-width="4"><title>点 ${i+1}</title></circle>`).join('');
|
||||
$('#pointStatus').textContent = `当前草稿:${points.length} 个点`;
|
||||
$('#pointList').innerHTML = points.length ? points.map((p,i) => `<li>点 ${i+1}:${(p.x/9.6).toFixed(1)}%, ${(p.y/5.4).toFixed(1)}%</li>`).join('') : '<li>尚未添加草稿点</li>';
|
||||
$('#draftBadge').textContent = points.length ? '草稿有未保存修改' : '草稿未发布';
|
||||
$('#draftBadge').className = points.length ? 'badge warning' : 'badge info';
|
||||
$('#draftBadge').textContent = points.length ? '区域有未保存修改' : '区域 v7';
|
||||
}
|
||||
function addPoint(x,y) { if (activeTool === 'pointer') return; if (activeTool === 'line' && points.length >= 2) points.length = 0; points.push({x:Math.max(0,Math.min(960,x)),y:Math.max(0,Math.min(540,y))}); drawDraft(); }
|
||||
zoneCanvas.addEventListener('click', e => { const r = zoneCanvas.getBoundingClientRect(); addPoint((e.clientX-r.left)/r.width*960,(e.clientY-r.top)/r.height*540); });
|
||||
@@ -497,10 +400,9 @@
|
||||
$('#undoPoint').addEventListener('click', () => { points.pop(); drawDraft(); });
|
||||
$('#clearPoints').addEventListener('click', () => { if (points.length && confirm('清空当前未保存的绘制草稿?')) { points.length=0; drawDraft(); } });
|
||||
$('#addCoordinate').addEventListener('click', () => { const x=Number($('#coordX').value), y=Number($('#coordY').value); if (!Number.isFinite(x)||!Number.isFinite(y)||x<0||x>100||y<0||y>100) { notify('坐标必须在 0–100% 之间。'); return; } addPoint(x*9.6,y*5.4); });
|
||||
$('#discardDraft').addEventListener('click', () => { if (!points.length || confirm('放弃未保存的区域修改?')) { points.length=0; drawDraft(); notify('已放弃本次修改,正式规则未受影响。'); } });
|
||||
$('#saveDraft').addEventListener('click', () => { if ((activeTool==='polygon'&&points.length<3)||(activeTool==='line'&&points.length<2)) { notify(activeTool==='line'?'警戒线至少需要 2 个点。':'多边形至少需要 3 个点。'); return; } notify('草稿已保存,尚未正式发布。'); $('#draftBadge').textContent='草稿已保存'; });
|
||||
$('#publishRule').addEventListener('click', () => { if (confirm('发布后将影响“北围墙 01”的正式规则。确认发布当前已保存草稿?')) { $('#draftBadge').className='badge success'; $('#draftBadge').textContent='规则已发布'; notify('规则版本 v7 已发布,可在版本记录中回滚。'); } });
|
||||
$('#dryRun').addEventListener('change', e => notify(e.target.checked?'已启用试运行:命中不会触发正式预警。':'已关闭试运行:发布后将进入正式链路。'));
|
||||
$('#discardDraft').addEventListener('click', () => { if (!points.length || confirm('放弃未保存的区域修改?')) { points.length=0; drawDraft(); notify('已放弃本次修改,已保存区域和 Bell 规则均未改变。'); } });
|
||||
$('#saveDraft').addEventListener('click', () => { if ((activeTool==='polygon'&&points.length<3)||(activeTool==='line'&&points.length<2)) { notify(activeTool==='line'?'警戒线至少需要 2 个点。':'多边形至少需要 3 个点。'); return; } points.length=0; drawDraft(); $('#draftBadge').className='badge success'; $('#draftBadge').textContent='区域 v8 已保存'; notify('检测区域 v8 已保存;关联的 Bell 规则不会被自动发布。'); });
|
||||
$('#simulateCalibration').addEventListener('click', () => { $('#calibrationBadge').className='badge warning'; $('#calibrationBadge').textContent='区域待校准'; notify('画面分辨率或旋转发生变化,现有区域已标记为待校准。'); });
|
||||
|
||||
const dialog = $('#cameraDialog');
|
||||
$('#openCameraDialog').addEventListener('click', () => dialog.showModal());
|
||||
@@ -508,6 +410,12 @@
|
||||
$('#saveCamera').addEventListener('click', () => notify('摄像头已保存为“待激活”,凭据不会显示在列表中。'));
|
||||
$('#fakeUpload').addEventListener('click', () => notify('演示:已选择 cameras-demo.csv,检测到 1 个重复序列号。'));
|
||||
drawDraft();
|
||||
const query = new URLSearchParams(location.search);
|
||||
if (query.get('view') === 'device-detail' || query.has('camera')) {
|
||||
openDeviceDetail(query.get('camera') || 'CAM-S2-001');
|
||||
if (query.get('return') === 'bell-rule') $('#ruleReturnContext').hidden = false;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user