refactor: 按角色分层目录, HTML 不再平铺根目录

根目录原本平铺 7 个 HTML, 而 js/ 已按功能域分子目录, 两级结构不对称;
且自包含实验页与主功能页平级摆放, 看不出可随时删除。现按角色分三层:

  tabs.html  标签外壳留在根 = 工具入口
  pages/     index / can / can_monitor / motor  主功能页
  lab/       continuity_test / spi_test  自包含实验页(零外部依赖)
  scripts/   build_mem.bat

随迁修正(全部实跑验证):
- pages/ 下 4 页共 22 处 css/js 引用加 ../ 前缀; 页面互链同目录无需改
- js/motor/main.js 的 Worker URL 改用 import.meta.url 解析: 原文档相对
  路径 "js/motor/tick.worker.js" 在 pages/ 下会解析成 /pages/js/... 而
  404, 静默退化到 setTimeout 兜底、丢失后台不节流能力(已验证旧写法 404)
- tabs.html 的 TABS 登记新路径; pageToTab 改按文件名匹配, 旧版
  localStorage 存的裸文件名(can.html)自动迁移到 pages/can.html
- esp32_server.py: GET / 与 /index.html 302 到 /tabs.html(根下已无
  index.html); 注入脚本的 ⌂ 主页判定基准由 index.html 改为 tabs.html
- serve.bat 打印外壳与 OTA 页新地址; README 补目录结构与路径约定

验证: 39/39 静态资源 200, 7 页 200 且注入生效, 逐页解析引用 0 坏链,
路由 / 与 /index.html 302、未知页 404, node --check 与 py_compile 通过。
This commit is contained in:
cbb
2026-09-03 21:51:17 +08:00
parent d2c86c425a
commit 31b90f68b3
12 changed files with 89 additions and 50 deletions
+20 -13
View File
@@ -1,6 +1,6 @@
<!--
tabs.html - ESP32 调试控制台「单窗口多标签」外壳
顶部一排标签, 下方 iframe 承载各功能页(index/can/motor/...)。
顶部一排标签, 下方 iframe 承载各功能页(pages/ 下功能页, lab/ 下实验页)。
各页面原样复用, 互不干扰; 页面内的"新窗口打开"/普通链接会在 iframe 内跳转,
外壳监听 iframe 的 load 事件同步高亮当前标签。
激活标签存 localStorage(dcu_tab), 下次打开自动恢复。
@@ -65,22 +65,27 @@
<script>
(function(){
var TABS = [
{ page: 'index.html', label: 'OTA 中继', ico: '⬆', color: 'var(--esp32)' },
{ page: 'can.html', label: 'CAN 通信', ico: '⇄', color: 'var(--can)' },
{ page: 'can_monitor.html', label: 'CAN 监控', ico: '👁', color: 'var(--can)' },
{ page: 'motor.html', label: '电机控制', ico: '⚙', color: 'var(--motor)' },
{ page: 'continuity_test.html',label: '通断测试', ico: '🧰', color: 'var(--gd32)' },
{ page: 'spi_test.html', label: 'SPI 测速', ico: '⚡', color: 'var(--ble)' }
{ page: 'pages/index.html', label: 'OTA 中继', ico: '⬆', color: 'var(--esp32)' },
{ page: 'pages/can.html', label: 'CAN 通信', ico: '⇄', color: 'var(--can)' },
{ page: 'pages/can_monitor.html', label: 'CAN 监控', ico: '👁', color: 'var(--can)' },
{ page: 'pages/motor.html', label: '电机控制', ico: '⚙', color: 'var(--motor)' },
{ page: 'lab/continuity_test.html', label: '通断测试', ico: '🧰', color: 'var(--gd32)' },
{ page: 'lab/spi_test.html', label: 'SPI 测速', ico: '⚡', color: 'var(--ble)' }
];
var bar = document.getElementById('tabbar');
var view = document.getElementById('view');
var spacer = bar.querySelector('.spacer');
var LS = 'dcu_tab';
function baseName(p){ return (p || '').replace(/^.*\//, '').split('?')[0]; }
/* 按文件名匹配, 与目录层级无关: iframe 里读到的是绝对 pathname
* (/pages/can.html), 而旧版 localStorage 里存的是裸文件名(can.html),
* 两者都能落到同一个标签。返回 TABS 中登记的相对路径。 */
function pageToTab(path){
path = (path || '').replace(/^.*\//, '').split('?')[0];
if (path === '' ) return 'index.html';
for (var i=0;i<TABS.length;i++) if (TABS[i].page === path) return path;
var b = baseName(path);
if (b === '') return 'pages/index.html';
for (var i=0;i<TABS.length;i++) if (baseName(TABS[i].page) === b) return TABS[i].page;
return null; // 未知页(如从页面内跳到别的 html)
}
function setActive(path){
@@ -114,9 +119,11 @@
if (tab) { setActive(tab); try { localStorage.setItem(LS, tab); } catch(e){} }
});
// 初始标签: 上次记忆, 否则 OTA 主页
var init = 'index.html';
try { var s = localStorage.getItem(LS); if (s && pageToTab(s)) init = s; } catch(e){}
// 初始标签: 上次记忆, 否则 OTA 主页。
// 取 pageToTab 的返回值而非 localStorage 原值, 这样旧版存的裸文件名
// (can.html) 会被自动迁移成新的 pages/can.html。
var init = 'pages/index.html';
try { var s = localStorage.getItem(LS); var m = s && pageToTab(s); if (m) init = m; } catch(e){}
go(init, false);
})();
</script>