From 3e955639e7d984db92766918cb93e17355c8ea0e Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 28 十月 2025 14:56:03 +0800
Subject: [PATCH] 恒辉科技-区域控制、视频监控页面开发
---
 src/layout/components/AppMain.vue                  |   14 
 src/views/monitorManagement/videoMonitor/index.vue |  990 +++++++++++++++++++++++++++++++++++++++++++++
 src/views/monitorManagement/areaControl/index.vue  |  264 ++++++++++++
 3 files changed, 1,265 insertions(+), 3 deletions(-)
diff --git a/src/layout/components/AppMain.vue b/src/layout/components/AppMain.vue
index a87ef7d..1e7a78b 100644
--- a/src/layout/components/AppMain.vue
+++ b/src/layout/components/AppMain.vue
@@ -2,9 +2,12 @@
   <section class="app-main">
     <router-view v-slot="{ Component, route }">
       <transition name="fade-transform" mode="out-in">
-        <keep-alive :include="tagsViewStore.cachedViews">
-          <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
-        </keep-alive>
+        <div v-if="!route.meta.link" class="route-view-wrapper">
+          <keep-alive :include="tagsViewStore.cachedViews">
+            <component :is="Component" :key="route.path"/>
+          </keep-alive>
+        </div>
+        <div v-else class="route-view-wrapper"></div>
       </transition>
     </router-view>
     <iframe-toggle />
@@ -43,6 +46,11 @@
   background: #F5F7FB;
 }
 
+.route-view-wrapper {
+  width: 100%;
+  height: 100%;
+}
+
 .fixed-header + .app-main {
   padding-top: 50px;
 }
diff --git a/src/views/monitorManagement/areaControl/index.vue b/src/views/monitorManagement/areaControl/index.vue
new file mode 100644
index 0000000..1dd5a36
--- /dev/null
+++ b/src/views/monitorManagement/areaControl/index.vue
@@ -0,0 +1,264 @@
+<template>
+  <div class="app-container">
+    <el-row :gutter="16">
+      <el-col :span="16">
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="card-header">
+              <span>鍖哄煙绠$悊锛堝弻閲嶉棬绂侊級</span>
+              <div class="header-actions">
+                <el-select v-model="selectedPlant" placeholder="閫夋嫨鍘傚尯" size="small" style="width: 160px" @change="filterZones">
+                  <el-option v-for="plant in plants" :key="plant.id" :label="plant.name" :value="plant.id" />
+                </el-select>
+                <el-switch v-model="onlyCritical" inline-prompt :active-text="'浠呭叧閿尯'" :inactive-text="'鍏ㄩ儴'" @change="filterZones" />
+              </div>
+            </div>
+          </template>
+          <el-table :data="filteredZones" border style="width: 100%" height="320">
+            <el-table-column type="index" width="60" label="搴忓彿" align="center" />
+            <el-table-column prop="name" label="鍖哄煙鍚嶇О" min-width="160" show-overflow-tooltip />
+            <el-table-column prop="zoneType" label="绫诲瀷" width="120" />
+            <el-table-column label="鍙岄棬鑱斿姩" width="120" align="center">
+              <template #default="{ row }">
+                <el-tag v-if="row.dualAccess" type="success">宸插惎鐢�</el-tag>
+                <el-tag v-else type="info">鏈惎鐢�</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="鍦ㄧ嚎浜烘暟" width="100" align="center">
+              <template #default="{ row }">{{ row.currentPersons }}</template>
+            </el-table-column>
+            <el-table-column label="瀹夊叏鐘舵��" width="140" align="center">
+              <template #default="{ row }">
+                <el-tag :type="row.status === '姝e父' ? 'success' : row.status === '棰勮' ? 'warning' : 'danger'">
+                  {{ row.status }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="鎿嶄綔" width="180" align="center" fixed="right">
+              <template #default="{ row }">
+                <el-button link type="primary" size="small" @click="toggleDual(row)">
+                  {{ row.dualAccess ? '鍋滅敤鍙岄棬' : '鍚敤鍙岄棬' }}
+                </el-button>
+                <el-button link type="success" size="small" @click="openAccessSim(row)">妯℃嫙寮�闂�</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="card-header">
+              <span>鍩硅鑱斿姩锛堟湭瀹屾垚/杩囨湡绂佹杩涘叆锛�</span>
+              <div class="header-actions">
+                <el-input v-model="accessSim.personId" placeholder="浜哄憳宸ュ彿" size="small" style="width: 140px" />
+                <el-select v-model="accessSim.targetZoneId" placeholder="閫夋嫨鐩爣鍖哄煙" size="small" style="width: 180px">
+                  <el-option v-for="z in zones" :key="z.id" :label="z.name" :value="z.id" />
+                </el-select>
+                <el-button type="primary" size="small" @click="simulateAccess">妫�楠屽噯鍏�</el-button>
+              </div>
+            </div>
+          </template>
+          <el-descriptions :column="3" border size="small" v-if="accessResult">
+            <el-descriptions-item label="宸ュ彿">{{ accessResult.person.id }}锛坽{ accessResult.person.dept }}锛�</el-descriptions-item>
+            <el-descriptions-item label="鍩硅鐘舵��">
+              <el-tag :type="accessResult.person.training.valid ? 'success' : 'danger'">
+                {{ accessResult.person.training.valid ? '鏈夋晥' : '澶辨晥/鏈畬鎴�' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="鐩爣鍖哄煙">{{ accessResult.zone.name }}</el-descriptions-item>
+            <el-descriptions-item label="鏈�杩戝煿璁�">{{ accessResult.person.training.lastDate }}</el-descriptions-item>
+            <el-descriptions-item label="閫傚矖璇佹湁鏁堟湡">{{ accessResult.person.training.expireDate }}</el-descriptions-item>
+            <el-descriptions-item label="鍑嗗叆缁撴灉">
+              <el-tag :type="accessResult.allowed ? 'success' : 'danger'">{{ accessResult.allowed ? '鍏佽杩涘叆' : '绂佹杩涘叆' }}</el-tag>
+            </el-descriptions-item>
+          </el-descriptions>
+          <el-empty v-else description="璇疯緭鍏ヤ汉鍛樹笌鍖哄煙杩涜妫�楠�" />
+        </el-card>
+      </el-col>
+
+      <el-col :span="8">
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="card-header">
+              <span>浣╂埓璁惧婊炵暀鍛婅锛堝嵄闄╁尯瓒呮椂锛�</span>
+              <div class="header-actions">
+                <el-select v-model="stayThreshold" size="small" style="width: 140px">
+                  <el-option :value="10" label="闃堝�� 10 鍒嗛挓" />
+                  <el-option :value="20" label="闃堝�� 20 鍒嗛挓" />
+                  <el-option :value="30" label="闃堝�� 30 鍒嗛挓" />
+                </el-select>
+                <el-switch v-model="alarmOn" inline-prompt :active-text="'鍛婅寮�'" :inactive-text="'鍛婅鍏�'" />
+              </div>
+            </div>
+          </template>
+          <el-timeline style="max-height: 520px; overflow: auto">
+            <el-timeline-item v-for="(item, idx) in alarms" :key="idx" :type="item.level" :timestamp="item.time">
+              <div class="alarm-item">
+                <div class="title">
+                  {{ item.personId }} 路 {{ item.zoneName }} 路 婊炵暀 {{ item.stayMins }} 鍒嗛挓
+                </div>
+                <div class="desc">璁惧锛歿{ item.deviceId }}锛堜俊鍙峰己搴� {{ item.rssi }} dBm锛�</div>
+              </div>
+            </el-timeline-item>
+          </el-timeline>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+  <el-dialog v-model="doorSimVisible" title="闂ㄧ寮�闂ㄦā鎷�" width="420px">
+    <el-form :model="doorSim" label-width="90px">
+      <el-form-item label="鍖哄煙">
+        <el-input v-model="doorSim.zoneName" disabled />
+      </el-form-item>
+      <el-form-item label="闂ㄧ1">
+        <el-switch v-model="doorSim.door1" />
+      </el-form-item>
+      <el-form-item label="闂ㄧ2">
+        <el-switch v-model="doorSim.door2" />
+      </el-form-item>
+      <el-alert type="info" show-icon :closable="false" title="鍙岄棬鍧囦负寮�鍚柟鍙�氳" />
+    </el-form>
+    <template #footer>
+      <el-button @click="doorSimVisible = false">鍏抽棴</el-button>
+      <el-button type="primary" :disabled="!(doorSim.door1 && doorSim.door2)" @click="confirmPass">閫氳</el-button>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted } from "vue";
+
+// 鍘傚尯涓庡尯鍩燂紙鐓ょ偔琛屼笟璇箟銆佸敖閲忚创杩戠湡瀹烇級
+const plants = ref([
+  { id: "P01", name: "涓�鍙烽�夌叅鍘�" },
+  { id: "P02", name: "浜屽彿娲楃叅鍒嗗巶" },
+]);
+const zones = ref([
+  { id: "Z01", plantId: "P01", name: "涓帶瀹�", zoneType: "鎺у埗瀹�", dualAccess: true, currentPersons: 4, status: "姝e父" },
+  { id: "Z02", plantId: "P01", name: "鐓ゅ満A鍖�", zoneType: "鍫嗗瓨鍖�", dualAccess: true, currentPersons: 12, status: "棰勮" },
+  { id: "Z03", plantId: "P01", name: "鍗遍櫓鍝佸簱", zoneType: "鍗卞寲鍝�", dualAccess: true, currentPersons: 1, status: "姝e父" },
+  { id: "Z04", plantId: "P01", name: "楂樺帇閰嶇數瀹�", zoneType: "鐢垫皵闂�", dualAccess: true, currentPersons: 2, status: "姝e父" },
+  { id: "Z05", plantId: "P02", name: "鐨甫寤婂寳娈�", zoneType: "杈撻�佸粖閬�", dualAccess: false, currentPersons: 5, status: "姝e父" },
+  { id: "Z06", plantId: "P02", name: "绛涘垎杞﹂棿", zoneType: "浣滀笟鍖�", dualAccess: false, currentPersons: 9, status: "棰勮" },
+]);
+
+const selectedPlant = ref(plants.value[0].id);
+const onlyCritical = ref(true);
+const filteredZones = ref([]);
+
+function filterZones() {
+  const data = zones.value.filter((z) => z.plantId === selectedPlant.value);
+  filteredZones.value = onlyCritical.value ? data.filter((z) => z.dualAccess) : data;
+}
+
+function toggleDual(row) {
+  row.dualAccess = !row.dualAccess;
+  filterZones();
+}
+
+// 闂ㄧ寮�闂ㄦā鎷�
+const doorSimVisible = ref(false);
+const doorSim = reactive({ zoneId: "", zoneName: "", door1: false, door2: false });
+function openAccessSim(row) {
+  doorSim.zoneId = row.id;
+  doorSim.zoneName = row.name;
+  doorSim.door1 = false;
+  doorSim.door2 = false;
+  doorSimVisible.value = true;
+}
+function confirmPass() {
+  doorSimVisible.value = false;
+}
+
+// 鍩硅鑱斿姩妯℃嫙
+const persons = ref([
+  { id: "EMP1001", dept: "鐢熶骇涓�闃�", training: { valid: true, lastDate: "2025-09-12", expireDate: "2026-09-12" } },
+  { id: "EMP1018", dept: "鏈虹數鐝�", training: { valid: false, lastDate: "2024-07-03", expireDate: "2025-07-03" } },
+  { id: "EMP1022", dept: "瀹夌洃绉�", training: { valid: true, lastDate: "2025-08-01", expireDate: "2026-08-01" } },
+]);
+const accessSim = reactive({ personId: "", targetZoneId: "" });
+const accessResult = ref(null);
+
+function simulateAccess() {
+  const person = persons.value.find((p) => p.id === accessSim.personId);
+  const zone = zones.value.find((z) => z.id === accessSim.targetZoneId);
+  if (!person || !zone) {
+    accessResult.value = null;
+    return;
+  }
+  const allowed = person.training.valid && (zone.zoneType !== "鍗卞寲鍝�" || person.dept === "瀹夌洃绉�");
+  accessResult.value = { allowed, person, zone };
+}
+
+// 浣╂埓璁惧婊炵暀鍛婅锛堝亣鏁版嵁瀹氭椂鎺ㄩ�侊級
+const stayThreshold = ref(20);
+const alarmOn = ref(true);
+const alarms = ref([
+  { time: "09:35", level: "warning", personId: "EMP1001", zoneName: "鐓ゅ満A鍖�", stayMins: 18, deviceId: "TAG-7A12", rssi: -67 },
+]);
+
+let timer = null;
+function pushMockAlarm() {
+  if (!alarmOn.value) return;
+  const candidates = [
+    { personId: "EMP1018", zoneName: "绛涘垎杞﹂棿", base: 12 },
+    { personId: "EMP1022", zoneName: "楂樺帇閰嶇數瀹�", base: 9 },
+    { personId: "EMP1001", zoneName: "鐓ゅ満A鍖�", base: 16 },
+  ];
+  const pick = candidates[Math.floor(Math.random() * candidates.length)];
+  const stay = pick.base + Math.floor(Math.random() * 10);
+  if (stay >= stayThreshold.value) {
+    const now = new Date();
+    const hh = String(now.getHours()).padStart(2, "0");
+    const mm = String(now.getMinutes()).padStart(2, "0");
+    alarms.value.unshift({
+      time: `${hh}:${mm}`,
+      level: stay >= stayThreshold.value + 10 ? "danger" : "warning",
+      personId: pick.personId,
+      zoneName: pick.zoneName,
+      stayMins: stay,
+      deviceId: `TAG-${Math.random().toString(16).slice(2, 6).toUpperCase()}`,
+      rssi: -60 - Math.floor(Math.random() * 15),
+    });
+    if (alarms.value.length > 30) alarms.value.pop();
+  }
+}
+
+onMounted(() => {
+  filterZones();
+  timer = setInterval(pushMockAlarm, 4500);
+});
+
+// 绂诲紑鏃舵竻鐞�
+if (import.meta.hot) {
+  import.meta.hot.dispose(() => {
+    if (timer) clearInterval(timer);
+  });
+}
+</script>
+
+<style scoped lang="scss">
+.section-card {
+  margin-bottom: 16px;
+}
+.card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.header-actions {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+.alarm-item .title {
+  font-weight: 600;
+  margin-bottom: 4px;
+}
+.alarm-item .desc {
+  color: #666;
+  font-size: 12px;
+}
+</style>
+
+
diff --git a/src/views/monitorManagement/videoMonitor/index.vue b/src/views/monitorManagement/videoMonitor/index.vue
new file mode 100644
index 0000000..b78c7f5
--- /dev/null
+++ b/src/views/monitorManagement/videoMonitor/index.vue
@@ -0,0 +1,990 @@
+<template>
+  <div class="app-container">
+    <el-row :gutter="16">
+      <!-- 宸︿晶锛氳棰戠洃鎺у垪琛ㄤ笌鎶撴媿璁板綍 -->
+      <el-col :span="16">
+        <!-- 瑙嗛鐩戞帶鍒楄〃 -->
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="card-header">
+              <span>瑙嗛鐩戞帶鐐逛綅绠$悊</span>
+              <div class="header-actions">
+                <el-select v-model="selectedArea" placeholder="閫夋嫨鍖哄煙" size="small" style="width: 160px" @change="filterCameras">
+                  <el-option label="鍏ㄩ儴鍖哄煙" value="all" />
+                  <el-option v-for="area in areas" :key="area.id" :label="area.name" :value="area.id" />
+                </el-select>
+                <el-select v-model="cameraStatus" placeholder="璁惧鐘舵��" size="small" style="width: 120px" @change="filterCameras">
+                  <el-option label="鍏ㄩ儴鐘舵��" value="all" />
+                  <el-option label="鍦ㄧ嚎" value="online" />
+                  <el-option label="绂荤嚎" value="offline" />
+                </el-select>
+              </div>
+            </div>
+          </template>
+          <el-table :data="filteredCameras" border style="width: 100%" max-height="320">
+            <el-table-column type="index" width="50" label="搴忓彿" align="center" />
+            <el-table-column prop="name" label="鐩戞帶鐐逛綅" min-width="140" show-overflow-tooltip />
+            <el-table-column prop="areaName" label="鎵�灞炲尯鍩�" width="120" />
+            <el-table-column label="璁惧鐘舵��" width="100" align="center">
+              <template #default="{ row }">
+                <el-tag :type="row.status === 'online' ? 'success' : 'danger'" size="small">
+                  {{ row.status === 'online' ? '鍦ㄧ嚎' : '绂荤嚎' }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="AI璇嗗埆" width="100" align="center">
+              <template #default="{ row }">
+                <el-tag v-if="row.aiEnabled" type="success" size="small">宸插惎鐢�</el-tag>
+                <el-tag v-else type="info" size="small">鏈惎鐢�</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="闂ㄧ鑱斿姩" width="100" align="center">
+              <template #default="{ row }">
+                <el-tag v-if="row.doorLinked" type="primary" size="small">宸茬粦瀹�</el-tag>
+                <el-tag v-else type="info" size="small">鏈粦瀹�</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="鎿嶄綔" width="180" align="center" fixed="right">
+              <template #default="{ row }">
+                <el-button link type="primary" size="small" @click="viewRealtime(row)">瀹炴椂鐢婚潰</el-button>
+                <el-button link type="success" size="small" @click="viewCaptures(row)">鎶撴媿璁板綍</el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+
+        <!-- 闂ㄧ鎶撴媿璁板綍 -->
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="card-header">
+              <span>闂ㄧ鎶撴媿璁板綍</span>
+              <div class="header-actions">
+                <el-date-picker
+                  v-model="captureDate"
+                  type="daterange"
+                  range-separator="鑷�"
+                  start-placeholder="寮�濮嬫棩鏈�"
+                  end-placeholder="缁撴潫鏃ユ湡"
+                  size="small"
+                  style="width: 260px"
+                  @change="loadCaptures"
+                />
+                <el-select v-model="captureEventType" placeholder="浜嬩欢绫诲瀷" size="small" style="width: 100px" clearable>
+                  <el-option label="鍏ㄩ儴" value="" />
+                  <el-option label="杩涘叆" value="entry" />
+                  <el-option label="绂诲紑" value="exit" />
+                </el-select>
+                <el-input v-model="captureSearch" placeholder="鎼滅储宸ュ彿/鍖哄煙" size="small" style="width: 140px" clearable>
+                  <template #prefix>
+                    <el-icon><Search /></el-icon>
+                  </template>
+                </el-input>
+                <el-button type="primary" size="small" @click="exportCaptures">
+                  <el-icon><Download /></el-icon>
+                  瀵煎嚭
+                </el-button>
+              </div>
+            </div>
+          </template>
+          
+          <!-- 缁熻淇℃伅 -->
+          <div class="capture-stats">
+            <el-row :gutter="16">
+              <el-col :span="6">
+                <div class="stat-item">
+                  <div class="stat-label">浠婃棩鎶撴媿</div>
+                  <div class="stat-value">{{ captureStats.today }}</div>
+                </div>
+              </el-col>
+              <el-col :span="6">
+                <div class="stat-item">
+                  <div class="stat-label">杩涘叆璁板綍</div>
+                  <div class="stat-value" style="color: #67c23a">{{ captureStats.entry }}</div>
+                </div>
+              </el-col>
+              <el-col :span="6">
+                <div class="stat-item">
+                  <div class="stat-label">绂诲紑璁板綍</div>
+                  <div class="stat-value" style="color: #e6a23c">{{ captureStats.exit }}</div>
+                </div>
+              </el-col>
+              <el-col :span="6">
+                <div class="stat-item">
+                  <div class="stat-label">浣庡尮閰嶅害</div>
+                  <div class="stat-value" style="color: #f56c6c">{{ captureStats.lowMatch }}</div>
+                </div>
+              </el-col>
+            </el-row>
+          </div>
+
+          <el-table 
+            :data="paginatedCaptures" 
+            border 
+            style="width: 100%" 
+            max-height="350"
+            @selection-change="handleCaptureSelection"
+          >
+            <el-table-column type="selection" width="45" align="center" />
+            <el-table-column type="index" width="50" label="搴忓彿" align="center" :index="indexMethod" />
+            <el-table-column prop="time" label="鎶撴媿鏃堕棿" width="155" sortable />
+            <el-table-column prop="personId" label="宸ュ彿" width="110" show-overflow-tooltip />
+            <el-table-column prop="department" label="閮ㄩ棬" width="100" show-overflow-tooltip />
+            <el-table-column prop="areaName" label="鍖哄煙" width="110" show-overflow-tooltip />
+            <el-table-column label="闂ㄧ浜嬩欢" width="90" align="center">
+              <template #default="{ row }">
+                <el-tag :type="row.eventType === 'entry' ? 'success' : 'warning'" size="small">
+                  {{ row.eventType === 'entry' ? '杩涘叆' : '绂诲紑' }}
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column label="浜鸿劯鍖归厤" width="95" align="center" sortable :sort-method="(a, b) => a.faceMatch - b.faceMatch">
+              <template #default="{ row }">
+                <el-tag :type="getFaceMatchType(row.faceMatch)" size="small">
+                  {{ row.faceMatch }}%
+                </el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="cameraName" label="鎽勫儚澶�" width="120" show-overflow-tooltip />
+            <el-table-column label="鎿嶄綔" width="150" align="center" fixed="right">
+              <template #default="{ row }">
+                <el-button link type="primary" size="small" @click="viewSnapshot(row)">
+                  <el-icon><View /></el-icon>
+                  鏌ョ湅
+                </el-button>
+                <el-button link type="success" size="small" @click="downloadSnapshot(row)">
+                  <el-icon><Download /></el-icon>
+                  涓嬭浇
+                </el-button>
+              </template>
+            </el-table-column>
+          </el-table>
+
+          <!-- 鍒嗛〉 -->
+          <div class="pagination-container">
+            <el-pagination
+              v-model:current-page="capturePage"
+              v-model:page-size="capturePageSize"
+              :page-sizes="[10, 20, 50, 100]"
+              :total="filteredCaptures.length"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handleSizeChange"
+              @current-change="handleCurrentChange"
+            />
+          </div>
+        </el-card>
+      </el-col>
+
+      <!-- 鍙充晶锛氳繚瑙勫憡璀� -->
+      <el-col :span="8">
+        <el-card shadow="never" class="section-card">
+          <template #header>
+            <div class="card-header">
+              <span>杩濊琛屼负鍛婅</span>
+              <div class="header-actions">
+                <el-badge :value="unreadAlarmCount" :max="99" type="danger">
+                  <el-switch v-model="alarmEnabled" inline-prompt active-text="鍛婅寮�" inactive-text="鍛婅鍏�" />
+                </el-badge>
+              </div>
+            </div>
+          </template>
+          <el-tabs v-model="alarmTab" type="border-card" style="height: 650px">
+            <el-tab-pane label="鍏ㄩ儴鍛婅" name="all">
+              <div>
+              <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+                <el-timeline-item
+                  v-for="(alarm, idx) in displayAlarms"
+                  :key="idx"
+                  :type="getAlarmType(alarm.type)"
+                  :timestamp="alarm.time"
+                  :hollow="alarm.handled"
+                >
+                  <div class="alarm-item" :class="{ handled: alarm.handled }">
+                    <div class="alarm-header">
+                      <el-tag :type="getAlarmTagType(alarm.type)" size="small">{{ alarm.typeText }}</el-tag>
+                      <span class="alarm-area">{{ alarm.areaName }}</span>
+                    </div>
+                    <div class="alarm-content">
+                      {{ alarm.description }}
+                    </div>
+                    <div class="alarm-footer">
+                      <span class="alarm-camera">鎽勫儚澶�: {{ alarm.cameraName }}</span>
+                      <el-button
+                        v-if="!alarm.handled"
+                        link
+                        type="primary"
+                        size="small"
+                        @click="handleAlarm(alarm)"
+                      >
+                        澶勭悊
+                      </el-button>
+                      <span v-else class="handled-text">宸插鐞�</span>
+                    </div>
+                  </div>
+                </el-timeline-item>
+              </el-timeline>
+              </div>
+            </el-tab-pane>
+            <el-tab-pane label="寮洪棷鍛婅" name="intrusion">
+              <div>
+              <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+                <el-timeline-item
+                  v-for="(alarm, idx) in intrusionAlarms"
+                  :key="idx"
+                  type="danger"
+                  :timestamp="alarm.time"
+                >
+                  <div class="alarm-item">
+                    <div class="alarm-header">
+                      <el-tag type="danger" size="small">寮洪棷鍛婅</el-tag>
+                      <span class="alarm-area">{{ alarm.areaName }}</span>
+                    </div>
+                    <div class="alarm-content">{{ alarm.description }}</div>
+                  </div>
+                </el-timeline-item>
+              </el-timeline>
+              </div>
+            </el-tab-pane>
+            <el-tab-pane label="灏鹃殢鍛婅" name="tailgating">
+              <div>
+              <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+                <el-timeline-item
+                  v-for="(alarm, idx) in tailgatingAlarms"
+                  :key="idx"
+                  type="warning"
+                  :timestamp="alarm.time"
+                >
+                  <div class="alarm-item">
+                    <div class="alarm-header">
+                      <el-tag type="warning" size="small">灏鹃殢鍛婅</el-tag>
+                      <span class="alarm-area">{{ alarm.areaName }}</span>
+                    </div>
+                    <div class="alarm-content">{{ alarm.description }}</div>
+                  </div>
+                </el-timeline-item>
+              </el-timeline>
+              </div>
+            </el-tab-pane>
+            <el-tab-pane label="澶氫汉閫氳" name="multiple">
+              <div>
+              <el-timeline style="max-height: 580px; overflow-y: auto; padding-right: 10px">
+                <el-timeline-item
+                  v-for="(alarm, idx) in multipleAlarms"
+                  :key="idx"
+                  type="warning"
+                  :timestamp="alarm.time"
+                >
+                  <div class="alarm-item">
+                    <div class="alarm-header">
+                      <el-tag type="warning" size="small">澶氫汉閫氳</el-tag>
+                      <span class="alarm-area">{{ alarm.areaName }}</span>
+                    </div>
+                    <div class="alarm-content">{{ alarm.description }}</div>
+                  </div>
+                </el-timeline-item>
+              </el-timeline>
+              </div>
+            </el-tab-pane>
+          </el-tabs>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 瀹炴椂鐢婚潰瀵硅瘽妗� -->
+    <el-dialog v-model="realtimeVisible" :title="`瀹炴椂鐩戞帶 - ${currentCamera.name}`" width="800px">
+      <div class="video-container">
+        <div class="video-placeholder">
+          <el-icon :size="80" color="#909399"><VideoCameraFilled /></el-icon>
+          <div class="video-info">
+            <p>鎽勫儚澶�: {{ currentCamera.name }}</p>
+            <p>浣嶇疆: {{ currentCamera.areaName }}</p>
+            <p>鐘舵��: <el-tag :type="currentCamera.status === 'online' ? 'success' : 'danger'" size="small">{{ currentCamera.status === 'online' ? '鍦ㄧ嚎' : '绂荤嚎' }}</el-tag></p>
+            <p class="tip">锛堝疄闄呯幆澧冨皢鏄剧ず瀹炴椂瑙嗛娴侊級</p>
+          </div>
+        </div>
+      </div>
+      <template #footer>
+        <el-button @click="realtimeVisible = false">鍏抽棴</el-button>
+        <el-button type="primary" @click="captureSnapshot">鎵嬪姩鎶撴媿</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 蹇収鏌ョ湅瀵硅瘽妗� -->
+    <el-dialog v-model="snapshotVisible" title="鎶撴媿蹇収璇︽儏" width="800px" :close-on-click-modal="false">
+      <div class="snapshot-dialog-body">
+        <el-row :gutter="20">
+        <el-col :span="14">
+          <div class="snapshot-preview">
+            <div class="snapshot-placeholder">
+              <el-icon :size="80" color="#909399"><Picture /></el-icon>
+              <p>鎶撴媿鍥剧墖棰勮</p>
+              <p class="tip">锛堝疄闄呯幆澧冨皢鏄剧ず楂樻竻鎶撴媿鍥剧墖锛�</p>
+              <div class="snapshot-tools">
+                <el-button-group>
+                  <el-button size="small">
+                    <el-icon><ZoomIn /></el-icon>
+                    鏀惧ぇ
+                  </el-button>
+                  <el-button size="small">
+                    <el-icon><ZoomOut /></el-icon>
+                    缂╁皬
+                  </el-button>
+                  <el-button size="small">
+                    <el-icon><RefreshRight /></el-icon>
+                    鏃嬭浆
+                  </el-button>
+                </el-button-group>
+              </div>
+            </div>
+          </div>
+        </el-col>
+        <el-col :span="10">
+          <el-descriptions :column="1" border size="small">
+            <el-descriptions-item label="鎶撴媿鏃堕棿">
+              <el-icon><Clock /></el-icon>
+              {{ currentSnapshot.time }}
+            </el-descriptions-item>
+            <el-descriptions-item label="宸ュ彿">
+              <el-icon><User /></el-icon>
+              {{ currentSnapshot.personId }}
+            </el-descriptions-item>
+            <el-descriptions-item label="閮ㄩ棬">
+              {{ currentSnapshot.department }}
+            </el-descriptions-item>
+            <el-descriptions-item label="鎵�灞炲尯鍩�">
+              <el-icon><Location /></el-icon>
+              {{ currentSnapshot.areaName }}
+            </el-descriptions-item>
+            <el-descriptions-item label="鎽勫儚澶�">
+              <el-icon><VideoCameraFilled /></el-icon>
+              {{ currentSnapshot.cameraName }}
+            </el-descriptions-item>
+            <el-descriptions-item label="浜嬩欢绫诲瀷">
+              <el-tag :type="currentSnapshot.eventType === 'entry' ? 'success' : 'warning'" size="small">
+                {{ currentSnapshot.eventType === 'entry' ? '杩涘叆' : '绂诲紑' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="浜鸿劯鍖归厤搴�">
+              <el-progress 
+                :percentage="currentSnapshot.faceMatch" 
+                :color="currentSnapshot.faceMatch >= 90 ? '#67c23a' : '#e6a23c'"
+                :stroke-width="12"
+              >
+                <span style="font-size: 12px">{{ currentSnapshot.faceMatch }}%</span>
+              </el-progress>
+            </el-descriptions-item>
+            <el-descriptions-item label="浣撴俯妫�娴�">
+              <span :style="{ color: currentSnapshot.temperature > 37.3 ? '#f56c6c' : '#67c23a' }">
+                {{ currentSnapshot.temperature }}掳C
+              </span>
+              <el-tag v-if="currentSnapshot.temperature > 37.3" type="danger" size="small" style="margin-left: 8px">
+                寮傚父
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="鍙g僵浣╂埓">
+              <el-tag :type="currentSnapshot.maskWearing ? 'success' : 'warning'" size="small">
+                {{ currentSnapshot.maskWearing ? '宸蹭僵鎴�' : '鏈僵鎴�' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="瀹夊叏甯�">
+              <el-tag :type="currentSnapshot.helmetWearing ? 'success' : 'danger'" size="small">
+                {{ currentSnapshot.helmetWearing ? '宸蹭僵鎴�' : '鏈僵鎴�' }}
+              </el-tag>
+            </el-descriptions-item>
+          </el-descriptions>
+        </el-col>
+        </el-row>
+        
+        <div class="snapshot-notes">
+        <el-divider content-position="left">澶囨敞淇℃伅</el-divider>
+        <el-input
+          v-model="currentSnapshot.notes"
+          type="textarea"
+          :rows="3"
+          placeholder="娣诲姞澶囨敞淇℃伅..."
+        />
+        </div>
+
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer-custom">
+          <div>
+            <el-button size="small" @click="printSnapshot">
+              <el-icon><Printer /></el-icon>
+              鎵撳嵃
+            </el-button>
+          </div>
+          <div>
+            <el-button @click="snapshotVisible = false">鍏抽棴</el-button>
+            <el-button type="success" @click="downloadSnapshot(currentSnapshot)">
+              <el-icon><Download /></el-icon>
+              涓嬭浇鍥剧墖
+            </el-button>
+            <el-button type="primary" @click="saveNotes">淇濆瓨澶囨敞</el-button>
+          </div>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup>
+import { ref, reactive, computed, onMounted, onBeforeUnmount } from "vue";
+import { 
+  VideoCameraFilled, Picture, Search, Download, View, 
+  Clock, User, Location, ZoomIn, ZoomOut, RefreshRight, Printer 
+} from "@element-plus/icons-vue";
+import { ElMessage, ElMessageBox } from "element-plus";
+
+// 鍖哄煙鏁版嵁
+const areas = ref([
+  { id: "A01", name: "鐓ゅ満鍏ュ彛" },
+  { id: "A02", name: "娲楃叅杞﹂棿" },
+  { id: "A03", name: "鍗遍櫓鍝佸簱" },
+  { id: "A04", name: "涓帶瀹�" },
+  { id: "A05", name: "閰嶇數瀹�" },
+]);
+
+// 鎽勫儚澶存暟鎹�
+const cameras = ref([
+  { id: "C001", name: "鐓ゅ満鍏ュ彛涓滈棬", areaId: "A01", areaName: "鐓ゅ満鍏ュ彛", status: "online", aiEnabled: true, doorLinked: true },
+  { id: "C002", name: "鐓ゅ満鍏ュ彛瑗块棬", areaId: "A01", areaName: "鐓ゅ満鍏ュ彛", status: "online", aiEnabled: true, doorLinked: true },
+  { id: "C003", name: "娲楃叅杞﹂棿涓婚�氶亾", areaId: "A02", areaName: "娲楃叅杞﹂棿", status: "online", aiEnabled: true, doorLinked: true },
+  { id: "C004", name: "娲楃叅杞﹂棿涓滀晶", areaId: "A02", areaName: "娲楃叅杞﹂棿", status: "online", aiEnabled: false, doorLinked: false },
+  { id: "C005", name: "鍗遍櫓鍝佸簱澶ч棬", areaId: "A03", areaName: "鍗遍櫓鍝佸簱", status: "online", aiEnabled: true, doorLinked: true },
+  { id: "C006", name: "鍗遍櫓鍝佸簱鍐呴儴", areaId: "A03", areaName: "鍗遍櫓鍝佸簱", status: "offline", aiEnabled: true, doorLinked: false },
+  { id: "C007", name: "涓帶瀹ゅ叆鍙�", areaId: "A04", areaName: "涓帶瀹�", status: "online", aiEnabled: true, doorLinked: true },
+  { id: "C008", name: "閰嶇數瀹ら棬绂�", areaId: "A05", areaName: "閰嶇數瀹�", status: "online", aiEnabled: true, doorLinked: true },
+]);
+
+const selectedArea = ref("all");
+const cameraStatus = ref("all");
+const filteredCameras = ref([]);
+
+function filterCameras() {
+  let result = cameras.value;
+  if (selectedArea.value !== "all") {
+    result = result.filter(c => c.areaId === selectedArea.value);
+  }
+  if (cameraStatus.value !== "all") {
+    result = result.filter(c => c.status === cameraStatus.value);
+  }
+  filteredCameras.value = result;
+}
+
+// 闂ㄧ鎶撴媿璁板綍
+const captureDate = ref([new Date(), new Date()]);
+const captureSearch = ref("");
+const captureEventType = ref("");
+const capturePage = ref(1);
+const capturePageSize = ref(10);
+const selectedCaptures = ref([]);
+
+// 鐢熸垚鏇村妯℃嫙鏁版嵁
+const generateCaptureData = () => {
+  const departments = ["鐢熶骇涓�闃�", "鏈虹數鐝�", "瀹夌洃绉�", "璋冨害瀹�", "鍖栭獙瀹�", "杩愯緭闃�", "缁翠慨鐝�", "浠撳偍绉�"];
+  const areaList = ["鐓ゅ満鍏ュ彛", "娲楃叅杞﹂棿", "鍗遍櫓鍝佸簱", "涓帶瀹�", "閰嶇數瀹�"];
+  const cameraList = [
+    "鐓ゅ満鍏ュ彛涓滈棬", "鐓ゅ満鍏ュ彛瑗块棬", "娲楃叅杞﹂棿涓婚�氶亾", "娲楃叅杞﹂棿涓滀晶", 
+    "鍗遍櫓鍝佸簱澶ч棬", "鍗遍櫓鍝佸簱鍐呴儴", "涓帶瀹ゅ叆鍙�", "閰嶇數瀹ら棬绂�"
+  ];
+  
+  const data = [];
+  const now = new Date();
+  
+  for (let i = 0; i < 86; i++) {
+    const randomMinutes = Math.floor(Math.random() * 1440); // 24灏忔椂鍐�
+    const captureTime = new Date(now.getTime() - randomMinutes * 60 * 1000);
+    const hh = String(captureTime.getHours()).padStart(2, "0");
+    const mm = String(captureTime.getMinutes()).padStart(2, "0");
+    const ss = String(captureTime.getSeconds()).padStart(2, "0");
+    
+    const area = areaList[Math.floor(Math.random() * areaList.length)];
+    const eventType = Math.random() > 0.5 ? "entry" : "exit";
+    const faceMatch = Math.floor(Math.random() * 15) + 85; // 85-100
+    const temperature = (36.0 + Math.random() * 1.5).toFixed(1);
+    
+    data.push({
+      id: i + 1,
+      time: `2025-10-28 ${hh}:${mm}:${ss}`,
+      personId: `EMP${String(1001 + i).padStart(4, '0')}`,
+      department: departments[Math.floor(Math.random() * departments.length)],
+      areaName: area,
+      cameraName: cameraList[Math.floor(Math.random() * cameraList.length)],
+      eventType: eventType,
+      faceMatch: faceMatch,
+      temperature: parseFloat(temperature),
+      maskWearing: Math.random() > 0.1,
+      helmetWearing: Math.random() > 0.15,
+      notes: ""
+    });
+  }
+  
+  return data.sort((a, b) => b.time.localeCompare(a.time));
+};
+
+const captures = ref(generateCaptureData());
+
+// 缁熻淇℃伅
+const captureStats = computed(() => {
+  const today = captures.value.length;
+  const entry = captures.value.filter(c => c.eventType === 'entry').length;
+  const exit = captures.value.filter(c => c.eventType === 'exit').length;
+  const lowMatch = captures.value.filter(c => c.faceMatch < 90).length;
+  
+  return { today, entry, exit, lowMatch };
+});
+
+// 杩囨护鎶撴媿璁板綍
+const filteredCaptures = computed(() => {
+  let result = captures.value;
+  
+  // 鎸夊叧閿瘝鎼滅储
+  if (captureSearch.value) {
+    const keyword = captureSearch.value.toLowerCase();
+    result = result.filter(c => 
+      c.personId.toLowerCase().includes(keyword) ||
+      c.areaName.toLowerCase().includes(keyword)
+    );
+  }
+  
+  // 鎸変簨浠剁被鍨嬭繃婊�
+  if (captureEventType.value) {
+    result = result.filter(c => c.eventType === captureEventType.value);
+  }
+  
+  return result;
+});
+
+// 鍒嗛〉鏁版嵁
+const paginatedCaptures = computed(() => {
+  const start = (capturePage.value - 1) * capturePageSize.value;
+  const end = start + capturePageSize.value;
+  return filteredCaptures.value.slice(start, end);
+});
+
+// 鍒嗛〉绱㈠紩鏂规硶
+const indexMethod = (index) => {
+  return (capturePage.value - 1) * capturePageSize.value + index + 1;
+};
+
+function loadCaptures() {
+  ElMessage.success("宸插姞杞介�夊畾鏃ユ湡鑼冨洿鐨勬姄鎷嶈褰�");
+}
+
+function handleSizeChange(val) {
+  capturePageSize.value = val;
+  capturePage.value = 1;
+}
+
+function handleCurrentChange(val) {
+  capturePage.value = val;
+}
+
+function handleCaptureSelection(selection) {
+  selectedCaptures.value = selection;
+}
+
+function getFaceMatchType(faceMatch) {
+  if (faceMatch >= 95) return 'success';
+  if (faceMatch >= 90) return 'info';
+  if (faceMatch >= 85) return 'warning';
+  return 'danger';
+}
+
+// 瀵煎嚭鎶撴媿璁板綍
+function exportCaptures() {
+  if (filteredCaptures.value.length === 0) {
+    ElMessage.warning("娌℃湁鍙鍑虹殑璁板綍");
+    return;
+  }
+  ElMessage.success(`姝e湪瀵煎嚭 ${filteredCaptures.value.length} 鏉℃姄鎷嶈褰�...`);
+  // 瀹為檯鐜涓繖閲屼細璋冪敤瀵煎嚭鎺ュ彛
+}
+
+// 涓嬭浇蹇収
+function downloadSnapshot(capture) {
+  ElMessage.success(`姝e湪涓嬭浇 ${capture.personId} 鐨勬姄鎷嶅浘鐗�...`);
+  // 瀹為檯鐜涓繖閲屼細涓嬭浇鍥剧墖鏂囦欢
+}
+
+// 鎵撳嵃蹇収
+function printSnapshot() {
+  ElMessage.info("姝e湪鍑嗗鎵撳嵃...");
+  // 瀹為檯鐜涓繖閲屼細璋冪敤鎵撳嵃鍔熻兘
+}
+
+// 淇濆瓨澶囨敞
+function saveNotes() {
+  ElMessage.success("澶囨敞淇℃伅宸蹭繚瀛�");
+  snapshotVisible.value = false;
+}
+
+// 鍛婅鏁版嵁
+const alarmEnabled = ref(true);
+const alarmTab = ref("all");
+const alarms = ref([
+  {
+    id: 1,
+    time: "09:25:15",
+    type: "intrusion",
+    typeText: "寮洪棷鍛婅",
+    areaName: "鍗遍櫓鍝佸簱",
+    cameraName: "鍗遍櫓鍝佸簱澶ч棬",
+    description: "妫�娴嬪埌鏈巿鏉冧汉鍛樺己琛岄棷鍏ワ紝鏈埛鍗$洿鎺ュ紑闂�",
+    handled: false
+  },
+  {
+    id: 2,
+    time: "09:18:42",
+    type: "tailgating",
+    typeText: "灏鹃殢鍛婅",
+    areaName: "涓帶瀹�",
+    cameraName: "涓帶瀹ゅ叆鍙�",
+    description: "妫�娴嬪埌灏鹃殢琛屼负锛屼竴浜哄埛鍗″悗涓や汉杩涘叆",
+    handled: false
+  },
+  {
+    id: 3,
+    time: "09:10:28",
+    type: "multiple",
+    typeText: "澶氫汉閫氳",
+    areaName: "鐓ゅ満鍏ュ彛",
+    cameraName: "鐓ゅ満鍏ュ彛涓滈棬",
+    description: "妫�娴嬪埌3浜哄悓鏃堕�氳繃鍗曚汉闂ㄧ閫氶亾",
+    handled: true
+  },
+]);
+
+const unreadAlarmCount = computed(() => alarms.value.filter(a => !a.handled).length);
+
+const displayAlarms = computed(() => {
+  if (alarmTab.value === "all") return alarms.value;
+  return alarms.value.filter(a => a.type === alarmTab.value);
+});
+
+const intrusionAlarms = computed(() => alarms.value.filter(a => a.type === "intrusion"));
+const tailgatingAlarms = computed(() => alarms.value.filter(a => a.type === "tailgating"));
+const multipleAlarms = computed(() => alarms.value.filter(a => a.type === "multiple"));
+
+function getAlarmType(type) {
+  const typeMap = { intrusion: "danger", tailgating: "warning", multiple: "warning" };
+  return typeMap[type] || "info";
+}
+
+function getAlarmTagType(type) {
+  const typeMap = { intrusion: "danger", tailgating: "warning", multiple: "warning" };
+  return typeMap[type] || "info";
+}
+
+function handleAlarm(alarm) {
+  alarm.handled = true;
+  ElMessage.success("鍛婅宸叉爣璁颁负宸插鐞�");
+}
+
+// 瀹炴椂鐢婚潰
+const realtimeVisible = ref(false);
+const currentCamera = ref({});
+
+function viewRealtime(camera) {
+  currentCamera.value = camera;
+  realtimeVisible.value = true;
+}
+
+function captureSnapshot() {
+  ElMessage.success("鎶撴媿鎴愬姛锛屽凡淇濆瓨鑷虫姄鎷嶈褰�");
+}
+
+// 鏌ョ湅鎶撴媿璁板綍璇︽儏
+const snapshotVisible = ref(false);
+const currentSnapshot = ref({});
+
+function viewSnapshot(capture) {
+  currentSnapshot.value = capture;
+  snapshotVisible.value = true;
+}
+
+function viewCaptures(camera) {
+  ElMessage.info(`鏌ョ湅 ${camera.name} 鐨勫巻鍙叉姄鎷嶈褰昤);
+}
+
+// 妯℃嫙鍛婅鎺ㄩ��
+let alarmTimer = null;
+const alarmTemplates = [
+  { type: "intrusion", typeText: "寮洪棷鍛婅", areas: ["鍗遍櫓鍝佸簱", "閰嶇數瀹�", "涓帶瀹�"] },
+  { type: "tailgating", typeText: "灏鹃殢鍛婅", areas: ["涓帶瀹�", "閰嶇數瀹�", "鍗遍櫓鍝佸簱"] },
+  { type: "multiple", typeText: "澶氫汉閫氳", areas: ["鐓ゅ満鍏ュ彛", "娲楃叅杞﹂棿"] },
+];
+
+function pushMockAlarm() {
+  if (!alarmEnabled.value) return;
+  
+  const template = alarmTemplates[Math.floor(Math.random() * alarmTemplates.length)];
+  const area = template.areas[Math.floor(Math.random() * template.areas.length)];
+  const camera = cameras.value.find(c => c.areaName === area);
+  
+  const now = new Date();
+  const hh = String(now.getHours()).padStart(2, "0");
+  const mm = String(now.getMinutes()).padStart(2, "0");
+  const ss = String(now.getSeconds()).padStart(2, "0");
+  
+  let description = "";
+  if (template.type === "intrusion") {
+    description = "妫�娴嬪埌鏈巿鏉冧汉鍛樺己琛岄棷鍏ワ紝鏈埛鍗$洿鎺ュ紑闂�";
+  } else if (template.type === "tailgating") {
+    description = "妫�娴嬪埌灏鹃殢琛屼负锛屼竴浜哄埛鍗″悗涓や汉杩涘叆";
+  } else if (template.type === "multiple") {
+    const count = Math.floor(Math.random() * 3) + 2;
+    description = `妫�娴嬪埌${count}浜哄悓鏃堕�氳繃鍗曚汉闂ㄧ閫氶亾`;
+  }
+  
+  alarms.value.unshift({
+    id: Date.now(),
+    time: `${hh}:${mm}:${ss}`,
+    type: template.type,
+    typeText: template.typeText,
+    areaName: area,
+    cameraName: camera ? camera.name : area + "鐩戞帶",
+    description: description,
+    handled: false
+  });
+  
+  // 闄愬埗鍛婅鍒楄〃闀垮害
+  if (alarms.value.length > 50) {
+    alarms.value = alarms.value.slice(0, 50);
+  }
+}
+
+onMounted(() => {
+  filterCameras();
+  // 姣忛殧8绉掓ā鎷熶竴娆″憡璀�
+  alarmTimer = setInterval(pushMockAlarm, 8000);
+});
+
+onBeforeUnmount(() => {
+  if (alarmTimer) {
+    clearInterval(alarmTimer);
+  }
+});
+</script>
+
+<style scoped lang="scss">
+.section-card {
+  margin-bottom: 16px;
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.header-actions {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.alarm-item {
+  padding: 8px;
+  background: #f5f7fa;
+  border-radius: 4px;
+  
+  &.handled {
+    opacity: 0.6;
+  }
+}
+
+.alarm-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 6px;
+}
+
+.alarm-area {
+  font-weight: 600;
+  color: #303133;
+}
+
+.alarm-content {
+  color: #606266;
+  font-size: 14px;
+  margin-bottom: 6px;
+  line-height: 1.5;
+}
+
+.alarm-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  font-size: 12px;
+  color: #909399;
+}
+
+.alarm-camera {
+  flex: 1;
+}
+
+.handled-text {
+  color: #67c23a;
+  font-size: 12px;
+}
+
+.video-container {
+  width: 100%;
+  height: 450px;
+  background: #000;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.video-placeholder {
+  text-align: center;
+  color: #909399;
+}
+
+.video-info {
+  margin-top: 20px;
+  
+  p {
+    margin: 8px 0;
+    font-size: 14px;
+  }
+  
+  .tip {
+    color: #c0c4cc;
+    font-size: 12px;
+    margin-top: 16px;
+  }
+}
+
+.snapshot-placeholder {
+  margin-top: 20px;
+  height: 300px;
+  background: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border-radius: 4px;
+  color: #909399;
+  
+  p {
+    margin: 8px 0;
+  }
+  
+  .tip {
+    color: #c0c4cc;
+    font-size: 12px;
+  }
+}
+
+:deep(.el-tabs--border-card) {
+  border: none;
+  box-shadow: none;
+}
+
+:deep(.el-tabs__content) {
+  padding: 10px;
+}
+
+// 鎶撴媿璁板綍缁熻
+.capture-stats {
+  margin-bottom: 16px;
+  padding: 16px;
+  background: #f5f7fa;
+  border-radius: 4px;
+}
+
+.stat-item {
+  text-align: center;
+  padding: 8px;
+  background: #fff;
+  border-radius: 4px;
+}
+
+.stat-label {
+  font-size: 13px;
+  color: #909399;
+  margin-bottom: 8px;
+}
+
+.stat-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #303133;
+}
+
+// 鍒嗛〉瀹瑰櫒
+.pagination-container {
+  margin-top: 16px;
+  display: flex;
+  justify-content: flex-end;
+}
+
+// 蹇収棰勮
+.snapshot-preview {
+  width: 100%;
+}
+
+.snapshot-placeholder {
+  width: 100%;
+  height: 420px;
+  background: #f5f7fa;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  border-radius: 4px;
+  border: 1px dashed #dcdfe6;
+  color: #909399;
+  
+  p {
+    margin: 8px 0;
+    font-size: 14px;
+  }
+  
+  .tip {
+    color: #c0c4cc;
+    font-size: 12px;
+  }
+}
+
+.snapshot-tools {
+  margin-top: 20px;
+}
+
+.snapshot-notes {
+  margin-top: 20px;
+  
+  :deep(.el-divider__text) {
+    font-weight: 600;
+    color: #606266;
+  }
+}
+
+.dialog-footer-custom {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  
+  > div {
+    display: flex;
+    gap: 8px;
+  }
+}
+
+// 鎻忚堪鍒楄〃鏍峰紡浼樺寲
+:deep(.el-descriptions__label) {
+  width: 100px;
+}
+
+:deep(.el-descriptions__content) {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+</style>
+
--
Gitblit v1.9.3