Browse Source

修改bug

11868 1 week ago
parent
commit
13f98ac67c

+ 387 - 181
src/views/doorcarManage/car_infoManage/index.vue

@@ -1,158 +1,189 @@
 <template>
   <div class="dashboard">
-    <el-row :gutter="20">
-      <!-- 左上角:设备状态 -->
-      <el-col :span="12">
-        <el-card>
-<!--          <el-tabs v-model=" chooseDevice" type="card" style="float:left;">-->
-<!--            <el-tab-pane label="设备1" name="device1"></el-tab-pane>-->
-<!--            <el-tab-pane label="设备2" name="device2"></el-tab-pane>-->
-<!--          </el-tabs>-->
-          <el-button
-            v-if="!deviceStatus.ok"
-            type="danger"
-            size="mini"
-            style="float:right;"
-            @click="reconnectDevice">
-            重新连接
-          </el-button>
-          <div slot="header" class="clearfix">
-            <span>设备状态</span>
-          </div>
-          <div v-if="deviceStatus.ok" style="text-align:center; padding:20px;">
-            <el-result icon="success" title="运行正常"></el-result>
-          </div>
-          <div v-else style="text-align:center; padding:20px;">
-            <el-result icon="error" title="连接失败"></el-result>
-          </div>
+    <el-row :gutter="10">
+      <!-- 左侧内容 -->
+      <el-col :span="12" class="left-col">
+        <div class="left-wrapper">
+          <!-- 设备信息卡片 -->
+          <el-card class="flex-card device-card" shadow="hover">
+            <div slot="header">
+              <span>设备信息</span>
+            </div>
+            <!-- 加载中 -->
+            <el-skeleton v-if="deviceLoading" :rows="3" animated />
+            <!-- 无设备时 -->
+            <el-empty
+              v-else-if="devices.length === 0"
+              description="暂无设备,请检查设备连接或配置"
+            />
+            <!-- 有设备时 -->
+            <el-tabs
+              style="height: 100%"
+              v-else
+              v-model="chooseDevice"
+              type="card"
+              @tab-click="handleTabClick"
+            >
+              <el-tab-pane
+                style="height: 100%; width: 100%"
+                v-for="device in devices"
+                :key="device.id"
+                :label="device.name"
+                :name="device.id"
+              >
+                <el-row style="height: 150px; display: flex; align-items: center; margin-bottom: 10px;">
+                  <!-- 左侧设备信息 -->
+                  <el-col :span="14" style="height: 100%; display: flex; align-items: center; padding: 0 10px;">
+                    <div style="width: 100%; display: flex; flex-direction: column; justify-content: center; height: 100%;">
+                      <p><strong>设备位置:</strong>{{ device.location || '未配置' }}</p>
+                      <p><strong>设备 IP:</strong>{{ device.ip || '-' }}</p>
+                      <p><strong>设备端口:</strong>{{ device.port || '-' }}</p>
+                    </div>
+                  </el-col>
 
-        </el-card>
-      </el-col>
+                  <!-- 中间状态 -->
+                  <el-col :span="6" style="height: 100%; display: flex; align-items: center; justify-content: center;">
+                    <div style="height: 100%; display: flex; align-items: center; justify-content: center; width: 100%;">
+                      <el-result
+                        :icon="device.isConnect ? 'success' : 'error'"
+                        :title="device.isConnect ? '运行正常' : '连接失败'"
+                        style="width: 100%;"
+                      />
+                    </div>
+                  </el-col>
 
-      <!-- 右上角:远程控制 -->
-      <el-col :span="12">
+                  <!-- 右侧按钮 -->
+                  <el-col :span="4" style="height: 100%; display: flex; align-items: center; justify-content: center;">
+                    <el-button type="danger" size="mini" @click="reconnectDevice(device)">
+                      重新连接
+                    </el-button>
+                  </el-col>
+                </el-row>
+                <!-- 视频流 -->
+                <el-row
+                  v-if="device && device.channels && device.channels.length > 0"
+                  style="height: 40%; display: flex; gap: 10px; margin-bottom: 10px;">
+                  <div
+                    v-for="ch in device.channels.slice(0, 3)"
+                    :key="ch.id"
+                    class="video-box"
+                    style="flex: 1; height: 180px; color: #fff; display: flex; align-items: center; justify-content: center;"
+                  >
+                    {{ ch.name }} 视频流
+                  </div>
+                  <!--                  <el-button-->
+                  <!--                    v-if="device.channels.length > 3"-->
+                  <!--                    type="text"-->
+                  <!--                    size="small"-->
+                  <!--                    @click="device.showAllChannels = !device.showAllChannels"-->
+                  <!--                  >-->
+                  <!--                    {{ device.showAllChannels ? '收起更多摄像头' : `查看更多摄像头(${device.channels.length - 2}个)` }}-->
+                  <!--                  </el-button>-->
+                  <!--                  <div v-if="device.showAllChannels" class="video-container" style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px;">-->
+                  <!--                    <div-->
+                  <!--                      v-for="ch in device.channels.slice(2)"-->
+                  <!--                      :key="ch.id"-->
+                  <!--                      class="video-box"-->
+                  <!--                      style="flex: 1; min-width: 200px; height: 180px; background: #000; color: #fff; display: flex; align-items: center; justify-content: center;"-->
+                  <!--                    >-->
+                  <!--                      {{ ch.name }} 视频流-->
+                  <!--                    </div>-->
+                  <!--                  </div>-->
+                </el-row>
+                <!-- 控制门-->
+                <el-row style="height: 40%; display: flex; gap: 10px; margin-top: 20px;">
+                  <!-- <el-row style="height: 30%; display: flex; justify-content: center; background-color: #8cb963;">-->
+                  <div
+                    v-if="device && device.doors && device.doors.length > 0"
+                    class="door-container"
+                    :class="{ 'multi-row': device.doors.length > 3 }"
+                  >
+                    <div
+                      v-for="door in device.doors"
+                      :key="door.doorId"
+                      class="door-wrapper"
+                    >
+                      <div class="door-label">门 {{ door.doorId }}</div>
+                      <!-- 道闸动画 -->
+                      <div class="barrier">
+                        <div class="bar" :class="{ open: door.status === 'open', closed: door.status === 'closed' }"></div>
+                      </div>
+                      <!-- 开关按钮 -->
+                      <el-button
+                        size="mini"
+                        type="primary"
+                        @click="toggleDoor(device, door)"
+                      >
+                        {{ door.status === 'open' ? '关门' : '开门' }}
+                      </el-button>
+                    </div>
+                  </div>
+                </el-row>
+              </el-tab-pane>
+            </el-tabs>
+          </el-card>
+          <!-- 车辆信息总览 -->
+          <el-card class="flex-card people-card" shadow="hover">
+            <div slot="header">
+              <span>车辆信息总览</span>
+            </div>
+            <div class="people-info">
+              <div class="info-block">
+                <img src="/resources/people1.jfif" class="info-icon" />
+                <p>已注册车辆总数</p>
+                <span>{{ carCount }}</span>
+              </div>
+              <div class="info-block">
+                 <img src="/resources/face1.png" class="info-icon" />
+                <p>内部车辆总数</p>
+                <span>{{ registeredCount_car }}</span>
+              </div>
+              <div class="info-block">
+                 <img src="/resources/face1.png" class="info-icon" />
+                <p>临时访问车辆总数</p>
+                <span>{{ unregisteredCount_car }}</span>
+              </div>
+            </div>
+          </el-card>
+        </div>
+      </el-col>
+      <!-- 右侧内容 -->
+      <el-col :span="12" style="height: 100%">
         <el-card>
-          <div slot="header" class="clearfix">
-            <span>远程控制</span>
-<!--            <el-button type="primary" size="mini" style="float:right;" @click="toggleDoor">-->
-<!--              {{ doorOpen ? '关门' : '开门' }}-->
-<!--            </el-button>-->
-            <el-button type="primary" @click="controlGate('open')">开门</el-button>
-            <el-button type="danger" @click="controlGate('close')">关门</el-button>
-            <p>{{ message }}</p>
+          <div slot="header">
+            <span>实时事件</span>
           </div>
-          <el-tabs v-model="activeDevice">
-            <el-tab-pane
-              v-for="d in devices"
-              :key="d.id"
-              :label="d.name"
-              :name="d.id">
-              <div style="background:#000; height:200px; display:flex; align-items:center; justify-content:center; color:white;">
-                {{ d.name }} 视频流
-              </div>
-            </el-tab-pane>
-          </el-tabs>
+          <el-table
+            :data="events"
+            style="width: 100%;"
+            size="mini"
+            border
+            :header-cell-style="changeHeaderCellStyle"
+          >
+            <el-table-column prop="index" label="序号" min-width="50" align="center"/>
+            <el-table-column prop="personId" label="工号" min-width="50" align="center"/>
+            <el-table-column prop="name" label="姓名" min-width="50" align="center"/>
+            <el-table-column prop="carNum" label="卡号" min-width="100" align="center"/>
+            <el-table-column prop="doorNum" label="门" min-width="50" align="center"/>
+            <el-table-column prop="type" label="事件类型" min-width="100" align="center"/>
+            <el-table-column prop="time" label="时间" min-width="100" align="center"/>
+            <el-table-column prop="operate" label="操作" min-width="80" align="center"/>
+          </el-table>
+          <!-- 分页 -->
+          <el-pagination
+            class="custom-pagination"
+            style="margin-top: 20px; text-align: right;"
+            :current-page="currentPage"
+            :page-size="pageSize"
+            :total="total"
+            :page-sizes="[10, 15, 20, 25, 30]"
+            layout="total, prev, pager, next, jumper, sizes"
+            @current-change="handlePageChange"
+            @size-change="handleSizeChange"
+          />
         </el-card>
       </el-col>
     </el-row>
-
-    <!--    <el-row :gutter="20" style="margin-top:20px;">-->
-    <!--      &lt;!&ndash; 左下角:人员信息 &ndash;&gt;-->
-    <!--      <el-col :span="12">-->
-    <!--        <el-card>-->
-    <!--          <div slot="header">人员信息</div>-->
-    <!--          <p>内部人员:{{ people.internal }}</p>-->
-    <!--          <p>临时访问:{{ people.temporary }}</p>-->
-    <!--          <p>内部人脸:{{ faces.internal }}</p>-->
-    <!--          <p>外部人脸:{{ faces.external }}</p>-->
-    <!--        </el-card>-->
-    <!--      </el-col>-->
-    <!--    </el-row>-->
-    <el-col :span="12">
-      <el-card shadow="hover" class="people-card">
-        <h2 class="section-title">车辆信息总览</h2>
-        <div class="people-info">
-          <div class="info-block">
-            <img src="/resources/people1.jfif" class="info-icon" />
-            <p>已注册车辆总数</p>
-            <span>{{ carCount }}</span>
-          </div>
-          <div class="info-block">
-            <!--          <img src="/resources/face1.png" class="info-icon" />-->
-            <p>内部车辆总数</p>
-            <span>{{ registeredCount_car }}</span>
-          </div>
-          <div class="info-block">
-            <!--          <img src="/resources/face1.png" class="info-icon" />-->
-            <p>临时访问车辆总数</p>
-            <span>{{ unregisteredCount_car }}</span>
-          </div>
-        </div>
-        <!--      <el-row :gutter="20">-->
-        <!--        <el-col :span="6">-->
-        <!--          <el-card shadow="hover" class="overview-card">-->
-        <!--            <img src="/public/resources/people1.jfif" class="card-img" />-->
-        <!--            <div class="card-text">人员信息</div>-->
-        <!--          </el-card>-->
-        <!--        </el-col>-->
-
-        <!--        <el-col :span="6">-->
-        <!--          <el-card shadow="hover" class="overview-card">-->
-        <!--            <img src="/public/resources/face1.png" class="card-img" />-->
-        <!--            <div class="card-text">人脸信息</div>-->
-        <!--          </el-card>-->
-        <!--        </el-col>-->
-
-        <!--        <el-col :span="6">-->
-        <!--          <el-card shadow="hover" class="overview-card">-->
-        <!--            <img src="/public/resources/device.png" class="card-img" />-->
-        <!--            <div class="card-text">设备信息</div>-->
-        <!--          </el-card>-->
-        <!--        </el-col>-->
-
-        <!--        <el-col :span="6">-->
-        <!--          <el-card shadow="hover" class="overview-card">-->
-        <!--            <img src="/public/resources/other.png" class="card-img" />-->
-        <!--            <div class="card-text">其他信息</div>-->
-        <!--          </el-card>-->
-        <!--        </el-col>-->
-        <!--      </el-row>-->
-      </el-card>
-<!--      <el-card shadow="hover" class="face-card">-->
-<!--        <h2 class="section-title">人脸信息总览</h2>-->
-<!--        <div class="face-info">-->
-<!--          <div class="info-block">-->
-<!--            <img src="/resources/face1.png" class="info-icon" />-->
-<!--            <p>人员总数</p>-->
-<!--            <span>{{ faceCount }}</span>-->
-<!--          </div>-->
-<!--          <div class="info-block">-->
-
-<!--            <p>内部人脸总数</p>-->
-<!--            <span>{{ registeredCount_face }}</span>-->
-<!--          </div>-->
-<!--          <div class="info-block">-->
-<!--            &lt;!&ndash;            <img src="/resources/face1.png" class="info-icon" />&ndash;&gt;-->
-<!--            <p>临时访问人脸总数</p>-->
-<!--            <span>{{ unregisteredCount_face }}</span>-->
-<!--          </div>-->
-<!--        </div>-->
-<!--      </el-card>-->
-    </el-col>
-
-    <!--      &lt;!&ndash; 右下角:实时事件 &ndash;&gt;-->
-    <el-col :span="12">
-      <el-card>
-        <div slot="header">实时事件</div>
-        <el-table :data="events" size="mini" height="400" border>
-          <el-table-column prop="eventTime" label="时间" width="150"/>
-          <el-table-column prop="ownerName" label="车主姓名" width="100"/>
-          <el-table-column prop="plateNumber" label="车牌号码" width="120" />
-          <el-table-column prop="eventType" label="进出类型"/>
-        </el-table>
-      </el-card>
-    </el-col>
-
   </div>
 </template>
 
@@ -173,19 +204,52 @@ export default {
         device1: { ok: true },
         device2: { ok: false }
       },
-      doorOpen: false,
-      chooseDevice: "device1",
+
       activeDevice: '1',
+
+      deviceLoading: false,
       devices: [
-        { id: '1', name: '进门车辆监控' },
-        { id: '2', name: '出门闸道监控' }
+        {
+          id: '1',
+          name: '出入口控制道闸A',             // 双门,2个摄像头
+          location: '大门口左侧',
+          ip: '192.168.1.10',
+          port: 8000,
+          isConnect: true,
+          doors: [
+            { doorId: 1, status: 'closed' },
+          ],
+          channels: [
+            { id: 'ch1', name: '门前摄像头' },
+            { id: 'ch2', name: '门后摄像头' },
+          ],
+        },
+        {
+          id: '2',
+          name: '出入口控制道闸B',             // 单门,1个摄像头
+          location: '大门口右侧',
+          ip: '192.168.1.11',
+          port: 8001,
+          isConnect: false,
+          doors: [
+            { doorId: 1, status: 'open' },
+          ],
+          channels: [
+            { id: 'ch1', name: '门前摄像头' },
+            { id: 'ch2', name: '门后摄像头' },
+          ],
+        },
       ],
+      chooseDevice: '1',
+
       people: { internal: 10, temporary: 2 },
       faces: { internal: 8, external: 5 },
 
-      events: [
-      ],
-      // total: 0,
+      events: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 0,
+
       carCount: 0,
       registeredCount_car: 0,
       unregisteredCount_car: 0,
@@ -200,23 +264,6 @@ export default {
     this.fetchPeople()
   },
   methods: {
-    // 控制闸道
-    async controlGate(action) {
-      try {
-        const res = await axios.post('/api/barrier/control', null, {
-          params: {
-            ip: this.ip,
-            tdh: this.tdh,
-            action: action
-          }
-        });
-        this.message = res.data;
-      } catch (err) {
-        this.message = '操作失败: ' + err.message;
-      }
-    },
-
-
     fetchData() {
       request({
         url: "/car/event/log/list",
@@ -241,15 +288,42 @@ export default {
 
       })
     },
-    reconnectDevice() {
-      // TODO: 调后端接口
-      this.deviceStatus.ok = true
+    handleTabClick(tab) {
+      console.log("切换到设备:", tab.name)
+      // 这里可以根据选中设备刷新状态或视频流
     },
-    toggleDoor() {
-      // TODO: 调后端接口
-      this.doorOpen = !this.doorOpen
+    reconnectDevice(device) {
+      console.log("重新连接设备:", device.name)
+      // 调用你的后端接口进行重连操作
     },
+    toggleDoor() {
+      if (door.loading) return; // 防止连点
+      // door.loading = true;
+      // // 调用后端接口控制门
+      // request({
+      //   url: `/door/${device.id}/${door.doorId}/${door.status === 'open' ? 'close' : 'open'}`,
+      //   method: 'POST'
+      // })
+      //   .then(() => {
+      //     this.$message.success(`已发送操作到门 ${door.doorId}`);
+      //     // 等待后端状态刷新,这里可以选择轮询或者短延时刷新
+      //     setTimeout(() => this.fetchDoorStatus(device), 500);
+      //   })
+      //   .catch(() => {
+      //     this.$message.error(`操作门 ${door.doorId} 失败`);
+      //   })
+      //   .finally(() => {
+      //     door.loading = false;
+      //   });
 
+      // console.log(`操作设备device ${device}`);
+      // console.log(`操作设备door ${door}`);
+      console.log(`操作设备 ${device.name} 的门 ${door.doorId}`);
+      console.log(`门之前的状态 ${door.status}`);
+      const newStatus = door.status === 'open' ? 'closed' : 'open';
+      this.$set(door, 'status', newStatus);
+      console.log(`门之后的状态 ${door.status}`);
+    },
     fetchPeople() {
       this.loading = true
       request({
@@ -276,18 +350,150 @@ export default {
 
       }).finally(() => { this.loading = false })
     },
+    // 表格样式修改
+    changeHeaderCellStyle(row, column, rowIndex, columnIndex) {
+      if (row.columnIndex === 0) {
+        return "background: #004279 ; color:#fff;"; // 修改的样式
+      } else {
+        return "background: #004279 ;color:#fff; ";
+      }
+    },
+    handlePageChange(page) {
+      this.currentPage = page;
+      this.fetchEventsData()();
+    },
+    handleSizeChange(size) {
+      this.currentPage = 1;
+      this.pageSize = size;
+      this.fetchEventsData()();
+    },
   }
 }
 </script>
 
 <style scoped>
 .dashboard {
-  padding: 20px;
+  padding: 10px;
 }
+
+.left-col {
+  height: calc(100vh - 100px); /* 根据顶部导航高度调整 */
+}
+
+.left-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  gap: 10px; /* 卡片间距 */
+}
+
+.flex-card.device-card {
+  flex: 3; /* 上半部分占 2/3 */
+}
+
+.flex-card.people-card {
+  flex: 1; /* 下半部分占 1/3 */
+}
+
+.video-container {
+  display: flex;
+  flex-wrap: wrap;  /* 自动换行 */
+  gap: 10px;
+  justify-content: center;
+}
+
+.video-box {
+  flex: 1 1 200px;    /* 最小宽 200px,可扩展 */
+  max-width: 300px;   /* 限制最大宽度 */
+  height: 180px;
+  background: #000;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
+}
+
+.people-info {
+  display: flex;
+  justify-content: space-around;
+  margin-top: 10px;
+}
+
+.info-block {
+  text-align: center;
+}
+
 .info-icon {
-  width: 60px;         /* 图片宽度 */
-  height: 60px;        /* 图片高度 */
-  object-fit: contain; /* 保持比例 */
-  margin-bottom: 8px;  /* 图片和文字之间的间距 */
+  width: 60px;
+  height: 60px;
+  object-fit: contain;
+  margin-bottom: 8px;
 }
+
+
+.door-container {
+  display: flex;           /* 必须加上 flex */
+  justify-content: center; /* 水平居中 */
+  gap: 10px;               /* 门之间的间距 */
+  margin-bottom: 10px;
+  flex-wrap: nowrap;       /* 默认不换行 */
+}
+
+.door-container.multi-row {
+  flex-wrap: wrap;         /* 超过三个门时换行 */
+}
+
+.door-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 180px;  /* 固定宽度 */
+  height: 180px; /* 固定高度 */
+  justify-content: space-between; /* 顶部门名,中间bar,底部按钮均分 */
+  background-color: #f0f0f0; /* 可选背景色 */
+  border-radius: 6px;        /* 可选圆角 */
+  padding: 10px;
+}
+
+.door-label {
+  margin-top: 80px;
+  font-weight: bold;
+  margin-bottom: 5px;
+  font-size: 18px;
+}
+
+/* 模拟道闸底座 */
+.barrier {
+  width: 100%;        /* 自适应门宽度 */
+  max-width: 150px;   /* 最大横杆宽度 */
+  height: 20px;
+  background-color: #444;
+  position: relative;
+  border-radius: 4px;
+  margin-bottom: 5px;
+}
+
+/* 横杆 */
+.bar {
+  width: 100%;        /* 随barrier宽度 */
+  height: 8px;
+  background-color: #2aabd2;
+  position: absolute;
+  top: 6px;
+  left: 0;
+  transform-origin: left center;
+  transition: transform 0.8s ease;
+  border-radius: 4px;
+}
+
+/* 开关状态 */
+.bar.closed {
+  transform: rotate(0deg);
+}
+
+.bar.open {
+  transform: rotate(-60deg);
+}
+
 </style>

+ 338 - 0
src/views/doormanManage/deviceManage/DeviceEditDialog.vue

@@ -0,0 +1,338 @@
+<template>
+  <el-dialog
+    :visible.sync="visible"
+    :title="formData.id ? '编辑设备' : '新增设备'"
+    @close="handleCancel"
+    custom-class="custom-dialog"
+  >
+    <el-form
+      ref="editForm"
+      :model="formData"
+      :rules="formRules"
+      label-width="120px"
+      label-position="right"
+      class="device-form"
+    >
+      <!-- 基本信息 -->
+      <div class="form-row">
+        <el-form-item label="设备名称" prop="name">
+          <el-input v-model="formData.name" placeholder="请输入设备名称" />
+        </el-form-item>
+        <el-form-item label="设备序列号" prop="serialNumber">
+          <el-input v-model="formData.serialNumber" placeholder="请输入序列号" />
+        </el-form-item>
+        <el-form-item label="设备类型" prop="type">
+          <el-select v-model="formData.type" placeholder="请选择设备类型">
+            <el-option label="人行摆闸" value="peopleDoor" />
+            <el-option label="车道闸" value="carDoor" />
+            <el-option label="双类型" value="carDoor|peopleDoor" />
+          </el-select>
+        </el-form-item>
+      </div>
+
+      <div class="form-row">
+        <el-form-item label="设备位置" prop="location">
+          <el-input v-model="formData.location" placeholder="请输入设备位置" />
+        </el-form-item>
+        <el-form-item label="固件版本" prop="firmwareVersion">
+          <el-input v-model="formData.firmwareVersion" placeholder="请输入固件版本" />
+        </el-form-item>
+      </div>
+
+      <div class="form-row">
+        <el-form-item label="IP地址" prop="ip">
+          <el-input v-model="formData.ip" placeholder="请输入IP" />
+        </el-form-item>
+        <el-form-item label="端口号" prop="port">
+          <el-input-number v-model.number="formData.port" :min="1" />
+        </el-form-item>
+      </div>
+
+      <div class="form-row">
+        <el-form-item label="用户名" prop="account">
+          <el-input v-model="formData.account" placeholder="请输入用户名" />
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input v-model="formData.password" type="password" show-password placeholder="请输入密码" />
+        </el-form-item>
+      </div>
+
+      <div class="form-row">
+        <el-form-item label="备注" prop="notes" style="flex: 1;">
+          <el-input v-model="formData.notes" placeholder="请输入备注" />
+        </el-form-item>
+        <el-form-item label="启用" prop="isConnect">
+          <el-switch
+            v-model="formData.isConnect"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+            :active-value="true"
+            :inactive-value="false"
+          />
+        </el-form-item>
+      </div>
+
+      <!-- 门体信息 -->
+      <div style="margin-top: 20px;">
+        <h4>门体信息</h4>
+        <el-table :data="formData.doors" style="width: 100%" border>
+          <el-table-column prop="doorId" label="ID" width="60"/>
+          <el-table-column prop="doorName" label="门名称" width="120">
+            <template #default="{ row }">
+              <el-input v-model="row.doorName" size="small"/>
+            </template>
+          </el-table-column>
+          <el-table-column prop="type" label="类型" width="100"/>
+          <el-table-column prop="status" label="状态" width="100">
+            <template #default="{ row }">
+              <el-select v-model="row.status" placeholder="选择状态" size="small">
+                <el-option label="开启" value="open"/>
+                <el-option label="关闭" value="closed"/>
+                <el-option label="阻塞" value="blocked"/>
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column prop="lastActionTime" label="最近操作" width="180"/>
+          <el-table-column prop="errorCode" label="异常代码" width="100"/>
+        </el-table>
+      </div>
+
+      <!-- 摄像头信息 -->
+      <div style="margin-top: 20px;">
+        <h4>摄像头信息</h4>
+        <el-table :data="formData.channels" style="width: 100%" border>
+          <el-table-column prop="id" label="ID" width="60"/>
+          <el-table-column prop="name" label="摄像头名称" width="150">
+            <template #default="{ row }">
+              <el-input v-model="row.name" size="small"/>
+            </template>
+          </el-table-column>
+          <el-table-column prop="rtspUrl" label="RTSP URL" min-width="250"/>
+          <el-table-column prop="status" label="状态" width="100">
+            <template #default="{ row }">
+              <el-select v-model="row.status" size="small">
+                <el-option label="在线" value="online"/>
+                <el-option label="离线" value="offline"/>
+              </el-select>
+            </template>
+          </el-table-column>
+          <el-table-column prop="type" label="类型" width="100"/>
+        </el-table>
+      </div>
+    </el-form>
+
+<!--      <div class="form-row">-->
+<!--        <el-form-item label="同步人员">-->
+<!--          <el-radio-group v-model="formData.syncMode">-->
+<!--            <el-radio :label="0">不同步</el-radio>-->
+<!--            <el-radio :label="1">同步所有人员</el-radio>-->
+<!--            <el-radio :label="2">从指定设备同步</el-radio>-->
+<!--          </el-radio-group>-->
+<!--        </el-form-item>-->
+<!--        &lt;!&ndash; 如果选择了 2,则输入来源设备ID &ndash;&gt;-->
+<!--        <el-form-item v-if="formData.syncMode === 2" label="来源设备ID">-->
+<!--          <el-input v-model="formData.syncFromDeviceId" placeholder="请输入来源设备ID" />-->
+<!--        </el-form-item>-->
+<!--      </div>-->
+<!--    </el-form>-->
+
+    <span slot="footer" class="dialog-footer">
+      <el-button @click="handleCancel">取消</el-button>
+      <el-button type="primary" @click="handleSubmit">保存</el-button>
+    </span>
+  </el-dialog>
+</template>
+
+<script>
+export default {
+  name: "DeviceEditDialog",
+  props: {
+    visible: Boolean,
+    deviceData: {
+      type: Object,
+      default: () => ({}),
+    },
+  },
+  data() {
+    return {
+      deviceTypeOptions: [
+        { label: "网络摄像机 (IPC)", value: "IPC" },
+        { label: "人脸门禁终端 (MINMOE)", value: "MINMOE" },
+        { label: "车牌识别一体机 (LPR_GATE)", value: "LPR_GATE" },
+        { label: "通道控制设备 / 摆闸 (TURNSTILE)", value: "TURNSTILE" }
+      ],
+
+      formData: {
+        id: null,
+        serialNumber: "",       // 设备序列号
+        name: "",               // 设备名称
+        type: "",               // peopleDoor / carDoor / carDoor|peopleDoor
+        location: "",           // 设备位置
+        ip: "",
+        port: 8554,
+        account: "",
+        password: "",
+        isConnect: true,        // 启用/连接状态
+        status: "offline",      // online/offline
+        firmwareVersion: "",    // 固件版本
+        doors: [],              // JSON.parse(doors)
+        channels: [],           // JSON.parse(channels)
+        notes: "",
+        createTime: "",
+        updateTime: "",
+        enable: 1,
+      },
+      formRules: {
+        name: [{ required: true, message: "请输入设备名称", trigger: "blur" }],
+        deviceType: [
+          { required: true, message: "请选择设备类型", trigger: "change" },
+        ],
+        ip: [{ required: true, message: "请输入设备IP", trigger: "blur" }],
+        port: [
+          { required: true, message: "请输入端口号", trigger: "blur" },
+          { type: "number", message: "端口号必须是数字" },
+        ],
+        username: [
+          { required: true, message: "请输入用户名", trigger: "blur" },
+        ],
+        password: [{ required: true, message: "请输入密码", trigger: "blur" }],
+        channel: [
+          {
+            required: true,
+            type: "number",
+            message: "请输入通道号",
+            trigger: "blur",
+          },
+        ],
+        channelName: [
+          { required: true, message: "请输入通道名称", trigger: "blur" },
+        ],
+        rtspUrl: [
+          { required: true, message: "请输入RTSP地址", trigger: "blur" },
+        ],
+        previewType: [
+          { required: true, message: "请选择取流方式", trigger: "change" },
+        ],
+      },
+    };
+  },
+  watch: {
+    deviceData: {
+      immediate: true,
+      handler(newVal) {
+        if (!newVal || Object.keys(newVal).length === 0) {
+          // 新增设备
+          this.resetForm();
+        } else {
+          this.formData = {
+            ...this.formData,
+            ...newVal,
+            doors: this.parseJsonArray(newVal.doors),
+            channels: this.parseJsonArray(newVal.channels)
+          };
+        }
+      },
+    },
+  },
+  methods: {
+    parseJsonArray(field) {
+      if (!field) return [];
+      if (Array.isArray(field)) return field;
+      try { return JSON.parse(field); } catch(e) { return []; }
+    },
+    resetForm() {
+      this.formData = {
+        id: null,
+        serialNumber: "",
+        name: "",
+        type: "",
+        location: "",
+        ip: "",
+        port: 8554,
+        account: "",
+        password: "",
+        isConnect: true,
+        status: "offline",
+        firmwareVersion: "",
+        doors: [],
+        channels: [],
+        notes: "",
+        createTime: "",
+        updateTime: "",
+        enable: 1,
+      }
+    },
+    handleCancel() {
+      this.$emit("cancel");
+    },
+    handleSubmit() {
+      this.$refs.editForm.validate((valid) => {
+        if (!valid) {
+          this.$message.error("请检查表单填写项");
+          return false;
+        }
+        // 提交时将数组转 JSON 字符串
+        const payload = {
+          ...this.formData,
+          doors: JSON.stringify(this.formData.doors),
+          channels: JSON.stringify(this.formData.channels)
+        };
+        this.$emit("submit", payload);
+      });
+    }
+  },
+};
+</script>
+
+<style>
+.custom-dialog {
+  background-color: #003c68;
+  width: 1000px !important;
+}
+
+/* 标签文字颜色深色背景下可读 */
+.custom-dialog .el-dialog__title {
+  color: #fff;
+}
+
+.custom-dialog .el-dialog__header {
+  background-color: #003c68;
+  color: #ffffff;
+}
+
+.custom-dialog .el-dialog__headerbtn {
+  color: #ffffff;
+}
+
+.device-form {
+  background-color: #003c68;
+  padding: 15px 20px;
+}
+
+/* 两列布局 */
+.form-row {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 10px;
+}
+
+/* 标签文字颜色深色背景下可读 */
+.device-form .el-form-item__label {
+  color: #fff;
+}
+
+/* 输入框文字颜色 */
+.device-form .el-input__inner,
+.device-form .el-select .el-input__inner,
+.device-form .el-input-number__input {
+  color: #000;
+  background-color: #fff;
+}
+
+/* 对话框底部按钮右对齐 */
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+</style>

+ 143 - 282
src/views/doormanManage/deviceManage/index.vue

@@ -1,95 +1,94 @@
 <template>
-  <div class="device-management">
-    <div class="header">
-      <h2>设备管理</h2>
-      <el-button type="primary" @click="openAddDialog">新增设备</el-button>
+  <div style="padding: 10px">
+    <!-- 搜索和新增 -->
+    <div style="height: 50px; display: flex; align-items: center; gap: 10px">
+      <!-- 输入框:回车自动查询 -->
+      <el-input
+        v-model="searchDeviceName"
+        placeholder="设备名称"
+        size="small"
+        style="width: 150px"
+        clearable
+        @keyup.enter.native="handleSearch"
+      />
+      <el-button type="primary" size="small" @click="handleSearch"
+      >查询</el-button
+      >
+      <el-button type="primary" size="small" @click="handleAdd">新增</el-button>
     </div>
 
-    <el-table :data="devices" style="width: 100%" border v-loading="loading">
-      <el-table-column label="序号" width="70">
+    <el-table
+      :data="devices"
+      style="width: 100%;
+      margin-top: 10px"
+      border
+      :header-cell-style="changeHeaderCellStyle"
+      v-loading="loading"
+    >
+      <el-table-column type="index" label="序号" width="60" align="center">
         <template slot-scope="scope">{{ scope.$index + 1 }}</template>
       </el-table-column>
-      <el-table-column prop="id" label="设备ID" min-width="180" />
-      <el-table-column prop="model" label="设备型号" min-width="120" />
-      <el-table-column prop="location" label="设备位置" min-width="140" />
-      <el-table-column prop="ip" label="设备IP" min-width="140" />
-      <el-table-column prop="port" label="设备Port" min-width="140" />
-      <el-table-column prop="account" label="账号" width="120" />
-      <el-table-column prop="password" label="密码" width="120" />
-      <el-table-column label="操作" fixed="right" width="160">
-        <template slot-scope="scope">
-          <el-button type="text" size="small" @click="viewDevice(scope.row)">查看</el-button>
-          <el-button type="text" size="small" @click="deleteDevice(scope.row)">删除</el-button>
+      <el-table-column prop="id" label="设备ID" align="center" width="60" />
+      <el-table-column prop="name" label="设备名称" align="center" min-width="100" />
+      <el-table-column prop="serialNumber" label="设备序列号" align="center" min-width="100" />
+      <el-table-column prop="type" label="设备类型" align="center" min-width="100" />
+      <el-table-column prop="location" label="设备位置" align="center" min-width="150" />
+      <el-table-column prop="ip" label="设备IP" align="center" min-width="100" />
+      <el-table-column prop="port" label="设备Port" align="center" min-width="100" />
+      <el-table-column prop="account" label="账号" align="center" min-width="100" />
+      <el-table-column prop="password" label="密码" align="center" min-width="100" />
+      <el-table-column prop="status" label="设备状态" align="center" min-width="100" />
+      <el-table-column prop="firmwareVersion" label="固件版本" align="center" min-width="100" />
+      <el-table-column prop="notes" label="备注" align="center" min-width="100" />
+      <el-table-column label="操作" fixed="right" align="center" min-width="100">
+        <template #default="{ row }">
+          <el-button type="text" size="small" @click="handleEdit(row)">编辑</el-button>
+          <el-button type="text" size="small" @click="handleDelete(row)">删除</el-button>
         </template>
       </el-table-column>
     </el-table>
-
-    <!-- Add Dialog -->
-    <el-dialog title="新增设备" :visible.sync="dialogVisible" width="600px">
-      <el-form :model="form" :rules="rules" ref="deviceForm" label-width="110px" style="background: #001b2f">
-        <el-form-item label="设备型号" prop="model">
-          <el-input v-model="form.model" placeholder="请输入设备型号" />
-        </el-form-item>
-        <el-form-item label="设备位置" prop="location">
-          <el-input v-model="form.location" placeholder="请输入设备位置" />
-        </el-form-item>
-        <el-form-item label="设备IP" prop="ip">
-          <el-input v-model="form.ip" placeholder="请输入设备IP" />
-        </el-form-item>
-        <el-form-item label="设备端口" prop="port">
-          <el-input v-model="form.port" placeholder="请输入设备Port" />
-        </el-form-item>
-        <el-form-item label="账号" prop="account">
-          <el-input v-model="form.account" placeholder="请输入账号" />
-        </el-form-item>
-        <el-form-item label="密码" prop="password">
-          <el-input type="password" v-model="form.password" placeholder="请输入密码" />
-        </el-form-item>
-
-        <!-- 新增:同步模式选择 -->
-        <el-form-item label="同步人员">
-          <el-radio-group v-model="form.syncMode">
-            <el-radio :label="0">不同步</el-radio>
-            <el-radio :label="1">同步所有人员</el-radio>
-            <el-radio :label="2">从指定设备同步</el-radio>
-          </el-radio-group>
-        </el-form-item>
-
-        <!-- 如果选择了 2,则输入来源设备ID -->
-        <el-form-item v-if="form.syncMode === 2" label="来源设备ID">
-          <el-input v-model="form.syncFromDeviceId" placeholder="请输入来源设备ID" />
-        </el-form-item>
-      </el-form>
-
-      <div slot="footer" class="dialog-footer">
-        <el-button @click="dialogVisible = false">取 消</el-button>
-        <el-button type="primary" @click="submitDevice">确 定</el-button>
-      </div>
-    </el-dialog>
+    <!-- 分页 -->
+    <el-pagination
+      class="custom-pagination"
+      style="margin-top: 20px; text-align: right;"
+      :current-page="currentPage"
+      :page-size="pageSize"
+      :total="total"
+      :page-sizes="[10, 15, 20, 25, 30]"
+      layout="total, prev, pager, next, jumper, sizes"
+      @current-change="handlePageChange"
+      @size-change="handleSizeChange"
+    />
+
+    <!-- 新增/编辑弹窗 -->
+    <DeviceEditDialog
+      :visible.sync="dialogVisible"
+      :device-data="currentDeviceData"
+      @submit="handleDeviceSubmit"
+      @cancel="handleDeviceCancel"
+    />
   </div>
 </template>
 
 <script>
-import axios from 'axios'
+import DeviceEditDialog from "./DeviceEditDialog.vue";
 import request from "@/utils/request";
 
 export default {
   name: 'DeviceManagement',
+  components: { DeviceEditDialog },
   data() {
     return {
+      searchDeviceName: "",
+
       loading: false,
       devices: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 0,
+
       dialogVisible: false,
-      form: {
-        model: '',
-        location: '',
-        ip: '',
-        port: '',
-        account: '',
-        password: '',
-        syncMode: 0,
-        syncFromDeviceId: null
-      },
+      currentDeviceData: {},
       rules: {
         model: [{ required: true, message: '请输入设备型号', trigger: 'blur' }],
         location: [{ required: true, message: '请输入设备位置', trigger: 'blur' }],
@@ -97,53 +96,20 @@ export default {
         port: [{ required: true, message: '请输入设备Port', trigger: 'blur' }],
         account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
         password: [{ required: true, message: '请输入密码', trigger: 'blur' }]
-      }
+      },
     }
   },
   mounted() {
-    this.fetchDevices()
+    this.getDeviceInfo()
   },
   methods: {
-    // 获取设备列表
-    async fetchDevices() {
-      this.loading = true
-      try {
-        const res = await request({
-          url: '/device/list',
-          method: 'GET',
-        })
-        console.log("设备信息", res)
-        this.devices = res
-      } finally {
-        this.loading = false
-      }
+    handleSearch() {
+      this.currentPage = 1;
+      this.getDeviceInfo();
     },
-    openAddDialog() {
-      this.resetForm()
-      this.dialogVisible = true
-      this.$nextTick(() => {
-        this.$refs.deviceForm && this.$refs.deviceForm.clearValidate()
-      })
-    },
-    async submitDevice() {
-      this.$refs.deviceForm.validate(async (valid) => {
-        if (!valid) return
-        try {
-          await request({
-            url: '/device/add',
-            method: 'post',
-            data: this.form
-          })
-          this.$message.success('新增设备成功')
-          this.dialogVisible = false
-          this.fetchDevices()
-        } catch (e) {
-          this.$message.error('新增设备失败')
-        }
-      })
-    },
-    resetForm() {
-      this.form = {
+    handleAdd(){
+      this.currentDeviceData = {
+        name: '',
         model: '',
         location: '',
         ip: '',
@@ -153,187 +119,82 @@ export default {
         syncMode: 0,
         syncFromDeviceId: null
       }
+      this.dialogVisible = true;
+    },
+    handleEdit(row) {
+      this.currentDeviceData = { ...row };
+      this.dialogVisible = true;
+    },
+    async handleDelete(row) {
+      await request({
+        url: `/device/delete/${row.id}`,
+        method: "DELETE",
+      });
+      this.$message.success("删除成功");
+      await this.getDeviceInfo();
+    },
+    // 表格样式修改
+    changeHeaderCellStyle(row, column, rowIndex, columnIndex) {
+      if (row.columnIndex === 0) {
+        return "background: #004279 ; color:#fff;"; // 修改的样式
+      } else {
+        return "background: #004279 ;color:#fff; ";
+      }
     },
-    viewDevice(row) {
-      this.$router.push({path: '/peopleInfoManage', query: {deviceId: row.id}})
+    handlePageChange(page) {
+      this.currentPage = page;
+      this.getDeviceInfo();
     },
-    async deleteDevice(row) {
+    handleSizeChange(size) {
+      this.currentPage = 1;
+      this.pageSize = size;
+      this.getDeviceInfo();
+    },
+    async handleDeviceSubmit(deviceData) {
+      const url = deviceData.id ? "/device/update" : "/device/add";
+      const res = await request({ url, method: "POST", data: deviceData });
+      this.dialogVisible = false;
+      await this.$message(res.data);
+      await this.getDeviceInfo();
+    },
+    handleDeviceCancel() {
+      this.dialogVisible = false;
+    },
+    async getDeviceInfo() {
       try {
+        this.loading = true
         const res = await request({
-          url: `/device/delete/${row.id}` ,
-          method: 'DELETE',
-        })
-        this.$message.success('删除设备成功')
-        this.fetchDevices()
-
-      } catch (e) {
-        this.$message.error('删除失败')
+          url: "/device/list",
+          method: "POST",
+          data: {
+            page: this.currentPage,
+            size: this.pageSize,
+            deviceName: this.searchDeviceName?.trim() || null, // 去掉多余空格,避免 "" 导致条件错乱
+          },
+        });
+
+        // 判断返回结构
+        if (res && res.list) {
+          this.devices = res.list;
+          this.total = res.total || 0;
+        } else {
+          this.devices = [];
+          this.total = 0;
+          this.$message.warning("未获取到设备数据");
+        }
+      } catch (error) {
+        console.error("获取设备信息失败:", error);
+        this.$message.error("获取设备列表失败,请检查网络或后端接口!");
+      } finally {
+        this.loading = false
       }
-      // this.getDeviceList()
-
-    }
+    },
   }
 }
 </script>
 
 <style scoped>
 
-.device-management { padding: 18px; background: #fff; border-radius: 6px; }
-.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
 .dialog-footer { text-align: right; }
-:deep(.el-input__inner::placeholder) {
-  color: #666;
-  opacity: 1;
-}
 
-:deep(.el-input__inner::placeholder) {
-  color: #666;
-}
 </style>
-
-
-
-
-<!--<template>-->
-<!--  <div class="device-management">-->
-<!--    <div class="header">-->
-<!--      <h2>设备管理</h2>-->
-<!--      <el-button type="primary" @click="openAddDialog">新增设备</el-button>-->
-<!--    </div>-->
-
-<!--    <el-table-->
-<!--      :data="devices"-->
-<!--      style="width: 100%"-->
-<!--      border-->
-<!--      v-loading="loading"-->
-<!--    >-->
-<!--      <el-table-column label="序号" width="70">-->
-<!--        <template slot-scope="scope">{{ scope.$index + 1 }}</template>-->
-<!--      </el-table-column>-->
-
-<!--      <el-table-column prop="deviceId" label="设备ID" min-width="180" />-->
-<!--      <el-table-column prop="model" label="设备型号" min-width="120" />-->
-<!--      <el-table-column prop="location" label="设备位置" min-width="140" />-->
-<!--      <el-table-column prop="ip" label="设备IP" min-width="140" />-->
-<!--      <el-table-column prop="account" label="账号" width="120" />-->
-<!--      <el-table-column prop="password" label="密码" width="120" />-->
-
-<!--      <el-table-column label="操作" fixed="right" width="120">-->
-<!--        <template slot-scope="scope">-->
-<!--          <el-button type="text" size="small" @click="viewDevice(scope.row)">查看</el-button>-->
-<!--        </template>-->
-<!--      </el-table-column>-->
-<!--    </el-table>-->
-
-<!--    &lt;!&ndash; Add Dialog &ndash;&gt;-->
-<!--    <el-dialog title="新增设备" :visible.sync="dialogVisible" width="600px">-->
-<!--      <el-form :model="form" :rules="rules" ref="deviceForm" label-width="110px">-->
-<!--        <el-form-item label="设备型号" prop="model">-->
-<!--          <el-input v-model="form.model" placeholder="请输入设备型号" />-->
-<!--        </el-form-item>-->
-
-<!--        <el-form-item label="设备位置" prop="location">-->
-<!--          <el-input v-model="form.location" placeholder="请输入设备位置" />-->
-<!--        </el-form-item>-->
-
-<!--        <el-form-item label="设备IP" prop="ip">-->
-<!--          <el-input v-model="form.ip" placeholder="请输入设备IP" />-->
-<!--        </el-form-item>-->
-
-<!--        <el-form-item label="账号" prop="account">-->
-<!--          <el-input v-model="form.account" placeholder="请输入账号" />-->
-<!--        </el-form-item>-->
-
-<!--        <el-form-item label="密码" prop="password">-->
-<!--          <el-input type="password" v-model="form.password" placeholder="请输入密码" />-->
-<!--        </el-form-item>-->
-<!--      </el-form>-->
-
-<!--      <div slot="footer" class="dialog-footer">-->
-<!--        <el-button @click="dialogVisible = false">取 消</el-button>-->
-<!--        <el-button type="primary" @click="submitDevice">确 定</el-button>-->
-<!--      </div>-->
-<!--    </el-dialog>-->
-<!--  </div>-->
-<!--</template>-->
-
-<!--<script>-->
-<!--export default {-->
-<!--  name: 'DeviceManagement',-->
-<!--  data() {-->
-<!--    return {-->
-<!--      loading: false,-->
-<!--      devices: [-->
-<!--        // 示例数据-->
-<!--        {-->
-<!--          deviceId: this.generateDeviceId(),-->
-<!--          model: 'ZK-T100',-->
-<!--          location: '大门东侧',-->
-<!--          ip: '192.168.1.101',-->
-<!--          account: 'admin',-->
-<!--          password: '123456'-->
-<!--        },-->
-<!--        {-->
-<!--          deviceId: this.generateDeviceId(),-->
-<!--          model: 'HIK-9000',-->
-<!--          location: '大门西侧',-->
-<!--          ip: '192.168.1.102',-->
-<!--          account: 'root',-->
-<!--          password: '654321'-->
-<!--        }-->
-<!--      ],-->
-
-<!--      dialogVisible: false,-->
-<!--      form: {-->
-<!--        model: '',-->
-<!--        location: '',-->
-<!--        ip: '',-->
-<!--        account: '',-->
-<!--        password: ''-->
-<!--      },-->
-<!--      rules: {-->
-<!--        model: [{ required: true, message: '请输入设备型号', trigger: 'blur' }],-->
-<!--        location: [{ required: true, message: '请输入设备位置', trigger: 'blur' }],-->
-<!--        ip: [{ required: true, message: '请输入设备IP', trigger: 'blur' }],-->
-<!--        account: [{ required: true, message: '请输入账号', trigger: 'blur' }],-->
-<!--        password: [{ required: true, message: '请输入密码', trigger: 'blur' }]-->
-<!--      }-->
-<!--    }-->
-<!--  },-->
-<!--  methods: {-->
-<!--    generateDeviceId() {-->
-<!--      return 'DEV-' + Math.random().toString(36).substr(2, 9)-->
-<!--    },-->
-<!--    openAddDialog() {-->
-<!--      this.resetForm()-->
-<!--      this.dialogVisible = true-->
-<!--      this.$nextTick(() => { this.$refs.deviceForm && this.$refs.deviceForm.clearValidate() })-->
-<!--    },-->
-<!--    submitDevice() {-->
-<!--      this.$refs.deviceForm.validate((valid) => {-->
-<!--        if (!valid) return-->
-<!--        const newDevice = Object.assign({ deviceId: this.generateDeviceId() }, this.form)-->
-<!--        this.devices.push(newDevice)-->
-<!--        this.dialogVisible = false-->
-<!--        this.$message.success('新增设备成功')-->
-<!--      })-->
-<!--    },-->
-<!--    resetForm() {-->
-<!--      this.form = { model: '', location: '', ip: '', account: '', password: '' }-->
-<!--    },-->
-<!--    viewDevice(row) {-->
-<!--      this.$router.push({-->
-<!--        path: '/peopleInfoManage',-->
-<!--        query: { deviceId: row.deviceId }   // 推荐用 query 传参-->
-<!--      })-->
-<!--    }-->
-
-<!--  }-->
-<!--}-->
-<!--</script>-->
-
-<!--<style scoped>-->
-<!--.device-management { padding: 18px; background: #fff; border-radius: 6px; }-->
-<!--.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }-->
-<!--.dialog-footer { text-align: right; }-->
-<!--</style>-->

+ 565 - 190
src/views/doormanManage/infoManage/index.vue

@@ -1,168 +1,189 @@
 <template>
   <div class="dashboard">
-    <el-row :gutter="20">
-      <!-- 左上角:设备状态 -->
-      <el-col :span="12">
-        <el-card>
-          <el-tabs v-model=" chooseDevice" type="card" style="float:left;">
-            <el-tab-pane label="设备1" name="device1"></el-tab-pane>
-            <el-tab-pane label="设备2" name="device2"></el-tab-pane>
-          </el-tabs>
-          <el-button
-            v-if="!deviceStatus.ok"
-            type="danger"
-            size="mini"
-            style="float:right;"
-            @click="reconnectDevice">
-            重新连接
-          </el-button>
-          <div slot="header" class="clearfix">
-            <span>设备状态</span>
-          </div>
-          <div v-if="deviceStatus.ok" style="text-align:center; padding:20px;">
-            <el-result icon="success" title="运行正常"></el-result>
-          </div>
-          <div v-else style="text-align:center; padding:20px;">
-            <el-result icon="error" title="连接失败"></el-result>
-          </div>
+    <el-row :gutter="10">
+      <!-- 左侧内容 -->
+      <el-col :span="12" class="left-col">
+        <div class="left-wrapper">
+          <!-- 设备信息卡片 -->
+          <el-card class="flex-card device-card" shadow="hover">
+            <div slot="header">
+              <span>设备信息</span>
+            </div>
+            <!-- 加载中 -->
+            <el-skeleton v-if="deviceLoading" :rows="3" animated />
+            <!-- 无设备时 -->
+            <el-empty
+              v-else-if="devices.length === 0"
+              description="暂无设备,请检查设备连接或配置"
+            />
+            <!-- 有设备时 -->
+            <el-tabs
+              style="height: 100%"
+              v-else
+              v-model="chooseDevice"
+              type="card"
+              @tab-click="handleTabClick"
+            >
+              <el-tab-pane
+                style="height: 100%; width: 100%"
+                v-for="device in devices"
+                :key="device.id"
+                :label="device.name"
+                :name="device.id"
+              >
+                <el-row style="height: 150px; display: flex; align-items: center; margin-bottom: 10px;">
+                  <!-- 左侧设备信息 -->
+                  <el-col :span="14" style="height: 100%; display: flex; align-items: center; padding: 0 10px;">
+                    <div style="width: 100%; display: flex; flex-direction: column; justify-content: center; height: 100%;">
+                      <p><strong>设备位置:</strong>{{ device.location || '未配置' }}</p>
+                      <p><strong>设备 IP:</strong>{{ device.ip || '-' }}</p>
+                      <p><strong>设备端口:</strong>{{ device.port || '-' }}</p>
+                    </div>
+                  </el-col>
 
-        </el-card>
-      </el-col>
+                  <!-- 中间状态 -->
+                  <el-col :span="6" style="height: 100%; display: flex; align-items: center; justify-content: center;">
+                    <div style="height: 100%; display: flex; align-items: center; justify-content: center; width: 100%;">
+                      <el-result
+                        :icon="device.isConnect ? 'success' : 'error'"
+                        :title="device.isConnect ? '运行正常' : '连接失败'"
+                        style="width: 100%;"
+                      />
+                    </div>
+                  </el-col>
 
-      <!-- 右上角:远程控制 -->
-      <el-col :span="12">
-        <el-card>
-          <div slot="header" class="clearfix">
-            <span>远程控制</span>
-            <el-button type="primary" size="mini" style="float:right;" @click="toggleDoor">
-              {{ doorOpen ? '关门' : '开门' }}
-            </el-button>
-          </div>
-          <el-tabs v-model="activeDevice">
-            <el-tab-pane label="大门设备">
-              <div style="display: flex; gap: 10px;">
-                <div
-                  v-for="d in new_devices"
-                  :key="d.id"
-                  style="background:#000; width: 50%; height:200px; display:flex; align-items:center; justify-content:center; color:white;">
-                  {{ d.name }} 视频流
-                </div>
+                  <!-- 右侧按钮 -->
+                  <el-col :span="4" style="height: 100%; display: flex; align-items: center; justify-content: center;">
+                    <el-button type="danger" size="mini" @click="reconnectDevice(device)">
+                      重新连接
+                    </el-button>
+                  </el-col>
+                </el-row>
+                <!-- 视频流 -->
+                <el-row
+                  v-if="device && device.channels && device.channels.length > 0"
+                  style="height: 40%; display: flex; gap: 10px; margin-bottom: 10px;">
+                  <div
+                    v-for="ch in device.channels.slice(0, 3)"
+                    :key="ch.id"
+                    class="video-box"
+                    style="flex: 1; height: 180px; color: #fff; display: flex; align-items: center; justify-content: center;"
+                  >
+                    {{ ch.name }} 视频流
+                  </div>
+<!--                  <el-button-->
+<!--                    v-if="device.channels.length > 3"-->
+<!--                    type="text"-->
+<!--                    size="small"-->
+<!--                    @click="device.showAllChannels = !device.showAllChannels"-->
+<!--                  >-->
+<!--                    {{ device.showAllChannels ? '收起更多摄像头' : `查看更多摄像头(${device.channels.length - 2}个)` }}-->
+<!--                  </el-button>-->
+<!--                  <div v-if="device.showAllChannels" class="video-container" style="display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px;">-->
+<!--                    <div-->
+<!--                      v-for="ch in device.channels.slice(2)"-->
+<!--                      :key="ch.id"-->
+<!--                      class="video-box"-->
+<!--                      style="flex: 1; min-width: 200px; height: 180px; background: #000; color: #fff; display: flex; align-items: center; justify-content: center;"-->
+<!--                    >-->
+<!--                      {{ ch.name }} 视频流-->
+<!--                    </div>-->
+<!--                  </div>-->
+                </el-row>
+                <!-- 控制门-->
+                <el-row style="height: 40%; display: flex; gap: 10px; margin-top: 20px;">
+<!--                <el-row style="height: 30%; display: flex; justify-content: center; background-color: #8cb963;">-->
+                  <div
+                    v-if="device && device.doors && device.doors.length > 0"
+                    class="door-container"
+                    :class="{ 'multi-row': device.doors.length > 3 }"
+                  >
+                    <div
+                      v-for="door in device.doors"
+                      :key="door.doorId"
+                      class="door-wrapper"
+                    >
+                      <div class="door-label">门 {{ door.doorId }}</div>
+                      <!-- 道闸动画 -->
+                      <div class="barrier">
+                        <div class="bar" :class="{ open: door.status === 'open', closed: door.status === 'closed' }"></div>
+                      </div>
+                      <!-- 开关按钮 -->
+                      <el-button
+                        size="mini"
+                        type="primary"
+                        @click="toggleDoor(device, door)"
+                      >
+                        {{ door.status === 'open' ? '关门' : '开门' }}
+                      </el-button>
+                    </div>
+                  </div>
+                </el-row>
+              </el-tab-pane>
+            </el-tabs>
+          </el-card>
+          <!-- 人员信息 -->
+          <el-card class="flex-card people-card" shadow="hover">
+            <div slot="header">
+              <span>人员信息总览</span>
+            </div>
+            <div class="people-info">
+              <div class="info-block">
+                <img src="/resources/people1.jfif" class="info-icon" />
+                <p>人员总数</p>
+                <span>{{ peopleCount }}</span>
               </div>
-            </el-tab-pane>
-
-            <el-tab-pane label="机关楼设备">
-              <div style="display: flex; gap: 10px;">
-                <div
-                  v-for="d in new_devices"
-                  :key="d.id"
-                  style="background:#000; width: 50%; height:200px; display:flex; align-items:center; justify-content:center; color:white;">
-                  {{ d.name }} 视频流
-                </div>
+              <div class="info-block">
+                <img src="/resources/people1.jfif" class="info-icon" />
+                <p>内部人员总数</p>
+                <span>{{ registeredCount_people }}</span>
               </div>
-            </el-tab-pane>
-
-          </el-tabs>
+              <div class="info-block">
+                <img src="/resources/people1.jfif" class="info-icon" />
+                <p>临时访问人员总数</p>
+                <span>{{ unregisteredCount_people }}</span>
+              </div>
+            </div>
+          </el-card>
+        </div>
+      </el-col>
+      <!-- 右侧内容 -->
+      <el-col :span="12" class="right-col">
+        <el-card>
+          <div slot="header">
+            <span>实时事件</span>
+          </div>
+          <el-table
+            :data="events"
+            style="width: 100%;"
+            size="mini"
+            border
+            :header-cell-style="changeHeaderCellStyle"
+          >
+            <el-table-column prop="index" label="序号" min-width="50" align="center"/>
+            <el-table-column prop="personId" label="工号" min-width="50" align="center"/>
+            <el-table-column prop="name" label="姓名" min-width="50" align="center"/>
+            <el-table-column prop="carNum" label="卡号" min-width="100" align="center"/>
+            <el-table-column prop="doorNum" label="门" min-width="50" align="center"/>
+            <el-table-column prop="type" label="事件类型" min-width="100" align="center"/>
+            <el-table-column prop="time" label="时间" min-width="100" align="center"/>
+            <el-table-column prop="operate" label="操作" min-width="80" align="center"/>
+          </el-table>
+          <!-- 分页 -->
+          <el-pagination
+            class="custom-pagination"
+            style="margin-top: 20px; text-align: right;"
+            :current-page="currentPage"
+            :page-size="pageSize"
+            :total="total"
+            :page-sizes="[10, 15, 20, 25, 30]"
+            layout="total, prev, pager, next, jumper, sizes"
+            @current-change="handlePageChange"
+            @size-change="handleSizeChange"
+          />
         </el-card>
       </el-col>
     </el-row>
-
-<!--    <el-row :gutter="20" style="margin-top:20px;">-->
-<!--      &lt;!&ndash; 左下角:人员信息 &ndash;&gt;-->
-<!--      <el-col :span="12">-->
-<!--        <el-card>-->
-<!--          <div slot="header">人员信息</div>-->
-<!--          <p>内部人员:{{ people.internal }}</p>-->
-<!--          <p>临时访问:{{ people.temporary }}</p>-->
-<!--          <p>内部人脸:{{ faces.internal }}</p>-->
-<!--          <p>外部人脸:{{ faces.external }}</p>-->
-<!--        </el-card>-->
-<!--      </el-col>-->
-<!--    </el-row>-->
-    <el-col :span="12">
-    <el-card shadow="hover" class="people-card">
-      <h2 class="section-title">人员信息总览</h2>
-      <div class="people-info">
-        <div class="info-block">
-          <img src="/resources/people1.jfif" class="info-icon" />
-          <p>人员总数</p>
-          <span>{{ peopleCount }}</span>
-        </div>
-        <div class="info-block">
-<!--          <img src="/resources/face1.png" class="info-icon" />-->
-          <p>内部人员总数</p>
-          <span>{{ registeredCount_people }}</span>
-        </div>
-        <div class="info-block">
-<!--          <img src="/resources/face1.png" class="info-icon" />-->
-          <p>临时访问人员总数</p>
-          <span>{{ unregisteredCount_people }}</span>
-        </div>
-      </div>
-<!--      <el-row :gutter="20">-->
-<!--        <el-col :span="6">-->
-<!--          <el-card shadow="hover" class="overview-card">-->
-<!--            <img src="/public/resources/people1.jfif" class="card-img" />-->
-<!--            <div class="card-text">人员信息</div>-->
-<!--          </el-card>-->
-<!--        </el-col>-->
-
-<!--        <el-col :span="6">-->
-<!--          <el-card shadow="hover" class="overview-card">-->
-<!--            <img src="/public/resources/face1.png" class="card-img" />-->
-<!--            <div class="card-text">人脸信息</div>-->
-<!--          </el-card>-->
-<!--        </el-col>-->
-
-<!--        <el-col :span="6">-->
-<!--          <el-card shadow="hover" class="overview-card">-->
-<!--            <img src="/public/resources/device.png" class="card-img" />-->
-<!--            <div class="card-text">设备信息</div>-->
-<!--          </el-card>-->
-<!--        </el-col>-->
-
-<!--        <el-col :span="6">-->
-<!--          <el-card shadow="hover" class="overview-card">-->
-<!--            <img src="/public/resources/other.png" class="card-img" />-->
-<!--            <div class="card-text">其他信息</div>-->
-<!--          </el-card>-->
-<!--        </el-col>-->
-<!--      </el-row>-->
-    </el-card>
-<!--      <el-card shadow="hover" class="face-card">-->
-<!--        <h2 class="section-title">人脸信息总览</h2>-->
-<!--        <div class="face-info">-->
-<!--          <div class="info-block">-->
-<!--            <img src="/resources/face1.png" class="info-icon" />-->
-<!--            <p>人员总数</p>-->
-<!--            <span>{{ faceCount }}</span>-->
-<!--          </div>-->
-<!--          <div class="info-block">-->
-
-<!--            <p>内部人脸总数</p>-->
-<!--            <span>{{ registeredCount_face }}</span>-->
-<!--          </div>-->
-<!--          <div class="info-block">-->
-<!--&lt;!&ndash;            <img src="/resources/face1.png" class="info-icon" />&ndash;&gt;-->
-<!--            <p>临时访问人脸总数</p>-->
-<!--            <span>{{ unregisteredCount_face }}</span>-->
-<!--          </div>-->
-<!--        </div>-->
-<!--      </el-card>-->
-    </el-col>
-
-    <!--      &lt;!&ndash; 右下角:实时事件 &ndash;&gt;-->
-    <el-col :span="12">
-      <el-card>
-        <div slot="header">实时事件</div>
-        <el-table :data="events" size="mini" height="400" border>
-          <el-table-column prop="eventTime" label="时间" width="150"/>
-          <el-table-column prop="name" label="姓名" width="100"/>
-          <el-table-column prop="personId" label="人员id" width="120" />
-          <el-table-column prop="eventType" label="事件类型"/>
-        </el-table>
-      </el-card>
-    </el-col>
-
   </div>
 </template>
 
@@ -173,47 +194,258 @@ export default {
   name: "Dashboard",
   data() {
     return {
-      deviceStatus: {
-        device1: { ok: true },
-        device2: { ok: false }
-      },
-      doorOpen: false,
-      chooseDevice: "device1",
-      activeDevice: '1',
+      deviceLoading: false,
       devices: [
-        { id: '1', name: '设备1' },
-        { id: '2', name: '设备2' }
-      ],
-      new_devices: [
-        { id: '1', name: '进门监控' },
-        { id: '2', name: '出门监控' }
+        {
+          id: '1',
+          name: '摆闸A',             // 双门,2个摄像头
+          location: '大厅入口',
+          ip: '192.168.1.10',
+          port: 8000,
+          isConnect: true,
+          doors: [
+            { doorId: 1, status: 'closed' },
+          ],
+          channels: [
+            { id: 'ch1', name: '左门摄像头' },
+          ],
+        },
+        {
+          id: '2',
+          name: '摆闸B',             // 单门,1个摄像头
+          location: '侧门出口',
+          ip: '192.168.1.11',
+          port: 8001,
+          isConnect: false,
+          doors: [
+            { doorId: 1, status: 'closed' },
+            { doorId: 2, status: 'open' },
+          ],
+          channels: [
+            { id: 'ch1', name: '单门摄像头' },
+            { id: 'ch2', name: '右门摄像头' },
+          ],
+        },
+        {
+          id: '3',
+          name: '摆闸C',             // 三门,3个摄像头
+          location: '地下停车场入口',
+          ip: '192.168.1.12',
+          port: 8002,
+          isConnect: true,
+          doors: [
+            { doorId: 1, status: 'closed' },
+            { doorId: 2, status: 'closed' },
+            { doorId: 3, status: 'open' },
+          ],
+          channels: [
+            { id: 'ch1', name: '左门摄像头' },
+            { id: 'ch2', name: '中门摄像头' },
+            { id: 'ch3', name: '右门摄像头' },
+          ],
+        },
+        {
+          id: '4',
+          name: '摆闸D',
+          code: 'D',
+          type: 'carDoor|peopleDoor',
+          location: '大厅监控点',
+          ip: '192.168.1.20',
+          port: 8554,
+          account: 'admin',
+          password: '123456',
+          isConnect: true,
+          doors: [
+            { doorId: 1, status: 'closed' },
+            { doorId: 2, status: 'closed' },
+            { doorId: 3, status: 'open' },
+            { doorId: 4, status: 'open' },
+          ],
+          channels: [
+            { id: 'ch1', name: '左门摄像头' },
+            { id: 'ch2', name: '中门摄像头' },
+            { id: 'ch3', name: '右门摄像头' },
+            { id: 'ch4', name: '大厅摄像头' },
+          ],
+          createTime: '2021-01-01 00:00:00',
+          updateTime: '2021-01-01 00:00:00',
+        },
       ],
+      chooseDevice: '1',
+
       people: { internal: 10, temporary: 2 },
       faces: { internal: 8, external: 5 },
 
-      events: [
-      ],
-      // total: 0,
+      events: [],
+      currentPage: 1,
+      pageSize: 10,
+      total: 0,
+
       peopleCount: 0,
       registeredCount_people: 0,
       unregisteredCount_people: 0,
       registeredCount_face: 0,
       unregisteredCount_face: 0,
+
+      deviceTypeOptions: [
+        { label: "网络摄像机 (IPC)", value: "IPC" },
+        { label: "人脸门禁终端 (MINMOE)", value: "MINMOE" },
+        { label: "车牌识别一体机 (LPR_GATE)", value: "LPR_GATE" },
+        { label: "通道控制设备 / 摆闸 (TURNSTILE)", value: "TURNSTILE" }
+      ],
     }
   },
   created() {
     // 页面加载时自动拉取第一页数据
-    this.fetchData()
+    this.fetchEventsData()
     this.peopleData()
     this.fetchPeople()
+    // this.fetchDevices()
   },
   methods: {
-    fetchData() {
+    fetchDoorStatus(device) {
+      request({
+        url: `/door/status/${device.id}`,
+        method: 'GET'
+      }).then(res => {
+        // 假设 res 返回 doors 数组,每个门有 doorId 和 status
+        device.doors = res.doors.map(d => ({
+          ...d,
+          loading: false // 操作状态初始化为 false
+        }));
+      });
+    },
+    toggleDoor(device, door) {
+      if (door.loading) return; // 防止连点
+      // door.loading = true;
+      // // 调用后端接口控制门
+      // request({
+      //   url: `/door/${device.id}/${door.doorId}/${door.status === 'open' ? 'close' : 'open'}`,
+      //   method: 'POST'
+      // })
+      //   .then(() => {
+      //     this.$message.success(`已发送操作到门 ${door.doorId}`);
+      //     // 等待后端状态刷新,这里可以选择轮询或者短延时刷新
+      //     setTimeout(() => this.fetchDoorStatus(device), 500);
+      //   })
+      //   .catch(() => {
+      //     this.$message.error(`操作门 ${door.doorId} 失败`);
+      //   })
+      //   .finally(() => {
+      //     door.loading = false;
+      //   });
+
+      // console.log(`操作设备device ${device}`);
+      // console.log(`操作设备door ${door}`);
+      console.log(`操作设备 ${device.name} 的门 ${door.doorId}`);
+      console.log(`门之前的状态 ${door.status}`);
+      const newStatus = door.status === 'open' ? 'closed' : 'open';
+      this.$set(door, 'status', newStatus);
+      console.log(`门之后的状态 ${door.status}`);
+    },
+    // 获取设备列表
+    async fetchDevices() {
+      this.deviceLoading = true
+      try {
+        const res = await request({
+          url: '/device/list',
+          method: 'GET'
+        })
+        console.log("设备信息", res)
+
+        this.devices = res || []
+
+        // 如果有设备,默认选中第一个
+        if (this.devices.length > 0) {
+          this.chooseDevice = this.devices[0].id
+        } else {
+          this.chooseDevice = ''
+        }
+      } finally {
+        this.deviceLoading = false
+      }
+    },
+    handleTabClick(tab) {
+      console.log("切换到设备:", tab)
+      console.log("切换到设备:", tab.name)
+      const selectedDevice = this.devices.find(d => d.id === tab.name);
+      console.log("选中的设备对象:", selectedDevice);
+
+      // // 1 获取设备连接状态
+      // this.getDeviceStatus(selectedDevice.id);
+      //
+      // // 2 获取门禁通道状态
+      // this.getDoorStatus(selectedDevice.id);
+      //
+      // // 3 获取摄像头视频流并展示
+      // this.loadDeviceCameras(selectedDevice.id);
+    },
+    async getDeviceStatus(deviceId) {
+      try {
+        const res = await request({
+          url: `/hikvision/device/status`,
+          method: "GET",
+          params: { id: deviceId },
+        });
+        this.deviceStatus = res.data; // 保存状态信息
+        console.log("设备连接状态:", res.data);
+      } catch (e) {
+        console.error("获取设备状态失败:", e);
+      }
+    },
+    async getDoorStatus(deviceId) {
+      try {
+        const res = await request({
+          url: `/hikvision/door/status`,
+          method: "GET",
+          params: { deviceId },
+        });
+        this.doorStatusList = res.data; // 例:[{doorId:1, state:"open"}, ...]
+        console.log("门禁通道状态:", this.doorStatusList);
+      } catch (e) {
+        console.error("获取门状态失败:", e);
+      }
+    },
+    async loadDeviceCameras(deviceId) {
+      try {
+        const res = await request({
+          url: `/hikvision/video/streams`,
+          method: "GET",
+          params: { deviceId },
+        });
+        this.videoStreams = res.data; // 例:[ { ch:1, url:"rtsp://..." }, ... ]
+        console.log("摄像头流信息:", this.videoStreams);
+
+        // 更新播放器展示
+        this.refreshVideoDisplay(this.videoStreams);
+      } catch (e) {
+        console.error("获取视频流失败:", e);
+      }
+    },
+    refreshVideoDisplay(streams) {
+      // 示例:展示前3路
+      this.displayStreams = streams.slice(0, 3);
+    },
+    reconnectDevice(device) {
+      console.log("重新连接设备:", device.name)
+      // 调用你的后端接口进行重连操作
+    },
+    // 格式化设备类型
+    formatDeviceType(row) {
+      console.log("设备类型", row)
+      const map = {
+        IPC: "网络摄像机",
+        MINMOE: "人脸门禁终端",
+        LPR_GATE: "车牌识别一体机 / 车闸",
+        TURNSTILE: "摆闸 / 翼闸 / 三辊闸",
+      };
+      return map[row.deviceType] || row.deviceType || "-";
+    },
+    fetchEventsData() {
       request({
         url: "/event/list",
         method: "get",
       }).then(res => {
-
         this.events = res.records || []
         console.log("数据",res)
         console.log("总条数",res.total)
@@ -232,15 +464,6 @@ export default {
 
       })
     },
-    reconnectDevice() {
-      // TODO: 调后端接口
-      this.deviceStatus.ok = true
-    },
-    toggleDoor() {
-      // TODO: 调后端接口
-      this.doorOpen = !this.doorOpen
-    },
-
     fetchPeople() {
       this.loading = true
       request({
@@ -265,7 +488,26 @@ export default {
         this.unregisteredCount_people = data.filter(p => p.isTemporary  === true).length
         console.log("内部人员:", this.registeredCount_people)
 
-      }).finally(() => { this.loading = false })
+      }).finally(() => {
+          this.loading = false
+      })
+    },
+    // 表格样式修改
+    changeHeaderCellStyle(row, column, rowIndex, columnIndex) {
+      if (row.columnIndex === 0) {
+        return "background: #004279 ; color:#fff;"; // 修改的样式
+      } else {
+        return "background: #004279 ;color:#fff; ";
+      }
+    },
+    handlePageChange(page) {
+      this.currentPage = page;
+      this.fetchEventsData()();
+    },
+    handleSizeChange(size) {
+      this.currentPage = 1;
+      this.pageSize = size;
+      this.fetchEventsData()();
     },
   }
 }
@@ -273,12 +515,145 @@ export default {
 
 <style scoped>
 .dashboard {
-  padding: 20px;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+.el-row {
+  flex: 1; /* 让 row 自动填满 dashboard 剩余高度 */
+  display: flex;
+}
+
+.el-col {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.left-col,
+.right-col {
+  flex: 1; /* 各占一半 */
+  height: 100%;
+  overflow: auto; /* 内容多时可滚动 */
+}
+
+
+.left-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  gap: 10px; /* 卡片间距 */
+}
+
+.flex-card.device-card {
+  flex: 3; /* 上半部分占 2/3 */
+}
+
+.flex-card.people-card {
+  flex: 1; /* 下半部分占 1/3 */
+}
+
+.video-container {
+  display: flex;
+  flex-wrap: wrap;  /* 自动换行 */
+  gap: 10px;
+  justify-content: center;
+}
+
+.video-box {
+  flex: 1 1 200px;    /* 最小宽 200px,可扩展 */
+  max-width: 300px;   /* 限制最大宽度 */
+  height: 180px;
+  background: #000;
+  color: #fff;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  text-align: center;
 }
+
+.people-info {
+  display: flex;
+  justify-content: space-around;
+  margin-top: 10px;
+}
+
+.info-block {
+  text-align: center;
+}
+
 .info-icon {
-  width: 60px;         /* 图片宽度 */
-  height: 60px;        /* 图片高度 */
-  object-fit: contain; /* 保持比例 */
-  margin-bottom: 8px;  /* 图片和文字之间的间距 */
+  width: 60px;
+  height: 60px;
+  object-fit: contain;
+  margin-bottom: 8px;
+}
+
+
+.door-container {
+  display: flex;           /* 必须加上 flex */
+  justify-content: center; /* 水平居中 */
+  gap: 10px;               /* 门之间的间距 */
+  margin-bottom: 10px;
+  flex-wrap: nowrap;       /* 默认不换行 */
+}
+
+.door-container.multi-row {
+  flex-wrap: wrap;         /* 超过三个门时换行 */
+}
+
+.door-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 180px;  /* 固定宽度 */
+  height: 180px; /* 固定高度 */
+  justify-content: space-between; /* 顶部门名,中间bar,底部按钮均分 */
+  background-color: #f0f0f0; /* 可选背景色 */
+  border-radius: 6px;        /* 可选圆角 */
+  padding: 10px;
+}
+
+.door-label {
+  margin-top: 80px;
+  font-weight: bold;
+  margin-bottom: 5px;
+  font-size: 18px;
+}
+
+/* 模拟道闸底座 */
+.barrier {
+  width: 100%;        /* 自适应门宽度 */
+  max-width: 150px;   /* 最大横杆宽度 */
+  height: 20px;
+  background-color: #444;
+  position: relative;
+  border-radius: 4px;
+  margin-bottom: 5px;
+}
+
+/* 横杆 */
+.bar {
+  width: 100%;        /* 随barrier宽度 */
+  height: 8px;
+  background-color: #2aabd2;
+  position: absolute;
+  top: 6px;
+  left: 0;
+  transform-origin: left center;
+  transition: transform 0.8s ease;
+  border-radius: 4px;
+}
+
+/* 开关状态 */
+.bar.closed {
+  transform: rotate(0deg);
+}
+
+.bar.open {
+  transform: rotate(-60deg);
 }
 </style>

+ 117 - 32
src/views/doormanManage/peopleInfoManage/index.vue

@@ -1,21 +1,37 @@
 <template>
-  <div class="people-info-management">
-    <!-- 顶部操作 -->
-    <div class="header">
-      <h2>人员信息管理</h2>
-      <el-select v-model="selectedDeviceId" placeholder="选择设备" @change="fetchPeople">
+  <div style="padding: 10px">
+    <!-- 搜索和新增 -->
+    <div style="height: 50px; display: flex; align-items: center; gap: 10px">
+      <el-select
+        v-model="selectedDeviceId"
+        placeholder="选择设备"
+        size="small"
+        style="width: 130px"
+        clearable
+        @change="handleSearch"
+      >
         <el-option
+          style="color: black"
           v-for="device in devices"
           :key="device.id"
-          :label="device.model + ' - ' + device.location"
+          :label="device.name"
           :value="device.id"
         />
       </el-select>
-      <el-button type="primary" @click="openAddDialog">新增人员</el-button>
-      <el-input v-model="searchName" placeholder="按姓名搜索" style="width: 180px; margin-left: 10px;" />
-      <el-button @click="fetchPeople">搜索</el-button>
+      <!-- 输入框:回车自动查询 -->
+      <el-input
+        v-model="searchName"
+        placeholder="人员名称"
+        size="small"
+        style="width: 150px"
+        clearable
+        @keyup.enter.native="handleSearch"
+      />
+      <el-button type="primary" size="small" @click="handleSearch">查询</el-button>
+      <el-button type="primary" size="small" @click="handleAdd">新增</el-button>
+<!--      <el-button @click="fetchPeople">搜索</el-button>-->
+<!--      <el-button type="primary" @click="openAddDialog">新增人员</el-button>-->
     </div>
-
     <!-- 人员列表 -->
     <el-table
       :data="peopleList"
@@ -156,35 +172,104 @@ export default {
     }
   },
   mounted() {
-    this.fetchDevices()
+    // this.fetchDevices()
+    this.getDeviceNameInfo()
   },
   methods: {
+    handleSearch(){
+      this.currentPage = 1;
+      this.getDevicePeopleInfo();
+    },
+    handleAdd(){
 
-    fetchDevices() {
-      this.loading = true;
-      request({
-        url: '/device/list',
-        method: 'GET'
-      }).then(res => {
-        console.log("设备接口原始返回:", res)
-
-        // 因为返回本身就是数组
-        this.devices = Array.isArray(res) ? res : (res.rows || res.data || [])
+    },
+    async getDevicePeopleInfo() {
+      try {
+        // 发起分页+查询请求
+        const res = await request({
+          url: "/peopleDoor/listPeopleInfo",
+          method: "POST",
+          data: {
+            page: this.currentPage,
+            size: this.pageSize,
+            deviceId: this.selectedDeviceId
+          },
+        });
+
+        // 判断返回结构
+        if (res && res.list) {
+          this.peopleInfo = res.list;
+          this.total = res.total || 0;
+        } else {
+          this.peopleInfo = [];
+          this.total = 0;
+          this.$message.warning("未获取到视频数据");
+        }
+      } catch (error) {
+        console.error("获取视频信息失败:", error);
+        this.$message.error("获取视频列表失败,请检查网络或后端接口!");
+      }
+    },
+    async getDeviceNameInfo() {
+      try {
+        this.loading = true
+        const res = await request({
+          url: "/device/listDeviceName",
+          method: "GET",
+        });
+
+        // 判断返回结构
+        if (res && res.list) {
+          this.devices = res.list;
+          this.total = res.total || 0;
+          console.log("设备列表:", this.devices);
+
+          if (this.devices.length > 0) {
+            this.selectedDeviceId = this.devices[0].id
+            console.log("选中的设备ID:", this.selectedDeviceId)
+
+            // 获取该设备的人员信息
+            this.getDevicePeopleInfo()
+          }
 
-        console.log("解析后的设备列表:", this.devices)
 
-        if (this.devices.length > 0) {
-          this.selectedDeviceId = this.devices[0].id
-          console.log("选中的设备ID:", this.selectedDeviceId)
-          this.fetchPeople()
         } else {
-          console.warn("⚠️ 没有设备数据,无法调用 fetchPeople")
+          this.devices = [];
+          this.total = 0;
+          this.$message.warning("未获取到设备数据");
         }
-      }).catch(err => {
-        console.error("获取设备列表失败:", err)
-        this.devices = []
-      })
+      } catch (error) {
+        console.error("获取设备信息失败:", error);
+        this.$message.error("获取设备列表失败,请检查网络或后端接口!");
+      } finally {
+        this.loading = false
+      }
     },
+    // fetchDevices() {
+    //   this.loading = true;
+    //   request({
+    //     url: '/device/list',
+    //     method: 'GET'
+    //   }).then(res => {
+    //     console.log("设备接口原始返回:", res)
+    //
+    //     // 因为返回本身就是数组
+    //     this.devices = Array.isArray(res) ? res : (res.rows || res.data || [])
+    //
+    //     console.log("解析后的设备列表:", this.devices)
+    //
+    //     if (this.devices.length > 0) {
+    //       this.selectedDeviceId = this.devices[0].id
+    //       console.log("选中的设备ID:", this.selectedDeviceId)
+    //       this.fetchPeople()
+    //     } else {
+    //       console.warn("⚠️ 没有设备数据,无法调用 fetchPeople")
+    //     }
+    //   }).catch(err => {
+    //     console.error("获取设备列表失败:", err)
+    //     this.devices = []
+    //   })
+    // },
     fetchPeople() {
       if (!this.selectedDeviceId) return
       this.loading = true
@@ -284,7 +369,7 @@ export default {
           method: 'DELETE',
         })
         this.$message.success('删除人员成功')
-        this.fetchDevices()
+        // this.fetchDevices()
 
       } catch (e) {
         this.$message.error('删除失败')

+ 182 - 207
src/views/monitoring/components/hk/monitorHK.vue

@@ -6,7 +6,7 @@
   ></div>
 </template>
 <script>
-import { isInteger } from "lodash";
+
 export default {
   name: "monitorHK",
   props: {
@@ -33,10 +33,6 @@ export default {
       type: Number,
       required: true,
     },
-    chooseData: {
-      type: Object,
-      required: true,
-    },
     isFullScreen: Boolean,
   },
   data() {
@@ -78,14 +74,6 @@ export default {
     if (this.resizeTimeout) clearTimeout(this.resizeTimeout);
   },
   watch: {
-    chooseData: {
-      handler(val, oldval) {
-        // console.log("chooseData666:", this.chooseData)
-        // console.log("chooseData6662:", val, oldval)
-        // console.log("chooseData666:", this.chooseData)
-        // console.log("chooseData6662:", val, oldval)
-      },
-    },
     num: {
       handler(val) {
         console.log("监控num值的变化:", val);
@@ -112,29 +100,29 @@ export default {
         // });
       },
     },
-    companyVideoData: {
-      handler(val) {
-        console.log("companyVideoData:", val);
-        const layoutMap = [
-          { max: 1, num: 1, wnd: 1 },
-          { max: 4, num: 4, wnd: 2 },
-          { max: 9, num: 9, wnd: 3 },
-          { max: 16, num: 16, wnd: 4 },
-        ];
-
-        let layout =
-          layoutMap.find((l) => val.length <= l.max) ||
-          layoutMap[layoutMap.length - 1];
-
-        this.windowNum = layout.num;
-        console.log("开始分屏companyVideoData", layout.wnd);
-        WebVideoCtrl.I_ChangeWndNum(layout.wnd);
-
-        this.$emit("button-value", this.windowNum);
-      },
-      // immediate: true, //关键
-      // deep: true,
-    },
+    // companyVideoData: {
+    //   handler(val) {
+    //     console.log("companyVideoData:", val);
+    //     const layoutMap = [
+    //       { max: 1, num: 1, wnd: 1 },
+    //       { max: 4, num: 4, wnd: 2 },
+    //       { max: 9, num: 9, wnd: 3 },
+    //       { max: 16, num: 16, wnd: 4 },
+    //     ];
+    //
+    //     let layout =
+    //       layoutMap.find((l) => val.length <= l.max) ||
+    //       layoutMap[layoutMap.length - 1];
+    //
+    //     this.windowNum = layout.num;
+    //     console.log("开始分屏companyVideoData", layout.wnd);
+    //     WebVideoCtrl.I_ChangeWndNum(layout.wnd);
+    //
+    //     this.$emit("button-value", this.windowNum);
+    //   },
+    //   // immediate: true, //关键
+    //   // deep: true,
+    // },
     videoType: {
       handler(val, oldval) {
         if (oldval) {
@@ -334,79 +322,141 @@ export default {
       return loginParams;
     },
     async login(loginParams) {
-      console.log("开始登录设备", loginParams);
-      const that = this
-      // for (const loginParam of Object.values(loginParams)) {
-      //   await that.doLogin(loginParam)
-      // }
-      await Promise.all(
+      // console.log("开始登录设备", loginParams);
+      // 将 Object.values(loginParams) 转成 Promise 数组
+      const results = await Promise.all(
         Object.values(loginParams).map(async (loginParam) => {
-          await that.doLogin(loginParam);
-        }),
+          try {
+            await this.doLogin(loginParam);
+            return { ip: loginParam.ip, port: loginParam.port, success: true };
+          } catch (err) {
+            console.error("登录失败:", loginParam, err);
+            return { ip: loginParam.ip, port: loginParam.port, success: false, errorCode: err };
+          }
+        })
       );
+
+      // 这里返回每台设备登录结果,包括失败码
+      return results;
     },
     async doLogin(loginParam) {
+      const that = this;
       console.log("登录设备信息", loginParam);
-      const iRet = WebVideoCtrl.I_Login(
-        loginParam.ip, // szIP 设备的 IP 地址或者普通域名
-        1, // iPrototocol http 协议,1 表示 http 协议 2 表示 https 协议
-        loginParam.Port, // iPort 登录设备的 http/https 端口号,根据 iPrototocol 选择传入不同的端口
-        loginParam.Username, // szUserName 登录用户名称
-        loginParam.Password, // szPassword 用户密码
-        {
-          timeout: 1000,
-          success: function(xmlDoc) { // 成功回调函数
-            console.log(loginParam.ip + '_' + loginParam.Port + '登录成功!')
-            setTimeout(function () {
-              this.getChannelInfo(loginParam.ip);
-              this.getDevicePort(loginParam.ip);
-            }, 10);
-          },
-          error: function(status, xmlDoc) { // 失败回调函数
-            console.log(loginParam.ip + '_' + loginParam.Port + '登录失败!', status, xmlDoc)
+      return new Promise((resolve, reject) => {
+        const iRet = WebVideoCtrl.I_Login(
+          loginParam.ip, // szIP 设备的 IP 地址或者普通域名
+          1, // iPrototocol http 协议,1 表示 http 协议 2 表示 https 协议
+          loginParam.port, // iPort 登录设备的 http/https 端口号,根据 iPrototocol 选择传入不同的端口
+          loginParam.username, // szUserName 登录用户名称
+          loginParam.password, // szPassword 用户密码
+          {
+            timeout: 1000,
+            success: function(xmlDoc) { // 成功回调函数
+              console.log(`${loginParam.ip}_${loginParam.port} 登录成功!`);
+              // 用 setTimeout 模拟异步处理
+              setTimeout(() => {
+                // 登录成功后再获取通道等信息
+                that.getChannelInfo(loginParam.ip);
+                that.getDevicePort(loginParam.ip);
+                resolve(true);
+              }, 10);
+            },
+            error: function(status, xmlDoc) { // 失败回调函数
+              console.error(`${loginParam.ip}_${loginParam.port} 登录失败!`, status, xmlDoc);
+              reject(status);
+            }
           }
+        )
+          // .catch((error) => {
+          // // 捕获错误并获取错误代码
+          // const errorCode = error.errorCode
+          // // console.log(errorCode);
+          // if (errorCode == 2001) {
+          //   //继续播放
+          //   this.$emit('handlePlayError', '2001')
+          //   that.doGetChannelInfo(loginParam.ip).then((channels) => {
+          //     var s = JSON.stringify(channels)
+          //     console.log(
+          //       'tezt',
+          //       JSON.stringify(channels),
+          //       channels.length,
+          //       channels[0],
+          //       channels[0].channelId,
+          //       channels[0]['channelId']
+          //     )
+          //     // console.log("🚀 通道列表 >> ", loginParam.ip, channels, channels.length, s[0]["channelName"]);
+          //     var channelId = that.getChannelIdByName(
+          //       loginParam.channelName,
+          //       channels
+          //     )
+          //     console.log('🚀 获得id >> ', channelId)
+          //     if (channelId !== null) {
+          //       console.log('🚀 获得id >> ', channelId)
+          //       that.startPlay(loginParam.ip, channelId)
+          //     } else {
+          //       console.log(
+          //         '🚀 通道号无效 >> ',
+          //         loginParam.ip,
+          //         loginParam.channelName
+          //       )
+          //     }
+          //   });
+          // }
+        // })
+
+        // 如果返回 -1 表示已经登录过
+        if (iRet === -1) {
+          console.log(`${loginParam.ip}_${loginParam.port} 已登录过!`);
+          resolve(true);
         }
-      )
-        // .catch((error) => {
-        // // 捕获错误并获取错误代码
-        // const errorCode = error.errorCode
-        // // console.log(errorCode);
-        // if (errorCode == 2001) {
-        //   //继续播放
-        //   this.$emit('handlePlayError', '2001')
-        //   that.doGetChannelInfo(loginParam.ip).then((channels) => {
-        //     var s = JSON.stringify(channels)
-        //     console.log(
-        //       'tezt',
-        //       JSON.stringify(channels),
-        //       channels.length,
-        //       channels[0],
-        //       channels[0].channelId,
-        //       channels[0]['channelId']
-        //     )
-        //     // console.log("🚀 通道列表 >> ", loginParam.ip, channels, channels.length, s[0]["channelName"]);
-        //     var channelId = that.getChannelIdByName(
-        //       loginParam.channelName,
-        //       channels
-        //     )
-        //     console.log('🚀 获得id >> ', channelId)
-        //     if (channelId !== null) {
-        //       console.log('🚀 获得id >> ', channelId)
-        //       that.startPlay(loginParam.ip, channelId)
-        //     } else {
-        //       console.log(
-        //         '🚀 通道号无效 >> ',
-        //         loginParam.ip,
-        //         loginParam.channelName
-        //       )
-        //     }
-        //   });
-        // }
-      // })
+      });
+    },
+    // 播放某个视频到指定窗口
+    async playVideoInSelectWindow(video) {
+      const that = this;
+      console.log("开始播放", video);
 
-      if (-1 == iRet) {
-        console.log(loginParam.ip + '_' + loginParam.Port + '已登录过!')
-      }
+      return new Promise((resolve, reject) => {
+        WebVideoCtrl.I_StartRealPlay(video.ip, {
+          iWndIndex: video.iWndIndex, // 播放窗口
+          iStreamType: 1,             // 主码流
+          iChannelID: video.channelId,// 通道号
+          bZeroChannel: false,        // 是否播放零通道
+          success: function () {
+            console.log(`窗口 ${video.iWndIndex} 播放 ${video.ip} 成功`);
+            // 更新 windowList
+            if (!video.windowList) video.windowList = [];
+            if (!video.windowList.includes(video.iWndIndex)) {
+              video.windowList.push(video.iWndIndex);
+            }
+            that.$emit("handleVideoPlay", "playSuccess");
+            that.$emit("init", true);
+            resolve(true);
+          },
+          error: function (oError) {
+            console.error(`窗口 ${video.iWndIndex} 播放 ${video.ip} 失败`, oError);
+            WebVideoCtrl.I_Stop();
+            that.$emit("handleVideoPlay", "playFail");
+            reject(oError);
+          },
+        });
+          // .then(() => {
+          //   that.$emit("handleVideoPlay", "bofangchenggong");
+          //   that.$emit("init", true);
+          // })
+          // .catch((error) => {
+          //   // 捕获错误并获取错误代码
+          //   const errorCode = error.errorCode;
+          //   console.log("errorCode", errorCode);
+          //   if (errorCode == 3001) {
+          //     // 播放失败
+          //     this.$emit("handleVideoPlay", "3001");
+          //   }
+          //   if (errorCode == 1000) {
+          //     this.$emit("handleVideoPlay", "1000");
+          //   }
+          // });
+      });
     },
     getChannelIdByName(channelName, channels) {
       const channel = channels.find((channel) => {
@@ -678,82 +728,22 @@ export default {
         iChannelID: channelId, // 播放通道号,默认通道1
         bZeroChannel: false,// 是否播放零通道,默认为false
         success: function () {
-          that.$emit("handleVideoPlay", "bofangchenggong");
+          that.$emit("handleVideoPlay", "playSuccess");
         },
         error: function (oError) {
-          that.$emit("handleVideoPlay", "bofangshibai");
+          that.$emit("handleVideoPlay", "playFail");
           WebVideoCtrl.I_Stop();
         },
       })
       .then(() => {
-        that.$emit("handleVideoPlay", "bofangchenggong");
+        that.$emit("handleVideoPlay", "playSuccess");
         that.$emit("init", true);
       })
       .catch((error) => {
         // 捕获错误并获取错误代码
         const errorCode = error.errorCode;
         console.log("errorCode", errorCode);
-        if (errorCode == 3001) {
-          // 播放失败
-          this.$emit("handleVideoPlay", "3001");
-        }
-        if (errorCode == 1000) {
-          this.$emit("handleVideoPlay", "1000");
-        }
-      });
-    },
-    clickLogin() {
-      return new Promise((resolve) => {
-        const uniqueArr = [];
-        const uniqueSet = new Set();
-        this.companyVideoData.forEach((obj) => {
-          if (obj !== undefined) {
-            const key = JSON.stringify(obj);
-            if (!uniqueSet.has(key)) {
-              uniqueSet.add(key);
-              uniqueArr.push(obj);
-            }
-          }
-        });
-        console.log(uniqueArr);
-        for (let index = 0; index < uniqueArr.length; index++) {
-          this.singleLogin(uniqueArr[index], index);
-        } // 完成登录操作后调用resolve
-        resolve();
-      });
-    },
-    singleLogin(Data, index) {
-      setTimeout(() => {
-        var that = this;
-        WebVideoCtrl.I_Login(
-          Data.ip,
-          1,
-          Data.Port,
-          Data.Username,
-          Data.Password,
-          {
-            timeout: 1000,
-            async: false,
-            success: function (xmlDoc) {
-              console.log("开始预览"); //不能删除
-              let oChannels = that.getChannelInfo();
-              that.initPlay(Data, index, oChannels);
-            },
-            error: function () {
-              console.log("login error");
-            },
-          },
-        );
-      }, 100);
-    },
-    // 初始化视频,为了让用户进来就可以看到视频播放
-    initPlay(data, index, oChannels) {
-      let szIP = data.ip; // ip地址
-      // 循环16次是因为插件分屏最大为4x4(可以根据情况而定)
-      WebVideoCtrl.I_StartRealPlay(szIP, {
-        iStreamType: 1,
-        iChannelID: oChannels.id, // 按格式修改
-        iWndIndex: index,
+        this.$emit("handleVideoPlay", "playFail");
       });
     },
     // 点击查看具体哪个监控
@@ -771,64 +761,49 @@ export default {
         iStreamType: iStreamType,
         iChannelID: iChannelID,
         success: function () {
-          that.$emit("handleVideoPlay", "bofangchenggong");
+          that.$emit("handleVideoPlay", "playSuccess");
         },
         error: function (oError) {
-          that.$emit("handleVideoPlay", "bofangshibai");
+          that.$emit("handleVideoPlay", "playSuccess");
           WebVideoCtrl.I_Stop();
         },
       });
     },
-    // 停止预览全部
-    clickStopAllRealPlay() {
+    // 停止停止全部窗口播放视频
+    stopAllRealPlay() {
+      const that = this;
       WebVideoCtrl.I_StopAll().then(() => {
-        console.log("停止预览全部成功");
+        that.$emit("handleVideoPlay", { code: "stopAllSuccess" });
       }).catch(() => {
-        console.log("停止预览全部失败");
-      })
-    },
-    stopPlayVideo() {
-      // 停止播放一个视频
-      let that = this;
-      WebVideoCtrl.I_Stop({
-        iWndIndex: this.dangqianchuangkou,
-        success: function () {
-          that.$emit("handleVideoPlay", "stoponechenggong");
-        },
-        error: function (oError) {
-          that.$emit("handleVideoPlay", "stoponeshibai");
-        },
+        that.$emit("handleVideoPlay", { code: "stopAllFail" });
       });
     },
-    stopallPlayVideo(a) {
-      // 停止播放多个视频
-      this.$emit("handleVideoPlay", "stopmore");
+    // 停止指定窗口播放视频
+    stopOneWindowPlayVideo(iWndIndex) {
       let that = this;
-      let stopCount = 0;
-      let totalCount = a.length;
-      a.forEach(function (element) {
+      return new Promise((resolve) => {
         WebVideoCtrl.I_Stop({
-          iWndIndex: parseInt(element) - 1,
-          success: function () {
-            stopCount++;
-            that.$emit("handleVideoPlay", "stopmorechenggong" + element);
-
-            // 当所有视频都停止后,触发一次统一的事件
-            if (stopCount === totalCount) {
-              that.$emit("handleVideoPlay", "stopallchenggong");
-            }
-          },
-          error: function (oError) {
-            stopCount++;
-            that.$emit("handleVideoPlay", "stopmoreshibai" + element);
-            // 当所有视频都尝试停止后,触发一次统一的事件
-            if (stopCount === totalCount) {
-              that.$emit("handleVideoPlay", "stopallshibai");
-            }
+          iWndIndex,
+          success() {
+            that.$emit("handleVideoPlay", { code: "stopSuccess", iWndIndex });
+            resolve();
           },
+          error() {
+            that.$emit("handleVideoPlay", { code: "stopFail", iWndIndex });
+            resolve();
+          }
         });
       });
     },
+    // 停止停止多个窗口播放视频
+    stopAllWindowPlayVideo(windows) {
+      let that = this;
+      const promises = windows.map(win => this.stopOneWindowPlayVideo(parseInt(win)));
+      return Promise.all(promises.map(p => p.catch(e => e)))
+        .then(() => {
+          that.$emit("handleVideoPlay", { code: "stopAllSuccess" });
+        });
+    },
   },
 };
 </script>

+ 120 - 56
src/views/monitoring/manage/VideoEditDialog.vue

@@ -10,71 +10,81 @@
       ref="editForm"
       :model="formData"
       :rules="formRules"
-      label-width="120px"
+      label-width="100px"
       label-position="right"
-      style="background-color: #003c68"
+      class="video-form"
     >
-      <el-form-item label="视频名称:" prop="name">
-        <el-input v-model="formData.name" />
-      </el-form-item>
+      <div class="form-row">
+        <el-form-item label="视频名称" prop="name">
+          <el-input v-model="formData.name" placeholder="请输入视频名称"/>
+        </el-form-item>
+        <el-form-item label="设备类型" prop="deviceType">
+          <el-select
+            v-model="formData.deviceType"
+            placeholder="选择设备类型">
+            <el-option
+              style="color: black"
+              v-for="item in deviceTypeOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+            />
+          </el-select>
+        </el-form-item>
+      </div>
 
-      <el-form-item label="设备类型:" prop="deviceType">
-        <el-select v-model="formData.deviceType" placeholder="选择设备类型">
-          <el-option label="IPC" value="IPC" />
-          <el-option label="MINMOE" value="MINMOE" />
-          <el-option label="LPR_GATE" value="LPR_GATE" />
-          <el-option label="TURNSTILE" value="TURNSTILE" />
-        </el-select>
-      </el-form-item>
+      <div class="form-row">
+        <el-form-item label="视频IP" prop="ip">
+          <el-input v-model="formData.ip" placeholder="请输入IP地址"/>
+        </el-form-item>
+        <el-form-item label="端口号" prop="port">
+          <el-input-number v-model.number="formData.port" :min="1"/>
+        </el-form-item>
+      </div>
 
-      <el-form-item label="视频IP:" prop="ip">
-        <el-input v-model="formData.ip" />
-      </el-form-item>
+      <div class="form-row">
+        <el-form-item label="用户名" prop="username">
+          <el-input v-model="formData.username" placeholder="请输入用户名"/>
+        </el-form-item>
+        <el-form-item label="密码" prop="password">
+          <el-input v-model="formData.password" type="password" show-password placeholder="请输入密码"/>
+        </el-form-item>
+      </div>
 
-      <el-form-item label="端口号:" prop="port">
-        <el-input v-model.number="formData.port" type="number" />
-      </el-form-item>
+      <div class="form-row">
+        <el-form-item label="通道号" prop="channel">
+          <el-input-number v-model="formData.channel" :min="1"/>
+        </el-form-item>
+        <el-form-item label="通道名称" prop="channelName">
+          <el-input v-model="formData.channelName" placeholder="请输入通道名称"/>
+        </el-form-item>
+      </div>
 
-      <el-form-item label="用户名:" prop="username">
-        <el-input v-model="formData.username" />
+      <el-form-item label="RTSP地址" prop="rtspUrl">
+        <el-input v-model="formData.rtspUrl" placeholder="自动生成RTSP地址" style="width:85%" />
       </el-form-item>
 
-      <el-form-item label="密码:" prop="password">
-        <el-input v-model="formData.password" type="password" show-password />
-      </el-form-item>
-
-      <el-form-item label="通道号:" prop="channel">
-        <el-input-number v-model="formData.channel" :min="1" />
-      </el-form-item>
-
-      <el-form-item label="通道名称:" prop="channelName">
-        <el-input v-model="formData.channelName" />
-      </el-form-item>
-
-      <el-form-item label="RTSP地址:" prop="rtspUrl">
-        <el-input v-model="formData.rtspUrl" />
-      </el-form-item>
-
-      <el-form-item label="取流方式:" prop="previewType">
-        <el-select v-model="formData.previewType" placeholder="选择取流方式">
-          <el-option label="RTSP" value="RTSP" />
-          <el-option label="SDK" value="SDK" />
-          <el-option label="ISAPI" value="ISAPI" />
-        </el-select>
-      </el-form-item>
-
-      <el-form-item label="启用:" prop="enable">
-        <el-switch
-          v-model="formData.enable"
-          active-color="#13ce66"
-          inactive-color="#ff4949"
-          :active-value="true"
-          :inactive-value="false"
-        />
-      </el-form-item>
+      <div class="form-row">
+        <el-form-item label="取流方式" prop="previewType">
+          <el-select v-model="formData.previewType" placeholder="选择取流方式">
+            <el-option label="RTSP" value="RTSP" />
+            <el-option label="SDK" value="SDK" />
+            <el-option label="ISAPI" value="ISAPI" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="启用" prop="enable">
+          <el-switch
+            v-model="formData.enable"
+            active-color="#13ce66"
+            inactive-color="#ff4949"
+            :active-value="true"
+            :inactive-value="false"
+          />
+        </el-form-item>
+      </div>
     </el-form>
 
-    <span slot="footer">
+    <span slot="footer" class="dialog-footer">
       <el-button @click="handleCancel">取消</el-button>
       <el-button type="primary" @click="handleSubmit">保存</el-button>
     </span>
@@ -93,6 +103,13 @@ export default {
   },
   data() {
     return {
+      deviceTypeOptions: [
+        { label: "网络摄像机 (IPC)", value: "IPC" },
+        { label: "人脸门禁终端 (MINMOE)", value: "MINMOE" },
+        { label: "车牌识别一体机 (LPR_GATE)", value: "LPR_GATE" },
+        { label: "通道控制设备 / 摆闸 (TURNSTILE)", value: "TURNSTILE" }
+      ],
+
       formData: {
         id: null,
         name: "",
@@ -203,8 +220,55 @@ export default {
 };
 </script>
 
-<style scoped>
+<style>
 .custom-dialog {
   background-color: #003c68;
+  width: 800px !important;
+}
+
+/* 标签文字颜色深色背景下可读 */
+.custom-dialog .el-dialog__title {
+  color: #fff;
+}
+
+.custom-dialog .el-dialog__header {
+  background-color: #003c68;
+  color: #ffffff;
+}
+
+.custom-dialog .el-dialog__headerbtn {
+  color: #ffffff;
+}
+
+.video-form {
+  background-color: #003c68;
+  padding: 15px 20px;
+}
+
+/* 两列布局 */
+.form-row {
+  display: flex;
+  gap: 20px;
+  margin-bottom: 10px;
+}
+
+/* 标签文字颜色深色背景下可读 */
+.video-form .el-form-item__label {
+  color: #fff;
+}
+
+/* 输入框文字颜色 */
+.video-form .el-input__inner,
+.video-form .el-select .el-input__inner,
+.video-form .el-input-number__input {
+  color: #000;
+  background-color: #fff;
+}
+
+/* 对话框底部按钮右对齐 */
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
 }
 </style>

+ 100 - 38
src/views/monitoring/manage/index.vue

@@ -2,23 +2,31 @@
   <div style="padding: 10px">
     <!-- 搜索和新增 -->
     <div style="height: 50px; display: flex; align-items: center; gap: 10px">
+      <!-- 输入框:回车自动查询 -->
       <el-input
         v-model="searchName"
         placeholder="视频名称"
         size="small"
         style="width: 150px"
+        clearable
+        @keyup.enter.native="handleSearch"
       />
+      <!-- 选择框:改变后自动查询 -->
       <el-select
         v-model="searchDeviceType"
         placeholder="设备类型"
         size="small"
         style="width: 130px"
         clearable
+        @change="handleSearch"
       >
-        <el-option label="IPC" value="IPC" />
-        <el-option label="MINMOE" value="MINMOE" />
-        <el-option label="LPR_GATE" value="LPR_GATE" />
-        <el-option label="TURNSTILE" value="TURNSTILE" />
+        <el-option
+          style="color: black"
+          v-for="item in deviceTypeOptions"
+          :key="item.value"
+          :label="item.label"
+          :value="item.value"
+        />
       </el-select>
       <el-button type="primary" size="small" @click="handleSearch"
         >查询</el-button
@@ -27,9 +35,14 @@
     </div>
 
     <!-- 视频表格 -->
-    <el-table :data="tableData" border style="width: 100%; margin-top: 10px">
+    <el-table
+      :data="tableData"
+      style="width: 100%;
+      margin-top: 10px"
+      border
+      :header-cell-style="changeHeaderCellStyle"
+    >
       <el-table-column type="index" label="序号" width="60" align="center" />
-
       <el-table-column
         prop="name"
         label="视频名称"
@@ -39,8 +52,9 @@
       <el-table-column
         prop="deviceType"
         label="设备类型"
-        min-width="100"
+        min-width="120"
         align="center"
+        :formatter="formatDeviceType"
       />
       <el-table-column
         prop="channel"
@@ -88,16 +102,16 @@
           <a :href="row.rtspUrl" target="_blank">{{ row.rtspUrl }}</a>
         </template>
       </el-table-column>
-      <el-table-column
-        prop="playUrl"
-        label="播放地址"
-        min-width="250"
-        align="center"
-      >
-        <template #default="{ row }">
-          <a :href="row.playUrl" target="_blank">{{ row.playUrl }}</a>
-        </template>
-      </el-table-column>
+      <!--      <el-table-column-->
+      <!--        prop="playUrl"-->
+      <!--        label="播放地址"-->
+      <!--        min-width="250"-->
+      <!--        align="center"-->
+      <!--      >-->
+      <!--        <template #default="{ row }">-->
+      <!--          <a :href="row.playUrl" target="_blank">{{ row.playUrl }}</a>-->
+      <!--        </template>-->
+      <!--      </el-table-column>-->
 
       <el-table-column prop="enable" label="启用" width="80" align="center">
         <template #default="{ row }">
@@ -122,12 +136,15 @@
 
     <!-- 分页 -->
     <el-pagination
-      style="margin-top: 20px; text-align: right"
-      v-model:current-page="currentPage"
+      class="custom-pagination"
+      style="margin-top: 20px; text-align: right;"
+      :current-page="currentPage"
       :page-size="pageSize"
       :total="total"
-      layout="total, prev, pager, next, jumper"
+      :page-sizes="[10, 15, 20, 25, 30]"
+      layout="total, prev, pager, next, jumper, sizes"
       @current-change="handlePageChange"
+      @size-change="handleSizeChange"
     />
 
     <!-- 新增/编辑弹窗 -->
@@ -152,6 +169,13 @@ export default {
       searchName: "",
       searchDeviceType: "",
 
+      deviceTypeOptions: [
+        { label: "网络摄像机 (IPC)", value: "IPC" },
+        { label: "人脸门禁终端 (MINMOE)", value: "MINMOE" },
+        { label: "车牌识别一体机 (LPR_GATE)", value: "LPR_GATE" },
+        { label: "通道控制设备 / 摆闸 (TURNSTILE)", value: "TURNSTILE" }
+      ],
+
       tableData: [],
       currentPage: 1,
       pageSize: 10,
@@ -161,24 +185,10 @@ export default {
       currentVideoData: {},
     };
   },
-  created() {
+  mounted() {
     this.getVideoInfo();
   },
   methods: {
-    async getVideoInfo() {
-      const res = await request({
-        url: "/video/list",
-        method: "POST",
-        data: {
-          page: this.currentPage,
-          size: this.pageSize,
-          name: this.searchName,
-          deviceType: this.searchDeviceType,
-        },
-      });
-      this.tableData = res.list;
-      this.total = res.total;
-    },
     handleSearch() {
       this.currentPage = 1;
       this.getVideoInfo();
@@ -210,21 +220,73 @@ export default {
         method: "DELETE",
       });
       this.$message.success("删除成功");
-      this.getVideoInfo();
+      await this.getVideoInfo();
+    },
+    // 表格样式修改
+    changeHeaderCellStyle(row, column, rowIndex, columnIndex) {
+      if (row.columnIndex === 0) {
+        return "background: #004279 ; color:#fff;"; // 修改的样式
+      } else {
+        return "background: #004279 ;color:#fff; ";
+      }
     },
     handlePageChange(page) {
       this.currentPage = page;
       this.getVideoInfo();
     },
+    handleSizeChange(size) {
+      this.currentPage = 1;
+      this.pageSize = size;
+      this.getVideoInfo();
+    },
     async handleVideoSubmit(videoData) {
       const url = videoData.id ? "/video/update" : "/video/add";
-      await request({ url, method: "POST", data: videoData });
+      const res = await request({ url, method: "POST", data: videoData });
       this.dialogVisible = false;
-      this.getVideoInfo();
+      await this.$message(res.data);
+      await this.getVideoInfo();
     },
     handleVideoCancel() {
       this.dialogVisible = false;
     },
+    async getVideoInfo() {
+      try {
+        // 发起分页+查询请求
+        const res = await request({
+          url: "/video/list",
+          method: "POST",
+          data: {
+            page: this.currentPage,
+            size: this.pageSize,
+            name: this.searchName?.trim() || null, // 去掉多余空格,避免 "" 导致条件错乱
+            deviceType: this.searchDeviceType || null, // 为空时传 null
+          },
+        });
+
+        // 判断返回结构
+        if (res && res.list) {
+          this.tableData = res.list;
+          this.total = res.total || 0;
+        } else {
+          this.tableData = [];
+          this.total = 0;
+          this.$message.warning("未获取到视频数据");
+        }
+      } catch (error) {
+        console.error("获取视频信息失败:", error);
+        this.$message.error("获取视频列表失败,请检查网络或后端接口!");
+      }
+    },
+    // 格式化设备类型
+    formatDeviceType(row) {
+      const map = {
+        IPC: "网络摄像机",
+        MINMOE: "人脸门禁终端",
+        LPR_GATE: "车牌识别一体机 / 车闸",
+        TURNSTILE: "摆闸 / 翼闸 / 三辊闸",
+      };
+      return map[row.deviceType] || row.deviceType || "-";
+    },
   },
 };
 </script>

+ 353 - 234
src/views/monitoring/view/index.vue

@@ -6,7 +6,6 @@
         flex: 'none',
         width: isFullScreen ? '100%' : '75%',
         height: '100%',
-        backgroundColor: '#0c5cab',
         padding: '10px 0 10px 10px', // 上10px 右0 下10px 左10px
       }"
     >
@@ -14,9 +13,29 @@
         <el-image
           style="width: 20px; height: 20px; cursor: pointer; flex-shrink: 0"
           @click="showFull"
-          :src="require('@/assets/img/small.svg')"
+          :src="
+            isFullScreen
+              ? require('@/assets/img/small.svg')
+              : require('@/assets/img/iconMore.svg')
+          "
           fit="contain"
         />
+        <div style="display: flex; align-items: center">
+          <el-select
+            v-model="selectedScreen"
+            placeholder="选择分屏"
+            clearable
+            @change="changeWndNum"
+            style="margin-left: 10px; flex-shrink: 0; width: 120px"
+          >
+            <el-option label="1x1" :value="1" style="color: black" />
+            <el-option label="1x2" :value="12" style="color: black" />
+            <el-option label="2x1" :value="21" style="color: black" />
+            <el-option label="2x2" :value="2" style="color: black" />
+            <el-option label="3x3" :value="3" style="color: black" />
+            <el-option label="4x4" :value="4" style="color: black" />
+          </el-select>
+        </div>
         <div
           style="
             margin-left: 8px;
@@ -39,9 +58,8 @@
           @handleVideoPlay="handleVideoPlay"
           @changeWindow="handleChangeWindow"
           :num="selectedScreen"
-          :bofang="issbofang"
+          :bofang="isBofang"
           :chooseWindow="selectWindow"
-          :chooseData="bofangyuan"
           :companyVideoData="bigcamaraData.data"
           :isFullScreen="isFullScreen"
           @init="HKbigCamaraInit"
@@ -54,42 +72,66 @@
         flex: 'none',
         width: isFullScreen ? '0%' : '25%',
         height: '100%',
-        backgroundColor: '#0c5cab',
         padding: '10px 10px 10px 0px', // 上10px 右10px 下10px 左0px
         boxSizing: 'border-box', // 让padding参与宽度计算
         display: 'flex', //  新增
         flexDirection: 'column', //  新增
       }"
     >
-      <div style="height: 30px; display: flex; align-items: center">
-        <span style="font-size: 18px">窗口分割数</span>
+      <!-- 搜索栏 -->
+      <div
+        style="
+          height: 36px;
+          display: flex;
+          align-items: center;
+          gap: 8px;
+          padding: 0 4px;
+        "
+      >
+        <!-- 输入框:回车自动查询 -->
+        <el-input
+          v-model="searchName"
+          placeholder="视频名称"
+          size="small"
+          clearable
+          @keyup.enter.native="handleSearch"
+          style="width: 120px"
+        />
+
+        <!-- 选择框:改变后自动查询 -->
         <el-select
-          v-model="selectedScreen"
-          placeholder="选择分屏"
+          v-model="searchDeviceType"
+          placeholder="设备类型"
+          size="small"
           clearable
-          @change="changeWndNum"
-          style="color: black; width: 150px; margin-left: 10px"
+          @change="handleSearch"
+          style="width: 120px"
         >
-          <el-option label="1x1" :value="1" style="color: black" />
-          <el-option label="1x2" :value="12" style="color: black" />
-          <el-option label="2x1" :value="21" style="color: black" />
-          <el-option label="2x2" :value="2" style="color: black" />
-          <el-option label="3x3" :value="3" style="color: black" />
-          <el-option label="4x4" :value="4" style="color: black" />
+          <el-option
+            style="color: black"
+            v-for="item in deviceTypeOptions"
+            :key="item.value"
+            :label="item.label"
+            :value="item.value"
+          />
         </el-select>
-        <el-button
-          size="mini"
-          icon="el-icon-circle-close"
-          @click="stopAllVideo"
-          class="close-btn"
-        />
+        <!-- 查询按钮 -->
+        <el-button type="primary" size="small" @click="handleSearch"
+          >查询</el-button
+        >
+        <el-button type="primary" size="small" @click="batchPlayVideos"
+        >批量播放</el-button
+        >
+        <el-button type="primary" size="small" @click="stopAllVideo"
+        >关闭</el-button
+        >
       </div>
-      <div style="height: calc(100% - 30px); width: 100%; flex: 1">
+      <div style="display: flex; flex-direction: column; overflow: hidden">
         <el-table
           style="width: 100%"
           :data="bigcamaraData.data"
-          :header-cell-style="changeHeaderCellStyle"
           border
+          :header-cell-style="changeHeaderCellStyle"
         >
           <el-table-column
             type="index"
@@ -105,7 +147,7 @@
           />
           <el-table-column
             label="播放窗口"
-            prop="windowList"
+            :formatter="(row) => row.windowList.join(',')"
             align="center"
             min-width="50"
           />
@@ -128,15 +170,17 @@
             </template>
           </el-table-column>
         </el-table>
+        <!-- 分页 -->
         <el-pagination
-          style="margin-top: 0px; float: right"
-          :total="bigcamaraData.total"
+          class="custom-pagination"
+          style="margin-top: 20px; text-align: right;"
           :current-page="bigcamaraData.currentPage"
           :page-size="bigcamaraData.pageSize"
-          :page-sizes="[10, 15, 20]"
-          layout="total, prev, pager, next, sizes"
-          @current-change="onCurrentPageChange_bigcamaraData"
-          @size-change="onPageSizeChange_bigcamaraData"
+          :total="bigcamaraData.total"
+          :page-sizes="[10, 15, 20, 25, 30]"
+          layout="total, prev, pager, next, jumper, sizes"
+          @current-change="handlePageChange"
+          @size-change="handleSizeChange"
         />
       </div>
     </div>
@@ -198,7 +242,7 @@ export default {
         left: "0px",
         width: "100%",
         height: "100%",
-        backgroundColor: "#10d519",
+        backgroundColor: '#0c5cab',
       },
       isFullScreen: false,
       selectedScreen: 1, // 分屏数
@@ -206,20 +250,7 @@ export default {
       selectCamaraRow: 0,
       stopCamara: false,
       stopCamaraRow: 0,
-      bofangyuan: {
-        // 当前选择视频源
-        window: 0,
-        data: [],
-        Password: "sgw123456",
-        Port: "80",
-        Username: "admin",
-        channelName: "test1",
-        ip: "172.3.11.115",
-        name: "11",
-        isbofang: 0,
-        windowList: [],
-      },
-      issbofang: false, // 播放状态
+      isBofang: false, // 播放状态
       dataSrc:
         "https://cctvwbndbd.a.bdydns.com/cctvwbnd/cctv1_2/index.m3u8?BR=single",
       isShow: false,
@@ -236,6 +267,14 @@ export default {
       },
       bigcamaraSrc: [],
       bigcamaraName: [],
+      searchName: "",
+      searchDeviceType: "",
+      deviceTypeOptions: [
+        { label: "网络摄像机 (IPC)", value: "IPC" },
+        { label: "人脸门禁终端 (MINMOE)", value: "MINMOE" },
+        { label: "车牌识别一体机 (LPR_GATE)", value: "LPR_GATE" },
+        { label: "通道控制设备 / 摆闸 (TURNSTILE)", value: "TURNSTILE" }
+      ],
     };
   },
   mounted() {
@@ -272,112 +311,211 @@ export default {
   },
   activated() {
     if (!this.bigcamaraData.data.length) {
-      this.loadBigCamaraList();
+      this.getVideoInfo();
     }
   },
   methods: {
+    // 初始化
     init() {
-      console.log("传递的参数", this.$route.query);
       setTimeout(() => {
         this.HKbigCamaraShow = true;
       }, 500);
       // 只在第一次加载时初始化
       if (!this.bigcamaraData.data.length) {
-        this.loadBigCamaraList();
+        this.getVideoInfo();
+      }
+    },
+    // 查询视频流信息
+    handleSearch() {
+      this.currentPage = 1;
+      this.getVideoInfo();
+    },
+    // 分页切换
+    async getVideoInfo() {
+      try {
+        // 发起分页+查询请求
+        const res = await request({
+          url: "/video/list",
+          method: "POST",
+          data: {
+            page: this.bigcamaraData.currentPage,
+            size: this.bigcamaraData.pageSize,
+            name: this.searchName?.trim() || null, // 去掉多余空格,避免 "" 导致条件错乱
+            deviceType: this.searchDeviceType || null, // 为空时传 null
+          },
+        });
+
+        // 判断返回结构
+        if (res && res.list) {
+          this.bigcamaraData.total = res.total || 0;
+          this.bigcamaraData.data = res.list.map((item) => ({
+            name: item.name,
+            ip: item.ip,
+            port: item.port,
+            userName: item.username,
+            password: item.password,
+            channelName: item.channelName,
+            url: item.url,
+            playUrl: item.playUrl,
+            windowList: [],
+          }));
+        } else {
+          this.bigcamaraData.total = 0;
+          this.bigcamaraData.data = [];
+          this.$message.warning("未获取到视频数据");
+        }
+      } catch (error) {
+        console.error("获取视频信息失败:", error);
+        this.$message.error("获取视频列表失败,请检查网络或后端接口!");
       }
     },
-    async loadBigCamaraList() {
-      const res = await request({
-        url: "/video/list",
-        method: "POST",
-        data: {
-          page: this.bigcamaraData.currentPage,
-          size: this.bigcamaraData.pageSize,
-          name: "",
-        },
+    // 批量播放视频(顺序播放,稳定版)
+    async batchPlayVideos() {
+      // 1. 映射布局到实际窗口数
+      const layoutMap = {
+        1: 1,   // 1x1
+        12: 2,  // 1x2
+        21: 2,  // 2x1
+        2: 4,   // 2x2
+        3: 9,   // 3x3
+        4: 16,  // 4x4
+      };
+
+      const totalWindows = layoutMap[this.selectedScreen] || 1;
+      const videoList = this.bigcamaraData.data;
+
+      if (!videoList.length) return this.$message.warning("没有可播放的视频");
+
+      console.log("批量播放开始,窗口数:", totalWindows, "视频数:", videoList.length);
+
+      // 2. 停止所有窗口播放
+      await this.$refs.hkbigCamara.stopAllRealPlay();
+
+      // 3. 清空所有视频 windowList
+      videoList.forEach(video => {
+        this.$set(video, 'windowList', []);
       });
 
-      this.bigcamaraData.total = res.total;
-      this.bigcamaraData.data = res.list.map((item) => ({
-        name: item.name,
-        ip: item.ip,
-        port: item.port,
-        userName: item.username,
-        password: item.password,
-        channelName: item.channelName,
-        url: item.url,
-        playUrl: item.playUrl,
-        windowList: [],
-      }));
+      // 4. 窗口数和视频数取最小
+      const playCount = Math.min(totalWindows, videoList.length);
+      console.log(`批量播放 ${playCount} 个视频`);
+
+      // 5. 顺序播放视频
+      for (let i = 0; i < playCount; i++) {
+        try {
+          await this.playCameraVideoInSelectWindow({ row: videoList[i], $index: i }, i);
+        } catch (err) {
+          console.error(`窗口 ${i} 播放失败`, err);
+        }
+      }
+
+      this.$message.success("批量播放完成");
+      console.log("批量播放结束,当前窗口列表状态:", videoList.map(v => v.windowList));
+    },
+    // 播放某个视频到指定窗口
+    async playCameraVideoInSelectWindow(camera, iWndIndex) {
+      const bofangyuan = {
+        name: camera.row.name,
+        channelName: camera.row.channelName,
+        ip: camera.row.ip,
+        port: camera.row.port,
+        username: camera.row.userName,
+        password: camera.row.password,
+        url: camera.row.url,
+        playUrl: camera.row.playUrl,
+        windowList: camera.row.windowList || [],
+        iWndIndex: iWndIndex,
+        isbofang: 1,
+        channelId: camera.row.channelId || 1, // 默认通道1
+      };
+      console.log("播放视频源", bofangyuan);
+
+      try {
+        const loginResult = await this.$refs.hkbigCamara.login([bofangyuan]);
+        const deviceResult = loginResult[0]; // 单设备登录结果
+
+        if (!deviceResult.success) {
+          this.$message.error(`登录摄像机失败: ${bofangyuan.ip}, 错误码: ${deviceResult.errorCode}`);
+          console.error("登录失败信息:", deviceResult);
+          return; // 登录失败就不继续播放
+        }
+
+        // 2. 停止目标窗口已有视频
+        await this.checkWindow(iWndIndex);
+
+        // 3. 播放视频
+        await this.$refs.hkbigCamara.playVideoInSelectWindow(bofangyuan);
+
+        // 4. 更新 windowList(保证响应式)
+        this.$set(
+          camera.row,
+          'windowList',
+          [...new Set([...camera.row.windowList, iWndIndex])].sort((a, b) => a - b)
+        );
+
+        this.isBofang = true;
+        console.log("窗口列表更新:", camera.row.windowList);
+
+      } catch (err) {
+        this.$message.error(`播放摄像机异常: ${bofangyuan.ip}`);
+        console.error("播放异常信息:", err);
+      }
     },
+    // 停止播放所有窗口的视频
     stopAllVideo() {
-      this.$refs.hkbigCamara.clickStopAllRealPlay();
+      this.$refs.hkbigCamara.stopAllRealPlay();
     },
-    handleVideoPlay(code) {
-      console.log("错误码:", code);
+    // 处理播放视频的返回结果
+    handleVideoPlay(event) {
+      console.log("错误码:", event);
+      // 统一事件结构
+      const { code, iWndIndex } = typeof event === 'object' ? event : { code: event, iWndIndex: this.selectWindow };
       const targetData = this.bigcamaraData.data[this.selectCamaraRow];
-      const windowNum = this.selectWindow + 1;
 
-      const playFailCodes = ["16", "1000", "3001"];
-      const playSuccessCode = "bofangchenggong";
-      const playFailCode = "bofangshibai";
+      switch(code) {
+        case "playSuccess":
+          this.$message.success(`窗口 ${iWndIndex + 1} 播放成功`);
+          if(!targetData.windowList.includes(iWndIndex)) {
+            targetData.windowList.push(iWndIndex);
+          }
+          break;
 
-      // 统一失败处理
-      if (playFailCodes.includes(code)) {
-        if (code === "3001") this.$refs.hkbigCamara.stopPlayVideo();
-        this.$message(`窗口 ${windowNum} 播放失败`);
-        console.log(`窗口 ${windowNum} 播放失败`);
-        return;
-      }
+        case "playFail":
+          this.$message.error(`窗口 ${iWndIndex + 1} 播放失败`);
+          break;
 
-      // 播放成功
-      if (code === playSuccessCode) {
-        this.$message(`窗口 ${windowNum} 播放成功`);
-        console.log(`窗口 ${windowNum} 播放成功`);
+        case "stopSuccess":
+          this.$message.success(`窗口 ${iWndIndex + 1} 停止成功`);
+          targetData.windowList = targetData.windowList.filter(win => win != iWndIndex);
+          break;
 
-        // 添加到 windowList 并去重排序
-        targetData.windowList.push(windowNum.toString());
-        targetData.windowList = [...new Set(targetData.windowList)].sort(
-          (a, b) => a - b,
-        );
+        case "stopFail":
+          this.$message.error(`窗口 ${iWndIndex + 1} 停止失败`);
+          break;
 
-        console.log("当前窗口列表:", targetData.windowList);
-        return;
-      }
+        case "stopAllSuccess":
+          this.$message.success("所有窗口停止成功");
+          targetData.windowList = [];
+          break;
 
-      // 播放失败
-      if (code === playFailCode) {
-        console.log("播放失败");
-        return;
+        case "stopAllFail":
+          this.$message.error("所有窗口停止失败");
+          break;
       }
 
-      // 停止视频逻辑
-      if (this.stopCamara) {
-        const stopMessages = {
-          stoponechenggong: "暂停一个视频成功",
-          stoponeshibai: "暂停一个视频失败",
-          stopmorechenggong: "暂停所有视频成功",
-          stopmoreshibai: "暂停所有视频失败",
-        };
-
-        if (stopMessages[code]) {
-          if (code.includes("chenggong"))
-            this.$message(`窗口 ${windowNum} ${stopMessages[code]}`);
-          console.log(`窗口 ${windowNum} ${stopMessages[code]}`);
-        }
-      }
-      console.log("所有数据", this.bigcamaraData);
+      console.log("当前窗口列表:", targetData.windowList);
     },
+    // 改变窗口
     handleChangeWindow(value) {
-      console.log("父组件接收到 change-window 事件,值:", value);
       this.selectWindow = value;
       console.log("当前选中的窗口this.selectWindow", this.selectWindow);
     },
     // 停止指定窗口的视频播放
-    async stopone_bigcamaraData(a) {
-      const selectedCamera = this.bigcamaraData.data[a.$index];
+    async stopone_bigcamaraData(camera) {
+      console.log("当前选中的摄像机", camera);
+      const selectedCamera = this.bigcamaraData.data[camera.$index];
       try {
-        await this.$refs.hkbigCamara.stopPlayVideo(); // 停止播放
+        await this.$refs.hkbigCamara.stopOneWindowPlayVideo(this.selectWindow); // 停止播放
         // 更新数据,移除已停止窗口
         selectedCamera.windowList = selectedCamera.windowList.filter(
           (element) => element !== this.selectWindow,
@@ -386,56 +524,72 @@ export default {
         console.error("停止单个视频失败:", error);
       }
     },
-    // 停止指定摄像机所有窗口的视频播放
-    async stopall_bigcamaraData(a) {
-      const selectedCamera = this.bigcamaraData.data[a.$index];
-      try {
-        await this.$refs.hkbigCamara.stopallPlayVideo(
-          selectedCamera.windowList,
-        );
-        // 清空窗口列表
-        selectedCamera.windowList = [];
-      } catch (error) {
-        console.error("停止所有视频失败:", error);
-      }
-    },
     // 播放摄像机视频
     async begin_bigcamaraData(camera) {
       console.log("当前选中的摄像机", camera);
-      this.selectCamaraRow = camera.$index;
       console.log("当前选中的窗口", this.selectWindow);
+      this.selectCamaraRow = camera.$index;
       const selectedCamera = this.bigcamaraData.data[camera.$index];
 
-      // 限制同一摄像机同时播放窗口数
-      if (selectedCamera.windowList.length < 6) {
-        // 停止当前窗口可能正在播放的视频
-        await this.checkWindow(this.selectWindow);
-        console.log("camera", camera);
-        // 更新播放源信息
-        const bofangyuan = {
-          Password: camera.row.password,
-          Port: camera.row.port,
-          Username: camera.row.userName,
-          channelName: camera.row.channelName,
-          ip: camera.row.ip,
-          name: camera.row.name,
-          window: this.selectWindow,
-          windowList: camera.row.windowList,
-          playUrl: camera.row.playUrl,
-          url: camera.row.url,
-          isbofang: 1,
-        };
-        this.issbofang = true;
-        console.log("bofangyuan", bofangyuan);
-        // 调用登录/播放
-        const myArray = [bofangyuan];
-        await this.$refs.hkbigCamara.login(myArray);
-
-        // 将当前窗口加入摄像机的窗口列表
-        selectedCamera.windowList.push(this.selectWindow);
-        console.log("当前窗口列表:", selectedCamera.windowList);
-      } else {
-        console.log("当前摄像机已满");
+      // 限制单摄像机最多 6 个窗口
+      if (selectedCamera.windowList.length >= 6) {
+        return this.$message.warning("摄像机已满");
+      }
+
+      // 停止当前窗口已有视频
+      await this.checkWindow(this.selectWindow);
+
+      // 当前选择视频源
+      const bofangyuan = {
+        name: camera.row.name,
+        channelName: camera.row.channelName,
+        ip: camera.row.ip,
+        port: camera.row.port,
+        username: camera.row.userName,
+        password: camera.row.password,
+        url: camera.row.url,
+        playUrl: camera.row.playUrl,
+        windowList: camera.row.windowList,
+        isbofang: 1,
+      };
+      console.log("bofangyuan", bofangyuan);
+
+      try {
+        const result = await this.$refs.hkbigCamara.login([bofangyuan]);
+        // login 返回的是数组,每台设备有 success/errorCode
+        const deviceResult = result[0]; // 这里只有一台设备
+        if (deviceResult.success) {
+          // 登录成功,更新窗口列表
+          selectedCamera.windowList = [...new Set([...selectedCamera.windowList, this.selectWindow])].sort((a, b) => a - b);
+          this.isBofang = true;
+          console.log("当前窗口列表:", selectedCamera.windowList);
+        } else {
+          this.$message.error(`登录摄像机失败: ${bofangyuan.ip}, 错误码: ${deviceResult.errorCode}`);
+          console.error("登录失败信息:", deviceResult);
+        }
+      } catch (err) {
+        // 捕获意外错误
+        this.$message.error(`登录摄像机异常: ${bofangyuan.ip}`);
+        console.error("登录异常:", err);
+      }
+    },
+    // 停止指定摄像机所有窗口的视频播放
+    async stopall_bigcamaraData(camera) {
+      console.log("当前选中的摄像机", camera);
+      const selectedCamera = this.bigcamaraData.data[camera.$index];
+      if (!selectedCamera.windowList.length) {
+        this.$message.info("该摄像机当前没有正在播放的视频");
+        return;
+      }
+
+      const windows = [...selectedCamera.windowList]; // 防止引用问题
+      try {
+        await this.$refs.hkbigCamara.stopallPlayVideo(windows);
+        selectedCamera.windowList = [];
+        this.$message.success("停止该视频流在所有窗口上播放-操作成功");
+      } catch (error) {
+        console.error("停止该视频流在所有窗口上播放-操作失败:", error);
+        this.$message.error("停止该视频流在所有窗口上播放-操作失败");
       }
     },
     // 检查指定窗口是否正在播放其他视频,如在播放则停止
@@ -445,18 +599,15 @@ export default {
         const promises = [];
         console.log("this.bigcamaraData.data", this.bigcamaraData.data);
         this.bigcamaraData.data.forEach((camera) => {
-          console.log("camera", camera);
-
+          // console.log("camera", camera);
           if (camera.windowList.length > 0) {
             camera.windowList.forEach((win, winIndex) => {
               if (parseInt(win) === selectWindow) {
                 console.log(`窗口 ${selectWindow} 已被占用,停止播放...`);
                 this.stopCamara = true;
                 this.stopCamaraRow = winIndex;
-
                 // 停止播放并更新数据
-                const stopPromise = this.$refs.hkbigCamara
-                  .stopPlayVideo()
+                const stopPromise = this.$refs.hkbigCamara.stopOneWindowPlayVideo(selectWindow)
                   .then(() => {
                     camera.windowList = camera.windowList.filter(
                       (element) => element !== selectWindow,
@@ -466,13 +617,13 @@ export default {
                   .catch((error) => {
                     console.error("停止视频失败:", error);
                   });
-                promises.push(stopPromise);
+                promises.push(stopPromise); // Promise 已经加入数组
               } else {
                 console.log(`窗口 ${win} 未被占用`);
               }
             });
           } else {
-            console.log("没有窗口正在播放");
+            // console.log("当前视频没有窗口正在播放", camera.name);
           }
         });
 
@@ -499,72 +650,15 @@ export default {
       }
     },
     // 页码改变
-    onCurrentPageChange_bigcamaraData(page) {
+    handlePageChange(page) {
       this.bigcamaraData.currentPage = page;
-      this.loadBigCamaraList(); // 重新加载
+      this.getVideoInfo(); // 重新加载
     },
-
     // 每页条数改变
-    onPageSizeChange_bigcamaraData(size) {
-      this.bigcamaraData.pageSize = size;
+    handleSizeChange(size) {
       this.bigcamaraData.currentPage = 1;
-      this.loadBigCamaraList(); // 重新加载
-    },
-    /**
-     * 获取表格数据
-     * @param {Integer} pageNum 当前分页
-     * @param {Integer} pageSize 每页数量
-     * @param {Boolean} reload 是否重新获取数据
-     */
-    loadTableDataImpl_bigcamaraData(pageNum, pageSize, reload = false) {
-      // 判断是否需要重新加载数据
-      if (
-        this.bigcamaraData.data.length == 0 ||
-        this.bigcamaraData.length == 0
-      ) {
-        return Promise.resolve([]);
-      }
-      if (
-        reload ||
-        !this.bigcamaraData.data ||
-        this.bigcamaraData.data.length === 0
-      ) {
-        // 调用后端接口或其他数据来源获取数据
-        // 这里省略具体实现
-        // 模拟一个返回结果的 Promise 对象
-        const mockData = new Promise((resolve) => {
-          const data = [];
-          for (let i = 1; i <= pageSize; i++) {
-            data.push({
-              id: pageNum * pageSize + i,
-              name: `任务${pageNum * pageSize + i}`,
-              status: Math.floor(Math.random() * 4),
-            });
-          }
-          resolve(data);
-        });
-        // 返回 Promise 对象,以便分页组件可以在数据加载完成后进行下一步处理。
-        return mockData.then((data) => {
-          // 更新任务列表数据
-          this.bigcamaraData.data = data;
-          // 返回新的任务列表数据
-          return this.bigcamaraData.data.slice(
-            (pageNum - 1) * pageSize,
-            pageNum * pageSize,
-          );
-        });
-      } else {
-        // 直接返回已有的任务列表数据
-        return Promise.resolve(
-          this.bigcamaraData.data.slice(
-            (pageNum - 1) * pageSize,
-            pageNum * pageSize,
-          ),
-        );
-      }
-    },
-    HKbigCamaraInit() {
-      this.HKbigCamaraShow = true;
+      this.bigcamaraData.pageSize = size;
+      this.getVideoInfo(); // 重新加载
     },
     // 窗口分割数
     changeWndNum(val) {
@@ -573,6 +667,11 @@ export default {
       this.selectedScreen = val; // 不需要转换为整数,直接使用原始值
       console.log("当前选择的分屏数量", this.selectedScreen);
     },
+    // 播放大屏
+    HKbigCamaraInit() {
+      this.HKbigCamaraShow = true;
+    },
+    // 全屏切换
     showFull() {
       console.log("showFull", this.isFullScreen);
       this.isFullScreen = !this.isFullScreen;
@@ -750,4 +849,24 @@ export default {
   width: 419px;
   height: 182px;
 }
+
+/*分页样式*/
+/* 全局或 scoped 样式 */
+.custom-pagination {
+  color: #000; /* 分页文字颜色 */
+}
+
+.custom-pagination .el-pager li {
+  color: #000;
+  border-color: #000;
+}
+
+.custom-pagination .el-pager li.active {
+  background-color: #000;
+  color: #da2121;
+}
+
+.custom-pagination .el-pager li:hover {
+  color: #000;
+}
 </style>