Przeglądaj źródła

使用workbuddy继续整体优化webui在移动手机端的样式等,使得看起来更加规整舒服

niujiuru 9 godzin temu
rodzic
commit
747b3cfc2e

+ 40 - 69
web-ui.service/web/pages/page1.html

@@ -8,6 +8,37 @@
 
 <body>
 <div class="page1">
+  <div class="page1-card page1-dash">
+    <div class="page1-header"><svg class="icon"><use href="#i-trend" xlink:href="#i-trend"/></svg> 运行总览</div>
+    <div class="dash-kpis">
+      <div class="dash-kpi">
+        <div class="dash-kpi-name">CPU 占用</div>
+        <div class="dash-kpi-value" id="dk-cpu">--</div>
+        <div class="dash-kpi-bar"><i class="dash-bar-fill" id="dk-cpu-bar"></i></div>
+      </div>
+      <div class="dash-kpi">
+        <div class="dash-kpi-name">内存占用</div>
+        <div class="dash-kpi-value" id="dk-mem">--</div>
+        <div class="dash-kpi-bar"><i class="dash-bar-fill" id="dk-mem-bar"></i></div>
+      </div>
+      <div class="dash-kpi">
+        <div class="dash-kpi-name">硬盘占用</div>
+        <div class="dash-kpi-value" id="dk-disk">--</div>
+        <div class="dash-kpi-bar"><i class="dash-bar-fill" id="dk-disk-bar"></i></div>
+      </div>
+      <div class="dash-kpi">
+        <div class="dash-kpi-name">服务正常</div>
+        <div class="dash-kpi-value" id="dk-svc">--</div>
+      </div>
+      <div class="dash-kpi">
+        <div class="dash-kpi-name">持续开机</div>
+        <div class="dash-kpi-value dash-kpi-txt" id="dk-uptime">--</div>
+      </div>
+    </div>
+    <div class="dash-grid" id="dash-grid"></div>
+    <div class="dash-foot" id="dash-updated">数据未刷新</div>
+  </div>
+
   <div class="page1-grid-1">
     <div class="page1-card">
       <div class="page1-header"><svg class="icon"><use href="#i-cpu" xlink:href="#i-cpu"/></svg> CPU</div>
@@ -69,9 +100,6 @@
             <div class="info-desc">local-startup.service</div>
           </div>
           <span class="info-value" id="var10">--</span>
-          <span class="info-value"></span>
-          <span></span>
-          <span></span>
         </div>
 
         <div class="info-item">
@@ -80,13 +108,7 @@
             <div class="info-desc">networkd.service</div>
           </div>
           <span class="info-value" id="var1">--</span>
-          <span class="info-value" id="var11">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-networkd.service', 'factory')">
-            恢复出厂版本
-          </button>
-          <button class="btn" onclick="switchServiceVersion('yfkj-networkd.service', 'previous')">
-            回退上一版本
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var11">--</span><button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-networkd.service', 'factory')">恢复出厂版本</button><button class="btn" onclick="switchServiceVersion('yfkj-networkd.service', 'previous')">回退上一版本</button></span>
         </div>
 
         <div class="info-item">
@@ -95,13 +117,7 @@
             <div class="info-desc">timesyncd.service</div>
           </div>
           <span class="info-value" id="var2">--</span>
-          <span class="info-value" id="var22">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-timesyncd.service', 'factory')">
-            恢复出厂版本
-          </button>
-          <button class="btn" onclick="switchServiceVersion('yfkj-timesyncd.service', 'previous')">
-            回退上一版本
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var22">--</span><button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-timesyncd.service', 'factory')">恢复出厂版本</button><button class="btn" onclick="switchServiceVersion('yfkj-timesyncd.service', 'previous')">回退上一版本</button></span>
         </div>
 
         <div class="info-item">
@@ -110,13 +126,7 @@
             <div class="info-desc">gnss.service</div>
           </div>
           <span class="info-value" id="var3">--</span>
-          <span class="info-value" id="var33">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-gnss.service', 'factory')">
-            恢复出厂版本
-          </button>
-          <button class="btn" onclick="switchServiceVersion('yfkj-gnss.service', 'previous')">
-            回退上一版本
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var33">--</span><button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-gnss.service', 'factory')">恢复出厂版本</button><button class="btn" onclick="switchServiceVersion('yfkj-gnss.service', 'previous')">回退上一版本</button></span>
         </div>
 
         <div class="info-item">
@@ -125,13 +135,7 @@
             <div class="info-desc">sshd-mqtt-bridge.service</div>
           </div>
           <span class="info-value" id="var4">--</span>
-          <span class="info-value" id="var44">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-sshd-mqtt-bridge.service', 'factory')">
-            恢复出厂版本
-          </button>
-          <button class="btn" onclick="switchServiceVersion('yfkj-sshd-mqtt-bridge.service', 'previous')">
-            回退上一版本
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var44">--</span><button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-sshd-mqtt-bridge.service', 'factory')">恢复出厂版本</button><button class="btn" onclick="switchServiceVersion('yfkj-sshd-mqtt-bridge.service', 'previous')">回退上一版本</button></span>
         </div>
 
         <div class="info-item">
@@ -140,13 +144,7 @@
             <div class="info-desc">camera-capture.service</div>
           </div>
           <span class="info-value" id="var5">--</span>
-          <span class="info-value" id="var55">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-camera-capture.service', 'factory')">
-            恢复出厂版本
-          </button>
-          <button class="btn" onclick="switchServiceVersion('yfkj-camera-capture.service', 'previous')">
-            回退上一版本
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var55">--</span><button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-camera-capture.service', 'factory')">恢复出厂版本</button><button class="btn" onclick="switchServiceVersion('yfkj-camera-capture.service', 'previous')">回退上一版本</button></span>
         </div>
 
         <div class="info-item">
@@ -155,13 +153,7 @@
             <div class="info-desc">app-install.service</div>
           </div>
           <span class="info-value" id="var6">--</span>
-          <span class="info-value" id="var66">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-app-install.service', 'factory')">
-            恢复出厂版本
-          </button>
-          <button class="btn" onclick="switchServiceVersion('yfkj-app-install.service', 'previous')">
-            回退上一版本
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var66">--</span><button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-app-install.service', 'factory')">恢复出厂版本</button><button class="btn" onclick="switchServiceVersion('yfkj-app-install.service', 'previous')">回退上一版本</button></span>
         </div>
 
         <div class="info-item">
@@ -170,13 +162,7 @@
             <div class="info-desc">web-ui.service</div>
           </div>
           <span class="info-value" id="var7">--</span>
-          <span class="info-value" id="var77">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-web-ui.service', 'factory')">
-            恢复出厂版本
-          </button>
-          <button class="btn" onclick="switchServiceVersion('yfkj-web-ui.service', 'previous')">
-            回退上一版本
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var77">--</span><button class="btn" style="margin-right:32px;" onclick="switchServiceVersion('yfkj-web-ui.service', 'factory')">恢复出厂版本</button><button class="btn" onclick="switchServiceVersion('yfkj-web-ui.service', 'previous')">回退上一版本</button></span>
         </div>
 
         <div class="info-item">
@@ -185,13 +171,7 @@
             <div class="info-desc">web-ui.service</div>
           </div>
           <span class="info-value" id="var8">--</span>
-          <span class="info-value" id="var88">--</span>
-          <button class="btn" style="margin-right:32px;" onclick="startPublicAccess(event)">
-            开启公网访问
-          </button>
-          <button class="btn" style="margin-right:32px;" onclick="stopPublicAccess(event)">
-            关闭公网访问
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var88">--</span><button class="btn" style="margin-right:32px;" onclick="startPublicAccess(event)">开启公网访问</button><button class="btn" style="margin-right:32px;" onclick="stopPublicAccess(event)">关闭公网访问</button></span>
         </div>
 
         <div class="info-item">
@@ -200,16 +180,7 @@
             <div class="info-desc">upgrade.service</div>
           </div>
           <span class="info-value" id="var9">--</span>
-          <span class="info-value" id="var99">--</span>
-          <button class="btn" style="margin-right:02px;" onclick="switchAutoUpgrade(event, 'enable --now')">
-            开启
-          </button>
-          <button class="btn" style="margin-right:32px;" onclick="switchAutoUpgrade(event, 'disable --now')">
-            关闭
-          </button>
-          <button class="btn" onclick="downloadUpgradeLogFile(event)">
-            下载升级日志
-          </button>
+          <span class="svc-ops"><span class="info-value" id="var99">--</span><button class="btn" style="margin-right:02px;" onclick="switchAutoUpgrade(event, 'enable --now')">开启</button><button class="btn" style="margin-right:32px;" onclick="switchAutoUpgrade(event, 'disable --now')">关闭</button><button class="btn" onclick="downloadUpgradeLogFile(event)">下载升级日志</button></span>
         </div>
       </div>
     </div>

+ 79 - 13
web-ui.service/web/static/css/app.css

@@ -16,6 +16,10 @@ body {
     /* 移动端点按高亮块去除, 文字渲染更细腻 (不支持的浏览器自动忽略) */
     -webkit-tap-highlight-color: transparent;
     -webkit-font-smoothing: antialiased;
+    /* 禁止移动端浏览器"字体自动放大": 安卓 Chrome 会在页面高度变化后重算字号,
+       导致标题栏文字变大、自适应高度被顶高, 出现"顶部突然留白变多"的跳动 */
+    -webkit-text-size-adjust: 100%;
+    text-size-adjust: 100%;
 }
 
 /* 文字选中色: 与主题蓝一致 */
@@ -605,18 +609,18 @@ textarea:focus {
       视口高度低于 600px 时竖排侧栏必然放不下
    横屏不再单独成一套规则 —— 横竖屏走完全相同的代码路径, 只重算宽度, 行为可预期 */
 @media (max-width: 768px), (max-height: 600px) {
-    /* 顶栏: 单行紧凑布局。
-       flex-shrink:0 防止内容超高时标题栏被 flex 压缩裁切(窄屏遮挡问题的根因);
-       标题/IMEI 单行省略, 不再换行撑高标题栏;
-       env(safe-area-inset-top) 兼容刘海屏/全面屏安全区 */
+    /* 顶栏: 单行紧凑布局, 高度写死不随任何状态浮动。
+       不用 env() 安全区叠加 —— 本项目未启用 viewport-fit=cover,
+       页面不会延伸到刘海下方, env 恒为 0 没有收益;
+       且部分安卓内核旋转屏幕后会误报 safe-area-inset, 造成
+       "横竖屏来回切换标题栏忽高忽低、留白变多", 去掉最稳妥。
+       flex-shrink:0 防止被 flex 压缩裁切; 标题/IMEI 单行省略不换行 */
     .topbar {
-        height: auto;
+        height: 50px;
         min-height: 50px;
         flex-shrink: 0;
-        /* 内容实测 39px(标题+IMEI 两行), 上下各 5px => 恰好 50px;
-           换成固定 height 会在系统大字号时裁切文字, 所以用 min-height 定下限 */
         padding: 5px 10px;
-        padding-top: max(5px, env(safe-area-inset-top));
+        overflow: hidden;
         position: relative;
         z-index: 20;
     }
@@ -644,6 +648,9 @@ textarea:focus {
 
     .title-line,
     .imei-line {
+        /* 行高定死为像素: 即使个别内核无视 text-size-adjust 强行放大字号,
+           行盒也不会被撑高, 配合 overflow:hidden 标题栏高度恒定 */
+        line-height: 18px;
         overflow: hidden;
         white-space: nowrap;
         text-overflow: ellipsis;
@@ -655,6 +662,7 @@ textarea:focus {
 
     .imei-line {
         font-size: 11px;
+        line-height: 14px;
     }
 
     .actions {
@@ -754,6 +762,66 @@ textarea:focus {
     .log-level-select {
         font-size: 16px;
     }
+
+    /* 卡片标题行允许换行, 日志操作按钮组不再与标题互相挤压 */
+    .card-header {
+        flex-wrap: wrap;
+        row-gap: 6px;
+    }
+
+    /* 日志/操作按钮组: 独占整行(从卡片头换行下来), 宽度固定 100% —
+       保证内部等分按钮始终铺满行, 各页按钮组左右边缘严格对齐 */
+    .log-actions {
+        flex-wrap: wrap;
+        row-gap: 6px;
+        justify-content: flex-start;
+        width: 100%;
+        max-width: 100%;
+    }
+
+    /* 按钮组统一对齐: 所有操作行的按钮等分铺满整行, 左右边缘与卡片内边距齐平 —
+       日志卡(page2~6)/拍照/供电/自检等所有按钮组同一套规格, 页面内严格成列;
+       块级渲染让 text-overflow 生效, 极窄时显示"..."而非硬裁切 */
+    .log-actions .btn {
+        flex: 1 1 0;
+        min-width: 0;
+        display: block;
+        height: auto;
+        padding: 6px 4px;
+        line-height: 18px;
+        font-size: 13px;
+        text-align: center;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+
+    .log-actions .log-level-select {
+        flex: 0 0 72px;
+        height: 32px;
+        min-width: 72px;
+        padding: 4px 6px;
+    }
+
+    /* 输出/日志区统一规格: 各页同高(200~280px, 超出内部滚动)、同内边距,
+       页面之间观感一致; 内容少时也保持饱满高度, 不再高矮不一。
+       .log-card .output 单独写一遍: 桌面基础规则 min-height:0 特异性更高, 需同特异性覆盖 */
+    .output {
+        min-height: 200px;
+        max-height: 280px;
+        padding: 8px 10px;
+    }
+
+    .log-card .output {
+        min-height: 200px;
+        max-height: 280px;
+        padding-bottom: 8px;
+    }
+
+    /* 日志卡高度交给输出区决定, 不再单独设保底(由上面的统一高度接管) */
+    .log-card {
+        min-height: 0;
+    }
 }
 
 /* ================= 矮屏微调(手机横屏等, 高度 ≤600px)=================
@@ -762,12 +830,10 @@ textarea:focus {
    与竖屏是同一套逻辑, 所以横竖屏切换不会出现"能显示但不能滚"的割裂状态 */
 @media (max-height: 600px) {
     .topbar {
+        /* 横屏固定 44px, 不叠加 env(): 旋转后内核误报安全区会把标题栏顶高 */
+        height: 44px;
         min-height: 44px;
-        padding-top: max(5px, env(safe-area-inset-top));
-        padding-bottom: 5px;
-        /* 横屏时刘海位于左右两侧 */
-        padding-left: max(10px, env(safe-area-inset-left));
-        padding-right: max(10px, env(safe-area-inset-right));
+        padding: 4px 10px;
     }
 
     .device .icon-box {

+ 311 - 2
web-ui.service/web/static/css/page1.css

@@ -85,8 +85,15 @@
     font-family: ui-monospace, Consolas, monospace;
 }
 
-/* ================= 移动端适配 (≤768px) ================= */
-@media (max-width: 768px) {
+/* 服务操作组: 桌面端 display:contents 不产生盒子, 内部的版本号与按钮
+   仍作为 .info-item 的直接子项参与原有 flex 排版, 桌面布局零变化;
+   移动端在媒体查询中改为真实容器, 参与两行网格布局 */
+.page1 .svc-ops {
+    display: contents;
+}
+
+/* ================= 移动端/矮屏适配 (≤768px 或 ≤600px, 一行"或"判定) ================= */
+@media (max-width: 768px), (max-height: 600px) {
     .page1 {
         height: auto;
     }
@@ -104,3 +111,305 @@
         margin-top: 12px;
     }
 }
+
+/* ================= 运行总览看板 (仅移动端显示, 桌面端保持原布局) ================= */
+.page1 .page1-dash {
+    display: none;
+    margin-bottom: 12px;
+}
+
+.page1 .dash-kpis {
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+    gap: 8px;
+    margin-bottom: 10px;
+}
+
+.page1 .dash-kpi {
+    background: #111c33;
+    border: 1px solid #1f2937;
+    border-radius: 6px;
+    padding: 8px 10px;
+    overflow: hidden;
+}
+
+.page1 .dash-kpi-name {
+    color: #64748b;
+    font-size: 12px;
+    margin-bottom: 2px;
+}
+
+.page1 .dash-kpi-value {
+    color: #e2e8f0;
+    font-size: 18px;
+    font-weight: 700;
+    font-family: ui-monospace, Consolas, monospace;
+}
+
+.page1 .dash-kpi-value .dot {
+    margin-right: 8px;
+    vertical-align: 3px;
+}
+
+.page1 .dash-kpi-txt {
+    font-size: 13px;
+    line-height: 24px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.page1 .dash-kpi-value.warm {
+    color: #eab308;
+}
+
+.page1 .dash-kpi-value.hot {
+    color: #ef4444;
+}
+
+.page1 .dash-kpi-bar {
+    height: 4px;
+    background: #1f2937;
+    border-radius: 2px;
+    margin-top: 8px;
+    overflow: hidden;
+}
+
+.page1 .dash-bar-fill {
+    display: block;
+    height: 100%;
+    width: 0;
+    background: #22c55e;
+    border-radius: 2px;
+    transition: width 0.4s ease;
+}
+
+.page1 .dash-bar-fill.warm {
+    background: #eab308;
+}
+
+.page1 .dash-bar-fill.hot {
+    background: #ef4444;
+}
+
+/* 模块卡片网格: 左侧边框随状态着色 */
+.page1 .dash-grid {
+    display: grid;
+    grid-template-columns: repeat(2, minmax(0, 1fr));
+    gap: 8px;
+}
+
+.page1 .dash-mod {
+    background: #111c33;
+    border: 1px solid #1f2937;
+    border-left: 3px solid #64748b;
+    border-radius: 6px;
+    padding: 8px 10px;
+    min-width: 0;
+}
+
+.page1 .dash-mod.dash-ok {
+    border-left-color: #22c55e;
+}
+
+.page1 .dash-mod.dash-info {
+    border-left-color: #3b82f6;
+}
+
+.page1 .dash-mod.dash-warn {
+    border-left-color: #eab308;
+}
+
+.page1 .dash-mod.dash-err {
+    border-left-color: #ef4444;
+}
+
+.page1 .dash-mod-head {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 6px;
+    margin-bottom: 4px;
+}
+
+.page1 .dash-name {
+    color: #e2e8f0;
+    font-size: 12px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.page1 .dash-unit {
+    color: #64748b;
+    font-size: 10px;
+    font-family: ui-monospace, Consolas, monospace;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    margin-bottom: 4px;
+}
+
+.page1 .dash-info {
+    font-family: ui-monospace, Consolas, monospace;
+    color: #94a3b8;
+    font-size: 11px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
+.page1 .dash-dot {
+    display: inline-block;
+    width: 6px;
+    height: 6px;
+    border-radius: 50%;
+    margin-right: 4px;
+    background: #64748b;
+    vertical-align: middle;
+    flex-shrink: 0;
+}
+
+.page1 .dash-dot.dash-ok {
+    background: #22c55e;
+    box-shadow: 0 0 6px rgba(34, 197, 94, 0.6);
+}
+
+.page1 .dash-dot.dash-info {
+    background: #3b82f6;
+    box-shadow: 0 0 6px rgba(59, 130, 246, 0.6);
+}
+
+.page1 .dash-dot.dash-warn {
+    background: #eab308;
+    box-shadow: 0 0 6px rgba(234, 179, 8, 0.6);
+}
+
+.page1 .dash-dot.dash-err {
+    background: #ef4444;
+    box-shadow: 0 0 6px rgba(239, 68, 68, 0.6);
+    animation: dot-pulse 1.2s ease-in-out infinite;
+}
+
+/* 状态徽章 */
+.page1 .dash-badge {
+    display: inline-flex;
+    align-items: center;
+    padding: 2px 6px;
+    border-radius: 999px;
+    font-size: 10px;
+    color: #cbd5e1;
+    background: rgba(148, 163, 184, 0.1);
+    white-space: nowrap;
+    flex-shrink: 0;
+}
+
+.page1 .dash-badge.dash-ok {
+    color: #4ade80;
+    background: rgba(34, 197, 94, 0.12);
+}
+
+.page1 .dash-badge.dash-info {
+    color: #60a5fa;
+    background: rgba(59, 130, 246, 0.12);
+}
+
+.page1 .dash-badge.dash-warn {
+    color: #facc15;
+    background: rgba(234, 179, 8, 0.14);
+}
+
+.page1 .dash-badge.dash-err {
+    color: #f87171;
+    background: rgba(239, 68, 68, 0.14);
+}
+
+.page1 .dash-foot {
+    margin-top: 8px;
+    text-align: right;
+    color: #64748b;
+    font-size: 11px;
+}
+
+/* ================= 移动端启用看板并去掉重复内容 ================= */
+@media (max-width: 768px), (max-height: 600px) {
+    /* 启用运行总览看板 */
+    .page1 .page1-dash {
+        display: block;
+    }
+
+    /* 概览信息已由看板提供, echarts 图表卡在手机上占屏, 隐藏 */
+    .page1 .page1-grid-1 {
+        display: none;
+    }
+
+    /* 服务卡: 两行网格, 列边缘严格对齐 —
+       第一行: 名称+描述(左) | 状态(右)
+       第二行: 版本号 | 操作按钮弹性等分填满整行
+       竖屏横屏同一套规则, 所有条目的状态/版本/按钮起始位置完全一致 */
+    .page1 .info-item {
+        height: auto;
+        min-width: 0;
+        display: grid;
+        grid-template-columns: minmax(0, 1fr) auto;
+        align-items: center;
+        column-gap: 8px;
+        row-gap: 6px;
+        padding: 8px 0;
+    }
+
+    .page1 .info-info {
+        grid-column: 1;
+        grid-row: 1;
+        width: auto;
+    }
+
+    .page1 .info-item > .info-value {
+        width: auto;
+        margin-left: 0;
+    }
+
+    /* 状态值: 第一行右侧 */
+    .page1 .info-item > .info-value:first-of-type {
+        grid-column: 2;
+        grid-row: 1;
+        text-align: right;
+    }
+
+    /* 操作组: 第二行整行 */
+    .page1 .svc-ops {
+        grid-column: 1 / -1;
+        grid-row: 2;
+        display: flex;
+        align-items: center;
+        gap: 8px;
+        min-width: 0;
+    }
+
+    /* 组内版本号: 定宽对齐 */
+    .page1 .svc-ops .info-value {
+        flex: 0 0 78px;
+        width: 78px;
+        text-align: left;
+        font-size: 12px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+    }
+
+    /* 组内按钮: 等分剩余宽度; 字号与全站按钮组统一为 13px */
+    .page1 .svc-ops .btn {
+        flex: 1 1 0;
+        min-width: 0;
+        display: block;
+        height: auto;
+        padding: 6px 4px;
+        line-height: 18px;
+        font-size: 13px;
+        text-align: center;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        margin-right: 0 !important;
+    }
+}

+ 19 - 0
web-ui.service/web/static/css/page2.css

@@ -100,4 +100,23 @@
     .page-network .left-pane {
         flex-direction: column;
     }
+
+    /* 纵向堆叠时解除裁切, 由 #view 统一滚动; 各输出卡给定保底高度, 内部各自滚动 */
+    .page-network .panes {
+        flex: none;
+        overflow: visible;
+    }
+
+    .page-network .left-pane {
+        overflow: visible;
+    }
+
+    .page-network .left-pane .card:nth-child(1) {
+        min-height: 240px;
+    }
+
+    .page-network .left-pane .card:nth-child(2),
+    .page-network .left-pane .card:nth-child(3) {
+        min-height: 180px;
+    }
 }

+ 6 - 0
web-ui.service/web/static/css/page3.css

@@ -63,4 +63,10 @@
         flex-wrap: wrap;
         row-gap: 6px;
     }
+
+    /* 日志区不再强制占满剩余高度, 随内容与保底高度自然撑开, 由 #view 滚动 */
+    .page-time .full-pane {
+        flex: none;
+        overflow: visible;
+    }
 }

+ 6 - 0
web-ui.service/web/static/css/page4.css

@@ -63,4 +63,10 @@
         flex-wrap: wrap;
         row-gap: 6px;
     }
+
+    /* 日志区不再强制占满剩余高度, 随内容与保底高度自然撑开, 由 #view 滚动 */
+    .page-gnss .full-pane {
+        flex: none;
+        overflow: visible;
+    }
 }

+ 165 - 0
web-ui.service/web/static/css/page5.css

@@ -139,6 +139,88 @@
     min-width: 0;
 }
 
+/* ================= 移动端状态条统一 (竖屏+横屏共用) =================
+   状态项从"标签在上值在下"改为"标签居左、值居右"的行式布局,
+   上下成列像信息表一样整齐; 连接状态渲染为药丸徽章, 一眼可辨 */
+@media (max-width: 768px), (max-height: 600px) {
+    .page-camera .page5-top {
+        height: auto;
+        flex-wrap: wrap;
+    }
+
+    /* 两条状态栏都在 .page5-top 内: 各自独占整行、不可收缩(否则 flex 会
+       在换行前先把条目压扁, IP 值被挤成竖排), 条目内部再换行 */
+    .page-camera .page5-top .status-bar {
+        height: auto;
+        width: 100%;
+        flex: none;
+        flex-wrap: wrap;
+        row-gap: 6px;
+    }
+
+    .page-camera .status-bar .status-item {
+        flex-direction: row;
+        align-items: center;
+        justify-content: space-between;
+        gap: 8px;
+        flex-shrink: 0;
+    }
+
+    .page-camera .status-item .label {
+        color: #94a3b8;
+        flex-shrink: 0;
+    }
+
+    .page-camera .status-item .value {
+        text-align: right;
+        min-width: 0;
+        word-break: break-all;
+    }
+
+    /* 连接状态(第二条状态栏第 3 项): 药丸徽章突出显示, 色点随状态变色 */
+    .page-camera .page5-top .status-bar:last-of-type > .status-item:nth-child(3) .value {
+        display: inline-flex;
+        align-items: center;
+        padding: 2px 10px;
+        border-radius: 999px;
+        background: rgba(148, 163, 184, 0.12);
+        font-weight: 600;
+    }
+
+    /* 第二条状态栏行内分配: GPIO22 弹性填充, 连接状态徽章紧贴右缘 —
+       标签长的 GPIO22 不再被挤压, 徽章位置恒定在最右 */
+    .page-camera .page5-top .status-bar:last-of-type > .status-item:nth-child(2) {
+        flex: 1 1 auto;
+        width: auto;
+        min-width: 0;
+    }
+
+    .page-camera .page5-top .status-bar:last-of-type > .status-item:nth-child(3) {
+        flex: 0 0 auto;
+        width: auto;
+        margin-left: auto;
+    }
+
+    /* 操作按钮行: 独占整行, 按钮等分, 与全站按钮组同一规格 */
+    .page-camera .status-item.button-item {
+        width: 100%;
+        margin-left: 0;
+    }
+
+    .page-camera .button-item .btn {
+        flex: 1 1 0;
+        min-width: 0;
+        display: block;
+        height: auto;
+        padding: 6px 4px;
+        line-height: 18px;
+        text-align: center;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+    }
+}
+
 /* ================= 移动端适配 (≤768px) ================= */
 @media (max-width: 768px) {
     .page-camera .status-bar {
@@ -162,4 +244,87 @@
     .page-camera .left-pane {
         flex-direction: column;
     }
+
+    /* 纵向堆叠时解除裁切, 由 #view 统一滚动 */
+    .page-camera .panes {
+        flex: none;
+        overflow: visible;
+    }
+
+    .page-camera .left-pane,
+    .page-camera .left-pane .card {
+        overflow: visible;
+    }
+
+    /* 顶部状态条高度随内容换行撑开 */
+    .page-camera .page5-top {
+        height: auto;
+    }
+
+    /* 图像预览区自适应: 不再依赖剩余高度, 限高防止占满整屏 */
+    .page-camera .camera-image-box {
+        flex: none;
+        height: auto;
+    }
+
+    .page-camera .camera-image-box img {
+        height: auto;
+        max-height: 280px;
+    }
+
+    /* 相机信息表: 4 列在窄屏收紧字号与留白 */
+    .page-camera .camera-info-table {
+        height: auto;
+    }
+
+    .page-camera .camera-info-table td {
+        font-size: 12px;
+        padding: 4px 6px;
+        white-space: normal;
+        word-break: break-all;
+    }
+
+    .page-camera .camera-info-table td:nth-child(1),
+    .page-camera .camera-info-table td:nth-child(3) {
+        width: 88px;
+    }
+
+    /* 拍照操作按钮: 标题与按钮分两行排列, 4 个按钮完整可见不裁切 */
+    .page-camera .camera-card .card-header {
+        flex-direction: column;
+        align-items: flex-start;
+        gap: 8px;
+    }
+
+    .page-camera .camera-card .log-actions {
+        justify-content: flex-start;
+        width: 100%;
+    }
+
+    /* 第一条状态栏(相机服务/相机总数/海康/大华): 两列排布 */
+    .page-camera .page5-top .status-bar .status-item:not(.button-item) {
+        width: calc(50% - 6px);
+    }
+
+    /* 第二条状态栏: 网口相机值长占整行, GPIO22/连接状态 由共用规则分配(填充+贴右) */
+    .page-camera .page5-top .status-bar:last-of-type > .status-item {
+        width: 100%;
+    }
+}
+
+/* ================= 横屏/矮屏适配 (高度 ≤600px) =================
+   状态条与竖屏同一套行式布局; 宽度充裕, 每行放 3~4 个状态项 */
+@media (max-height: 600px) {
+    .page-camera .page5-top .status-bar .status-item:not(.button-item) {
+        width: calc(25% - 9px);
+    }
+
+    .page-camera .page5-top .status-bar:last-of-type > .status-item:not(.button-item) {
+        width: calc(25% - 9px);
+    }
+
+    /* 网口相机值长(IP/MASK), 单独放宽; GPIO22/连接状态 由共用规则分配 */
+    .page-camera .page5-top .status-bar:last-of-type > .status-item:nth-child(1) {
+        width: calc(40% - 9px);
+    }
 }

+ 12 - 0
web-ui.service/web/static/css/page6.css

@@ -144,4 +144,16 @@
     .page-userapp .right-pane {
         width: 100%;
     }
+
+    /* 纵向堆叠时解除裁切, 由 #view 统一滚动; 信息卡随内容自然撑开 */
+    .page-userapp .panes {
+        flex: none;
+        overflow: visible;
+    }
+
+    .page-userapp .left-pane,
+    .page-userapp .left-pane .card,
+    .page-userapp .card-body {
+        overflow: visible;
+    }
 }

+ 23 - 2
web-ui.service/web/static/css/page7.css

@@ -81,11 +81,32 @@
 
 /* ================= 移动端适配 (≤768px) ================= */
 @media (max-width: 768px) {
+    /* 4 列改 2x2: 第一行 项目|状态, 第二行 结果|说明, 表头同样对齐, 无需横向滚动 */
     .page-health-check .check-table {
-        overflow-x: auto;
+        overflow: visible;
+    }
+
+    /* 开始/停止自检: 等分铺满整行, 与全站按钮组同一规格 */
+    .page-health-check .check-actions .btn {
+        flex: 1 1 0;
+        min-width: 0;
+        display: block;
+        height: auto;
+        padding: 6px 4px;
+        line-height: 18px;
+        text-align: center;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
     }
 
     .page-health-check .check-row {
-        min-width: 560px;
+        min-width: 0;
+        grid-template-columns: 1.1fr 1fr;
+    }
+
+    .page-health-check .check-row > div {
+        padding: 6px 8px;
+        font-size: 12px;
     }
 }

+ 129 - 0
web-ui.service/web/static/js/page1.js

@@ -14,6 +14,9 @@ function getSystemInfo() {
             return r.json();
         })
         .then(function (d) {
+            dashSetUsage("dk-cpu", d.cpu_usage != null ? Number(d.cpu_usage) : null);
+            dashSetUsage("dk-mem", d.mem_percent != null ? Number(d.mem_percent) : null);
+            dashSetUsage("dk-disk", d.disk_percent != null ? Number(d.disk_percent) : null);
             if (!cpuChart || !memChart || !diskChart) {
                 return;
             }
@@ -140,6 +143,7 @@ function getLoadInfo() {
                     return setText(id, data[id], "page1");
                 }
             );
+            dashSetUptime(data ? data.uptime : null);
         })
         .catch(function (e) {
             console.error("load info failed:", e);
@@ -148,6 +152,7 @@ function getLoadInfo() {
                     return setText(id, null, "page1");
                 }
             );
+            dashSetUptime(null);
         });
 }
 
@@ -183,6 +188,7 @@ function getServiceInfo() {
             ].forEach(function (id) {
                 return setText(id, data[id], "page1");
             });
+            dashRenderStatus(data);
         })
         .catch(function (e) {
             console.error("getAllServiceStatus() failed:", e);
@@ -209,6 +215,7 @@ function getServiceInfo() {
             ].forEach(function (id) {
                 return setText(id, null, "page1");
             });
+            dashRenderStatus(null);
         });
 }
 
@@ -467,6 +474,7 @@ function initChart(id, option) {
 }
 
 function initPage1() {
+    dashBuildRows();
     cpuChart = initChart("cpu-chart", gaugeOption());
     memChart = initChart("mem-chart", gaugeOption());
     diskChart = initChart("disk-chart", {
@@ -540,3 +548,124 @@ function exitPage1() {
     memChart = null;
     diskChart = null;
 }
+
+// ===================== 运行总览看板 (仅移动端显示) =====================
+
+// 看板模块: 展示名 / 服务单元 / 状态字段 / 版本字段 (与 /api/system/serviceStatus 一一对应)
+var dashModules = [
+    { name: "设备自动联网", unit: "networkd.service", status: "var1", version: "var11" },
+    { name: "同步系统时间", unit: "timesyncd.service", status: "var2", version: "var22" },
+    { name: "地理坐标定位", unit: "gnss.service", status: "var3", version: "var33" },
+    { name: "远程运维通道", unit: "sshd-mqtt-bridge.service", status: "var4", version: "var44" },
+    { name: "相机图像采集", unit: "camera-capture.service", status: "var5", version: "var55" },
+    { name: "应用安装卸载", unit: "app-install.service", status: "var6", version: "var66" },
+    { name: "本地配置管理", unit: "web-ui.service", status: "var7", version: "var77" },
+    { name: "公网配置管理", unit: "frp-client.service", status: "var8", version: "var88" },
+    { name: "设备自动升级", unit: "upgrade.service", status: "var9", version: "var99" },
+    { name: "开机任务执行", unit: "local-startup.service", status: "var10", version: "var1010" }
+];
+
+// 首次进入页面时生成模块卡片骨架, 之后仅原地更新, 避免每次刷新整块重建造成闪烁
+function dashBuildRows() {
+    var grid = document.getElementById("dash-grid");
+    if (!grid || grid.children.length) {
+        return;
+    }
+    var html = "";
+    dashModules.forEach(function (m, i) {
+        html +=
+            '<div class="dash-mod" id="dash-mod-' + i + '">' +
+            '<div class="dash-mod-head"><span class="dash-name">' + m.name + '</span>' +
+            '<span class="dash-badge" id="dash-badge-' + i + '"><span class="dash-dot" id="dash-dot-' + i + '"></span><span id="dash-text-' + i + '">--</span></span></div>' +
+            '<div class="dash-unit">' + m.unit + "</div>" +
+            '<div class="dash-info" id="dash-info-' + i + '">--</div>' +
+            "</div>";
+    });
+    grid.innerHTML = html;
+}
+
+// 用 /api/system/serviceStatus 的数据刷新模块状态与"服务正常"计数
+// data 为 null 时(接口失败)整块置灰为 "--"
+function dashRenderStatus(data) {
+    var okCount = 0;
+    dashModules.forEach(function (m, i) {
+        var v = data ? data[m.status] : null;
+        var level = "na";
+        var text = "--";
+        if (v && typeof v === "object") {
+            level = v.level || "na";
+            text = v.text || "--";
+        }
+        // ok=运行中, info=一次性任务已完成, 两者都视为正常
+        if (level === "ok" || level === "info") {
+            okCount++;
+        }
+        var mod = document.getElementById("dash-mod-" + i);
+        var dot = document.getElementById("dash-dot-" + i);
+        var badge = document.getElementById("dash-badge-" + i);
+        var txt = document.getElementById("dash-text-" + i);
+        var info = document.getElementById("dash-info-" + i);
+        if (mod) {
+            mod.className = "dash-mod dash-" + level;
+        }
+        if (badge) {
+            badge.className = "dash-badge dash-" + level;
+        }
+        if (dot) {
+            dot.className = "dash-dot dash-" + level;
+        }
+        if (txt) {
+            txt.textContent = text;
+        }
+        if (info) {
+            var ver = data ? data[m.version] : null;
+            info.textContent = ver && typeof ver === "object" ? ver.text || "--" : ver || "--";
+        }
+    });
+    setText(
+        "dk-svc",
+        {
+            level: okCount === dashModules.length ? "ok" : "warn",
+            text: okCount + " / " + dashModules.length
+        },
+        "page1"
+    );
+    var foot = document.getElementById("dash-updated");
+    if (foot) {
+        foot.textContent = data
+            ? "数据更新于 " + new Date().toTimeString().slice(0, 8)
+            : "数据获取失败, 稍后自动重试";
+    }
+}
+
+// 更新占用率指标: 数值 + 迷你进度条; >=85% 红色(hot), >=60% 黄色(warm), 其余绿色
+function dashSetUsage(id, pct) {
+    var el = document.getElementById(id);
+    if (!el) {
+        return;
+    }
+    var bar = document.getElementById(id + "-bar");
+    if (pct == null || isNaN(pct)) {
+        el.textContent = "--";
+        el.className = "dash-kpi-value";
+        if (bar) {
+            bar.style.width = "0";
+        }
+        return;
+    }
+    var tone = pct >= 85 ? " hot" : pct >= 60 ? " warm" : "";
+    el.textContent = pct.toFixed(1) + "%";
+    el.className = "dash-kpi-value" + tone;
+    if (bar) {
+        bar.style.width = Math.min(100, pct) + "%";
+        bar.className = "dash-bar-fill" + tone;
+    }
+}
+
+// 更新"持续开机"KPI, 仅显示文本
+function dashSetUptime(text) {
+    var el = document.getElementById("dk-uptime");
+    if (el) {
+        el.textContent = text || "--";
+    }
+}