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:
+334
@@ -0,0 +1,334 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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">
|
||||
</head>
|
||||
<body class="can-page">
|
||||
<div class="container can-container">
|
||||
|
||||
<header class="header-bar can-header">
|
||||
<div class="header-brand">
|
||||
<span class="brand-logo" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="6" y="6" width="12" height="12" rx="2.5"/>
|
||||
<rect x="9.5" y="9.5" width="5" height="5" rx="1" fill="currentColor" stroke="none" opacity=".85"/>
|
||||
<path d="M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3"/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="brand-text">
|
||||
<strong>ESP32 CAN 通信控制台</strong>
|
||||
<span class="brand-sub">DCU · Robot_v2 · CAN FD</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-status">
|
||||
<span class="pill offline" id="canConnPill"><span class="indicator"></span><span id="canConnTxt">未连接</span></span>
|
||||
<span class="pill" id="canIpPill">IP --</span>
|
||||
<span class="pill" id="canRssiPill">RSSI --</span>
|
||||
<span class="pill" id="canUptimePill">运行 --</span>
|
||||
<span class="pill" id="canVersionPill">v--</span>
|
||||
<span class="pill" id="canChannelPill">CH0</span>
|
||||
<span class="pill" id="dbcSummaryPill">未导入 DBC</span>
|
||||
<span class="pill" id="messageSummaryPill">0 报文</span>
|
||||
<span class="pill" id="fbModePill">模式: --</span>
|
||||
<span class="pill" id="fbPollStatus">轮询: --</span>
|
||||
</div>
|
||||
<div class="header-sub can-header-actions">
|
||||
<a class="pill can-link-pill" href="index.html">返回 OTA 控制台</a>
|
||||
<a class="pill can-link-pill" href="can_monitor.html" title="独立全屏监控页">监控全屏</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- DBC 并入报文列表:无文件时列表内大拖放区;有文件时标题栏紧凑摘要 + 更换 -->
|
||||
<input type="file" id="dbcFileInput" accept=".dbc,text/plain" multiple style="display:none;">
|
||||
|
||||
<div class="can-editor-layout">
|
||||
<div class="can-main-col">
|
||||
<section class="card can-table-card" id="dbcDropZone">
|
||||
<div class="card-head can-msg-list-head">
|
||||
<span class="ico">📚</span>
|
||||
<span>报文列表</span>
|
||||
<!-- 已导入:合计 + 文件 chip(可逐个移除) -->
|
||||
<div class="dbc-summary-strip dbc-summary-inline" id="dbcFileInfo" hidden>
|
||||
<span class="dbc-summary-meta">
|
||||
<span><b id="dbcFileCount">0</b>文件</span>
|
||||
<span><b id="dbcMessageCount">0</b>报文</span>
|
||||
<span><b id="dbcSignalCount">0</b>信号</span>
|
||||
<span><b id="dbcNodeCount">0</b>节点</span>
|
||||
<span id="dbcFileSize">0 B</span>
|
||||
</span>
|
||||
<span class="dbc-file-chips" id="dbcFileChips"></span>
|
||||
</div>
|
||||
<div class="can-msg-head-actions">
|
||||
<button class="btn btn-ghost btn-sm" id="clearDbcBtn" type="button" title="清除全部 DBC" hidden>全部清除</button>
|
||||
<button class="btn btn-primary btn-sm" id="importDbcBtn" type="button">导入 DBC</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 通道 / 搜索 / 勾选 / 节点 / 批量 同一工具栏 -->
|
||||
<div class="can-msg-toolbar" id="canMsgToolbar">
|
||||
<label class="can-msg-channel" for="defaultChannel">
|
||||
<span class="can-msg-channel-label">通道</span>
|
||||
<select class="input can-msg-channel-select" id="defaultChannel" title="默认发送通道">
|
||||
<option value="0">CH0</option>
|
||||
<option value="1">CH1</option>
|
||||
<option value="2">CH2</option>
|
||||
</select>
|
||||
</label>
|
||||
<div class="can-msg-search-wrap">
|
||||
<input class="input can-msg-search" type="text" id="dbcSearch"
|
||||
placeholder="搜索报文名 / ID / 节点 / 信号" autocomplete="off">
|
||||
</div>
|
||||
<div class="can-batch-actions">
|
||||
<span class="pill" id="selectedCountPill">已勾选 0</span>
|
||||
<div class="can-node-filter" id="nodeFilterWrap">
|
||||
<button class="btn btn-outline btn-sm can-node-filter-btn" id="nodeFilterBtn" type="button" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="can-node-filter-label" id="nodeFilterLabel">发送节点:全部</span>
|
||||
<span class="can-node-filter-caret">▾</span>
|
||||
</button>
|
||||
<div class="can-node-filter-panel" id="nodeFilterPanel" hidden>
|
||||
<div class="can-node-filter-head">
|
||||
<input class="input can-node-filter-search" id="nodeFilterSearch" type="text" placeholder="筛选节点名…">
|
||||
<div class="can-node-filter-actions">
|
||||
<button class="btn btn-ghost btn-sm" id="nodeFilterAllBtn" type="button">全选</button>
|
||||
<button class="btn btn-ghost btn-sm" id="nodeFilterNoneBtn" type="button">清空</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="can-node-filter-list" id="nodeFilterList">
|
||||
<div class="status-empty">先导入 DBC。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="checkbox-inline can-batch-all-label" title="全选/取消当前过滤的所有报文">
|
||||
<input type="checkbox" id="selectAllBox">
|
||||
<span>全选当前过滤</span>
|
||||
</label>
|
||||
<button class="btn btn-outline btn-sm" id="expandAllBtn" type="button">展开全部</button>
|
||||
<button class="btn btn-outline btn-sm" id="collapseAllBtn" type="button">收起全部</button>
|
||||
<button class="btn btn-primary btn-sm" id="sendCheckedBtn" type="button">批量发送勾选</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body can-table-card-body">
|
||||
<div class="message-list" id="messageList">
|
||||
<div class="dbc-empty-drop" id="dbcEmptyDrop">
|
||||
<div class="upload-ico">📥</div>
|
||||
<h4>导入 DBC 开始编辑报文</h4>
|
||||
<p>点击此处或拖拽 <code>.dbc</code> 文件到列表区域</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="helper-note" id="dbcHint" hidden></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card can-history-card">
|
||||
<div class="card-head"><span class="ico">🕘</span> 最近发送</div>
|
||||
<div class="card-body">
|
||||
<div class="history-list history-list-compact" id="historyList">
|
||||
<div class="status-empty">尚未发送任何 CAN 帧。</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<aside class="can-sidepanel">
|
||||
<section class="card can-preview-card">
|
||||
<div class="card-head"><span class="ico">🛰️</span> 帧预览</div>
|
||||
<div class="card-body">
|
||||
<div class="can-frame-panel can-frame-panel-compact">
|
||||
<div class="can-frame-hex" id="generatedHex">--</div>
|
||||
<div class="can-byte-strip" id="generatedBytes"></div>
|
||||
<div class="helper-note" id="encodeHint">选择报文后,信号值会实时编码为 CAN 数据。</div>
|
||||
<div class="btn-row can-frame-actions">
|
||||
<button class="btn btn-outline btn-sm" id="loadPreviewToRawBtn" type="button">载入到原始发送区</button>
|
||||
<button class="btn btn-primary btn-sm" id="sendSelectedBtn" type="button">发送当前预览帧</button>
|
||||
</div>
|
||||
<div class="can-response" id="dbcResp" style="display:none;">
|
||||
<div class="can-response-label">设备响应</div>
|
||||
<div id="dbcRespText"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card can-raw-card">
|
||||
<div class="card-head"><span class="ico">✍️</span> 原始帧发送</div>
|
||||
<div class="card-body">
|
||||
<div class="row-2 can-raw-grid">
|
||||
<div class="form-row" style="margin:0;">
|
||||
<label class="form-label" for="manualChannel">通道</label>
|
||||
<select class="input" id="manualChannel">
|
||||
<option value="0">CH0</option>
|
||||
<option value="1">CH1</option>
|
||||
<option value="2">CH2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-row" style="margin:0;">
|
||||
<label class="form-label" for="manualId">CAN ID</label>
|
||||
<input class="input" type="text" id="manualId" value="0x18DA00F1" placeholder="支持 0x 或十进制">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="margin-top:10px;">
|
||||
<label class="form-label" for="manualDlc">DLC</label>
|
||||
<input class="input" type="number" id="manualDlc" value="8" min="0" max="64">
|
||||
</div>
|
||||
<div class="form-row" style="margin-top:10px;">
|
||||
<label class="form-label" for="manualData">HEX 数据</label>
|
||||
<input class="input" type="text" id="manualData" value="0102030405060708" placeholder="01020304...">
|
||||
</div>
|
||||
<div class="helper-note" id="manualHint">
|
||||
原始帧模式适合绕过 DBC 直接发 HEX,或发送当前预览帧后二次微调。
|
||||
</div>
|
||||
<div class="btn-row">
|
||||
<button class="btn btn-primary" id="sendManualBtn" type="button">发送原始帧</button>
|
||||
<button class="btn btn-outline" id="resetManualBtn" type="button">重置</button>
|
||||
</div>
|
||||
<div class="can-response" id="manualResp" style="display:none;">
|
||||
<div class="can-response-label">设备响应</div>
|
||||
<div id="manualRespText"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧滑出:CAN 接收监控(与 OTA 页「堆与任务栈」同样交互) -->
|
||||
<aside class="sys-drawer can-monitor-drawer" id="canMonitorDrawer" aria-label="CAN 接收监控">
|
||||
<div class="sys-drawer-rail" title="悬停展开 CAN 接收监控">
|
||||
<span class="sys-drawer-rail-ico">📊</span>
|
||||
<span class="sys-drawer-rail-text">CAN 接收监控</span>
|
||||
</div>
|
||||
<div class="sys-drawer-panel card">
|
||||
<div class="card-head sys-drawer-head">
|
||||
<span class="ico">📊</span> CAN 接收监控
|
||||
</div>
|
||||
<div class="card-body sys-drawer-body">
|
||||
<div class="status-section-title">接收模式</div>
|
||||
<div class="input-group" style="margin-bottom:12px;flex-wrap:wrap;">
|
||||
<button class="btn btn-primary btn-sm" id="btnModeAllPass" type="button">全收模式</button>
|
||||
<button class="btn btn-outline btn-sm" id="btnModeFilter" type="button">过滤模式</button>
|
||||
<button class="btn btn-outline btn-sm" id="btnModeBridge" type="button" title="CAN0 收到的每一帧按原帧格式自动转发到 CAN1 与 CAN2">CAN0→1/2 桥接</button>
|
||||
</div>
|
||||
<div class="helper-note" id="monHelper" style="margin-bottom:14px;">
|
||||
地址与顶栏一致;1Hz <code>/can/feedback</code>。悬停抽屉保持展开。
|
||||
</div>
|
||||
|
||||
<div class="status-section-title">三路 CAN 通道(1Hz)</div>
|
||||
<div class="feedback-grid feedback-grid-drawer">
|
||||
<div class="feedback-channel collapsed" id="fbCh0">
|
||||
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
|
||||
<span class="ch-dot ch0"></span> CAN0
|
||||
<span class="feedback-expand-caret">▾</span>
|
||||
</div>
|
||||
<div class="feedback-primary">
|
||||
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb0State">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb0Rx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb0Tx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb0Drop">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb0Err">--</span></div>
|
||||
</div>
|
||||
<div class="feedback-detail">
|
||||
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb0Tec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb0Rec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb0Bus">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb0LastId">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb0Dlc">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb0Flags">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb0Seq">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb0Delta">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb0LastData">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feedback-channel collapsed" id="fbCh1">
|
||||
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
|
||||
<span class="ch-dot ch1"></span> CAN1
|
||||
<span class="feedback-expand-caret">▾</span>
|
||||
</div>
|
||||
<div class="feedback-primary">
|
||||
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb1State">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb1Rx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb1Tx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb1Drop">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb1Err">--</span></div>
|
||||
</div>
|
||||
<div class="feedback-detail">
|
||||
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb1Tec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb1Rec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb1Bus">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb1LastId">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb1Dlc">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb1Flags">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb1Seq">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb1Delta">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb1LastData">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feedback-channel collapsed" id="fbCh2">
|
||||
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
|
||||
<span class="ch-dot ch2"></span> CAN2
|
||||
<span class="feedback-expand-caret">▾</span>
|
||||
</div>
|
||||
<div class="feedback-primary">
|
||||
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb2State">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb2Rx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb2Tx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb2Drop">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb2Err">--</span></div>
|
||||
</div>
|
||||
<div class="feedback-detail">
|
||||
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb2Tec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb2Rec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb2Bus">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb2LastId">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb2Dlc">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb2Flags">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb2Seq">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb2Delta">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb2LastData">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-section-title" style="margin-top:16px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
||||
<span>每 ID 快照表</span>
|
||||
<select class="input" id="idsnapChannel" style="width:100px;text-transform:none;">
|
||||
<option value="0">CAN0</option>
|
||||
<option value="1">CAN1</option>
|
||||
<option value="2">CAN2</option>
|
||||
</select>
|
||||
<span class="pill" id="idsnapStatus">--</span>
|
||||
</div>
|
||||
<div class="idsnap-table-wrap">
|
||||
<table class="idsnap-table" id="idsnapTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th><th>CAN ID</th><th>DLC</th>
|
||||
<th>FD</th><th>BRS</th><th>ESI</th><th>数据 (HEX)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="idsnapBody">
|
||||
<tr><td colspan="7" class="status-empty">等待数据...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="toast" id="toast">
|
||||
<span class="toast-ico" id="toastIco">✓</span>
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="../js/can.js?v=20260801f"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,189 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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">
|
||||
</head>
|
||||
<body class="can-page">
|
||||
<div class="container can-container">
|
||||
|
||||
<header class="header-bar can-header">
|
||||
<div class="header-brand">
|
||||
<span class="dot-mark"></span>
|
||||
<strong>ESP32 CAN 接收监控</strong>
|
||||
</div>
|
||||
<div class="header-status">
|
||||
<span class="pill offline" id="monConnPill"><span class="indicator"></span><span id="monConnTxt">未连接</span></span>
|
||||
<span class="pill" id="fbModePill">模式: --</span>
|
||||
<span class="pill" id="fbPollStatus">轮询: 待连接</span>
|
||||
</div>
|
||||
<div class="header-sub can-header-actions">
|
||||
<a class="pill can-link-pill" href="can.html">CAN 发送控制台</a>
|
||||
<a class="pill can-link-pill" href="index.html">返回 OTA 控制台</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ═══ 工作台 ═══ -->
|
||||
<section class="card can-toolbar-card">
|
||||
<div class="card-head"><span class="ico">🧰</span> 连接设置</div>
|
||||
<div class="card-body">
|
||||
<div class="can-toolbar-grid" style="grid-template-columns: minmax(260px, 1.2fr) 1fr;">
|
||||
<div class="can-toolbar-block">
|
||||
<label class="form-label" for="monIp">ESP32 IP</label>
|
||||
<div class="input-group">
|
||||
<input class="input" type="text" id="monIp" value="192.168.31.127" placeholder="输入 IP 地址">
|
||||
<button class="btn btn-outline" id="monTestBtn" type="button">测试</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="can-toolbar-block">
|
||||
<label class="form-label">接收模式</label>
|
||||
<div class="input-group">
|
||||
<button class="btn btn-primary btn-sm" id="btnModeAllPass" type="button">全收模式</button>
|
||||
<button class="btn btn-outline btn-sm" id="btnModeFilter" type="button">过滤模式</button>
|
||||
<button class="btn btn-outline btn-sm" id="btnModeBridge" type="button" title="CAN0 收到的每一帧按原帧格式自动转发到 CAN1 与 CAN2">CAN0→1/2 桥接</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="can-toolbar-foot">
|
||||
<div class="conn-indicator err" id="monConnIndicator">
|
||||
<span class="light"></span>
|
||||
<span id="monConnIndicatorTxt">等待连接测试</span>
|
||||
</div>
|
||||
<div class="helper-note" id="monHelper">
|
||||
页面每 1 秒通过 <code>/can/feedback</code> 拉取三路 CAN 通道快照。无 RX 的通道显示 <b>空(无 RX)</b>。<br><b>桥接模式</b>:CAN0 收到的每一帧(11/29 位、FD、BRS)自动由 CAN1 与 CAN2 转发出去。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ 三路反馈 ═══ -->
|
||||
<section class="card can-feedback-card">
|
||||
<div class="card-head"><span class="ico">📊</span> 三路 CAN 通道反馈(1Hz)</div>
|
||||
<div class="card-body">
|
||||
<div class="feedback-grid">
|
||||
<div class="feedback-channel collapsed" id="fbCh0">
|
||||
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
|
||||
<span class="ch-dot ch0"></span> CAN0
|
||||
<span class="feedback-expand-caret">▾</span>
|
||||
</div>
|
||||
<div class="feedback-primary">
|
||||
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb0State">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb0Rx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb0Tx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb0Drop">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb0Err">--</span></div>
|
||||
</div>
|
||||
<div class="feedback-detail">
|
||||
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb0Tec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb0Rec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb0Bus">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb0LastId">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb0Dlc">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb0Flags">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb0Seq">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb0Delta">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb0LastData">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feedback-channel collapsed" id="fbCh1">
|
||||
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
|
||||
<span class="ch-dot ch1"></span> CAN1
|
||||
<span class="feedback-expand-caret">▾</span>
|
||||
</div>
|
||||
<div class="feedback-primary">
|
||||
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb1State">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb1Rx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb1Tx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb1Drop">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb1Err">--</span></div>
|
||||
</div>
|
||||
<div class="feedback-detail">
|
||||
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb1Tec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb1Rec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb1Bus">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb1LastId">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb1Dlc">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb1Flags">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb1Seq">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb1Delta">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb1LastData">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feedback-channel collapsed" id="fbCh2">
|
||||
<div class="feedback-ch-head" role="button" tabindex="0" title="点击展开/折叠详情">
|
||||
<span class="ch-dot ch2"></span> CAN2
|
||||
<span class="feedback-expand-caret">▾</span>
|
||||
</div>
|
||||
<div class="feedback-primary">
|
||||
<div class="feedback-row"><span class="fb-label">状态</span><span class="fb-val" id="fb2State">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">接收数</span><span class="fb-val" id="fb2Rx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">发送数</span><span class="fb-val" id="fb2Tx">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">丢帧</span><span class="fb-val" id="fb2Drop">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">错误</span><span class="fb-val" id="fb2Err">--</span></div>
|
||||
</div>
|
||||
<div class="feedback-detail">
|
||||
<div class="feedback-row"><span class="fb-label">TEC</span><span class="fb-val" id="fb2Tec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">REC</span><span class="fb-val" id="fb2Rec">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">总线状态</span><span class="fb-val" id="fb2Bus">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新ID</span><span class="fb-val mono" id="fb2LastId">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">DLC</span><span class="fb-val" id="fb2Dlc">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">FD/BRS/ESI</span><span class="fb-val" id="fb2Flags">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Seq</span><span class="fb-val" id="fb2Seq">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">Δrx/s</span><span class="fb-val" id="fb2Delta">--</span></div>
|
||||
<div class="feedback-row"><span class="fb-label">最新HEX</span><span class="fb-val mono fb-data-cell" id="fb2LastData">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══ 每 ID 快照表 ═══ -->
|
||||
<section class="card" id="idSnapshotSection" style="margin-top:18px;">
|
||||
<div class="card-head"><span class="ico">🔬</span> 每 ID 快照表(1Hz 刷新)
|
||||
<div class="feedback-controls">
|
||||
<select class="input" id="idsnapChannel" style="width:100px;">
|
||||
<option value="0">CAN0</option>
|
||||
<option value="1">CAN1</option>
|
||||
<option value="2">CAN2</option>
|
||||
</select>
|
||||
<span class="pill" id="idsnapStatus">--</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="idsnap-table-wrap">
|
||||
<table class="idsnap-table" id="idsnapTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th><th>CAN ID</th><th>DLC</th>
|
||||
<th>FD</th><th>BRS</th><th>ESI</th><th>数据 (HEX)</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="idsnapBody">
|
||||
<tr><td colspan="7" class="status-empty">等待数据...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast">
|
||||
<span class="toast-ico" id="toastIco">✓</span>
|
||||
<span id="toastMsg">操作成功</span>
|
||||
</div>
|
||||
|
||||
<script type="module" src="../js/can_monitor.js?v=20260727a"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,521 @@
|
||||
<!--
|
||||
index.html - ESP32 OTA 中继控制台主页面(DCU Mission Control · 美化版)
|
||||
支持 WiFi OTA(ESP32 本机 + GD32 中继)和 BLE OTA 双通道固件升级。
|
||||
配套脚本:app.js (WiFi OTA), ble_ota.js (BLE OTA)
|
||||
子系统配色:ESP32=青绿 · GD32=琥珀 · BLE=蓝 · CAN=绿 · 内存/调试=紫
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<!-- ====== HEADER: 命令栏 ====== -->
|
||||
<header class="header-bar">
|
||||
<div class="header-brand">
|
||||
<span class="brand-logo" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="6" y="6" width="12" height="12" rx="2.5"/>
|
||||
<rect x="9.5" y="9.5" width="5" height="5" rx="1" fill="currentColor" stroke="none" opacity=".85"/>
|
||||
<path d="M9 2v3M15 2v3M9 19v3M15 19v3M2 9h3M2 15h3M19 9h3M19 15h3"/>
|
||||
</svg>
|
||||
</span>
|
||||
<div class="brand-text">
|
||||
<strong>ESP32 OTA 中继控制台</strong>
|
||||
<span class="brand-sub">DCU · Robot_v2 · Relay</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-status">
|
||||
<span class="pill offline" id="pillConn"><span class="indicator"></span><span id="pillConnTxt">未连接</span></span>
|
||||
<span class="pill" id="pillVer">v--</span>
|
||||
<span class="pill" id="pillIP">--</span>
|
||||
<span class="pill" id="pillRSSI">--</span>
|
||||
<span class="pill" id="pillUptime">运行 --</span>
|
||||
</div>
|
||||
<div class="header-sub">
|
||||
<span class="pill">设备名: <b class="pill-val" id="deviceName">-</b></span>
|
||||
<span class="pill">设备ID: <b class="pill-val" id="deviceId">-</b></span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ====== MAIN LAYOUT ====== -->
|
||||
<div class="main-layout">
|
||||
|
||||
<!-- ====== LEFT: workflow ====== -->
|
||||
<div>
|
||||
|
||||
<!-- 1. Connection -->
|
||||
<div class="card card--ble">
|
||||
<div class="card-head"><span class="ico">🔗</span> 设备连接</div>
|
||||
<div class="card-body">
|
||||
<div style="display:flex;gap:6px;margin-bottom:14px;">
|
||||
<button id="modeWifiBtn" class="btn btn-primary" style="flex:1;font-size:12px;padding:7px 10px;" data-action="set-console-mode" data-arg="wifi">WiFi OTA</button>
|
||||
<button id="modeBleBtn" class="btn btn-outline" style="flex:1;font-size:12px;padding:7px 10px;" data-action="set-console-mode" data-arg="ble">BLE 蓝牙</button>
|
||||
</div>
|
||||
<div id="wifiModeCards">
|
||||
<div class="form-row">
|
||||
<label class="form-label" for="espIp">ESP32 IP 地址</label>
|
||||
<div class="input-group">
|
||||
<input class="input" type="text" id="espIp" value="192.168.31.127" placeholder="输入 IP 地址">
|
||||
<button id="testConnBtn" class="btn btn-outline" data-action="test-connection">测试</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BLE 模式:launch 风格连接按钮(checkbox 驱动:勾选=已连接) -->
|
||||
<div id="bleModeCards" style="display:none;">
|
||||
<div class="ble-launch-wrap">
|
||||
<label class="ble-launch-btn" for="bleConnectToggle" data-ble-state="idle">
|
||||
<input id="bleConnectToggle" type="checkbox">
|
||||
<div class="back">
|
||||
<div class="spot-shadow"></div>
|
||||
<div class="mid">
|
||||
<div class="spot-main"></div>
|
||||
<div class="ble-dial">
|
||||
<div class="spot-light"></div>
|
||||
<div class="center">
|
||||
<div class="icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#bleIconGlow)">
|
||||
<path d="M7 7 L17 17 L12 22 V2 L17 7 L7 17"
|
||||
stroke="#BBE7E9" stroke-width="3"
|
||||
stroke-linecap="round" stroke-linejoin="round" fill="none"
|
||||
shape-rendering="geometricPrecision"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="bleIconGlow" x="-50%" y="-50%" width="200%" height="200%"
|
||||
filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="bg"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="a"/>
|
||||
<feOffset/>
|
||||
<feGaussianBlur stdDeviation="2"/>
|
||||
<feComposite in2="a" operator="out"/>
|
||||
<feColorMatrix type="matrix"
|
||||
values="0 0 0 0 0.37 0 0 0 0 0.66 0 0 0 0 1 0 0 0 0.88 0"/>
|
||||
<feBlend mode="normal" in2="bg" result="g"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="g" result="shape"/>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="launch">
|
||||
<div style="--i:1">蓝</div>
|
||||
<div style="--i:2">牙</div>
|
||||
<div style="--i:3">连</div>
|
||||
<div style="--i:4">接</div>
|
||||
</div>
|
||||
<div class="start">
|
||||
<div class="text1">等待连接</div>
|
||||
<div class="text2">
|
||||
<span style="--n:0">已</span>
|
||||
<span style="--n:1">连</span>
|
||||
<span style="--n:2">接</span>
|
||||
<span style="--n:3">.</span>
|
||||
<span style="--n:4">.</span>
|
||||
<span style="--n:5">.</span>
|
||||
</div>
|
||||
<div class="text3">连接中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
<div class="ble-launch-hint">点击按钮连接 / 断开蓝牙设备</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 连接文案只在顶栏 #pillConnTxt 显示,此处不再重复 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== Shared Target + Firmware + Actions ====== -->
|
||||
<div class="card card--gd32">
|
||||
<div class="card-head"><span class="ico">🎯</span> 升级目标、固件文件与执行操作</div>
|
||||
<div class="card-body">
|
||||
<div style="display:flex;gap:6px;margin-bottom:14px;">
|
||||
<button id="targetGd32Tab" class="btn btn-outline" style="flex:1;font-size:12px;padding:7px 10px;" data-action="select-flash-target" data-arg="gd32">GD32</button>
|
||||
<button id="targetEsp32Tab" class="btn btn-primary" style="flex:1;font-size:12px;padding:7px 10px;" data-action="select-flash-target" data-arg="esp32">ESP32</button>
|
||||
</div>
|
||||
<div style="border-top:1px solid var(--border);margin:0 -16px 14px;padding-top:14px;padding-left:16px;padding-right:16px;">
|
||||
<div id="wifiEsp32UploadPane">
|
||||
<div class="upload-zone upload-zone-sm" id="uploadZoneEsp32" data-action="pick-shared-firmware" data-arg="esp32">
|
||||
<div class="upload-ico">📤</div>
|
||||
<h4>点击或拖拽 ESP32 固件</h4>
|
||||
</div>
|
||||
<input type="file" id="fileInputEsp32" accept=".bin" style="display:none;">
|
||||
<div class="file-info" id="fileInfoEsp32">
|
||||
<div class="file-info-row">
|
||||
<span style="font-weight:600;">📄 <span id="fileNameEsp32">-</span></span>
|
||||
<button class="btn btn-ghost btn-sm" data-action="clear-shared-firmware" data-arg="esp32">清除</button>
|
||||
</div>
|
||||
<div class="file-info-grid">
|
||||
<div class="file-meta-item"><span>类型</span><b id="fileTypeEsp32">-</b></div>
|
||||
<div class="file-meta-item"><span>大小</span><b id="fileSizeEsp32">-</b></div>
|
||||
</div>
|
||||
<div class="file-meta-item" style="margin-top:6px;">
|
||||
<span>SHA-256</span>
|
||||
<b id="fileSha256Esp32" style="font-size:10px;word-break:break-all;font-family:monospace;">-</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="wifiGd32UploadPane" style="display:none;">
|
||||
<div class="upload-zone upload-zone-sm" id="uploadZoneGd32" data-action="pick-shared-firmware" data-arg="gd32">
|
||||
<div class="upload-ico">📤</div>
|
||||
<h4>点击或拖拽 GD32 固件</h4>
|
||||
<p>支持 .bin 和 .hex(自动转换)</p>
|
||||
</div>
|
||||
<input type="file" id="fileInputGd32" accept=".hex,.bin" style="display:none;">
|
||||
<div class="file-info" id="fileInfoGd32">
|
||||
<div class="file-info-row">
|
||||
<span style="font-weight:600;">📄 <span id="fileNameGd32">-</span></span>
|
||||
<button class="btn btn-ghost btn-sm" data-action="clear-shared-firmware" data-arg="gd32">清除</button>
|
||||
</div>
|
||||
<div class="file-info-grid">
|
||||
<div class="file-meta-item"><span>类型</span><b id="fileTypeGd32">-</b></div>
|
||||
<div class="file-meta-item"><span>大小</span><b id="fileSizeGd32">-</b></div>
|
||||
</div>
|
||||
<div class="file-meta-item" style="margin-top:6px;">
|
||||
<span>SHA-256</span>
|
||||
<b id="fileSha256Gd32" style="font-size:10px;word-break:break-all;font-family:monospace;">-</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="bleUploadPane" style="display:none;">
|
||||
<div class="upload-zone upload-zone-sm" id="uploadZone" style="margin-bottom:12px;" data-action="ble-pick-file">
|
||||
<div class="upload-ico">📤</div>
|
||||
<h4>点击或拖拽文件到此处</h4>
|
||||
<p>支持 .bin 和 .hex 格式</p>
|
||||
</div>
|
||||
<input id="firmwareFile" type="file" accept=".bin,.hex,application/octet-stream" style="display:none;">
|
||||
<div class="file-info" id="fileInfo">
|
||||
<div class="file-info-row">
|
||||
<span style="font-weight:600;">📄 <span id="fileName">-</span></span>
|
||||
<button id="clearFileBtn" class="btn btn-outline btn-sm" data-action="ble-clear-file">清除</button>
|
||||
</div>
|
||||
<div class="file-info-grid">
|
||||
<div class="file-meta-item"><span>类型</span><b id="fileType">-</b></div>
|
||||
<div class="file-meta-item"><span>大小</span><b id="fileSize">0 B</b></div>
|
||||
<div class="file-meta-item"><span>总包数</span><b id="packetCount">0</b></div>
|
||||
<div class="file-meta-item"><span>已写包</span><b id="sentCount">0</b></div>
|
||||
</div>
|
||||
<div class="file-meta-item" style="margin-top:8px;">
|
||||
<span>SHA-256</span>
|
||||
<b id="fileSha256" style="font-size:10px;word-break:break-all;font-family:var(--font-mono);">-</b>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-note">建议使用 Chrome / Edge。每包 Payload 与触发命令由系统按目标自动设置。</div>
|
||||
</div>
|
||||
<div style="height:12px"></div>
|
||||
<div id="wifiEsp32ActionPane">
|
||||
<button class="btn btn-primary btn-block" id="uploadEsp32Btn" data-action="start-upload" data-arg="esp32" disabled>
|
||||
上传并升级 ESP32
|
||||
</button>
|
||||
</div>
|
||||
<div id="wifiGd32ActionPane" style="display:none;">
|
||||
<button class="btn btn-success btn-block" id="uploadGd32Btn" data-action="start-upload" data-arg="gd32" disabled>
|
||||
GD32 OTA
|
||||
</button>
|
||||
</div>
|
||||
<div class="row-2" id="bleActionPane" style="display:none;">
|
||||
<button id="autoFlashBtn" class="btn btn-primary" disabled>一键刷写</button>
|
||||
<button id="abortBtn" class="btn btn-danger" disabled>中止任务</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4. Flash Progress + Stage Flow -->
|
||||
<div class="card card--esp32">
|
||||
<div class="card-head"><span class="ico">⚡</span> 刷写进度与流程阶段</div>
|
||||
<div class="card-body">
|
||||
<!-- Phase step indicators -->
|
||||
<div class="progress-phase-row">
|
||||
<div class="pp-step active" id="ppEsp32"><span class="pp-ico">📦</span><span class="pp-txt">ESP32 OTA</span></div>
|
||||
<div class="pp-step" id="ppGd32Ota"><span class="pp-ico">📤</span><span class="pp-txt">GD32 OTA</span></div>
|
||||
</div>
|
||||
<!-- Unified progress bar -->
|
||||
<div class="progress-wrap show" id="progressWrap">
|
||||
<div class="progress-track"><div class="progress-fill" id="progressFill"></div></div>
|
||||
<div class="progress-text">
|
||||
<span id="progressTxt">等待操作...</span>
|
||||
<span id="progressPct">0%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height:14px"></div>
|
||||
<!-- ESP32 OTA stages -->
|
||||
<div class="stage-flow" id="esp32Stages" style="display:none;">
|
||||
<div class="stage-node" id="es1"><div class="stage-dot">1</div><div class="stage-label">固件上传</div><div class="stage-detail" id="esd1"></div></div>
|
||||
<div class="stage-node" id="es2"><div class="stage-dot">2</div><div class="stage-label">OTA 写入</div><div class="stage-detail" id="esd2"></div></div>
|
||||
<div class="stage-node" id="es3"><div class="stage-dot">3</div><div class="stage-label">SHA-256 校验</div><div class="stage-detail" id="esd3"></div></div>
|
||||
<div class="stage-node" id="es4"><div class="stage-dot">4</div><div class="stage-label">应用固件</div><div class="stage-detail" id="esd4"></div></div>
|
||||
<div class="stage-node" id="es5"><div class="stage-dot">5</div><div class="stage-label">重启中</div><div class="stage-detail" id="esd5"></div></div>
|
||||
<div class="stage-node" id="es6"><div class="stage-dot">6</div><div class="stage-label">启动完成</div><div class="stage-detail" id="esd6"></div></div>
|
||||
</div>
|
||||
<!-- BLE OTA stages -->
|
||||
<div class="stage-flow" id="bleStages" style="display:none;">
|
||||
<div class="stage-node" id="bs1"><div class="stage-dot">1</div><div class="stage-label">BLE 连接</div><div class="stage-detail" id="bsd1"></div></div>
|
||||
<div class="stage-node" id="bs2"><div class="stage-dot">2</div><div class="stage-label">GATT 准备</div><div class="stage-detail" id="bsd2"></div></div>
|
||||
<div class="stage-node" id="bs3"><div class="stage-dot">3</div><div class="stage-label">文件准备</div><div class="stage-detail" id="bsd3"></div></div>
|
||||
<div class="stage-node" id="bs4"><div class="stage-dot">4</div><div class="stage-label">数据写入</div><div class="stage-detail" id="bsd4"></div></div>
|
||||
<div class="stage-node" id="bs5"><div class="stage-dot">5</div><div class="stage-label" id="bsl5">校验比对</div><div class="stage-detail" id="bsd5"></div></div>
|
||||
<div class="stage-node" id="bs6"><div class="stage-dot">6</div><div class="stage-label" id="bsl6">完成触发</div><div class="stage-detail" id="bsd6"></div></div>
|
||||
</div>
|
||||
<!-- GD32 OTA stages -->
|
||||
<div class="stage-flow" id="gd32Stages">
|
||||
<div class="stage-node" id="s1"><div class="stage-dot">1</div><div class="stage-label">认证握手</div><div class="stage-detail" id="sd1"></div></div>
|
||||
<div class="stage-node" id="s2"><div class="stage-dot">2</div><div class="stage-label">传输协商</div><div class="stage-detail" id="sd2"></div></div>
|
||||
<div class="stage-node" id="s3"><div class="stage-dot">3</div><div class="stage-label">数据转发</div><div class="stage-detail" id="sd3"></div></div>
|
||||
<div class="stage-node" id="s4"><div class="stage-dot">4</div><div class="stage-label">收尾验证</div><div class="stage-detail" id="sd4"></div></div>
|
||||
<div class="stage-node" id="s5"><div class="stage-dot">5</div><div class="stage-label">APP 启动</div><div class="stage-detail" id="sd5"></div></div>
|
||||
<div class="stage-node" id="s6"><div class="stage-dot">6</div><div class="stage-label">OTA完成</div><div class="stage-detail" id="sd6"></div></div>
|
||||
</div>
|
||||
<div class="stage-status-bar" id="stageStatusBar">
|
||||
<span class="ssb-dot" id="stageStatusDot"></span>
|
||||
<span class="ssb-text" id="stageStatusText">待机</span>
|
||||
</div>
|
||||
<div class="stage-verify-info" id="stageVerifyInfo" style="display:none;">
|
||||
<div class="svi-row"><span class="svi-key">前端 SHA-256</span><span class="svi-val" id="sviFrontSha">-</span></div>
|
||||
<div class="svi-row"><span class="svi-key">设备 SHA-256</span><span class="svi-val" id="sviDeviceSha">-</span></div>
|
||||
<div class="svi-row"><span class="svi-key">校验结果</span><span class="svi-val" id="sviResult">-</span></div>
|
||||
</div>
|
||||
<div style="margin-top:12px;">
|
||||
<button class="btn btn-outline btn-block" data-action="restart-esp32">重启 ESP32</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ====== RIGHT: status + tools ====== -->
|
||||
<div>
|
||||
|
||||
<!-- Primary status -->
|
||||
<div class="card card--status">
|
||||
<div class="card-head"><span class="ico">📡</span> 设备状态</div>
|
||||
<div class="card-body">
|
||||
<div class="badge-grid">
|
||||
<div class="badge-stat">
|
||||
<div class="v err" id="bConn">离线</div>
|
||||
<div class="k">连接状态</div>
|
||||
</div>
|
||||
<div class="badge-stat badge-stat-click" id="healthBadge"
|
||||
role="button" tabindex="0" title="点击查看启动自检"
|
||||
aria-expanded="false" aria-controls="healthBootPanel">
|
||||
<div class="v" id="mHealth">--</div>
|
||||
<div class="k">健康状态 <span class="health-boot-hint">▾ 自检</span></div>
|
||||
</div>
|
||||
<div class="badge-stat">
|
||||
<div class="v err" id="connBadge">offline</div>
|
||||
<div class="k">BLE 连接</div>
|
||||
</div>
|
||||
<div class="badge-stat">
|
||||
<div class="v" id="gattState" style="font-size:16px;">未就绪</div>
|
||||
<div class="k">GATT 状态</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 启动自检:点健康状态展开,默认折叠 -->
|
||||
<div class="health-boot-panel collapsed" id="healthBootPanel" hidden>
|
||||
<div class="health-boot-head">
|
||||
<span class="health-boot-title">启动自检</span>
|
||||
<span class="health-boot-summary" id="mBootResult">--</span>
|
||||
</div>
|
||||
<div class="health-boot-failed">失败/跳过:<b id="mBootFailed">--</b></div>
|
||||
<div class="boot-module-list" id="bootModuleList" aria-label="启动模块列表"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Secondary metrics -->
|
||||
<div class="card card--mem">
|
||||
<div class="card-head"><span class="ico">📊</span> 运行指标</div>
|
||||
<div class="card-body">
|
||||
<div class="metric-grid">
|
||||
<div class="metric"><div class="k">ESP32 温度</div><div class="v" id="mTemp">--</div></div>
|
||||
<div class="metric"><div class="k">GD32 温度</div><div class="v" id="mGd32Temp" title="SPI 0xC4;片内温度">--</div></div>
|
||||
<div class="metric"><div class="k">输入 VCC</div><div class="v" id="mVcc" title="PA6 分压反推:VCC—47k—ADC—4.7k—GND">--</div></div>
|
||||
<div class="metric"><div class="k">VDDA</div><div class="v" id="mVdda" title="片内 VREFINT 反推的 MCU 模拟供电">--</div></div>
|
||||
<div class="metric"><div class="k">GD32 APP</div><div class="v" id="mGd32App">--</div></div>
|
||||
<div class="metric"><div class="k">GD32 BOOT</div><div class="v" id="mGd32Boot">--</div></div>
|
||||
<div class="metric"><div class="k">GD32 自检</div><div class="v" id="mGd32Init" title="GD32 APP 上电初始化状态(SPI 0xA3;悬停看失败模块/复位源)">--</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card--mem" style="display:none">
|
||||
<div class="card-head">
|
||||
<span class="ico">🧾</span> 系统状态 (/system/status)
|
||||
<button class="btn btn-ghost btn-sm card-head-btn" data-action="refresh-system-status">刷新</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="status-kv-grid">
|
||||
<div class="status-kv"><span class="k">OTA 状态</span><b id="sOtaState">--</b></div>
|
||||
<div class="status-kv"><span class="k">Relay 状态</span><b id="sRelayState">--</b></div>
|
||||
<div class="status-kv"><span class="k">刷新时间</span><b id="sStatusUpdated">--</b></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Log panel -->
|
||||
<div class="card card--mem log-panel">
|
||||
<div class="card-head" style="justify-content:space-between;">
|
||||
<span><span class="ico">🖥️</span> 调试日志</span>
|
||||
<span style="font-size:11px;font-weight:400;color:var(--text-label);text-transform:none;letter-spacing:0;font-family:var(--font-mono);">UDP :6666</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="log-toolbar">
|
||||
<div class="log-filters">
|
||||
<button class="log-filter active" onclick="setLogFilter('all', this)">全部</button>
|
||||
<button class="log-filter" onclick="setLogFilter('E', this)">错误</button>
|
||||
<button class="log-filter" onclick="setLogFilter('W', this)">警告</button>
|
||||
<button class="log-filter" onclick="setLogFilter('I', this)">信息</button>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm" onclick="toggleAutoScroll()" id="autoScrollBtn" title="切换自动滚动">📌 自动滚动</button>
|
||||
</div>
|
||||
<div class="log-scroll" id="logScroll">
|
||||
<div class="log-line"><span class="lt">[--:--:--]</span><span class="lv I">I</span><span class="lg">[系统]</span><span class="lm">等待上位机连接...</span></div>
|
||||
</div>
|
||||
<div class="log-foot">
|
||||
<span>共 <span id="logCount">0</span> 条</span>
|
||||
<button class="btn btn-ghost btn-sm" onclick="clearLogs()">清除日志</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧滑出:堆与任务栈(默认露出一条,悬停展开) -->
|
||||
<aside class="sys-drawer" id="sysDrawer" aria-label="堆与任务栈">
|
||||
<div class="sys-drawer-rail" title="悬停展开堆与任务栈">
|
||||
<span class="sys-drawer-rail-ico">🧠</span>
|
||||
<span class="sys-drawer-rail-text">堆与任务栈</span>
|
||||
</div>
|
||||
<div class="sys-drawer-panel card card--mem">
|
||||
<div class="card-head sys-drawer-head">
|
||||
<span class="ico">🧠</span> 堆与任务栈
|
||||
<button class="btn btn-ghost btn-sm card-head-btn" type="button" onclick="refreshSystemStatusNow(event)">刷新</button>
|
||||
</div>
|
||||
<div class="card-body sys-drawer-body">
|
||||
<div class="status-section-title">CPU 占用</div>
|
||||
<div class="heap-group cpu-group">
|
||||
<div class="heap-group-title">ESP32 Runtime <span style="font-weight:400;color:var(--text-dim);font-size:10px;">(FreeRTOS)</span></div>
|
||||
<div class="heap-bar-row">
|
||||
<span class="heap-bar-text" id="cpuUsageText">等待采样...</span>
|
||||
<span class="heap-bar-track"><span class="heap-bar-fill cpu-fill" id="cpuUsageFill"></span></span>
|
||||
<span class="heap-bar-pct" id="cpuUsagePct">--</span>
|
||||
</div>
|
||||
<div class="heap-detail-row">
|
||||
<span>空闲: <b id="cpuIdlePct">--</b></span>
|
||||
<span>窗口: <b id="cpuSampleMs">--</b></span>
|
||||
<span>任务数: <b id="cpuTaskCount">--</b></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-task-list cpu-task-list list-scroll-top3" id="cpuTaskList" title="按占用降序,默认显示前 3 项,下滑查看其余">
|
||||
<div class="status-empty">等待下一次状态刷新...</div>
|
||||
</div>
|
||||
|
||||
<div class="status-section-title">内存概况</div>
|
||||
<div class="heap-group">
|
||||
<div class="heap-group-title">芯片 SRAM 总览 <span style="font-weight:400;color:var(--text-dim);font-size:10px;">(ESP32-C3 · 静态段可拆分)</span></div>
|
||||
<div class="heap-bar-row">
|
||||
<span class="heap-bar-text" id="memSramUsage">--</span>
|
||||
<span class="heap-bar-track mem-sram-track" id="memSramTrack" title="紫=IRAM · 橙=.data · 灰=.bss · 青=堆已用 · 暗=堆空闲">
|
||||
<span class="mem-seg mem-seg-iram" id="memSegIram"></span>
|
||||
<span class="mem-seg mem-seg-data" id="memSegData"></span>
|
||||
<span class="mem-seg mem-seg-bss" id="memSegBss"></span>
|
||||
<span class="mem-seg mem-seg-used" id="memSegUsed"></span>
|
||||
<span class="mem-seg mem-seg-free" id="memSegFree"></span>
|
||||
</span>
|
||||
<span class="heap-bar-pct" id="memSramPct">--</span>
|
||||
</div>
|
||||
<div class="heap-legend">
|
||||
<span class="heap-legend-item"><i class="mem-dot mem-dot-iram"></i>IRAM <b id="mIram">--</b></span>
|
||||
<span class="heap-legend-item"><i class="mem-dot mem-dot-data"></i>.data <b id="mData">--</b></span>
|
||||
<span class="heap-legend-item"><i class="mem-dot mem-dot-bss"></i>.bss <b id="mBss">--</b></span>
|
||||
<span class="heap-legend-item"><i class="mem-dot mem-dot-used"></i>堆已用 <b id="mHeapUsed">--</b></span>
|
||||
<span class="heap-legend-item"><i class="mem-dot mem-dot-free"></i>堆空闲 <b id="mHeapFree">--</b></span>
|
||||
</div>
|
||||
<div class="heap-detail-row">
|
||||
<span>SOC 窗口: <b id="mSramSoc">--</b></span>
|
||||
<span>尾部保留: <b id="mReservedTail">--</b></span>
|
||||
<span>任务栈合计: <b id="mTaskStackTotal">--</b></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="heap-group">
|
||||
<div class="heap-group-title">内部 DRAM 堆 <span style="font-weight:400;color:var(--text-dim);font-size:10px;">(malloc 数据堆)</span></div>
|
||||
<div class="heap-bar-row">
|
||||
<span class="heap-bar-text" id="memInternalUsage">--</span>
|
||||
<span class="heap-bar-track"><span class="heap-bar-fill" id="memInternalFill"></span></span>
|
||||
<span class="heap-bar-pct" id="memInternalPct">--</span>
|
||||
</div>
|
||||
<div class="heap-detail-row">
|
||||
<span>空闲: <b id="mInternalHeap">--</b></span>
|
||||
<span>最低空闲: <b id="mInternalMinHeap">--</b></span>
|
||||
<span>最大块: <b id="mInternalLargestBlock">--</b></span>
|
||||
</div>
|
||||
<div class="heap-detail-row">
|
||||
<span>DMA 空闲: <b id="mDmaFree">--</b></span>
|
||||
<span>RTC 堆空闲: <b id="mRtcFree">--</b></span>
|
||||
<span>分配块数: <b id="mAllocBlocks">--</b></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-section-title">任务栈水位 <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--text-dim);">(峰值占用 Top,下滑查看)</span></div>
|
||||
<div class="status-task-list list-scroll-top3" id="taskStackList" title="按峰值占用降序,默认显示前 3 项,下滑查看其余">
|
||||
<div class="status-empty">等待状态刷新...</div>
|
||||
</div>
|
||||
<div class="status-subfold collapsed" id="statusJsonFold">
|
||||
<div class="status-subfold-head" role="button" tabindex="0"
|
||||
onclick="this.parentElement.classList.toggle('collapsed')"
|
||||
onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();this.parentElement.classList.toggle('collapsed');}">
|
||||
<span class="status-section-title status-subfold-title">原始 JSON</span>
|
||||
<span class="collapse-arrow">▼</span>
|
||||
</div>
|
||||
<div class="status-subfold-body">
|
||||
<pre class="status-json" id="statusJson">--</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Toast -->
|
||||
<div class="toast" id="toast">
|
||||
<span class="toast-ico" id="toastIco">✓</span>
|
||||
<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>
|
||||
<!-- 内联兜底:同步 BLE 按钮 data-ble-state 与 checkbox.checked
|
||||
不依赖 ES 模块(子模块可能被浏览器缓存为旧版,不含 data-ble-state 逻辑)。
|
||||
connecting 态由 session.js click handler 设置,此处跳过不覆盖。 -->
|
||||
<script type="module">
|
||||
setInterval(function() {
|
||||
var btn = document.querySelector('.ble-launch-btn');
|
||||
if (!btn || !btn.offsetParent) return;
|
||||
var st = btn.getAttribute('data-ble-state');
|
||||
if (st === 'connecting') return;
|
||||
var toggle = document.getElementById('bleConnectToggle');
|
||||
if (!toggle) return;
|
||||
var expected = toggle.checked ? 'connected' : 'idle';
|
||||
if (st !== expected) btn.setAttribute('data-ble-state', expected);
|
||||
}, 500);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,393 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>电机控制台 — Motor CAN FD Console</title>
|
||||
<!-- 空 favicon:阻止浏览器自动请求 /favicon.ico(本地静态服务无此文件会 404) -->
|
||||
<link rel="icon" href="data:,">
|
||||
<link rel="stylesheet" href="../css/motor.css?v=20260816a">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ═══════════ TOOLBAR ═══════════ -->
|
||||
<header class="toolbar">
|
||||
<div class="toolbar-brand">
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" aria-hidden="true">
|
||||
<rect x="1" y="1" width="24" height="24" rx="6" stroke="var(--primary)" stroke-width="1.5" opacity=".6"/>
|
||||
<circle cx="13" cy="13" r="5.5" stroke="var(--primary)" stroke-width="1.8"/>
|
||||
<path d="M13 4v3.5M13 18.5V22M4 13h3.5M18.5 13H22" stroke="var(--primary)" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<circle cx="13" cy="13" r="1.8" fill="var(--primary)"/>
|
||||
</svg>
|
||||
<span>电机控制台</span>
|
||||
<span class="sub">Motor CAN FD</span>
|
||||
</div>
|
||||
<div class="toolbar-sep"></div>
|
||||
<div class="conn-group" title="与 OTA 主页共用设备 IP,本页不提供连接配置">
|
||||
<span class="led" id="connLed"></span>
|
||||
<span class="conn-ip-label" id="ipLabel">IP --</span>
|
||||
<span style="font-size:.72rem;color:var(--fg-dim);font-family:var(--font-mono)" id="devStatus">探测中…</span>
|
||||
</div>
|
||||
<div class="toolbar-sep"></div>
|
||||
<div class="conn-group">
|
||||
<label for="chSelect" style="font-size:.72rem;color:var(--fg-muted);font-family:var(--font-mono);letter-spacing:.06em">通道</label>
|
||||
<select class="ch-select" id="chSelect">
|
||||
<option value="0">CH0</option>
|
||||
<option value="1">CH1</option>
|
||||
<option value="2">CH2</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="toolbar-sep"></div>
|
||||
<div class="conn-group" title="电机状态刷新间隔(ms,50~5000)。同时驱动前端 HTTP 轮询与 GD32 0xC5 SPI 主动推送周期,端到端节拍一致">
|
||||
<label for="refreshRate" class="refresh-hint">刷新速度</label>
|
||||
<input type="number" id="refreshRate" class="refresh-input" value="250" min="50" max="5000" step="50" aria-label="电机状态刷新间隔(毫秒)" />
|
||||
<span class="refresh-hint">ms</span>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<a class="btn btn-sm" href="index.html" title="返回 OTA 主页配置连接">返回 OTA 控制台</a>
|
||||
<span class="node-badge" id="nodeBadge">NODE -</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="net-banner" id="netBanner">
|
||||
<span class="led led-err led-pulse" style="width:7px;height:7px"></span>
|
||||
<span id="netBannerText">未连接 — 使用主页共享 IP 自动探测中… 请先在 OTA 主页配置并测试连接</span>
|
||||
</div>
|
||||
|
||||
<!-- ═══════════ MAIN 3-ZONE GRID ═══════════ -->
|
||||
<main class="main-grid">
|
||||
|
||||
<!-- LEFT: NODE OVERVIEW -->
|
||||
<aside class="panel nodes-panel">
|
||||
<div class="panel-head">
|
||||
<span class="panel-title">节点总览</span>
|
||||
<span style="margin-left:auto;font-size:.68rem;color:var(--fg-dim);font-family:var(--font-mono)" id="nodeSummary">0/9 在线 · CAN FD</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<!-- 独立 BOOT 状态:总线检测到 0x103 心跳时显示,不归属任何 Nx
|
||||
显示/隐藏只靠 .boot-badge / .boot-badge.show,禁止内联 display -->
|
||||
<div id="bootBadge" class="boot-badge" aria-hidden="true">
|
||||
<span class="boot-dot" aria-hidden="true"></span>
|
||||
<span class="boot-txt">电机在 BOOT</span>
|
||||
<span class="boot-sub" id="bootBadgeSub">0x103</span>
|
||||
</div>
|
||||
<div class="node-list" id="nodeList" role="listbox" aria-label="电机节点列表 N1-N9(离线可选)">
|
||||
<!-- N1–N9 由 JS 固定渲染,离线也可选中刷写 -->
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- CENTER: CONTROL -->
|
||||
<section class="panel center-panel">
|
||||
<div class="panel-head">
|
||||
<span class="panel-title">CMD 控制</span>
|
||||
<span style="font-size:.68rem;color:var(--fg-dim);font-family:var(--font-mono);margin-left:auto" id="cmdIdLabel">ID 0x000-0200 · DLC 32</span>
|
||||
</div>
|
||||
<div class="cmd-section">
|
||||
<div class="cmd-grid">
|
||||
<div class="field">
|
||||
<span class="field-label">Control_Mode <span class="field-unit">控制模式</span></span>
|
||||
<select id="modeSelect" aria-label="控制模式">
|
||||
<option value="0">0 — Disable 关闭</option>
|
||||
<option value="1">1 — Angle 位置控制</option>
|
||||
<option value="2">2 — Velocity 速度控制</option>
|
||||
<option value="3" selected>3 — Velocity_Angle 速度+位置</option>
|
||||
<option value="4">4 — MIT Mode</option>
|
||||
<option value="5">5 — Current 电流/力矩</option>
|
||||
<option value="6">6 — 开环速度</option>
|
||||
<option value="7">7 — 开环速度 (2)</option>
|
||||
</select>
|
||||
<span class="mode-desc" id="modeDesc">速度+位置环联合控制,目标速度与目标角度同时生效</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<span class="field-label">Limit_Voltage <span class="field-unit">V · 0~65.535</span></span>
|
||||
<input type="number" id="inVoltage" value="24.000" step="0.5" min="0" max="65.535" aria-label="电压限制" />
|
||||
</div>
|
||||
<div class="field" id="fldCurrent">
|
||||
<span class="field-label">Target_Current <span class="field-unit">mA</span></span>
|
||||
<input type="number" id="inCurrent" value="0" step="100" aria-label="目标电流" />
|
||||
</div>
|
||||
<div class="field" id="fldVelocity">
|
||||
<span class="field-label">Target_Velocity <span class="field-unit">deg/s</span></span>
|
||||
<input type="number" id="inVelocity" value="360" step="10" aria-label="目标速度" />
|
||||
</div>
|
||||
<div class="field" id="fldAngle">
|
||||
<span class="field-label">Target_Angle <span class="field-unit">deg</span></span>
|
||||
<input type="number" id="inAngle" value="180.0" step="1" aria-label="目标角度" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field" style="margin-top:var(--sp-4)">
|
||||
<span class="field-label">PID 增益 <span class="field-unit">P × 3 环</span></span>
|
||||
<div class="pid-group">
|
||||
<div class="field"><span class="field-label" style="font-size:.62rem">电流环 P</span><input type="number" id="pidCurP" value="100" step="1" /></div>
|
||||
<div class="field"><span class="field-label" style="font-size:.62rem">速度环 P</span><input type="number" id="pidVelP" value="100" step="1" /></div>
|
||||
<div class="field"><span class="field-label" style="font-size:.62rem">角度环 P</span><input type="number" id="pidAngP" value="100" step="1" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quick-run" id="quickRun" title="把值写入当前模式的主目标字段并立即发送 CMD">
|
||||
<span class="qr-label">快捷运行 <span class="qr-target" id="qrTargetHint">→ 目标角度 deg</span></span>
|
||||
<div class="qr-btns">
|
||||
<button class="qr-btn" data-val="-9999">-9999</button>
|
||||
<button class="qr-btn" data-val="-200">-200</button>
|
||||
<button class="qr-btn" data-val="-100">-100</button>
|
||||
<button class="qr-btn" data-val="0">0</button>
|
||||
<button class="qr-btn" data-val="50">50</button>
|
||||
<button class="qr-btn" data-val="100">100</button>
|
||||
<button class="qr-btn" data-val="200">200</button>
|
||||
<button class="qr-btn" data-val="1000">1000</button>
|
||||
<button class="qr-btn" data-val="9999">9999</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cmd-actions">
|
||||
<button class="btn btn-primary" id="sendCmdBtn">
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M1.5 7h9M8 3.5L11.5 7 8 10.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
发送 CMD
|
||||
</button>
|
||||
<button class="btn" id="sendRepeatBtn">周期发送</button>
|
||||
<label class="repeat-period" title="周期发送间隔(最小 80ms)">
|
||||
<input type="number" id="repeatPeriod" value="100" min="80" max="10000" step="10" />
|
||||
<span>ms</span>
|
||||
</label>
|
||||
<span class="send-hint" id="sendHint">POST /can/send_batch · CH0</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TABS -->
|
||||
<nav class="tabs-bar" aria-label="辅助功能">
|
||||
<button class="tab-btn active" data-tab="config">配置操作</button>
|
||||
<button class="tab-btn" data-tab="version">版本信息</button>
|
||||
<button class="tab-btn" data-tab="nodeid">节点 ID</button>
|
||||
<button class="tab-btn" data-tab="ota">电机 OTA</button>
|
||||
<button class="tab-btn" data-tab="seq">命令序列</button>
|
||||
</nav>
|
||||
<div class="tab-content">
|
||||
<!-- CONFIG TAB -->
|
||||
<div class="tab-page active" id="tab-config">
|
||||
<div class="config-grid">
|
||||
<button class="config-btn danger-action" data-action="motReset">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><path d="M4 11a7 7 0 1 1 2 4.9M4 16v-5h5" stroke="var(--danger)" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
<span class="cb-name">电机复位</span><span class="cb-sig">motReset · bit0</span>
|
||||
</button>
|
||||
<button class="config-btn" data-action="saveConfig">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><path d="M5 3h9l4 4v12H5V3z" stroke="var(--primary)" stroke-width="1.6" stroke-linejoin="round"/><path d="M8 3v5h6V3M8 18v-5h7v5" stroke="var(--primary)" stroke-width="1.4"/></svg>
|
||||
<span class="cb-name">保存配置</span><span class="cb-sig">saveConfig · bit2</span>
|
||||
</button>
|
||||
<button class="config-btn" data-action="angleReset">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="7.5" stroke="var(--primary)" stroke-width="1.6"/><path d="M11 6v5l3.5 2" stroke="var(--primary)" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
<span class="cb-name">角度置零</span><span class="cb-sig">angle_reset · bit3</span>
|
||||
</button>
|
||||
<button class="config-btn" data-action="zeroCalib">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><circle cx="11" cy="11" r="7" stroke="var(--primary)" stroke-width="1.6"/><path d="M11 2v4M11 16v4M2 11h4M16 11h4" stroke="var(--primary)" stroke-width="1.5" stroke-linecap="round"/><circle cx="11" cy="11" r="1.5" fill="var(--primary)"/></svg>
|
||||
<span class="cb-name">零点校准</span><span class="cb-sig">zero_calibration · bit5</span>
|
||||
</button>
|
||||
<button class="config-btn" data-action="sleep">
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" aria-hidden="true"><path d="M17 13.5A7 7 0 0 1 8.5 5a7 7 0 1 0 8.5 8.5z" stroke="var(--warn)" stroke-width="1.6" stroke-linejoin="round"/></svg>
|
||||
<span class="cb-name">休眠</span><span class="cb-sig">SLEEP · bit56</span>
|
||||
</button>
|
||||
</div>
|
||||
<p style="margin-top:var(--sp-4);font-size:.72rem;color:var(--fg-dim);line-height:1.6">
|
||||
CMD_CONFIG (0x000<span class="cfgNodeNibble">-</span>0301) · DLC 24 · 每个操作发送单帧脉冲信号,操作前需确认
|
||||
</p>
|
||||
</div>
|
||||
<!-- VERSION TAB -->
|
||||
<div class="tab-page" id="tab-version">
|
||||
<button class="btn btn-sm" id="queryVerBtn">
|
||||
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" aria-hidden="true"><circle cx="6.5" cy="6.5" r="5" stroke="currentColor" stroke-width="1.3"/><path d="M6.5 4.2v.1M6.5 6v3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||||
查询版本 (GET_VERSION)
|
||||
</button>
|
||||
<div class="version-grid" style="margin-top:var(--sp-4)" id="versionGrid">
|
||||
<div class="ver-card"><div class="ver-type">软件版本</div><div class="ver-date ver-empty" id="verSoft">--</div></div>
|
||||
<div class="ver-card"><div class="ver-type">硬件版本</div><div class="ver-date ver-empty" id="verHard">--</div></div>
|
||||
<div class="ver-card"><div class="ver-type">Bootloader</div><div class="ver-date ver-empty" id="verBoot">--</div></div>
|
||||
</div>
|
||||
<p style="margin-top:var(--sp-3);font-size:.72rem;color:var(--fg-dim)">请求/回报共用 0x000<span class="cfgNodeNibble">-</span>00FF(同 ID)· DLC 24 · major.minor.patch.build ×3 (soft/hard/boot,DBC v0.71) · GD32 解析后 0xC6 同步</p>
|
||||
</div>
|
||||
<!-- NODE ID TAB -->
|
||||
<div class="tab-page" id="tab-nodeid">
|
||||
<div class="nodeid-row">
|
||||
<div class="field" style="min-width:140px">
|
||||
<span class="field-label">目标 Node ID</span>
|
||||
<input type="number" id="nodeIdInput" min="1" max="9" value="3" />
|
||||
</div>
|
||||
<button class="btn btn-primary btn-sm" id="setNodeIdBtn">设置 ID (0xN0400)</button>
|
||||
<button class="btn btn-sm" id="getNodeIdBtn">读取 ID (0xN0401)</button>
|
||||
</div>
|
||||
<div class="nodeid-result" id="nodeIdResult">连接设备后读取节点 ID</div>
|
||||
<p style="margin-top:var(--sp-3);font-size:.72rem;color:var(--fg-dim);line-height:1.6">
|
||||
SET_NODE_ID (0x000N0400 DLC2) / GET_NODE_ID (0x000N0401 DLC2)。写入后 CAN ID 前缀变为 0x000Nxxxx。
|
||||
</p>
|
||||
</div>
|
||||
<!-- OTA TAB -->
|
||||
<div class="tab-page" id="tab-ota">
|
||||
<div class="ota-stages" id="otaStages" aria-label="OTA阶段">
|
||||
<div class="ota-stage" data-stage="upload"><span class="st-dot">1</span>上传固件</div>
|
||||
<div class="ota-stage" data-stage="boot"><span class="st-dot">2</span>进入BOOT</div>
|
||||
<div class="ota-stage" data-stage="handshake"><span class="st-dot">3</span>转发握手</div>
|
||||
<div class="ota-stage" data-stage="erase"><span class="st-dot">4</span>擦除Flash</div>
|
||||
<div class="ota-stage" data-stage="transfer"><span class="st-dot">5</span>数据传输</div>
|
||||
<div class="ota-stage" data-stage="app"><span class="st-dot">6</span>完成</div>
|
||||
</div>
|
||||
<div class="ota-file-row">
|
||||
<button class="btn btn-sm" id="otaFileBtn">选择固件 (.bin)</button>
|
||||
<input type="file" id="otaFileInput" accept=".bin" hidden />
|
||||
<span class="ota-file-name" id="otaFileName">未选择固件</span>
|
||||
<button class="btn btn-primary btn-sm" id="otaStartBtn">开始 OTA</button>
|
||||
<button class="btn btn-danger btn-sm" id="otaCancelBtn" disabled>取消</button>
|
||||
</div>
|
||||
<div class="ota-progress-wrap">
|
||||
<div class="ota-progress-bar"><div class="ota-progress-fill" id="otaProgressFill"></div></div>
|
||||
<div class="ota-stats">
|
||||
<span>包 <span class="hl" id="otaPktNum">0</span> / <span id="otaPktTotal">0</span></span>
|
||||
<span>速率 <span class="hl" id="otaSpeed">—</span></span>
|
||||
<span>状态 <span id="otaCrc">—</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ota-log-toolbar">
|
||||
<button id="otaLogCopyBtn" title="复制全部日志">复制</button>
|
||||
<button id="otaLogClearBtn" title="清空日志">清空</button>
|
||||
</div>
|
||||
<div class="ota-log" id="otaLog" aria-live="polite">
|
||||
<div class="log-info">[就绪] 电机 Bootloader OTA · 先上传固件到 ESP32 → 发 0x000N0000 进 BOOT → 触发转发 → GD32 本地 relay(AES-128 + CAN stop-and-wait)</div>
|
||||
<div>[信息] 选固件 → 上传并比对 flash SHA-256(失败重传≤2次)→ 发进BOOT命令 → 触发 /motor/ota/relay → GD32 完成握手/加密/传输 · 每包 48 B · 48 KB 上限</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 命令序列 TAB -->
|
||||
<div class="tab-page" id="tab-seq">
|
||||
<div class="seq-toolbar">
|
||||
<label class="repeat-period" title="整序列循环次数,0 = 无限循环">
|
||||
<span>循环</span>
|
||||
<input type="number" id="seqLoops" value="0" min="0" max="9999" step="1" />
|
||||
<span>次(0=无限)</span>
|
||||
</label>
|
||||
<button class="btn btn-sm" id="seqAddBtn">+ 添加步骤</button>
|
||||
<button class="btn btn-sm" id="seqClearBtn">清空</button>
|
||||
<button class="btn" id="seqStartBtn">▶ 开始序列</button>
|
||||
<span class="seq-status" id="seqStatus">未运行</span>
|
||||
</div>
|
||||
<div class="seq-list" id="seqList"></div>
|
||||
<p class="seq-hint">
|
||||
命令序列:每步为一个「同步动作组」,组内每个动作 = 一台电机目标(通道/节点/模式/电压/电流/速度/角度/PID);
|
||||
执行本步时组内全部动作经一次突发请求近同时下发(同组偏差微秒级;超过单次容量自动分组,组间约 1-2ms),
|
||||
发送成功后保持「保持 ms」再进入下一步。每步可「+」添加多个电机动作,支持多通道、多电机混合搭配,独立于左侧选中节点。
|
||||
循环 0 = 无限循环,与「周期发送」互斥。「+ 添加步骤」会复制当前 CMD 表单已提交的值及当前通道/选中节点。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- RIGHT: STATUS FEEDBACK -->
|
||||
<section class="panel status-panel">
|
||||
<div class="panel-head">
|
||||
<span class="panel-title">STATUS 实时反馈</span>
|
||||
<span style="font-size:.68rem;color:var(--fg-dim);font-family:var(--font-mono);margin-left:auto" id="statusIdLabel">0x000-0100 · 1 Hz</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="offline-overlay show" id="offlineOverlay">
|
||||
<svg class="off-icon" width="40" height="40" viewBox="0 0 40 40" fill="none" aria-hidden="true"><circle cx="20" cy="20" r="16" stroke="var(--fg-dim)" stroke-width="1.5" stroke-dasharray="4 4"/><path d="M14 26l12-12M26 26L14 14" stroke="var(--fg-dim)" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
<span style="font-size:.85rem" id="offlineTitle">节点离线 — 未收到 STATUS 帧</span>
|
||||
<span style="font-size:.72rem" id="offlineSub">检查电机供电与 CAN 总线连接</span>
|
||||
</div>
|
||||
<div class="status-live hide" id="statusLive">
|
||||
<div class="big-readings">
|
||||
<div class="big-reading" data-signal="angle" title="点击展开/收起角度波形">
|
||||
<div class="br-val" id="rtAngle">--</div>
|
||||
<div class="br-unit">deg</div>
|
||||
<div class="br-lbl">RT_Angle <span class="br-toggle">▾ 波形</span></div>
|
||||
<div class="br-target">目标 <span id="tgtAngle">--</span> deg</div>
|
||||
<div class="br-scope"><canvas id="miniAngle" aria-label="角度波形" role="img"></canvas></div>
|
||||
</div>
|
||||
<div class="big-reading" data-signal="velocity" title="点击展开/收起速度波形">
|
||||
<div class="br-val" id="rtVelocity">--</div>
|
||||
<div class="br-unit">deg/s</div>
|
||||
<div class="br-lbl">RT_Velocity <span class="br-toggle">▾ 波形</span></div>
|
||||
<div class="br-target">目标 <span id="tgtVelocity">--</span> deg/s</div>
|
||||
<div class="br-scope"><canvas id="miniVelocity" aria-label="速度波形" role="img"></canvas></div>
|
||||
</div>
|
||||
<div class="big-reading" data-signal="current" title="点击展开/收起电流波形">
|
||||
<div class="br-val" id="rtCurrent">--</div>
|
||||
<div class="br-unit">mA</div>
|
||||
<div class="br-lbl">RT_Current <span class="br-toggle">▾ 波形</span></div>
|
||||
<div class="br-target">目标 <span id="tgtCurrent">--</span> mA</div>
|
||||
<div class="br-scope"><canvas id="miniCurrent" aria-label="电流波形" role="img"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status-row">
|
||||
<div class="temp-block" data-signal="mcuTemp" title="点击展开/收起 MCU 温度曲线">
|
||||
<div class="temp-head"><span class="temp-name">MCU_Temp<span class="temp-toggle">▾ 曲线</span></span><span class="temp-val" id="mcuTemp">-- °C</span></div>
|
||||
<div class="temp-bar"><div class="temp-fill" id="mcuTempBar" style="width:0%"></div></div>
|
||||
<div class="br-scope"><canvas id="miniMcuTemp" aria-label="MCU温度曲线" role="img"></canvas></div>
|
||||
</div>
|
||||
<div class="temp-block" data-signal="motTemp" title="点击展开/收起电机温度曲线">
|
||||
<div class="temp-head"><span class="temp-name">Motor_Temp<span class="temp-toggle">▾ 曲线</span></span><span class="temp-val" id="motTemp">-- °C</span></div>
|
||||
<div class="temp-bar"><div class="temp-fill" id="motTempBar" style="width:0%"></div></div>
|
||||
<div class="br-scope"><canvas id="miniMotTemp" aria-label="电机温度曲线" role="img"></canvas></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="phase-currents">
|
||||
<div class="pc-head">
|
||||
<span class="pc-title">三相电流 U / V / W</span>
|
||||
<div class="pc-live-vals">
|
||||
<span class="pc-chip" style="--ch-color:var(--primary)">U <b id="valU">+0</b> mA</span>
|
||||
<span class="pc-chip" style="--ch-color:var(--ok)">V <b id="valV">+0</b> mA</span>
|
||||
<span class="pc-chip" style="--ch-color:var(--warn)">W <b id="valW">+0</b> mA</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="scope-wrap">
|
||||
<canvas id="scopeCanvas" aria-label="三相电流实时波形" role="img"></canvas>
|
||||
<div class="scope-grid-labels"><span>+5A</span><span>0</span><span>-5A</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="misc-grid">
|
||||
<div class="misc-item"><span class="mi-lbl">Power_Voltage</span><span class="mi-val" id="pwrVolt">-- V</span></div>
|
||||
<div class="misc-item"><span class="mi-lbl">test_float</span><span class="mi-val" id="testFloat">--</span></div>
|
||||
<div class="misc-item"><span class="mi-lbl">test_U16</span><span class="mi-val" id="testU16">--</span></div>
|
||||
<div class="cpu-ring-wrap" style="grid-column:2;grid-row:2">
|
||||
<div class="cpu-ring">
|
||||
<svg width="52" height="52" viewBox="0 0 52 52" role="img" aria-label="CPU占用率">
|
||||
<circle class="ring-bg" cx="26" cy="26" r="21"/>
|
||||
<circle class="ring-fg" id="cpuRingFg" cx="26" cy="26" r="21" stroke-dasharray="131.9" stroke-dashoffset="131.9"/>
|
||||
</svg>
|
||||
<span class="cpu-pct" id="cpuPct">--%</span>
|
||||
</div>
|
||||
<div class="cpu-info"><span class="mi-lbl">CPU_Usage</span><span class="mi-sub" id="cpuSub">-- </span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flag-lights">
|
||||
<div class="flag-item" id="flagItemMot"><span class="flag-led" id="flagMot"></span>motStatus 使能<span class="flag-state" id="flagMotState">—</span></div>
|
||||
<div class="flag-item" id="flagItemMag"><span class="flag-led" id="flagMag"></span>magStatus 磁编码<span class="flag-state" id="flagMagState">—</span></div>
|
||||
<div class="flag-item" id="flagItemThermal"><span class="flag-led" id="flagThermal"></span>thermal 过温<span class="flag-state" id="flagThermalState">—</span></div>
|
||||
<div class="flag-item" id="flagItemEeprom"><span class="flag-led" id="flagEeprom"></span>EEPROM 存储<span class="flag-state" id="flagEepromState">—</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<!-- ═══════════ STATUS BAR ═══════════ -->
|
||||
<footer class="statusbar">
|
||||
<div class="sb-item"><span class="led" id="sbLed" style="width:7px;height:7px"></span> <span id="sbCh">CH0</span> <span class="sb-val" id="sbRx">RX --</span></div>
|
||||
<div class="sb-sep"></div>
|
||||
<div class="sb-item">TX <span class="sb-val" id="sbTx">--</span></div>
|
||||
<div class="sb-sep"></div>
|
||||
<div class="sb-item">ERR <span class="sb-val" id="sbErr">--</span> · TEC <span id="sbTec">--</span> / REC <span id="sbRec">--</span></div>
|
||||
<div class="sb-sep"></div>
|
||||
<div class="sb-item">BUS <span class="sb-val" id="sbBus">--</span></div>
|
||||
<span class="sb-last-send" id="sbLastSend">尚未发送</span>
|
||||
</footer>
|
||||
|
||||
<!-- TOAST & MODAL -->
|
||||
<div class="toast-container" id="toastContainer" aria-live="polite"></div>
|
||||
<div class="modal-mask" id="modalMask" role="dialog" aria-modal="true">
|
||||
<div class="modal-box">
|
||||
<div class="modal-title" id="modalTitle">确认操作</div>
|
||||
<div class="modal-desc" id="modalDesc"></div>
|
||||
<div class="modal-actions">
|
||||
<button class="btn" id="modalCancel">取消</button>
|
||||
<button class="btn btn-primary" id="modalConfirm">确认执行</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module" src="../js/motor/main.js?v=20260816c"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user