Files
DCU_DesktopController/docs/compose/spec/tab-keepalive.md
T
cbb eaafee1312 feat(shell): tabs 多 iframe 常驻保活,切标签不再销毁功能页
单 iframe 换 src 会卸载文档,WebSocket/轮询/传输状态全灭。
改为一标签一懒创建 iframe,切换只改 visibility;F5/⟳ 只 reload 激活帧。
2026-09-16 02:37:18 +08:00

77 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
feature: tab-keepalive
status: designed
updated: 2026-09-16
branch: feat/tab-keepalive
commits:
---
# Tab Keep-Alive(多 iframe 常驻)
## Report
## [S1] Problem
`tabs.html` 用**单 iframe** 承载功能页,切换标签时执行 `view.src = path`。
这会销毁上一页的整个文档:
- WebSocket(`app-lease`、调试通道)断开
- `setInterval` / `setTimeout` 轮询停止
- 电机页 EtherCAT/WiFi 传输状态、`transport-ecat` 的 `started` 标志丢失
- OTA 页 `ecat_load` 轮询、SPI 状态轮询冷启动
- 用户「调完一切、切走再切回」后连接全灭,表现为界面假死/全 `--`
外壳自身的 `app-lease.js` 仍在,但功能页内的连接与状态无法幸存。
这是结构性缺陷,不是某个页面的 bug。
## [S2] Design
### 外壳:一标签一 iframe,只 show/hide
- `TABS` 每项对应一个懒创建 iframe(`class="page-frame"`,`data-page`)。
- 首次进入某标签才 `src=page`;之后切换只改可见性,**不改 src、不 remove**。
- 宿主容器 `id="viewHost"`(保留 `data-shell` 语义:注入脚本用 `#viewHost` 判定外壳)。
- 激活帧:`visibility:visible; pointer-events:auto`;非激活:`visibility:hidden; pointer-events:none`。
**不用 `display:none`**——部分引擎对 display:none iframe 的定时器/绘制更狠;`visibility` + 绝对定位铺满即可。
- 切换时同步 `localStorage.dcu_tab`(沿用现有键与 `pageToTab` 迁移)。
### 状态保全契约
| 状态 | 保活后 |
|------|--------|
| 功能页内 WS / 轮询 / Module 全局 | 留在各自 iframe 文档内,切走不断 |
| 外壳 `app-lease` | 仍由 `tabs.html` 自持(覆盖整个窗口) |
| `ecat_native` 后台 PDO 循环 | 服务端 + 租约决定,与是否隐藏无关 |
| F5 / ⟳ | **只 reload 激活 iframe**,不重载外壳(否则全部保活帧被销毁) |
### 注入刷新脚本(`esp32_server.py` `_RELOAD_SNIPPET`)
- `reloadCur()`:优先 `document.querySelector('.page-frame.active')`;无则退化 `location.reload()`。
- 外壳判定:`#viewHost` 存在(替代原 `#view` iframe 的语义)。
- 功能页内(非 TOP)行为不变:去掉悬浮钮,覆写 `_blank`。
### 导航与高亮
- 点击标签 → `go(page)`:确保帧存在 → 激活目标 → 其它帧隐藏。
- iframe `load`:若 pathname 映射到某 TAB,更新外壳高亮与 `dcu_tab`;
不因非激活帧的延迟 load 误抢激活(仅当该帧已是 active 才写 `dcu_tab`)。
- 未知页(页内跳到未登记 html):仍留在当前 iframe 内显示,外壳不高亮新标签。
### 内存
用户裁定「全部标签常驻」。6 个功能页同时活着的代价可接受;
不引入 LRU 卸载(卸载即回到现在的病)。
## [S3] Out of Scope
- 不合并成 SPA、不把连接迁到外壳进程
- 不改各页内部轮询/传输实现
- 不改服务端租约协议
- 不做「后台页降频」策略(浏览器 Page Visibility 自带节流即可)
## Tasks
- [ ] T1: `tabs.html` 多 iframe 宿主 + 懒创建 + show/hide 切换 — acceptance: 切走再切回,OTA/电机页的轮询与 WS 仍在,无需重新连接 (covers: S2)
- [ ] T2: `esp32_server.py` 注入的 F5/⟳ 只刷新激活帧 — acceptance: 外壳按 F5 仅重载当前标签文档,其它标签 iframe 仍在 DOM 且未 reload (covers: S2)
- [ ] T3: 语法/手工验收路径 — acceptance: `node --check` 若适用;人工路径:开 OTA→电机→切回 OTA,指标不全灭 (covers: S2)