- 实现局域网设备发现面板,支持探测ESP32中继板并自动切换端点 - 添加POST /api/lan/probe接口用于设备探测,返回三态判决结果 - 实现快扫和全段扫描两种模式,支持种子地址优先探测 - 添加端点唯一写手机制,防止多处同时修改设备IP - 优化链路胶囊UI显示WiFi/EtherCAT/BLE连接状态和设备名称 - 修复EtherCAT链路OTA刷写期间PDO循环暂停和AL状态处理问题 - 调整PDO周期设置范围为0.125-4ms,支持亚毫秒显示为微秒 - 添加ESP32 WiFi使能开关控件样式和数据来源角标显示
242 lines
9.5 KiB
CSS
242 lines
9.5 KiB
CSS
/* ============================================================
|
||
base.css — DCU Mission Control · 设计令牌与全局基底
|
||
风格:深色测控台(deep blue-slate),多色子系统语义配色
|
||
子系统色:ESP32=青绿 · GD32=琥珀 · BLE=蓝 · CAN=绿 · 内存/调试=紫
|
||
============================================================ */
|
||
|
||
:root {
|
||
/* ---- 底色层次:深蓝灰,层层抬升,拒绝纯黑 ---- */
|
||
--bg-root: #0a0f1a;
|
||
--bg-panel: #0e1624;
|
||
--bg-card: #131d2e;
|
||
--bg-elevated: #1a2740;
|
||
--bg-input: #0b1220;
|
||
--bg-hover: #20304c;
|
||
|
||
/* ---- 边框 ---- */
|
||
--border: #21304a;
|
||
--border-strong: #2e4262;
|
||
--border-subtle: #1a2740;
|
||
|
||
/* ---- 文字 ---- */
|
||
--text: #e9f0fa;
|
||
--text-dim: #93a8c4;
|
||
--text-label: #617a99;
|
||
|
||
/* ---- 子系统语义色 ---- */
|
||
--esp32: #2fd8c1; /* 青绿:ESP32 / 主操作 / WiFi OTA */
|
||
--esp32-hi: #6ff2e0;
|
||
--esp32-dim: rgba(47,216,193,.14);
|
||
--gd32: #fcbf3f; /* 琥珀:GD32 / 中继 OTA */
|
||
--gd32-hi: #ffd97a;
|
||
--gd32-dim: rgba(252,191,63,.14);
|
||
--ble: #5fa8ff; /* 蓝:BLE 蓝牙通道 */
|
||
--ble-hi: #93c6ff;
|
||
--ble-dim: rgba(95,168,255,.14);
|
||
--can: #43d97b; /* 绿:CAN 总线 / 健康 */
|
||
--can-hi: #7deda6;
|
||
--can-dim: rgba(67,217,123,.14);
|
||
--motor: #22d3ee; /* 青:电机控制台 */
|
||
--motor-hi: #67e8f9;
|
||
--motor-dim: rgba(34,211,238,.14);
|
||
--mem: #a88bfa; /* 紫:内存 / 调试 / 日志 */
|
||
--mem-hi: #c7b3ff;
|
||
--mem-dim: rgba(168,139,250,.14);
|
||
|
||
/* ---- 状态语义色 ---- */
|
||
--green: #3ddc8e;
|
||
--amber: #fcbf3f;
|
||
--red: #ff6b6b;
|
||
--red-dim: rgba(255,107,107,.14);
|
||
|
||
/* ---- 兼容旧令牌(大量旧样式引用,勿删)---- */
|
||
--accent: var(--esp32);
|
||
--accent-dim: var(--esp32-dim);
|
||
--border-focus: var(--esp32);
|
||
|
||
/* ---- 圆角 ---- */
|
||
--radius-sm: 8px;
|
||
--radius: 12px;
|
||
--radius-lg: 16px;
|
||
|
||
/* ---- 字体 ----
|
||
沿用原始系统字体方案:中文优先苹方/雅黑,西文 Segoe UI;
|
||
mono 用于 SHA / HEX / 日志 / 数值(SF Mono → Fira Code → Cascadia → Consolas)。
|
||
--font-display 保留变量名以兼容既有引用,指向同一系统栈。 */
|
||
--font-display: "PingFang SC","Microsoft YaHei","Segoe UI",system-ui,sans-serif;
|
||
--font: "PingFang SC","Microsoft YaHei","Segoe UI",system-ui,sans-serif;
|
||
--font-mono: "SF Mono","Fira Code","Cascadia Code","Consolas",monospace;
|
||
|
||
/* ---- 间距 / 阴影 ---- */
|
||
--gap: 18px;
|
||
--shadow-card: 0 10px 30px rgba(3,7,16,.45), 0 1px 0 rgba(255,255,255,.03) inset;
|
||
--shadow-pop: 0 18px 48px rgba(3,7,16,.6);
|
||
}
|
||
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
|
||
html, body {
|
||
height: 100%;
|
||
background: var(--bg-root);
|
||
color: var(--text);
|
||
font-family: var(--font);
|
||
font-size: 14px;
|
||
line-height: 1.55;
|
||
-webkit-font-smoothing: antialiased;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
/* ---- 环境光背景:细网格 + 双色氛围辉光 + 顶部扫光 ----
|
||
网格提供"工程图纸"质感;左上青绿、右下琥珀的两团极淡辉光
|
||
暗示 ESP32 / GD32 双核子系统,随视口固定,营造纵深。 */
|
||
body::before {
|
||
content: "";
|
||
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
||
background-image:
|
||
linear-gradient(rgba(148,180,226,.035) 1px, transparent 1px),
|
||
linear-gradient(90deg, rgba(148,180,226,.035) 1px, transparent 1px);
|
||
background-size: 44px 44px;
|
||
mask-image: radial-gradient(ellipse 120% 90% at 50% -8%, black 30%, transparent 78%);
|
||
}
|
||
body::after {
|
||
content: "";
|
||
position: fixed; inset: 0; z-index: 0; pointer-events: none;
|
||
background:
|
||
radial-gradient(900px 520px at 8% -10%, rgba(47,216,193,.10), transparent 60%),
|
||
radial-gradient(820px 560px at 100% 112%, rgba(252,191,63,.07), transparent 62%),
|
||
radial-gradient(700px 480px at 88% -6%, rgba(95,168,255,.06), transparent 60%);
|
||
}
|
||
|
||
.container {
|
||
position: relative; z-index: 1;
|
||
max-width: 1480px; margin: 0 auto; padding: 26px 32px 44px;
|
||
}
|
||
|
||
/* ---- 全局滚动条 ---- */
|
||
::-webkit-scrollbar { width: 9px; height: 9px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--border-strong); border-radius: 99px;
|
||
border: 2px solid var(--bg-root);
|
||
}
|
||
::-webkit-scrollbar-thumb:hover { background: #3a5378; }
|
||
|
||
/* ---- 选区 ---- */
|
||
::selection { background: rgba(47,216,193,.28); color: #eafffb; }
|
||
|
||
/* ---- 通用等宽数字(让指标/百分比对齐、不跳动)---- */
|
||
.tabular { font-variant-numeric: tabular-nums; }
|
||
|
||
/* ---- focus 可见性(键盘可达性)---- */
|
||
:focus-visible {
|
||
outline: 2px solid var(--esp32);
|
||
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); }
|