فهرست منبع

完成相机操作page5页面的前端设计

niujiuru 3 هفته پیش
والد
کامیت
0a24a41d04

+ 13 - 4
web-ui.service/page2_handler.go

@@ -52,14 +52,23 @@ func restartNetworkService(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	st, err := serviceStatus("yfkj-networkd.service")
+	if err != nil || !st.Loaded {
+		http.Error(w, "Failed to restart yfkj-networkd.service: Unit yfkj-networkd.service not found.\nexit status 5", http.StatusBadRequest)
+		return
+	}
+
 	w.Header().Set("Content-Type", "application/json")
-	w.Write([]byte(`{"status":"restarting..."}`))
+	w.Write([]byte(`{"status":"restarting"}`))
+
+	if f, ok := w.(http.Flusher); ok {
+		f.Flush()
+	}
 
 	go func() {
+		time.Sleep(1 * time.Second)
 		cmd := exec.Command("systemctl", "restart", "yfkj-networkd.service")
-
-		out, err := cmd.CombinedOutput()
-		if err != nil {
+		if out, err := cmd.CombinedOutput(); err != nil {
 			baseapp.Logger.Errorf("[重启yfkj-networkd失败!!]: %v, output=%s", err, string(out))
 		}
 	}()

+ 2 - 2
web-ui.service/web/app.html

@@ -42,10 +42,10 @@
 
       <div class="sidebar-actions">
         <div class="action-item" onclick="reboot()">
-          🔁 重启设备
+          🛑 重启设备
         </div>
         <div class="action-item" onclick="logout()">
-          ↪️ 退出登录
+           退出登录
         </div>
       </div>
     </aside>

+ 115 - 6
web-ui.service/web/pages/page5.html

@@ -11,12 +11,121 @@
 
 <body>
 <div class="app">
-  <div class="card">
-    <div class="icon">🚧</div>
-      <h1>页面5</h1>
-      <h1>敬请期待</h1>
-    <p class="subtitle">该功能正在开发中,敬请期待...</p>
-  </div>
+  <main class="view page-camera">
+    <div class="page5-top">
+      <div class="status-bar">
+        <div class="status-item">
+          <span class="label">相机服务</span>
+          <span class="value" id="avar1">--</span>
+        </div>
+        <div class="status-item">
+          <span class="label">相机总数</span>
+          <span class="value" id="avar2">--</span>
+        </div>
+        <div class="status-item">
+          <span class="label">海康数量</span>
+          <span class="value" id="avar3">--</span>
+        </div>
+        <div class="status-item">
+          <span class="label">大华数量</span>
+          <span class="value" id="avar4">--</span>
+        </div>
+      </div>
+
+      <div class="status-bar">
+        <div class="status-item">
+          <span class="label">网口相机</span>
+          <span class="value" id="avar5">--</span>
+        </div>
+        <div class="status-item">
+          <span class="label">相机地址</span>
+          <span class="value" id="avar6">--</span>
+        </div>
+        <div class="status-item">
+          <span class="label">子网掩码</span>
+          <span class="value" id="avar7">--</span>
+        </div>
+        <div class="status-item button-item"">
+          <span style="margin:0 1px;"></span>
+          <button class="btn" title="打开网口相机供电" onclick="cameraPower('true')">上电</button>
+          <span style="margin:0 1px;"></span>
+          <button class="btn" title="关闭网口相机供电" onclick="cameraPower('false')">断电</button>
+          <span style="margin:0 1px;"></span>
+          <button class="btn" title="检测网口相机是否连通(ping测试)" onclick="cameraPing()">检测</button>
+        </div>
+      </div>
+    </div>
+
+     <div class="panes">
+      <!-- 左栏 -->
+      <div class="left-pane">
+        <div class="card camera-card">
+          <div class="card-header">
+            <h2>拍照测试</h2>
+            <div class="camera-actions">
+              <button class="btn" onclick="takeOnePhoto()">单次拍照</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" onclick="startCameraPreview()">连续拍照</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" onclick="stopCameraPreview()">停止连拍</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" onclick="downloadCameraImage()">下载图像</button>
+            </div>
+          </div>
+          <div class="camera-image-box">
+            <img id="camera-image">
+          </div>
+          <table class="camera-info-table">
+            <tr>
+              <td>采集状态</td>
+              <td id="cvar1">--</td>
+              <td>采集总帧数</td>
+              <td id="cvar2">--</td>
+            </tr>
+            <tr>
+              <td>相机型号</td>
+              <td id="cvar3">--</td>
+              <td>相机序列号</td>
+              <td id="cvar4">--</td>
+            </tr>
+            <tr>
+              <td>曝光时长</td>
+              <td id="cvar5">--</td>
+              <td>图像分辨率</td>
+              <td id="cvar6">--</td>
+            </tr>
+          </table>
+        </div>
+      </div>
+      <!-- 右栏 -->
+      <div class="right-pane">
+        <div class="card log-card">
+          <div class="card-header">
+            <h2>相机服务</h2>
+            <div class="log-actions">
+              <select class="log-level-select" id="logLevel" title="当前服务日志级别" onchange="setCameraLogLevel()">
+                <option value="trace">trace</option>
+                <option value="debug">debug</option>
+                <option value="info" selected>info</option>
+                <option value="error">error</option>
+                <option value="fatal">fatal</option>
+              </select>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" title="复制当前屏显日志" onclick="copyLog('text1')">复制</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" title="清除当前屏显日志" onclick="clearLog('text1')">清除</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" title="下载服务日志文件" onclick="downloadCameraLogFile()">下载</button>
+              <span style="margin:0 1px;"></span>
+              <button class="btn" title="点击重启相机服务" onclick="restartCameraService()">重启</button>
+            </div>
+          </div>
+          <pre class="output" id="text1">自动实时获取数据...</pre>
+        </div>
+      </div>
+      <!-- 结束 -->
+     </div>
+  </main>
 </div>
 </body>
 

+ 1 - 1
web-ui.service/web/static/css/app.css

@@ -76,7 +76,7 @@ body{
     display:flex;
     align-items:center;
     gap:10px;
-    margin-right:10px;
+    margin-right:16px;
 }
 
 .clock {

+ 140 - 1
web-ui.service/web/static/css/page5.css

@@ -1 +1,140 @@
-/* reserved */
+.page-camera {
+  display:flex;
+  flex-direction:column;
+  gap:12px;
+  overflow:hidden;
+  width:100%;
+  height:100%;
+  min-height:0;
+}
+
+.page5-top {
+  display:flex;
+  gap:12px;
+  width:100%;
+  height:80px;
+  flex:none;
+}
+
+.page5-top .status-bar {
+  flex:1;
+  min-width:0;
+  height: 100%;
+}
+
+.status-bar {
+    display:flex;
+    align-items:center;
+    padding:8px 12px;
+    background:linear-gradient(180deg, #0f172a, #0b1220);
+    gap:16px;
+    border:1px solid rgba(148,163,184,0.15);
+    border-radius:4px;
+    flex-shrink:0;
+}
+
+.status-item {
+    display:flex;
+    flex-direction:column;
+    gap:4px;
+}
+
+.status-item .label {
+    font-size:14px;
+}
+
+.status-item .value {
+    font-size:14px;
+    font-family:ui-monospace, Consolas, monospace;
+}
+
+.button-item {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    margin-left:auto;
+    flex-direction:row;
+}
+
+/* 左右拆分的栏 */
+.panes {
+    display: flex;
+    gap: 12px;
+    flex: 1;
+    min-height: 0;
+    overflow: hidden;
+}
+
+/* 相机拍照预览 */
+.left-pane {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+    overflow: hidden;
+    min-width: 0;
+}
+
+.left-pane .card {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    min-height: 0;
+    overflow: hidden;
+}
+
+.camera-card {
+    flex:1;
+    display:flex;
+    flex-direction:column;
+    min-height:0;
+}
+
+.camera-image-box {
+    flex:1;
+    display:flex;
+    align-items:flex-start;
+    justify-content:center;
+    min-height:0;
+    overflow:hidden;
+    background:#020617;
+    border:1px solid #1f2937;
+    border-radius:4px;
+}
+
+.camera-image-box img {
+    max-width:100%;
+    max-height:100%;
+    aspect-ratio:4 / 3;
+    object-fit:contain;
+}
+
+.camera-info-table {
+    width:100%;
+    height:70px;
+    border-collapse:collapse;
+    border-spacing:0;
+    background:#111827;
+    table-layout:fixed;
+}
+
+.camera-info-table td {
+    border:1px solid #374151;
+    padding:0 10px;
+    white-space:nowrap;
+    color:#e2e8f0;
+}
+
+.camera-info-table td:nth-child(1),
+.camera-info-table td:nth-child(3) {
+    width:110px;
+    color:#94a3b8;
+}
+
+/* 展示服务日志 */
+.right-pane {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    min-width: 0;
+}

+ 29 - 0
web-ui.service/web_handler.go

@@ -316,3 +316,32 @@ func serviceLogLevelHandler(port int) http.HandlerFunc {
 		serviceLogLevel(w, r, port)
 	}
 }
+
+type SystemdServiceStatus struct {
+	Loaded  bool
+	Active  bool
+	Running bool
+	Raw     string
+}
+
+func serviceStatus(name string) (SystemdServiceStatus, error) {
+	var st SystemdServiceStatus
+
+	out, err := exec.Command("systemctl", "show", name,
+		"-p", "LoadState",
+		"-p", "ActiveState",
+		"-p", "SubState",
+	).Output()
+	if err != nil {
+		return st, err
+	}
+
+	s := string(out)
+	st.Raw = s
+
+	st.Loaded = strings.Contains(s, "LoadState=loaded")
+	st.Active = strings.Contains(s, "ActiveState=active")
+	st.Running = st.Active && strings.Contains(s, "SubState=running")
+
+	return st, nil
+}