128 lines
9.0 KiB
Markdown
128 lines
9.0 KiB
Markdown
# `/web/desktop` 2D 平面图 — 开发计划与现状(替代旧版开发文档)
|
||
|
||
> 本文档替代仓库内此前的 2D 开发/说明类文档,作为**唯一**跟进源。实现以当前代码为准(`FloorPlanState` v3、世界坐标 + 网格吸附、墙线/房间/Konva 等)。
|
||
|
||
## 1. 产品目标
|
||
|
||
在 `/desktop` 提供**数据流拓扑**与 **2D 平面视图**两种模式,2D 用于:
|
||
|
||
- 快速理解摄像头覆盖区域(扇区)与墙线/房间示意
|
||
- 绑定真实通道、悬停查看最近 AI 事件
|
||
- 从平面图**跳转录像回放**(与产品决策一致)
|
||
- 本地持久化布局(`localStorage`,第一版不接后端平面图存储)
|
||
|
||
## 2. 当前实现概要(与旧 MVP 文档的差异)
|
||
|
||
| 原 MVP 设想 | 当前实现 |
|
||
|-------------|----------|
|
||
| 纯四边形网格 row/col | 世界坐标 `(x,y)` + 网格吸附,并支持墙线、矩形房间、预设轮廓 |
|
||
| 简单编辑 | 多选、框选、编组、撤销栈、复制粘贴、对齐辅助线等 |
|
||
| hover 按需拉事件 | 已实现;事件缓存见 `floor_plan.events.ts` |
|
||
|
||
## 3. 数据模型(摘录)
|
||
|
||
见 `app/pages/desktop/floor_plan.types.ts`:`FloorPlanState`(含 `walls`、`cameras`、`view`)、`CameraMarker`(含 `channelId`、`latestEvent*` 等)。
|
||
|
||
## 4. 迭代计划(按优先级)
|
||
|
||
### 阶段 A — 闭环与首屏体验(P0)
|
||
|
||
- [x] 数据流 ↔ 2D **视图模式** `localStorage` 记忆(`desktop-view-mode.ts`,键 `desktop-view-mode`)
|
||
- [x] **浏览 / 编辑** 模式:浏览下禁止改墙/摄像头、禁止绘制;编辑下保持现有能力(`floor_plan.storage`:`desktop-floor-plan-interaction-mode`)
|
||
- [x] 绑定通道的摄像头:**跳转录像页** `/playback/detail?cid=…&date=…`(与 `recordings.tsx` 一致,见 `floor_plan.playback.ts`)
|
||
- [x] 悬浮卡片:视口内**防裁切**、「打开录像」按钮;缩略图 `onError` 打日志
|
||
- [x] 浏览模式:单击用于选中(看侧栏),**不**自动整页跳转;跳转统一走「打开录像」
|
||
|
||
### 阶段 B — 信息效率(P1)
|
||
|
||
- [x] 单选摄像头时侧栏展示**最近一次 AI 事件**(与 hover 共用 `getLatestCameraEvent`)
|
||
- [x] 通道名称**搜索**:未匹配摄像头在画布上淡化(`opacity`)
|
||
|
||
### 阶段 C — 增强(P2+)
|
||
|
||
- [x] 通道在线/离线:由 `FindPlannerChannelOptions` 拉平的 `isOnline` 映射到已绑定 `channelId`(画布描边绿/红;侧栏与悬停卡片展示文案)
|
||
- [x] 触控/缩放:画布容器 **滚轮** 以指针为锚缩放;**双指 pinch** 以两指中点为锚缩放(与工具栏缩放共用 0.35–3.2 范围)
|
||
- [x] 平面图内已绑定通道的**最近事件预取**:无专用批量接口时,用 `FindEvents` 按 `started_at desc` 分页,在客户端为每个 `cid` 保留首见的一条并写入缓存与 `CameraMarker.latestEvent*`(与单通道查询共用 `MapEventToLatestChannelEvent`);绑定集合变化时重新预取
|
||
|
||
### 阶段 D — 跨页跳转(延续原 MVP「告警」方向)
|
||
|
||
- [x] 悬停卡片与侧栏 **「打开告警」**:跳转 `/alerts?cid=…`,告警页读取 query 并预选通道筛选(与平面图 `channelId` 一致)
|
||
- [x] 跳转实现使用 **`<Link to={{ pathname, search }}>`**(与 `buildPlaybackDetailTo` / `buildAlertsTo`),避免在 `basename`(如 `/web`)下 `navigate` 字符串与 query 解析不一致导致「点了不跳」
|
||
- [x] 悬停卡片 **Portal + `fixed` + z-index 高于 FAB**,离开摄像头 **短延时清除** + 移入卡片取消清除,避免被右下角菜单占位挡住或移向按钮时卡片消失
|
||
|
||
### 阶段 E — 前端体验增强(不接后端存储)
|
||
|
||
- [x] **首次引导**:`Modal` + `localStorage` 键 `desktop-floor-plan-guide-dismissed`;顶栏地图钉可再次打开
|
||
- [x] **筛选导航**:匹配列表 **上一条 / 下一条**(自动选中并居中)、**框入全部匹配**(仅缩放至匹配摄像头包围盒)
|
||
- [x] **事件可信度**:侧栏 **刷新**(`clearLatestCameraEventCache` + 重拉);悬停卡片与侧栏展示 **事件发生相对时间** 与 **数据拉取相对时间**(30s tick)
|
||
- [x] **鸟瞰小地图**:`FloorPlanMinimap` 点击平移视口;顶栏 **框选全部已绑定摄像头**(`zoomToFitCameras`)
|
||
|
||
### 阶段 F — 纯前端续(备份与快捷)
|
||
|
||
- [x] **布局 JSON 导出 / 导入**:顶栏下载/上传,`floor_plan.export.ts` 包装版本与时间;导入后清空事件缓存并重置撤销栈
|
||
- [x] **浏览模式筛选快捷键**:`[` / `]`、`PgUp` / `PgDn` 切换匹配,`F` 框入全部,`Esc` 额外清空筛选关键词
|
||
- [x] **小地图拖拽**:轻点仍居中,移动超过阈值则按像素平移主视口(与 `view.x/y` 一致)
|
||
|
||
### 阶段 G — 移动端适配(≤767px)
|
||
|
||
- [x] **布局**:`flex-col` + 画布占满剩余高度(`min-h-0`);侧栏自 `md` 起固定右侧,小屏改为 **底部 Drawer**(浮动按钮打开)
|
||
- [x] **顶栏**:`sticky` + **横向滚动**,避免按钮换行压扁画布;桌面仍为 `absolute` 浮动条
|
||
- [x] **提示条**:小屏缩小字号、限制高度可滚动,避免占满屏幕
|
||
- [x] **小地图**:`compact` 缩小尺寸并抬高 `bottom`,避让底部提示与安全区
|
||
- [x] **桌面壳**:`100dvh` 避免移动浏览器地址栏导致高度跳动
|
||
- [x] **画布**:`touch-pan-y touch-pinch-zoom` 声明 pinch 手势
|
||
- [x] **触摸拖动**:document 级用 `pointermove`/`pointerup` 跟踪(替代仅 mouse),浏览模式小屏空白处单指拖动平移视口
|
||
|
||
## 5. 关键文件
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `app/pages/desktop/desktop.tsx` | 数据流 / 2D 切换、视图模式持久化 |
|
||
| `app/pages/desktop/floor_plan.tsx` | 2D 主编辑器 |
|
||
| `app/pages/desktop/floor_plan.storage.ts` | 平面图与浏览/编辑模式本地存储 |
|
||
| `app/pages/desktop/desktop-view-mode.ts` | 数据流 / 2D 视图记忆 |
|
||
| `app/pages/desktop/floor_plan.playback.ts` | 录像详情 URL 与列表页对齐 |
|
||
| `app/pages/desktop/floor_plan.alerts.ts` | 告警页深链接 `/alerts?cid=` |
|
||
| `app/pages/alerts/alerts.tsx` | 支持 URL `cid` 初始化通道筛选 |
|
||
| `app/pages/desktop/floor_plan.events.ts` | 最近 AI 事件查询、缓存与批量预取 |
|
||
| `app/service/api/event/event.ts` | `MapEventToLatestChannelEvent` 与单通道/批量字段一致 |
|
||
| `app/components/desktop/camera-hover-card.tsx` | 悬停卡片 |
|
||
| `app/components/desktop/camera-binding-panel.tsx` | 绑定与参数、侧栏最近事件展示 |
|
||
| `app/components/desktop/floor-plan-minimap.tsx` | 左下角鸟瞰与视口框 |
|
||
| `app/pages/desktop/floor_plan.relative-time.ts` | 事件/拉取时间的相对文案与绝对时间格式化 |
|
||
| `app/pages/desktop/floor_plan.export.ts` | 导出包装与导入解析(兼容裸 `FloorPlanState` JSON) |
|
||
|
||
## 6. 后端参考
|
||
|
||
事件、通道等与 [gowvp/owl](https://github.com/gowvp/owl) 对齐;前端通过现有 `FindEvents` / `FindPlannerChannelOptions` 等调用。
|
||
|
||
## 6.1 若需后端配合(交接用,当前未实现)
|
||
|
||
以下能力前端已用现有接口「尽力而为」;要进一步降延迟、减流量或做多端同步,建议 owl 侧迭代:
|
||
|
||
1. **按通道批量查询最近事件**
|
||
- **问题**:现用全局 `FindEvents` 分页在客户端按 `cid` 归并,通道多时页数多、延迟高。
|
||
- **建议**:`POST /api/.../events/latest-by-cids`,body `{ cids: string[] }`,返回每通道一条最新记录(与现有单通道字段一致)。可选 `since` 支持增量。
|
||
|
||
2. **平面图布局云端存储**
|
||
- **问题**:`localStorage` 无法跨浏览器/团队共享,清缓存即丢。
|
||
- **建议**:资源模型如 `floor_plan`:`tenant_id`、`name`、`state_json`(或版本化 blob)、`updated_at`;REST `GET/PUT` 或按用户默认布局单例。
|
||
|
||
3. **导出 / 导入 JSON(纯前端也可做)**
|
||
- 若需审计与备份,可增加后端「上传布局快照」存对象存储,与 2 类似。
|
||
|
||
## 7. 验收清单(2D)
|
||
|
||
- [x] **浏览 / 编辑与工具可见性**:浏览模式下隐藏仅编辑用的顶栏工具(绘制、撤销、剪贴板、编组、重置布局)与侧栏快捷预设及批量/单墙编辑按钮,避免满屏禁用态;绑定面板内通道绑定、朝向滑块与删除摄像头在浏览模式下亦隐藏,仅保留只读摘要与提示;编辑模式下仅显示当前可用的撤销/重做、复制/粘贴等(无选择时不占位)
|
||
- [x] 刷新后仍保持上次「数据流 / 2D」选择
|
||
- [x] 浏览模式下无法误改平面图;编辑模式可完整编辑
|
||
- [x] 已绑定通道的摄像头可从平面图进入录像详情(当日日期)
|
||
- [x] 悬停卡片在边缘不被裁切;侧栏可看到与 hover 一致的最近事件信息(有通道时)
|
||
- [x] 滚轮 / 双指缩放可用;已绑定通道显示在线状态(列表能解析时)
|
||
- [x] 首次进入可看到引导(可关闭并记忆);筛选支持匹配间跳转与框入全部匹配;侧栏可刷新事件并显示相对时间;小地图可点击导航
|
||
- [x] 可导出/导入布局 JSON;浏览模式下筛选快捷键可用
|
||
|
||
---
|
||
|
||
*文档版本:与仓库 `desktop_2d_plan_todo.md` 同步更新。*
|