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:
@@ -20,33 +20,50 @@
|
||||
同款结构。pywebview 窗口没有地址栏与刷新按钮,因此服务器会向 HTML 响应注入
|
||||
`_reload.html`(右下角 ⟳ 悬浮按钮 + F5/Ctrl+R 拦截)。
|
||||
|
||||
`build_mem.bat` 是从 Git Bash 调用 ESP-IDF 构建 `DCU/ESP32_COMM` 的辅助脚本
|
||||
`build_mem.bat`(在 `scripts/`)是从 Git Bash 调用 ESP-IDF 构建 `DCU/ESP32_COMM` 的辅助脚本
|
||||
(会先清掉 MSYS 环境变量绕过 IDF 的 MSys 检测)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
tabs.html 标签外壳 = 工具入口(GET / 会 302 到这里)
|
||||
pages/ 主功能页,各自引用 ../css 与 ../js
|
||||
lab/ 自包含实验页,零外部依赖,删文件即可移除
|
||||
css/ js/ 共享样式与 ES module
|
||||
scripts/ 构建辅助脚本
|
||||
esp32_server.py 本地页面服务 + 桌面窗口外壳
|
||||
esp32_app.vbs esp32_web.vbs serve.bat 三个启动入口
|
||||
```
|
||||
|
||||
## 页面
|
||||
|
||||
`tabs.html` 是单窗口多标签外壳,用 iframe 承载下列功能页,激活标签记忆在
|
||||
`localStorage` 的 `dcu_tab`:
|
||||
`localStorage` 的 `dcu_tab`(按文件名匹配,与目录层级无关):
|
||||
|
||||
| 页面 | 用途 | 主要脚本 |
|
||||
|------|------|----------|
|
||||
| `index.html` | OTA 中继控制台(ESP32 WiFi OTA / GD32 relay / BLE OTA) | `js/app.js`、`js/ota/*`、`js/ble_ota.js`、`js/ble/*` |
|
||||
| `can.html` | CAN 通信(DBC 解析、消息编辑与批量发送) | `js/can.js`、`js/can/*` |
|
||||
| `can_monitor.html` | CAN 接收监控 | `js/can_monitor.js` |
|
||||
| `motor.html` | 电机控制台(CAN FD) | `js/motor/main.js`、`js/motor/tick.worker.js` |
|
||||
| `continuity_test.html` | LAN9252 通断测试(自包含,含 WebGL 液态玻璃效果) | 内联 |
|
||||
| `spi_test.html` | 【临时功能 SPI-SPEEDTEST】SPI 链路测速,完全自包含,删文件即可移除 | 内联 |
|
||||
| `pages/index.html` | OTA 中继控制台(ESP32 WiFi OTA / GD32 relay / BLE OTA) | `js/app.js`、`js/ota/*`、`js/ble_ota.js`、`js/ble/*` |
|
||||
| `pages/can.html` | CAN 通信(DBC 解析、消息编辑与批量发送) | `js/can.js`、`js/can/*` |
|
||||
| `pages/can_monitor.html` | CAN 接收监控(独立页;也被 `can.html` 内嵌复用) | `js/can_monitor.js` |
|
||||
| `pages/motor.html` | 电机控制台(CAN FD) | `js/motor/main.js`、`js/motor/tick.worker.js` |
|
||||
| `lab/continuity_test.html` | LAN9252 通断测试(自包含,含 WebGL 液态玻璃效果) | 内联 |
|
||||
| `lab/spi_test.html` | 【临时功能 SPI-SPEEDTEST】SPI 链路测速,完全自包含,删文件即可移除 | 内联 |
|
||||
|
||||
## 目录约定
|
||||
|
||||
- `css/` —— `base`(变量/基础)、`layout`、`components`、`can`、`motor`、`responsive`。
|
||||
- `js/common/` —— 跨页公用:`base-url`(设备 IP)、`connection`、`polling`、`log`、`toast`、`utils`、`heap-low-history`。
|
||||
- `js/ota/`、`js/ble/`、`js/can/`、`js/motor/` —— 按功能域拆分的 ES module。
|
||||
- `js/ota/`、`js/ble/`、`js/can/`、`js/motor/` —— 按功能域拆分的 ES module,跨域引用只走 `js/common/`。
|
||||
- 子系统配色:ESP32 = 青绿 · GD32 = 琥珀 · BLE = 蓝 · CAN = 绿 · 内存/调试 = 紫。
|
||||
|
||||
## 改动须知
|
||||
|
||||
- **改 CSS/JS 后必须 bump 引用处的 `?v=` 缓存版本**,否则浏览器会加载旧代码。
|
||||
- `pages/` 与 `lab/` 下的页面引用共享资源要用 `../css/`、`../js/` 前缀;
|
||||
**JS 模块内部引用其他资源(如 Worker)须用 `import.meta.url` 解析**,
|
||||
不能写文档相对路径,否则页面换层级就会 404(见 `js/motor/main.js` 的 `TICK_WORKER_URL`)。
|
||||
- 新增功能页放 `pages/`,并在 `tabs.html` 的 `TABS` 数组登记;
|
||||
一次性/实验页放 `lab/` 并保持自包含。
|
||||
- OTA 流程以设备端为真相来源:WiFi OTA 由设备回读分区算 SHA-256 后自动重启;
|
||||
BLE OTA 由前端发 `0xF8 + 32B SHA-256`、设备端比较并通过 FINISH 事件报结果
|
||||
(前端不做 SHA 预比较);GD32 relay 状态以 `/system/status` 为准。
|
||||
|
||||
+16
-4
@@ -5,10 +5,12 @@
|
||||
而所有业务 API 由 ESP32 固件的 80 端口跨域提供(js/common/base-url.js 里的
|
||||
设备 IP, 存 localStorage 的 dcu_esp_ip)。因此本脚本不碰任何硬件, 只做静态服务。
|
||||
|
||||
GET / → index.html (OTA 控制台; 标签外壳为 tabs.html)
|
||||
GET /<file> → 本目录静态文件(html/css/js)
|
||||
GET / → 302 到 /tabs.html (标签外壳, 工具主页)
|
||||
GET /<file> → 本目录静态文件(tabs.html / pages/ / lab/ / css/ / js/)
|
||||
POST /api/shutdown → 结束本服务进程(窗口模式父进程用它通知子进程退出)
|
||||
|
||||
目录约定: tabs.html 在根(工具入口), 功能页在 pages/, 自包含实验页在 lab/。
|
||||
|
||||
启动模式:
|
||||
--window pywebview 桌面窗口(esp32_app.vbs) —— GUI 进程 + 独立服务子进程
|
||||
默认 浏览器标签页(esp32_web.vbs / serve.bat)
|
||||
@@ -78,7 +80,10 @@ user-select:none;opacity:.55;transition:opacity .15s}
|
||||
var home=document.getElementById('__home');
|
||||
var p=location.pathname;
|
||||
var isShell=!!document.getElementById('view');
|
||||
if(!isShell&&!/\\/$/.test(p)&&!/\\/index\\.html$/.test(p)){
|
||||
// 顶层且非标签外壳(浏览器直开 pages/xxx.html) → 给个 ⌂ 回到外壳。
|
||||
// 判定基准是外壳自身(有 #view / 路径为 tabs.html 或 /), 不能再看 index.html:
|
||||
// 重排后 pages/index.html 只是普通功能页, 直开它同样该显示 ⌂。
|
||||
if(!isShell&&!/\\/$/.test(p)&&!/\\/tabs\\.html$/.test(p)){
|
||||
home.style.display='block';
|
||||
home.addEventListener('click',function(){location.href='__HOME__';});
|
||||
}
|
||||
@@ -152,7 +157,14 @@ class Handler(SimpleHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
path = self.path.split("?")[0].lower()
|
||||
if path.endswith(".html") or path == "/" or not os.path.splitext(path)[1]:
|
||||
# 根路径不再有 index.html(已移入 pages/), 直接送去标签外壳
|
||||
if path in ("/", "/index.html"):
|
||||
self.send_response(302)
|
||||
self.send_header("Location", "/tabs.html")
|
||||
self.send_header("Content-Length", "0")
|
||||
self.end_headers()
|
||||
return
|
||||
if path.endswith(".html") or not os.path.splitext(path)[1]:
|
||||
# HTML(含无扩展名的 SPA 路由): 注入刷新悬浮按钮/快捷键后返回
|
||||
fp = self.translate_path(self.path)
|
||||
if os.path.isdir(fp):
|
||||
|
||||
+3
-1
@@ -967,7 +967,9 @@ function scheduleFeedbackPoll(gen, delayMs) {
|
||||
* 故把 motor 轮询的节拍源放到 tick.worker.js:Worker 按设定间隔定期发
|
||||
* 'tick' 消息,主线程收到后才真正发起一次 fetchMotorState。
|
||||
* 前台行为与原来一致;后台也能守住设定的刷新速度。 */
|
||||
const TICK_WORKER_URL = "js/motor/tick.worker.js?v=20260802g";
|
||||
/* 用 import.meta.url 解析: 本模块在 js/motor/ 下, 与引用它的页面在哪层目录无关
|
||||
* (页面已移入 pages/, 文档相对路径会解析成 /pages/js/... 而 404)。 */
|
||||
const TICK_WORKER_URL = new URL("./tick.worker.js?v=20260802g", import.meta.url);
|
||||
let tickWorker = null; /* Web Worker 节拍源(null = 未创建/失败) */
|
||||
let tickUseFallback = false; /* Worker 创建/加载失败 → 回退到 setTimeout 兜底 */
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
<title>ESP32 CAN 通信控制台</title>
|
||||
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="css/base.css?v=857f83a5">
|
||||
<link rel="stylesheet" href="css/layout.css?v=acd17001">
|
||||
<link rel="stylesheet" href="css/components.css?v=7bc7d5db">
|
||||
<link rel="stylesheet" href="css/can.css?v=c06f5688">
|
||||
<link rel="stylesheet" href="css/responsive.css?v=46449483">
|
||||
<link rel="stylesheet" href="../css/base.css?v=857f83a5">
|
||||
<link rel="stylesheet" href="../css/layout.css?v=acd17001">
|
||||
<link rel="stylesheet" href="../css/components.css?v=7bc7d5db">
|
||||
<link rel="stylesheet" href="../css/can.css?v=c06f5688">
|
||||
<link rel="stylesheet" href="../css/responsive.css?v=46449483">
|
||||
</head>
|
||||
<body class="can-page">
|
||||
<div class="container can-container">
|
||||
@@ -329,6 +329,6 @@
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/can.js?v=20260801f"></script>
|
||||
<script type="module" src="../js/can.js?v=20260801f"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,11 +6,11 @@
|
||||
<title>ESP32 CAN 接收监控</title>
|
||||
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="css/base.css?v=857f83a5">
|
||||
<link rel="stylesheet" href="css/layout.css?v=acd17001">
|
||||
<link rel="stylesheet" href="css/components.css?v=7bc7d5db">
|
||||
<link rel="stylesheet" href="css/can.css?v=c06f5688">
|
||||
<link rel="stylesheet" href="css/responsive.css?v=46449483">
|
||||
<link rel="stylesheet" href="../css/base.css?v=857f83a5">
|
||||
<link rel="stylesheet" href="../css/layout.css?v=acd17001">
|
||||
<link rel="stylesheet" href="../css/components.css?v=7bc7d5db">
|
||||
<link rel="stylesheet" href="../css/can.css?v=c06f5688">
|
||||
<link rel="stylesheet" href="../css/responsive.css?v=46449483">
|
||||
</head>
|
||||
<body class="can-page">
|
||||
<div class="container can-container">
|
||||
@@ -184,6 +184,6 @@
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/can_monitor.js?v=20260727a"></script>
|
||||
<script type="module" src="../js/can_monitor.js?v=20260727a"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -12,11 +12,11 @@
|
||||
<title>ESP32 OTA 中继控制台</title>
|
||||
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="css/base.css?v=857f83a5">
|
||||
<link rel="stylesheet" href="css/layout.css?v=acd17001">
|
||||
<link rel="stylesheet" href="css/components.css?v=a2c3d4e5">
|
||||
<link rel="stylesheet" href="css/can.css?v=c06f5688">
|
||||
<link rel="stylesheet" href="css/responsive.css?v=46449483">
|
||||
<link rel="stylesheet" href="../css/base.css?v=857f83a5">
|
||||
<link rel="stylesheet" href="../css/layout.css?v=acd17001">
|
||||
<link rel="stylesheet" href="../css/components.css?v=a2c3d4e5">
|
||||
<link rel="stylesheet" href="../css/can.css?v=c06f5688">
|
||||
<link rel="stylesheet" href="../css/responsive.css?v=46449483">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -499,9 +499,9 @@
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/app.js?v=20260901d"></script>
|
||||
<script type="module" src="js/ota/console-mode.js?v=20260901d"></script>
|
||||
<script type="module" src="js/ble_ota.js?v=20260901d"></script>
|
||||
<script type="module" src="../js/app.js?v=20260901d"></script>
|
||||
<script type="module" src="../js/ota/console-mode.js?v=20260901d"></script>
|
||||
<script type="module" src="../js/ble_ota.js?v=20260901d"></script>
|
||||
<!-- 内联兜底:同步 BLE 按钮 data-ble-state 与 checkbox.checked
|
||||
不依赖 ES 模块(子模块可能被浏览器缓存为旧版,不含 data-ble-state 逻辑)。
|
||||
connecting 态由 session.js click handler 设置,此处跳过不覆盖。 -->
|
||||
@@ -6,7 +6,7 @@
|
||||
<title>电机控制台 — Motor CAN FD Console</title>
|
||||
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="css/motor.css?v=20260816a">
|
||||
<link rel="stylesheet" href="../css/motor.css?v=20260816a">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -388,6 +388,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="js/motor/main.js?v=20260816c"></script>
|
||||
<script type="module" src="../js/motor/main.js?v=20260816c"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,7 +11,8 @@ echo ESP32 Debug Console (console mode)
|
||||
echo PORT: %PORT%
|
||||
echo ========================================
|
||||
echo.
|
||||
echo OTA Console: http://localhost:%PORT%/index.html
|
||||
echo Tab shell: http://localhost:%PORT%/tabs.html
|
||||
echo OTA Console: http://localhost:%PORT%/pages/index.html
|
||||
echo.
|
||||
echo Switch WiFi / BLE mode in the "Device Connect" card.
|
||||
echo No-black-box: double-click esp32_app.vbs instead.
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user