feat: 添加局域网设备发现功能和链路胶囊优化
- 实现局域网设备发现面板,支持探测ESP32中继板并自动切换端点 - 添加POST /api/lan/probe接口用于设备探测,返回三态判决结果 - 实现快扫和全段扫描两种模式,支持种子地址优先探测 - 添加端点唯一写手机制,防止多处同时修改设备IP - 优化链路胶囊UI显示WiFi/EtherCAT/BLE连接状态和设备名称 - 修复EtherCAT链路OTA刷写期间PDO循环暂停和AL状态处理问题 - 调整PDO周期设置范围为0.125-4ms,支持亚毫秒显示为微秒 - 添加ESP32 WiFi使能开关控件样式和数据来源角标显示
This commit is contained in:
+106
@@ -133,3 +133,109 @@ body::after {
|
||||
outline-offset: 2px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
局域网设备发现(js/common/lan-picker.js)
|
||||
这里只放弹窗本身和它特有的排布。按钮 / 表单 / 输入框 / 列表行
|
||||
一律复用 layout.css 的既有类(.btn .btn-outline .btn-sm .input
|
||||
.input-group .form-label .jump-history-item .jh-tag .jh-msg .jh-time
|
||||
与 .ok/.err/.info 状态色),不再另造一套控件外观。
|
||||
============================================================ */
|
||||
.lan-slot { display: flex; align-items: center; gap: 8px; padding: 0 6px; }
|
||||
|
||||
/* 遮罩:居中弹出 + 周围高斯模糊(backdrop-filter 作用于遮罩下方的整个页面) */
|
||||
.lan-mask {
|
||||
position: fixed; inset: 0; z-index: 900;
|
||||
display: none; align-items: center; justify-content: center;
|
||||
padding: 24px;
|
||||
background: rgba(4, 8, 14, .62);
|
||||
-webkit-backdrop-filter: blur(9px) saturate(.85);
|
||||
backdrop-filter: blur(9px) saturate(.85);
|
||||
animation: lanFade .16s ease;
|
||||
}
|
||||
.lan-mask.show { display: flex; }
|
||||
@keyframes lanFade { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes lanPop {
|
||||
from { opacity: 0; transform: translateY(10px) scale(.985); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
|
||||
/* 盒子沿用 .card 的渐变底与顶部子系统色带,描边/阴影/圆角同 .modal-box */
|
||||
.lan-box {
|
||||
position: relative;
|
||||
width: min(760px, 100%); max-height: min(82vh, 780px);
|
||||
overflow: auto;
|
||||
background: linear-gradient(170deg, var(--bg-card) 0%, rgba(15,23,38,.96) 100%);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: 0 24px 64px rgba(0,0,0,.6);
|
||||
animation: lanPop .18s ease;
|
||||
}
|
||||
.lan-box::before {
|
||||
content: ""; position: absolute; top: 0; left: 0; right: 0; height: 2px;
|
||||
background: linear-gradient(90deg, var(--esp32) 0%, var(--ble) 55%, var(--gd32) 100%);
|
||||
opacity: .8;
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
}
|
||||
.lan-head {
|
||||
position: sticky; top: 0; z-index: 2;
|
||||
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
|
||||
padding: 14px 18px; border-bottom: 1px solid var(--border-subtle);
|
||||
font-family: var(--font-display); font-size: 13px; font-weight: 600;
|
||||
letter-spacing: .05em; color: var(--text);
|
||||
background: linear-gradient(170deg, var(--bg-card) 0%, rgba(15,23,38,.96) 100%);
|
||||
}
|
||||
.lan-ep {
|
||||
font-family: var(--font-mono); font-size: 11px; font-weight: 400;
|
||||
letter-spacing: 0; color: var(--text-dim);
|
||||
}
|
||||
.lan-head .btn { margin-left: auto; }
|
||||
.lan-body { padding: 14px 18px 18px; display: flex; flex-direction: column; gap: 12px; }
|
||||
.lan-tools { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.lan-field { display: flex; align-items: center; gap: 8px; }
|
||||
.lan-field .form-label { margin-bottom: 0; }
|
||||
.lan-field .input { width: 172px; padding: 7px 10px; font-size: 12px; }
|
||||
.lan-check {
|
||||
display: flex; align-items: center; gap: 6px; cursor: pointer;
|
||||
font-family: var(--font-display); font-size: 11px;
|
||||
letter-spacing: .08em; text-transform: uppercase; color: var(--text-label);
|
||||
}
|
||||
.lan-progress { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-label); }
|
||||
.lan-progress.lan-busy { color: var(--esp32); }
|
||||
|
||||
/* 列表行 = .jump-history-item 既有排布,但改成网格:
|
||||
没有操作按钮的"当前端点"行和有按钮的行才能按同一套列宽对齐
|
||||
(flex 布局下两行的右侧列会各自漂移,就是"对不齐"的来源)。 */
|
||||
.lan-list {
|
||||
list-style: none; margin: 0; padding: 0; overflow: hidden;
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
background: var(--bg-card);
|
||||
}
|
||||
.lan-list .jump-history-item {
|
||||
display: grid; align-items: center; gap: 8px;
|
||||
grid-template-columns: 46px minmax(0, 1fr) auto 52px;
|
||||
padding: 6px 12px; cursor: pointer;
|
||||
}
|
||||
.lan-list .jump-history-item > .jh-msg { min-width: 0; }
|
||||
.lan-list .jump-history-item > .jh-time { text-align: right; }
|
||||
.lan-list .jump-history-item > .lan-sub { grid-column: 1 / -1; }
|
||||
.lan-list .jump-history-item:hover { background: var(--bg-hover); }
|
||||
.lan-list .jump-history-item.lan-cur { background: var(--esp32-dim); }
|
||||
.lan-list .jump-history-item.lan-stale { opacity: .55; }
|
||||
.lan-ip { color: var(--text); font-weight: 700; }
|
||||
.lan-meta { color: var(--text-dim); font-weight: 400; }
|
||||
.lan-acts { display: flex; gap: 4px; flex-shrink: 0; }
|
||||
.lan-sub { flex-basis: 100%; font-size: 11px; color: var(--text-label); }
|
||||
.lan-empty, .lan-cut { padding: 12px; font-size: 12px; color: var(--text-dim); text-align: center; }
|
||||
|
||||
.lan-note { min-height: 1.2em; font-family: var(--font-mono); font-size: 11.5px; color: var(--text-dim); }
|
||||
.lan-note-warn { color: var(--amber); }
|
||||
.lan-note-error { color: var(--red); }
|
||||
.lan-misc {
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
border-top: 1px solid var(--border-subtle); padding-top: 12px;
|
||||
}
|
||||
.lan-block { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-label); }
|
||||
.lan-block b { color: var(--text-dim); font-weight: 600; }
|
||||
.lan-block ul { margin: 4px 0 0; padding-left: 18px; }
|
||||
.lan-hint { color: var(--text-dim); }
|
||||
|
||||
+13
@@ -199,6 +199,19 @@ body.can-page {
|
||||
z-index: 5;
|
||||
backdrop-filter: blur(14px);
|
||||
background: rgba(15,22,31,.86);
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.can-header .header-status {
|
||||
gap: 6px;
|
||||
}
|
||||
.can-header .header-status .pill {
|
||||
padding: 5px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
.can-header .header-status .link-pill {
|
||||
padding: 5px 11px 5px 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
/* sticky+backdrop-filter 合成层冲突致 fadeInUp 卡首帧(translateY(10px)+opacity:0),
|
||||
* 禁用入场动画。选择器需 .header-bar.can-header 以胜过 responsive.css 的 .header-bar 规则 */
|
||||
|
||||
+52
-1
@@ -119,6 +119,27 @@
|
||||
overflow: hidden;
|
||||
transition: border-color .22s, transform .22s, box-shadow .22s;
|
||||
}
|
||||
|
||||
/* ---------- 数据来源角标(WiFi / ECAT / BLE) ----------
|
||||
右上角小胶囊:标出本格当前数据从哪条链路来。
|
||||
为双路径无感切换预留:通路变化时 JS 调 setCellSource() 改 data-src + 文案。 */
|
||||
.src-badge {
|
||||
position: absolute; top: 5px; right: 5px;
|
||||
font-family: var(--font-mono); font-size: 9px; font-weight: 600;
|
||||
letter-spacing: .06em; line-height: 1;
|
||||
padding: 3px 5px; border-radius: 4px;
|
||||
border: 1px solid transparent;
|
||||
opacity: .88; pointer-events: none; user-select: none;
|
||||
z-index: 2;
|
||||
}
|
||||
.src-badge.src-wifi { color: var(--esp32); background: var(--esp32-dim); border-color: rgba(47,216,193,.25); }
|
||||
.src-badge.src-ecat { color: var(--mem-hi); background: var(--mem-dim); border-color: rgba(168,139,250,.28); }
|
||||
.src-badge.src-ble { color: var(--ble); background: rgba(95,168,255,.12); border-color: rgba(95,168,255,.25); }
|
||||
.src-badge.src-dual {
|
||||
color: var(--text); background: linear-gradient(90deg, var(--esp32-dim), var(--mem-dim));
|
||||
border-color: rgba(47,216,193,.35);
|
||||
}
|
||||
.src-badge.src-off { color: var(--text-label); background: rgba(255,255,255,.04); border-color: var(--border); }
|
||||
.metric::before {
|
||||
content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
|
||||
background: var(--metric-accent, var(--esp32));
|
||||
@@ -169,8 +190,10 @@
|
||||
/* ================= 主状态徽章 ================= */
|
||||
.badge-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
|
||||
.badge-stat {
|
||||
position: relative; /* src-badge 绝对定位锚点(缺了会飘到卡片/页面角) */
|
||||
background: var(--bg-elevated); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 16px 16px 13px; text-align: center;
|
||||
border-radius: var(--radius); padding: 22px 16px 13px; text-align: center;
|
||||
overflow: hidden;
|
||||
transition: border-color .22s, transform .22s, box-shadow .22s;
|
||||
}
|
||||
.badge-stat:hover {
|
||||
@@ -718,6 +741,34 @@ code { color: var(--esp32-hi); font-family: var(--font-mono); }
|
||||
.ble-low-table td:nth-child(7), .ble-low-table td:nth-child(9) { text-align: left; }
|
||||
.ble-low-table td:first-child { color: var(--text-dim); }
|
||||
|
||||
/* ================= ESP32 WiFi 使能开关(PC9 / WLESS_EN) ================= */
|
||||
.wifi-en-inline {
|
||||
margin-left: auto;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
position: relative; z-index: 2;
|
||||
}
|
||||
.wifi-en-inline-label {
|
||||
font-size: 12px; font-weight: 600; color: var(--text-dim); white-space: nowrap;
|
||||
}
|
||||
.wifi-switch { position: relative; display: inline-block; width: 44px; height: 24px; flex-shrink: 0; }
|
||||
.wifi-switch--sm { width: 36px; height: 20px; }
|
||||
.wifi-switch input { opacity: 0; width: 0; height: 0; }
|
||||
.wifi-slider {
|
||||
position: absolute; inset: 0; cursor: pointer; border-radius: 999px;
|
||||
background: #3a4a63; transition: background .18s ease;
|
||||
}
|
||||
.wifi-slider::before {
|
||||
content: ""; position: absolute; width: 18px; height: 18px; left: 3px; top: 3px;
|
||||
border-radius: 50%; background: #fff; transition: transform .18s ease;
|
||||
}
|
||||
.wifi-switch--sm .wifi-slider::before {
|
||||
width: 14px; height: 14px; left: 3px; top: 3px;
|
||||
}
|
||||
.wifi-switch input:checked + .wifi-slider { background: var(--green); }
|
||||
.wifi-switch input:checked + .wifi-slider::before { transform: translateX(20px); }
|
||||
.wifi-switch--sm input:checked + .wifi-slider::before { transform: translateX(16px); }
|
||||
.wifi-switch input:focus-visible + .wifi-slider { outline: 2px solid var(--esp32); outline-offset: 2px; }
|
||||
|
||||
/* ================= BLE 连接按钮(launch 风格) =================
|
||||
圆形物理按键:未连接=暗淡待机;勾选=已连接,蓝光环绕 + 文字波浪动画。
|
||||
checkbox 隐藏,由 JS setConnectionState 驱动 checked,避免异步连接失败导致状态错位。 */
|
||||
|
||||
@@ -103,6 +103,27 @@
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* ---- 顶栏三链路胶囊(WiFi / ECAT / BLE) ---- */
|
||||
.link-pill { gap: 6px; padding: 5px 10px 5px 8px; }
|
||||
.link-pill-tag {
|
||||
font-size: 10px; font-weight: 700; letter-spacing: .04em;
|
||||
padding: 1px 5px; border-radius: 4px;
|
||||
color: var(--esp32); background: var(--esp32-dim);
|
||||
}
|
||||
.link-pill-tag.tag-ecat { color: var(--mem-hi); background: var(--mem-dim); }
|
||||
.link-pill-tag.tag-ble { color: var(--ble); background: rgba(95,168,255,.12); }
|
||||
.link-pill #pillWifiTxt,
|
||||
.link-pill #pillEcatTxt,
|
||||
.link-pill #pillBleTxt,
|
||||
.link-pill #canNetTxt {
|
||||
font-size: 11px; overflow: hidden; text-overflow: ellipsis;
|
||||
}
|
||||
/* WiFi 要带 IP+信号;ECAT/BLE 连接后显示设备名 */
|
||||
.link-pill #pillWifiTxt,
|
||||
.link-pill #canNetTxt { max-width: 22em; }
|
||||
.link-pill #pillEcatTxt { max-width: 14em; }
|
||||
.link-pill #pillBleTxt { max-width: 18em; }
|
||||
|
||||
/* ================= 主布局 ================= */
|
||||
.main-layout {
|
||||
display: grid;
|
||||
|
||||
Reference in New Issue
Block a user