From 2715e557a960d7637d2dadf04801399a45068a01 Mon Sep 17 00:00:00 2001 From: ila Date: Tue, 4 Aug 2026 09:04:50 +0800 Subject: [PATCH] docs(design): update Sense workflow --- docs/design/sense/index.html | 218 ++++++++++------------------------- 1 file changed, 63 insertions(+), 155 deletions(-) diff --git a/docs/design/sense/index.html b/docs/design/sense/index.html index 96fde3a..8fe99c2 100644 --- a/docs/design/sense/index.html +++ b/docs/design/sense/index.html @@ -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; } - } - - - - - -
PROTOTYPE - 仅供枚举交互,非实现依据
-
- - -
-
-
青藤寄宿学校 / 主校区 · 16 / 16 路
-
- - - -
-
- -
-
- -
-

运行总览

先判断是否影响业务,再进入设备或分片定位原因。

-
-
在线设备
15 / 1693.8% · 1 路重连中
-
活跃媒体流
15主/子码流均衡
-
待收敛项
2配置 1 · 时间漂移 1
-
今日设备告警
3与业务预警分开
-
-
-

分片健康

全部可用
-
media-018 / 32
-
media-028 / 32
-
推理绑定16 / 16
-
-

需要处理

2 项
-

北围墙 03
断线 02:14 · 正在第 4 次重连

-

实验楼入口 02
时间漂移 +3.8s · 建议重新校时

-
-
-
- -
-

实时监控

默认只并发预览 4 路子码流;切页会释放上一页播放器。

第 1 / 4 页
-
-
- -
-

摄像头

期望态与实际态分开展示;连接地址和凭据始终脱敏。

-
-
已用 16 / 128
-
设备期望态实际态最后在线时间漂移媒体分片操作
-
-
- -
-

区域与警戒线

在子码流快照上定义规则空间;保存草稿不会立即影响正式策略。

草稿未发布
-
- - -
- -
- - - + .mobile-nav button { min-h…3676 tokens truncated…> @@ -383,20 +269,21 @@
当前草稿:0 个点缩放 100% · 网格吸附关闭
-
+ + +
@@ -420,7 +307,7 @@
@@ -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 => `
${d[0]}${d[1]}
${d[2]}${d[3]}${d[4]}${d[5]}${d[6]}`).join(''); + $('#deviceRows').innerHTML = deviceData.map(d => `
${d[0]}${d[1]}
${d[2]}${d[3]}${d[4]}${d[5]}${d[6]}${d[8]}`).join(''); const cameraNames = ['北围墙 01','实验楼入口 02','宿舍东通道','配电房门口']; $('#cameraGrid').innerHTML = cameraNames.map((name, i) => `
${name} · 子码流
${name}704 × 576 · 5 FPS在线
`).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) => `点 ${i+1}`).join(''); $('#pointStatus').textContent = `当前草稿:${points.length} 个点`; $('#pointList').innerHTML = points.length ? points.map((p,i) => `
  • 点 ${i+1}:${(p.x/9.6).toFixed(1)}%, ${(p.y/5.4).toFixed(1)}%
  • `).join('') : '
  • 尚未添加草稿点
  • '; - $('#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; + } +