Selaa lähdekoodia

新增设备监控汇总页面

yawuga 11 kuukautta sitten
vanhempi
sitoutus
3f35f4f329

BIN
public/favicon.ico


+ 95 - 42
public/index.html

@@ -30,6 +30,47 @@
       width: 100%;
       height: 100%;
       z-index: 999999;
+      background: 
+        radial-gradient(circle at 20% 30%, rgba(76, 175, 80, 0.4) 0%, transparent 60%),
+        radial-gradient(circle at 80% 70%, rgba(102, 187, 106, 0.4) 0%, transparent 60%),
+        radial-gradient(circle at 40% 80%, rgba(76, 175, 80, 0.3) 0%, transparent 50%),
+        radial-gradient(circle at 60% 20%, rgba(129, 199, 132, 0.3) 0%, transparent 50%),
+        linear-gradient(135deg, #2E7D32 0%, #4CAF50 25%, #66BB6A 50%, #4CAF50 75%, #388E3C 100%);
+      overflow: hidden;
+    }
+    
+    #loader-wrapper::before {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background: 
+        repeating-linear-gradient(
+          45deg,
+          transparent,
+          transparent 3px,
+          rgba(255, 255, 255, 0.02) 3px,
+          rgba(255, 255, 255, 0.02) 6px
+        );
+      animation: shimmer 4s ease-in-out infinite;
+    }
+    
+    #loader-wrapper::after {
+      content: '';
+      position: absolute;
+      top: -25%;
+      left: -25%;
+      width: 150%;
+      height: 150%;
+      background: radial-gradient(
+        circle,
+        rgba(255, 255, 255, 0.08) 0%,
+        rgba(255, 255, 255, 0.04) 30%,
+        transparent 60%
+      );
+      animation: rotate 12s linear infinite;
     }
 
     #loader {
@@ -41,8 +82,16 @@
       height: 150px;
       margin: -75px 0 0 -75px;
       border-radius: 50%;
-      border: 3px solid transparent;
-      border-top-color: #FFF;
+      border: 4px solid transparent;
+      border-top: 4px solid rgba(255, 255, 255, 0.9);
+      border-right: 4px solid rgba(255, 255, 255, 0.3);
+      border-bottom: 4px solid rgba(255, 255, 255, 0.1);
+      border-left: 4px solid rgba(255, 255, 255, 0.6);
+      box-shadow: 
+        0 0 30px rgba(76, 175, 80, 0.4),
+        inset 0 0 20px rgba(255, 255, 255, 0.1),
+        0 0 60px rgba(76, 175, 80, 0.2);
+      backdrop-filter: blur(2px);
       -webkit-animation: spin 2s linear infinite;
       -ms-animation: spin 2s linear infinite;
       -moz-animation: spin 2s linear infinite;
@@ -60,7 +109,7 @@
       bottom: 5px;
       border-radius: 50%;
       border: 3px solid transparent;
-      border-top-color: #FFF;
+      border-top-color: rgba(255, 255, 255, 0.7);
       -webkit-animation: spin 3s linear infinite;
       -moz-animation: spin 3s linear infinite;
       -o-animation: spin 3s linear infinite;
@@ -77,7 +126,7 @@
       bottom: 15px;
       border-radius: 50%;
       border: 3px solid transparent;
-      border-top-color: #FFF;
+      border-top-color: rgba(255, 255, 255, 0.5);
       -moz-animation: spin 1.5s linear infinite;
       -o-animation: spin 1.5s linear infinite;
       -ms-animation: spin 1.5s linear infinite;
@@ -112,43 +161,39 @@
       }
     }
 
-
-    #loader-wrapper .loader-section {
-      position: fixed;
-      top: 0;
-      width: 51%;
-      height: 100%;
-      background: #7171C6;
-      z-index: 1000;
-      -webkit-transform: translateX(0);
-      -ms-transform: translateX(0);
-      transform: translateX(0);
+    @keyframes shimmer {
+      0%, 100% {
+        opacity: 0.3;
+        transform: translateX(-100%);
+      }
+      50% {
+        opacity: 0.8;
+        transform: translateX(100%);
     }
-
-    #loader-wrapper .loader-section.section-left {
-      left: 0;
     }
-
-    #loader-wrapper .loader-section.section-right {
-      right: 0;
+    
+    @keyframes rotate {
+      0% {
+        transform: rotate(0deg);
+      }
+      100% {
+        transform: rotate(360deg);
+      }
+    }
+    
+    @keyframes pulse {
+      0%, 100% {
+        opacity: 0.6;
+        transform: scale(1);
+      }
+      50% {
+        opacity: 1;
+        transform: scale(1.05);
+    }
     }
 
 
-    .loaded #loader-wrapper .loader-section.section-left {
-      -webkit-transform: translateX(-100%);
-      -ms-transform: translateX(-100%);
-      transform: translateX(-100%);
-      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-    }
 
-    .loaded #loader-wrapper .loader-section.section-right {
-      -webkit-transform: translateX(100%);
-      -ms-transform: translateX(100%);
-      transform: translateX(100%);
-      -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-      transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-    }
 
     .loaded #loader {
       opacity: 0;
@@ -174,9 +219,10 @@
     }
 
     #loader-wrapper .load_title {
-      font-family: 'Open Sans';
+      font-family: 'Open Sans', 'Microsoft YaHei', sans-serif;
       color: #FFF;
-      font-size: 19px;
+      font-size: 20px;
+      font-weight: 600;
       width: 100%;
       text-align: center;
       z-index: 9999999999999;
@@ -184,14 +230,23 @@
       top: 60%;
       opacity: 1;
       line-height: 30px;
+      text-shadow: 
+        0 2px 4px rgba(0, 0, 0, 0.3),
+        0 0 20px rgba(76, 175, 80, 0.6),
+        0 0 40px rgba(76, 175, 80, 0.3);
+      letter-spacing: 1px;
+      animation: pulse 2s ease-in-out infinite;
     }
 
     #loader-wrapper .load_title span {
       font-weight: normal;
       font-style: italic;
-      font-size: 13px;
-      color: #FFF;
-      opacity: 0.5;
+      font-size: 14px;
+      color: rgba(255, 255, 255, 0.8);
+      opacity: 0.7;
+      display: block;
+      margin-top: 8px;
+      text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
     }
   </style>
   </head>
@@ -199,8 +254,6 @@
     <div id="app">
 	    <div id="loader-wrapper">
 		    <div id="loader"></div>
-		    <div class="loader-section section-left"></div>
-		    <div class="loader-section section-right"></div>
 		    <div class="load_title">正在加载系统资源,请耐心等待</div>
         </div>
 	</div>

BIN
public/logo.png


BIN
src/assets/images/agriculture-tech-bg.jpg


BIN
src/assets/images/agritech-login-bg.jpg


BIN
src/assets/images/profile.jpg


BIN
src/assets/images/smart-farming-bg.jpg


BIN
src/assets/logo/logo.png


+ 18 - 7
src/assets/styles/sidebar.scss

@@ -15,7 +15,7 @@
     -webkit-transition: width .28s;
     transition: width 0.28s;
     width: $base-sidebar-width !important;
-    background-color: $base-menu-background;
+    background: $base-menu-background;
     height: 100%;
     position: fixed;
     font-size: 0px;
@@ -24,8 +24,10 @@
     left: 0;
     z-index: 1001;
     overflow: hidden;
-    -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
-    box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
+    box-shadow: 2px 0 6px rgba(0, 0, 0, 0.1);
+    
+    // 添加绿色边框效果
+    border-right: 2px solid rgba(76, 175, 80, 0.3);
 
     // reset element-ui css
     .horizontal-collapse-transition {
@@ -76,11 +78,14 @@
       white-space: nowrap !important;
     }
 
-    // menu hover
+    // menu hover - 绿色主题悬停效果
     .submenu-title-noDropdown,
     .el-submenu__title {
       &:hover {
-        background-color: rgba(0, 0, 0, 0.06) !important;
+        background: rgba(76, 175, 80, 0.08) !important;
+        color: #4CAF50 !important;
+        border-radius: 6px;
+        margin: 0 8px;
       }
     }
 
@@ -93,7 +98,10 @@
       min-width: $base-sidebar-width !important;
 
       &:hover {
-        background-color: rgba(0, 0, 0, 0.06) !important;
+        background: rgba(76, 175, 80, 0.08) !important;
+        color: #4CAF50 !important;
+        border-radius: 6px;
+        margin: 0 8px;
       }
     }
 
@@ -102,7 +110,10 @@
       background-color: $base-sub-menu-background !important;
 
       &:hover {
-        background-color: $base-sub-menu-hover !important;
+        background: $base-sub-menu-hover !important;
+        color: #4CAF50 !important;
+        border-radius: 6px;
+        margin: 0 8px;
       }
     }
   }

+ 7 - 7
src/assets/styles/variables.scss

@@ -8,18 +8,18 @@ $tiffany: #4AB7BD;
 $yellow:#FEC171;
 $panGreen: #30B08F;
 
-// 默认菜单主题风格
-$base-menu-color:#bfcbd9;
-$base-menu-color-active:#f4f4f5;
-$base-menu-background:#304156;
+// 爱智农绿色农业主题菜单风格
+$base-menu-color:#666666;
+$base-menu-color-active:#4CAF50;
+$base-menu-background:#ffffff;
 $base-logo-title-color: #ffffff;
 
 $base-menu-light-color:rgba(0,0,0,.70);
 $base-menu-light-background:#ffffff;
-$base-logo-light-title-color: #001529;
+$base-logo-light-title-color: #4CAF50;
 
-$base-sub-menu-background:#1f2d3d;
-$base-sub-menu-hover:#001528;
+$base-sub-menu-background:#f8f9fa;
+$base-sub-menu-hover:rgba(76, 175, 80, 0.1);
 
 // 自定义暗色菜单风格
 /**

+ 2 - 13
src/layout/components/Navbar.vue

@@ -9,14 +9,6 @@
       <template v-if="device!=='mobile'">
         <search id="header-search" class="right-menu-item" />
 
-        <el-tooltip content="源码地址" effect="dark" placement="bottom">
-          <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
-        </el-tooltip>
-
-        <el-tooltip content="文档地址" effect="dark" placement="bottom">
-          <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
-        </el-tooltip>
-
         <screenfull id="screenfull" class="right-menu-item hover-effect" />
 
         <el-tooltip content="布局大小" effect="dark" placement="bottom">
@@ -55,8 +47,7 @@ import Hamburger from '@/components/Hamburger'
 import Screenfull from '@/components/Screenfull'
 import SizeSelect from '@/components/SizeSelect'
 import Search from '@/components/HeaderSearch'
-import RuoYiGit from '@/components/RuoYi/Git'
-import RuoYiDoc from '@/components/RuoYi/Doc'
+
 
 export default {
   emits: ['setLayout'],
@@ -66,9 +57,7 @@ export default {
     Hamburger,
     Screenfull,
     SizeSelect,
-    Search,
-    RuoYiGit,
-    RuoYiDoc
+    Search
   },
   computed: {
     ...mapGetters([

+ 4 - 2
src/layout/components/Sidebar/Logo.vue

@@ -57,9 +57,11 @@ export default {
   width: 100%;
   height: 50px;
   line-height: 50px;
-  background: #2b2f3a;
+  background: #ffffff;
   text-align: center;
   overflow: hidden;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
+  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
 
   & .sidebar-logo-link {
     height: 100%;
@@ -75,7 +77,7 @@ export default {
     & .sidebar-title {
       display: inline-block;
       margin: 0;
-      color: #fff;
+      color: #2e7d32 !important;
       font-weight: 600;
       line-height: 50px;
       font-size: 14px;

+ 3 - 0
src/main.js

@@ -40,7 +40,10 @@ import DictData from '@/components/DictData'
 
 // 地图组件
 import VueAMap from 'vue-amap';
+import * as echarts from 'echarts'
+
 Vue.use(VueAMap)
+Vue.prototype.$echarts = echarts
 VueAMap.initAMapApiLoader({
   key: 'bee95ab1796ad6803492c1bf9da311ab',
   plugin: [

+ 2885 - 0
src/views/base/device/device-monitor.vue

@@ -0,0 +1,2885 @@
+<template>
+  <div class="device-monitor-container">
+    <!-- 控制面板 -->
+    <div class="control-panel">
+      <div class="control-left">
+        <div class="form-group">
+          <label>农场选择</label>
+          <select v-model="selectedFarm">
+            <option value="all">全部农场</option>
+            <option value="farm1">东湖智慧农场</option>
+            <option value="farm2">西湖有机农场</option>
+          </select>
+        </div>
+        <div class="form-group">
+          <label>地块选择</label>
+          <select v-model="selectedArea">
+            <option value="all">全部地块</option>
+            <option value="area1">1号地块</option>
+            <option value="area2">2号地块</option>
+          </select>
+        </div>
+        <div class="form-group">
+          <label>时间范围</label>
+          <select v-model="timeRange">
+            <option value="realtime">实时数据</option>
+            <option value="1h">近1小时</option>
+            <option value="24h">近24小时</option>
+          </select>
+        </div>
+      </div>
+      <div class="control-right">
+        <button class="refresh-btn" @click="refreshData">
+          <i class="icon-refresh"></i>
+          刷新数据
+        </button>
+        <div class="auto-refresh">
+          <span>自动刷新:</span>
+          <select v-model="autoRefresh" class="auto-refresh-select">
+            <option value="15">15秒</option>
+            <option value="30">30秒</option>
+            <option value="60">60秒</option>
+          </select>
+        </div>
+        <div class="last-update">
+          最后更新:<span>{{ lastUpdateTime }}</span>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 设备统计 -->
+    <div class="stats-container">
+      <!-- 综合统计卡片 -->
+      <div class="overview-stat-card">
+        <div class="overview-stats">
+          <div class="overview-item">
+            <div class="stat-number online">42</div>
+            <div class="stat-label">在线设备</div>
+          </div>
+          <div class="overview-item">
+            <div class="stat-number offline">3</div>
+            <div class="stat-label">离线设备</div>
+          </div>
+          <div class="overview-item">
+            <div class="stat-number alert">5</div>
+            <div class="stat-label">告警设备</div>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 分类统计卡片 -->
+      <div class="category-stats">
+        <div class="stat-card">
+          <div class="stat-number camera">12</div>
+          <div class="stat-label">摄像头</div>
+          <div class="stat-detail">11在线 / 1离线</div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-number sensor">15</div>
+          <div class="stat-label">传感器</div>
+          <div class="stat-detail">14在线 / 1告警</div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-number weather">8</div>
+          <div class="stat-label">气象设备</div>
+          <div class="stat-detail">7在线 / 1离线</div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-number control">10</div>
+          <div class="stat-label">控制设备</div>
+          <div class="stat-detail">10在线</div>
+        </div>
+        <div class="stat-card">
+          <div class="stat-number alert">7</div>
+          <div class="stat-label">今日告警</div>
+          <div class="stat-detail"><span class="processed">5已处理</span></div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 主要内容区域 -->
+    <div class="main-content">
+      <!-- 视频监控 -->
+      <div class="content-section">
+        <div class="video-header">
+          <div class="video-header-left">
+            <h2 class="section-title">视频监控</h2>
+            <div class="video-count">
+              共 {{ videoList.length }} 个摄像头
+            </div>
+          </div>
+          <div class="video-header-right">
+            <div class="video-nav-controls">
+              <button class="video-nav-btn" @click="prevPage" :disabled="currentPage === 1" title="上一页">
+                <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+                  <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
+                </svg>
+              </button>
+              <div class="page-indicator">
+                <span>{{ getPageRange() }} / {{ videoList.length }}</span>
+              </div>
+              <button class="video-nav-btn" @click="nextPage" :disabled="currentPage === totalPages" title="下一页">
+                <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+                  <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
+                </svg>
+              </button>
+            </div>
+            <button class="video-nav-btn" @click="toggleFullscreen" title="全屏显示">
+              <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+                <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
+              </svg>
+            </button>
+          </div>
+        </div>
+        
+        <div class="video-container">
+          <div class="video-grid">
+            <div v-for="video in paginatedVideos" :key="video.id" class="video-card">
+              <button class="camera-fullscreen-btn" @click="openVideoFullscreen(video)" title="全屏查看">
+                <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+                  <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
+                </svg>
+              </button>
+              <div class="video-preview">
+                <div class="camera-placeholder centered-container">
+                  <svg class="camera-icon centered-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                    <path d="M23 7l-7 5 7 5V7z"/>
+                    <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
+                  </svg>
+                </div>
+                <div class="video-info-overlay">
+                  <div class="video-details">
+                    <div class="video-name">{{ video.name }}</div>
+                    <div class="video-location">{{ video.location }}</div>
+                  </div>
+                  <span class="video-status" :class="video.status">
+                    {{ video.status === 'online' ? '在线' : '离线' }}
+                  </span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+      
+      <!-- 设备监控 -->
+      <div class="content-section">
+        <!-- 顶部控制栏 -->
+        <div class="device-monitor-header">
+          <div class="device-monitor-title-section">
+            <h2 class="section-title">设备监控</h2>
+            <div class="filter-buttons">
+              <button class="filter-btn" :class="{ active: deviceFilter === 'all' }" @click="setFilter('all')">
+                全部设备
+                <span class="filter-count">({{ deviceList.length }})</span>
+              </button>
+              <button class="filter-btn" :class="{ active: deviceFilter === 'warning' }" @click="setFilter('warning')">
+                <span class="status-dot warning"></span>
+                告警设备
+                <span class="filter-count">({{ getDeviceCountByStatus('warning') }})</span>
+              </button>
+              <button class="filter-btn" :class="{ active: deviceFilter === 'offline' }" @click="setFilter('offline')">
+                <span class="status-dot offline"></span>
+                离线设备
+                <span class="filter-count">({{ getDeviceCountByStatus('offline') }})</span>
+              </button>
+            </div>
+            <div class="filter-divider"></div>
+            <div class="type-filters">
+              <button class="filter-btn" :class="{ active: deviceTypeFilter === 'soil' }" @click="setTypeFilter('soil')">土壤监测</button>
+              <button class="filter-btn" :class="{ active: deviceTypeFilter === 'water' }" @click="setTypeFilter('water')">水质监测</button>
+              <button class="filter-btn" :class="{ active: deviceTypeFilter === 'weather' }" @click="setTypeFilter('weather')">气象监测</button>
+            </div>
+          </div>
+          <div class="device-monitor-controls">
+            <select class="sort-select" v-model="sortBy">
+              <option value="alert">告警优先</option>
+              <option value="time">更新时间</option>
+              <option value="name">设备名称</option>
+            </select>
+            <div class="device-pagination">
+              <button class="page-btn" @click="prevDevicePage" :disabled="deviceCurrentPage === 1">
+                <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+                  <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
+                </svg>
+              </button>
+              <span class="page-indicator">{{ getDevicePageRange() }} / {{ filteredDevices.length }}</span>
+              <button class="page-btn" @click="nextDevicePage" :disabled="deviceCurrentPage === deviceTotalPages">
+                <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+                  <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
+                </svg>
+              </button>
+            </div>
+          </div>
+        </div>
+
+        <!-- 设备卡片网格 -->
+        <div class="device-grid">
+          <div v-for="device in paginatedDevices" :key="device.id" class="device-card" :class="device.status">
+            <!-- 右上角数据按钮 -->
+            <button class="data-btn" @click="showDeviceHistory(device)" title="查看数据">
+              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                <path d="M12 20v-6M6 20V10M18 20V4"/>
+              </svg>
+            </button>
+            
+            <div class="device-header">
+              <div class="device-title-row">
+                <div class="device-name">{{ device.name }}</div>
+                <div class="device-status" :class="device.status">{{ device.statusText }}</div>
+              </div>
+            </div>
+            <div class="device-location">{{ device.location }}</div>
+            <div class="device-metrics">
+              <div class="metric">
+                <div class="metric-label">{{ device.metrics[0].name }}</div>
+                <div class="metric-value" :class="{ 'metric-warning': device.status === 'warning' && device.metrics[0].isAlert }">
+                  {{ device.metrics[0].value }}{{ device.metrics[0].unit }}
+                </div>
+              </div>
+              <div class="metric">
+                <div class="metric-label">{{ device.metrics[1].name }}</div>
+                <div class="metric-value" :class="{ 'metric-warning': device.status === 'warning' && device.metrics[1].isAlert }">
+                  {{ device.metrics[1].value }}{{ device.metrics[1].unit }}
+                </div>
+              </div>
+            </div>
+            <div class="device-footer">
+              <div class="last-update" :class="{ 'text-warning': device.status === 'warning', 'text-offline': device.status === 'offline' }">
+                {{ device.lastUpdate }}
+              </div>
+              <button class="detail-btn" :class="device.status">
+                {{ device.status === 'warning' ? '处理告警' : '查看详情' }}
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 单个摄像头全屏模式 -->
+    <div v-if="singleCameraFullscreen.show" class="single-camera-fullscreen">
+      <div class="video-area">
+        <div class="fullscreen-video-container">
+          <!-- 左上角摄像头名称 -->
+          <div class="camera-title-overlay" v-if="singleCameraFullscreen.camera">
+            <h3 class="camera-title-text">{{ singleCameraFullscreen.camera.name }}</h3>
+          </div>
+          
+          <!-- 右上角关闭按钮 -->
+          <div class="close-button-overlay">
+            <button class="video-nav-btn" @click="exitSingleFullscreen" title="退出全屏">
+              <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+                <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
+              </svg>
+            </button>
+          </div>
+          
+          <!-- 视频画面区域 -->
+          <div class="fullscreen-video-preview">
+            <svg class="fullscreen-camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+              <path d="M23 7l-7 5 7 5V7z"/>
+              <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
+            </svg>
+          </div>
+        </div>
+      </div>
+
+      <div class="control-panel">
+        <div class="control-sections">
+          <!-- 云台控制 -->
+          <div class="control-section">
+            <h4 class="control-title">云台控制</h4>
+                                      <div class="ptz-controls">
+                <button class="ptz-btn" title="左上">↖</button>
+                <button class="ptz-btn" title="向上">↑</button>
+                <button class="ptz-btn" title="右上">↗</button>
+                <button class="ptz-btn" title="向左">←</button>
+                <button class="ptz-btn" title="停止">●</button>
+                <button class="ptz-btn" title="向右">→</button>
+                <button class="ptz-btn" title="左下">↙</button>
+                <button class="ptz-btn" title="向下">↓</button>
+                <button class="ptz-btn" title="右下">↘</button>
+             </div>
+          </div>
+
+          <!-- 变倍控制 -->
+          <div class="control-section">
+            <h4 class="control-title">变倍控制</h4>
+            <div class="zoom-controls">
+              <button class="ptz-btn zoom-btn" title="放大">
+                <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"/>
+                </svg>
+              </button>
+              <button class="ptz-btn zoom-btn" title="缩小">
+                <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM7 10h6"/>
+                </svg>
+              </button>
+            </div>
+          </div>
+
+          <!-- 预置位 -->
+          <div class="control-section">
+            <h4 class="control-title">预置位</h4>
+            <div class="preset-grid">
+              <button v-for="n in 6" :key="n" class="preset-btn">位置{{ n }}</button>
+            </div>
+          </div>
+
+          <!-- 回放控制 -->
+          <div class="control-section">
+            <h4 class="control-title">回放控制</h4>
+            <div class="playback-controls">
+              <button class="ptz-btn playback-btn" title="开始回放">
+                <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/>
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
+                </svg>
+              </button>
+              <button class="ptz-btn playback-btn" title="暂停回放">
+                <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
+                </svg>
+              </button>
+            </div>
+          </div>
+
+          <!-- 其他控制 -->
+          <div class="control-section">
+            <h4 class="control-title">其他控制</h4>
+            <div class="other-controls">
+              <button class="ptz-btn other-btn" title="截图">
+                <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
+                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
+                </svg>
+              </button>
+              <button class="ptz-btn other-btn" title="录制">
+                <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                  <circle cx="12" cy="12" r="10" stroke-width="2"/>
+                  <circle cx="12" cy="12" r="3" fill="currentColor"/>
+                </svg>
+              </button>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 网格全屏模式 -->
+    <div v-if="gridFullscreen.show" class="grid-fullscreen-mode">
+      <div class="fullscreen-header">
+        <div class="fullscreen-title">
+          <h2 class="fullscreen-title-text">视频监控</h2>
+          <div class="fullscreen-subtitle">共 {{ videoList.length }} 个摄像头</div>
+        </div>
+        
+        <!-- 分页控制 -->
+        <div class="fullscreen-pagination" v-if="gridTotalPages > 1">
+          <button class="fullscreen-nav-btn" @click="gridPrevPage" :disabled="gridFullscreen.currentPage === 1" title="上一页">
+            <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+              <path fill-rule="evenodd" d="M12.707 5.293a1 1 0 010 1.414L9.414 10l3.293 3.293a1 1 0 01-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd" />
+            </svg>
+          </button>
+          <div class="fullscreen-page-indicator">
+            <span>{{ gridPageRange }} / {{ videoList.length }}</span>
+            <div class="fullscreen-page-dots">
+              <span 
+                v-for="page in gridTotalPages" 
+                :key="page" 
+                class="page-dot" 
+                :class="{ active: page === gridFullscreen.currentPage }"
+                @click="gridFullscreen.currentPage = page"
+              ></span>
+            </div>
+          </div>
+          <button class="fullscreen-nav-btn" @click="gridNextPage" :disabled="gridFullscreen.currentPage === gridTotalPages" title="下一页">
+            <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+              <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
+            </svg>
+          </button>
+        </div>
+        
+        <div class="fullscreen-controls">
+          <!-- 键盘快捷键提示 -->
+          <div class="keyboard-hints" v-if="gridTotalPages > 1">
+            <div class="hint-item">
+              <kbd>←</kbd><kbd>→</kbd> 翻页
+            </div>
+            <div class="hint-item">
+              <kbd>1-9</kbd> 跳转
+            </div>
+            <div class="hint-item">
+              <kbd>ESC</kbd> 退出
+            </div>
+          </div>
+          
+          <button class="fullscreen-close-btn" @click="exitGridFullscreen" title="退出全屏 (ESC)">
+            <svg class="w-6 h-6" viewBox="0 0 20 20" fill="currentColor">
+              <path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"/>
+            </svg>
+          </button>
+        </div>
+      </div>
+      
+      <div class="fullscreen-video-grid">
+        <div v-for="video in gridPaginatedVideos" :key="video.id" class="fullscreen-video-card">
+          <button class="fullscreen-camera-btn" @click="openVideoFullscreen(video)" title="单独查看">
+            <svg class="w-5 h-5" viewBox="0 0 20 20" fill="currentColor">
+              <path fill-rule="evenodd" d="M3 4a1 1 0 011-1h4a1 1 0 010 2H6.414l2.293 2.293a1 1 0 11-1.414 1.414L5 6.414V8a1 1 0 01-2 0V4zm9 1a1 1 0 010-2h4a1 1 0 011 1v4a1 1 0 01-2 0V6.414l-2.293 2.293a1 1 0 11-1.414-1.414L13.586 5H12zm-9 7a1 1 0 012 0v1.586l2.293-2.293a1 1 0 111.414 1.414L6.414 15H8a1 1 0 010 2H4a1 1 0 01-1-1v-4zm13-1a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 010-2h1.586l-2.293-2.293a1 1 0 111.414-1.414L15 13.586V12a1 1 0 011-1z" clip-rule="evenodd" />
+            </svg>
+          </button>
+          <div class="fullscreen-video-preview">
+            <div class="fullscreen-camera-placeholder">
+              <svg class="fullscreen-camera-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                <path d="M23 7l-7 5 7 5V7z"/>
+                <rect x="1" y="5" width="15" height="14" rx="2" ry="2"/>
+              </svg>
+            </div>
+            <div class="fullscreen-video-info">
+              <div class="fullscreen-video-details">
+                <div class="fullscreen-video-name">{{ video.name }}</div>
+                <div class="fullscreen-video-location">{{ video.location }}</div>
+              </div>
+              <span class="fullscreen-video-status" :class="video.status">
+                {{ video.status === 'online' ? '在线' : '离线' }}
+              </span>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    
+    <!-- 设备历史数据模态框 -->
+    <div v-if="historyModal.show" class="history-modal-backdrop" @click="closeHistoryModal">
+      <div class="history-modal-content" @click.stop>
+        <!-- 模态框头部 -->
+        <div class="history-modal-header">
+          <div class="history-modal-title-section">
+            <h3 class="history-modal-title">{{ historyModal.device.name }} - 历史数据</h3>
+            <span class="device-status" :class="historyModal.device.status">{{ historyModal.device.statusText }}</span>
+          </div>
+          <button class="history-modal-close" @click="closeHistoryModal">
+            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
+            </svg>
+          </button>
+        </div>
+        
+        <!-- 图表控制区域 -->
+        <div class="history-modal-controls">
+          <div class="time-range-buttons">
+            <button 
+              v-for="range in timeRanges" 
+              :key="range.value"
+              class="time-range-btn" 
+              :class="{ active: historyModal.timeRange === range.value }"
+              @click="setTimeRange(range.value)"
+            >
+              {{ range.label }}
+            </button>
+          </div>
+          <div class="indicator-buttons">
+            <button 
+              v-for="indicator in getDeviceIndicators()" 
+              :key="indicator.value"
+              class="indicator-btn" 
+              :class="{ active: historyModal.activeIndicator === indicator.value }"
+              @click="setActiveIndicator(indicator.value)"
+            >
+              {{ indicator.label }}
+            </button>
+          </div>
+        </div>
+        
+        <!-- 图表区域 -->
+        <div class="history-modal-body">
+          <div ref="chartContainer" class="chart-container"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'DeviceMonitor',
+  data() {
+    return {
+      selectedFarm: 'all',
+      selectedArea: 'all',
+      timeRange: 'realtime',
+      autoRefresh: '30',
+      lastUpdateTime: '刚刚',
+      currentPage: 1,
+      videosPerPage: 3,
+      
+      // 设备监控相关
+      deviceFilter: 'all',
+      deviceTypeFilter: 'all',
+      sortBy: 'alert',
+      deviceCurrentPage: 1,
+      devicePerPage: 8,
+      
+      // 单个摄像头全屏模式
+      singleCameraFullscreen: {
+        show: false,
+        camera: null
+      },
+      
+      // 网格全屏模式
+      gridFullscreen: {
+        show: false,
+        currentPage: 1,
+        videosPerPage: 6
+      },
+      
+      // 历史数据模态框
+      historyModal: {
+        show: false,
+        device: null,
+        timeRange: '24h',
+        activeIndicator: 'all'
+      },
+      
+      // 时间范围选项
+      timeRanges: [
+        { label: '近24小时', value: '24h' },
+        { label: '近7天', value: '7d' },
+        { label: '近30天', value: '30d' }
+      ],
+      
+      // 图表实例
+      chartInstance: null,
+      
+      // 原始样式保存
+      originalBodyBg: '',
+      originalBodyColor: '',
+      originalAppBg: '',
+      
+      videoList: [
+        { id: 1, name: '东区1号摄像头', location: '东区1号地块', status: 'online' },
+        { id: 2, name: '东区2号摄像头', location: '东区2号地块', status: 'online' },
+        { id: 3, name: '东区3号摄像头', location: '东区3号地块', status: 'offline' },
+        { id: 4, name: '西区1号摄像头', location: '西区1号地块', status: 'online' },
+        { id: 5, name: '西区2号摄像头', location: '西区2号地块', status: 'online' },
+        { id: 6, name: '西区3号摄像头', location: '西区3号地块', status: 'online' },
+        { id: 7, name: '南区1号摄像头', location: '南区1号地块', status: 'online' },
+        { id: 8, name: '南区2号摄像头', location: '南区2号地块', status: 'offline' },
+        { id: 9, name: '南区3号摄像头', location: '南区3号地块', status: 'online' },
+        { id: 10, name: '北区1号摄像头', location: '北区1号地块', status: 'online' },
+        { id: 11, name: '北区2号摄像头', location: '北区2号地块', status: 'online' },
+        { id: 12, name: '北区3号摄像头', location: '北区3号地块', status: 'online' }
+      ],
+      
+      deviceList: [
+        {
+          id: 1,
+          name: '土壤监测器 #1',
+          location: '东区1号地块',
+          status: 'online',
+          statusText: '在线',
+          lastUpdate: '2分钟前更新',
+          type: 'soil',
+          metrics: [
+            { name: '土壤湿度', value: '32.5', unit: '%', isAlert: false },
+            { name: '土壤温度', value: '24.2', unit: '℃', isAlert: false }
+          ]
+        },
+        {
+          id: 2,
+          name: '水质监测器 #2',
+          location: '西区1号地块',
+          status: 'warning',
+          statusText: '告警',
+          lastUpdate: 'pH值超标',
+          type: 'water',
+          metrics: [
+            { name: 'pH值', value: '8.5', unit: '', isAlert: true },
+            { name: '溶解氧', value: '6.2', unit: 'mg/L', isAlert: false }
+          ]
+        },
+        {
+          id: 3,
+          name: '气象监测器 #1',
+          location: '东区气象站',
+          status: 'online',
+          statusText: '在线',
+          lastUpdate: '1分钟前更新',
+          type: 'weather',
+          metrics: [
+            { name: '温度', value: '26.5', unit: '℃', isAlert: false },
+            { name: '湿度', value: '68', unit: '%', isAlert: false }
+          ]
+        },
+        {
+          id: 4,
+          name: '土壤监测器 #4',
+          location: '西区2号地块',
+          status: 'offline',
+          statusText: '离线',
+          lastUpdate: '设备离线 > 24h',
+          type: 'soil',
+          metrics: [
+            { name: '土壤湿度', value: '--', unit: '%', isAlert: false },
+            { name: '土壤温度', value: '--', unit: '℃', isAlert: false }
+          ]
+        },
+        {
+          id: 5,
+          name: '水质监测器 #3',
+          location: '东区2号地块',
+          status: 'online',
+          statusText: '在线',
+          lastUpdate: '5分钟前更新',
+          type: 'water',
+          metrics: [
+            { name: 'pH值', value: '7.2', unit: '', isAlert: false },
+            { name: '溶解氧', value: '5.8', unit: 'mg/L', isAlert: false }
+          ]
+        },
+        {
+          id: 6,
+          name: '气象监测器 #2',
+          location: '西区气象站',
+          status: 'warning',
+          statusText: '告警',
+          lastUpdate: '温度过高',
+          type: 'weather',
+          metrics: [
+            { name: '温度', value: '35.8', unit: '℃', isAlert: true },
+            { name: '湿度', value: '45', unit: '%', isAlert: false }
+          ]
+        },
+        {
+          id: 7,
+          name: '土壤监测器 #5',
+          location: '南区1号地块',
+          status: 'online',
+          statusText: '在线',
+          lastUpdate: '3分钟前更新',
+          type: 'soil',
+          metrics: [
+            { name: '土壤湿度', value: '28.5', unit: '%', isAlert: false },
+            { name: '土壤温度', value: '22.8', unit: '℃', isAlert: false }
+          ]
+        },
+        {
+          id: 8,
+          name: '水质监测器 #4',
+          location: '南区水质站',
+          status: 'online',
+          statusText: '在线',
+          lastUpdate: '1分钟前更新',
+          type: 'water',
+          metrics: [
+            { name: 'pH值', value: '7.1', unit: '', isAlert: false },
+            { name: '溶解氧', value: '6.5', unit: 'mg/L', isAlert: false }
+          ]
+        },
+        {
+          id: 9,
+          name: '土壤监测器 #6',
+          location: '北区1号地块',
+          status: 'offline',
+          statusText: '离线',
+          lastUpdate: '设备离线 > 12h',
+          type: 'soil',
+          metrics: [
+            { name: '土壤湿度', value: '--', unit: '%', isAlert: false },
+            { name: '土壤温度', value: '--', unit: '℃', isAlert: false }
+          ]
+        },
+        {
+          id: 10,
+          name: '气象监测器 #3',
+          location: '北区气象站',
+          status: 'online',
+          statusText: '在线',
+          lastUpdate: '30秒前更新',
+          type: 'weather',
+          metrics: [
+            { name: '温度', value: '28.2', unit: '℃', isAlert: false },
+            { name: '湿度', value: '72', unit: '%', isAlert: false }
+          ]
+        }
+      ]
+    }
+  },
+  
+  computed: {
+    totalPages() {
+      return Math.ceil(this.videoList.length / this.videosPerPage)
+    },
+    paginatedVideos() {
+      const start = (this.currentPage - 1) * this.videosPerPage
+      const end = start + this.videosPerPage
+      return this.videoList.slice(start, end)
+    },
+    
+    // 网格全屏模式计算属性
+    gridTotalPages() {
+      return Math.ceil(this.videoList.length / this.gridFullscreen.videosPerPage)
+    },
+    gridPaginatedVideos() {
+      const start = (this.gridFullscreen.currentPage - 1) * this.gridFullscreen.videosPerPage
+      const end = start + this.gridFullscreen.videosPerPage
+      return this.videoList.slice(start, end)
+    },
+    gridPageRange() {
+      const start = (this.gridFullscreen.currentPage - 1) * this.gridFullscreen.videosPerPage + 1
+      const end = Math.min(this.gridFullscreen.currentPage * this.gridFullscreen.videosPerPage, this.videoList.length)
+      return `${start}-${end}`
+    },
+    
+    // 设备监控计算属性
+    filteredDevices() {
+      let devices = this.deviceList
+
+      // 按状态过滤
+      if (this.deviceFilter !== 'all') {
+        devices = devices.filter(device => device.status === this.deviceFilter)
+      }
+
+      // 按类型过滤
+      if (this.deviceTypeFilter !== 'all') {
+        devices = devices.filter(device => device.type === this.deviceTypeFilter)
+      }
+
+      // 排序
+      if (this.sortBy === 'alert') {
+        devices = devices.sort((a, b) => {
+          const statusOrder = { warning: 0, offline: 1, online: 2 }
+          return statusOrder[a.status] - statusOrder[b.status]
+        })
+      } else if (this.sortBy === 'name') {
+        devices = devices.sort((a, b) => a.name.localeCompare(b.name))
+      } else if (this.sortBy === 'time') {
+        devices = devices.sort((a, b) => {
+          // 简单的时间排序逻辑,实际应该根据真实时间戳
+          const getUpdateTime = (update) => {
+            if (update.includes('分钟前')) return parseInt(update.match(/\d+/)[0])
+            if (update.includes('刚刚')) return 0
+            return 999 // 其他情况放最后
+          }
+          return getUpdateTime(a.lastUpdate) - getUpdateTime(b.lastUpdate)
+        })
+      }
+
+      return devices
+    },
+    
+    deviceTotalPages() {
+      return Math.ceil(this.filteredDevices.length / this.devicePerPage)
+    },
+    
+    paginatedDevices() {
+      const start = (this.deviceCurrentPage - 1) * this.devicePerPage
+      const end = start + this.devicePerPage
+      return this.filteredDevices.slice(start, end)
+    }
+  },
+  
+  methods: {
+    refreshData() {
+      console.log('刷新数据')
+      this.lastUpdateTime = '刚刚'
+      // 这里可以添加实际的数据刷新逻辑
+    },
+    
+    prevPage() {
+      if (this.currentPage > 1) {
+        this.currentPage--
+      }
+    },
+    
+    nextPage() {
+      if (this.currentPage < this.totalPages) {
+        this.currentPage++
+      }
+    },
+    
+    getPageRange() {
+      const start = (this.currentPage - 1) * this.videosPerPage + 1
+      const end = Math.min(this.currentPage * this.videosPerPage, this.videoList.length)
+      return `${start}-${end}`
+    },
+    
+    toggleFullscreen() {
+      console.log('切换网格全屏模式')
+      this.gridFullscreen.show = true
+    },
+    
+    exitGridFullscreen() {
+      this.gridFullscreen.show = false
+      this.gridFullscreen.currentPage = 1
+    },
+    
+    // 网格全屏分页方法
+    gridPrevPage() {
+      if (this.gridFullscreen.currentPage > 1) {
+        this.gridFullscreen.currentPage--
+      }
+    },
+    
+    gridNextPage() {
+      if (this.gridFullscreen.currentPage < this.gridTotalPages) {
+        this.gridFullscreen.currentPage++
+      }
+    },
+    
+    openVideoFullscreen(video) {
+      console.log('打开单个视频全屏:', video.name)
+      console.log('Video data:', video)
+      // 如果是从网格全屏模式进入,先关闭网格全屏
+      if (this.gridFullscreen.show) {
+        this.gridFullscreen.show = false
+      }
+      this.singleCameraFullscreen.show = true
+      this.singleCameraFullscreen.camera = video
+      // 确保数据更新后再强制更新视图
+      this.$nextTick(() => {
+        console.log('Fullscreen camera:', this.singleCameraFullscreen.camera)
+      })
+    },
+    
+    exitSingleFullscreen() {
+      this.singleCameraFullscreen.show = false
+      this.singleCameraFullscreen.camera = null
+    },
+    
+    // 设备监控方法
+    setFilter(filter) {
+      this.deviceFilter = filter
+      this.deviceCurrentPage = 1 // 重置到第一页
+    },
+    
+    setTypeFilter(type) {
+      this.deviceTypeFilter = this.deviceTypeFilter === type ? 'all' : type
+      this.deviceCurrentPage = 1 // 重置到第一页
+    },
+    
+    getDeviceCountByStatus(status) {
+      return this.deviceList.filter(device => device.status === status).length
+    },
+    
+    prevDevicePage() {
+      if (this.deviceCurrentPage > 1) {
+        this.deviceCurrentPage--
+      }
+    },
+    
+    nextDevicePage() {
+      if (this.deviceCurrentPage < this.deviceTotalPages) {
+        this.deviceCurrentPage++
+      }
+    },
+    
+    getDevicePageRange() {
+      if (this.filteredDevices.length === 0) return '0-0'
+      const start = (this.deviceCurrentPage - 1) * this.devicePerPage + 1
+      const end = Math.min(this.deviceCurrentPage * this.devicePerPage, this.filteredDevices.length)
+      return `${start}-${end}`
+    },
+    
+    showDeviceHistory(device) {
+      console.log('显示设备数据:', device.name)
+      this.historyModal.show = true
+      this.historyModal.device = device
+      this.historyModal.timeRange = '24h'
+      this.historyModal.activeIndicator = 'all'
+      
+      // 等待模态框渲染完成后初始化图表
+      this.$nextTick(() => {
+        this.initChart()
+      })
+    },
+    
+    closeHistoryModal() {
+      this.historyModal.show = false
+      this.historyModal.device = null
+      // 销毁图表实例
+      if (this.chartInstance) {
+        this.chartInstance.dispose()
+        this.chartInstance = null
+      }
+    },
+    
+    setTimeRange(range) {
+      this.historyModal.timeRange = range
+      this.updateChart()
+    },
+    
+    setActiveIndicator(indicator) {
+      this.historyModal.activeIndicator = indicator
+      this.updateChart()
+    },
+    
+    getDeviceIndicators() {
+      if (!this.historyModal.device) return []
+      
+      const indicators = {
+        soil: [
+          { label: '所有指标', value: 'all' },
+          { label: '土壤湿度', value: 'soilHumidity' },
+          { label: '土壤温度', value: 'soilTemperature' },
+          { label: 'EC值', value: 'ec' }
+        ],
+        water: [
+          { label: '所有指标', value: 'all' },
+          { label: 'pH值', value: 'ph' },
+          { label: '溶解氧', value: 'oxygen' },
+          { label: '电导率', value: 'conductivity' },
+          { label: '浊度', value: 'turbidity' }
+        ],
+        weather: [
+          { label: '所有指标', value: 'all' },
+          { label: '温度', value: 'temperature' },
+          { label: '湿度', value: 'humidity' },
+          { label: '光照', value: 'light' },
+          { label: '风速', value: 'windSpeed' },
+          { label: '降雨量', value: 'rainfall' }
+        ]
+      }
+      
+      return indicators[this.historyModal.device.type] || indicators.soil
+    },
+    
+    initChart() {
+      if (!this.$refs.chartContainer) return
+      
+      // 销毁已存在的图表实例
+      if (this.chartInstance) {
+        this.chartInstance.dispose()
+        this.chartInstance = null
+      }
+      
+      // 创建新的图表实例
+      this.chartInstance = this.$echarts.init(this.$refs.chartContainer)
+      this.updateChart()
+    },
+    
+    updateChart() {
+      if (!this.chartInstance) return
+      
+      // 生成模拟数据
+      const data = this.generateMockData()
+      const seriesNames = this.getSeriesNames()
+      
+      const option = {
+        backgroundColor: 'transparent',
+        tooltip: {
+          trigger: 'axis',
+          backgroundColor: 'rgba(15, 23, 42, 0.9)',
+          borderColor: '#334155',
+          textStyle: { color: '#f1f5f9' }
+        },
+        legend: {
+          data: seriesNames,
+          textStyle: { color: '#94a3b8' },
+          top: 0
+        },
+        grid: {
+          left: '3%',
+          right: '4%',
+          bottom: '3%',
+          containLabel: true,
+          top: 40
+        },
+        xAxis: {
+          type: 'category',
+          boundaryGap: false,
+          data: data.times,
+          axisLine: { lineStyle: { color: '#334155' } },
+          axisLabel: { color: '#94a3b8' }
+        },
+        yAxis: {
+          type: 'value',
+          axisLine: { lineStyle: { color: '#334155' } },
+          axisLabel: { color: '#94a3b8' },
+          splitLine: { lineStyle: { color: '#1e293b' } }
+        },
+        series: this.generateSeries(data)
+      }
+      
+      this.chartInstance.setOption(option)
+    },
+    
+    generateMockData() {
+      const times = []
+      const values = {}
+      const points = this.historyModal.timeRange === '24h' ? 24 : this.historyModal.timeRange === '7d' ? 7 : 30
+      const now = new Date()
+      
+      // 根据设备类型生成不同的数据
+      const ranges = this.getDataRanges()
+      
+      for (let i = points - 1; i >= 0; i--) {
+        const time = new Date(now - i * (this.historyModal.timeRange === '24h' ? 3600000 : 86400000))
+        times.push(this.historyModal.timeRange === '24h' ? 
+          time.getHours() + ':00' : 
+          (time.getMonth() + 1) + '/' + time.getDate())
+        
+        ranges.forEach((range, index) => {
+          if (!values[`values${index + 1}`]) {
+            values[`values${index + 1}`] = []
+          }
+          values[`values${index + 1}`].push(
+            (Math.random() * (range[1] - range[0]) + range[0]).toFixed(1)
+          )
+        })
+      }
+      
+      return { times, ...values }
+    },
+    
+    getDataRanges() {
+      const ranges = {
+        soil: [[20, 40], [15, 30], [0.5, 2]], // 土壤湿度, 土壤温度, EC值
+        water: [[6.5, 8.5], [4, 8], [0.5, 2], [0, 10]], // pH值, 溶解氧, 电导率, 浊度
+        weather: [[15, 35], [40, 80], [0, 100000], [0, 10], [0, 50]] // 温度, 湿度, 光照, 风速, 降雨量
+      }
+      return ranges[this.historyModal.device.type] || ranges.soil
+    },
+    
+    getSeriesNames() {
+      const names = {
+        soil: ['土壤湿度', '土壤温度', 'EC值'],
+        water: ['pH值', '溶解氧', '电导率', '浊度'],
+        weather: ['温度', '湿度', '光照', '风速', '降雨量']
+      }
+      const allNames = names[this.historyModal.device.type] || names.soil
+      
+      if (this.historyModal.activeIndicator === 'all') {
+        return allNames
+      } else {
+        const indicatorMap = {
+          soilHumidity: '土壤湿度',
+          soilTemperature: '土壤温度',
+          ec: 'EC值',
+          ph: 'pH值',
+          oxygen: '溶解氧',
+          conductivity: '电导率',
+          turbidity: '浊度',
+          temperature: '温度',
+          humidity: '湿度',
+          light: '光照',
+          windSpeed: '风速',
+          rainfall: '降雨量'
+        }
+        return [indicatorMap[this.historyModal.activeIndicator] || allNames[0]]
+      }
+    },
+    
+    generateSeries(data) {
+      const deviceType = this.historyModal.device.type
+      const activeIndicator = this.historyModal.activeIndicator
+      
+      const indicators = {
+        soil: {
+          names: ['土壤湿度', '土壤温度', 'EC值'],
+          colors: ['#0ea5e9', '#10b981', '#f59e0b']
+        },
+        water: {
+          names: ['pH值', '溶解氧', '电导率', '浊度'],
+          colors: ['#0ea5e9', '#10b981', '#f59e0b', '#8b5cf6']
+        },
+        weather: {
+          names: ['温度', '湿度', '光照', '风速', '降雨量'],
+          colors: ['#0ea5e9', '#10b981', '#f59e0b', '#8b5cf6', '#ec4899']
+        }
+      }[deviceType] || { names: [], colors: [] }
+      
+      if (activeIndicator === 'all') {
+        return indicators.names.map((name, index) => ({
+          name: name,
+          type: 'line',
+          data: data[`values${index + 1}`] || [],
+          smooth: true,
+          showSymbol: false,
+          lineStyle: { width: 2 },
+          areaStyle: { opacity: 0.1 },
+          itemStyle: { color: indicators.colors[index] }
+        }))
+      } else {
+        const index = indicators.names.indexOf(this.getIndicatorLabel(activeIndicator))
+        return [{
+          name: this.getIndicatorLabel(activeIndicator),
+          type: 'line',
+          data: data[`values${index + 1}`] || [],
+          smooth: true,
+          showSymbol: false,
+          lineStyle: { width: 2 },
+          areaStyle: { opacity: 0.1 },
+          itemStyle: { color: indicators.colors[index] }
+        }]
+      }
+    },
+    
+    getIndicatorLabel(value) {
+      const indicatorMap = {
+        soilHumidity: '土壤湿度',
+        soilTemperature: '土壤温度',
+        ec: 'EC值',
+        ph: 'pH值',
+        oxygen: '溶解氧',
+        conductivity: '电导率',
+        turbidity: '浊度',
+        temperature: '温度',
+        humidity: '湿度',
+        light: '光照',
+        windSpeed: '风速',
+        rainfall: '降雨量'
+      }
+      return indicatorMap[value] || value
+    }
+  },
+  
+  mounted() {
+    // 保存原始背景色和设置深色主题
+    this.originalBodyBg = document.body.style.backgroundColor || ''
+    this.originalBodyColor = document.body.style.color || ''
+    this.originalAppBg = document.getElementById('app')?.style.backgroundColor || ''
+    
+    // 应用深色主题
+    document.body.style.backgroundColor = '#0f172a'
+    document.body.style.color = '#f1f5f9'
+    const appEl = document.getElementById('app')
+    if (appEl) {
+      appEl.style.backgroundColor = '#0f172a'
+    }
+    
+    // 监听键盘事件
+    document.addEventListener('keydown', (e) => {
+      if (e.key === 'Escape') {
+        if (this.historyModal.show) {
+          this.closeHistoryModal()
+        } else if (this.singleCameraFullscreen.show) {
+          this.exitSingleFullscreen()
+        } else if (this.gridFullscreen.show) {
+          this.exitGridFullscreen()
+        }
+      } else if (this.gridFullscreen.show) {
+        // 网格全屏模式下的键盘快捷键
+        if (e.key === 'ArrowLeft') {
+          e.preventDefault()
+          this.gridPrevPage()
+        } else if (e.key === 'ArrowRight') {
+          e.preventDefault()
+          this.gridNextPage()
+        } else if (e.key >= '1' && e.key <= '9') {
+          // 数字键快速跳转页面
+          const pageNum = parseInt(e.key)
+          if (pageNum <= this.gridTotalPages) {
+            this.gridFullscreen.currentPage = pageNum
+          }
+        }
+      }
+    })
+  },
+  
+  beforeDestroy() {
+    // 恢复原始背景色
+    document.body.style.backgroundColor = this.originalBodyBg
+    document.body.style.color = this.originalBodyColor
+    const appEl = document.getElementById('app')
+    if (appEl) {
+      appEl.style.backgroundColor = this.originalAppBg
+    }
+    
+    // 销毁图表实例
+    if (this.chartInstance) {
+      this.chartInstance.dispose()
+      this.chartInstance = null
+    }
+  }
+}
+</script>
+
+
+
+<style scoped>
+.device-monitor-container {
+  padding: 20px 20px 20px;
+  background-color: #0f172a;
+  color: #f1f5f9;
+  min-height: 100vh;
+}
+
+
+
+/* 控制面板 */
+.device-monitor-container > .control-panel {
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 8px;
+  padding: 1.5rem;
+  margin-bottom: 2rem;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 2rem;
+  transition: all 0.3s ease;
+}
+
+.device-monitor-container > .control-panel:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+  border-color: #0ea5e9;
+}
+
+.control-left {
+  display: flex;
+  gap: 1.5rem;
+  flex: 1;
+}
+
+.form-group {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+}
+
+.form-group label {
+  font-size: 0.875rem;
+  color: #94a3b8;
+  white-space: nowrap;
+}
+
+.form-group select {
+  background-color: #1e293b;
+  border: 1px solid #334155;
+  color: #f1f5f9;
+  padding: 8px 12px;
+  border-radius: 6px;
+  min-width: 150px;
+}
+
+.form-group select:focus {
+  outline: none;
+  border-color: #0ea5e9;
+}
+
+.control-right {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+}
+
+.refresh-btn {
+  background-color: #0ea5e9;
+  color: white;
+  border: none;
+  padding: 10px 20px;
+  border-radius: 6px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 500;
+}
+
+.refresh-btn:hover {
+  background-color: #0369a1;
+}
+
+.auto-refresh {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  font-size: 0.875rem;
+  color: #94a3b8;
+}
+
+.auto-refresh-select {
+  background-color: #1e293b;
+  border: 1px solid #334155;
+  color: #f1f5f9;
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 0.875rem;
+}
+
+.auto-refresh-select:focus {
+  outline: none;
+  border-color: #0ea5e9;
+}
+
+.last-update {
+  font-size: 0.875rem;
+  color: #94a3b8;
+}
+
+.last-update span {
+  color: #f1f5f9;
+}
+
+/* 统计容器 */
+.stats-container {
+  display: grid;
+  grid-template-columns: 2fr 5fr;
+  gap: 1rem;
+  margin-bottom: 2rem;
+}
+
+/* 综合统计卡片 */
+.overview-stat-card {
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 8px;
+  padding: 1.5rem;
+  transition: all 0.3s ease;
+}
+
+.overview-stat-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+  border-color: #0ea5e9;
+}
+
+.overview-stats {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 1.5rem;
+}
+
+.overview-item {
+  text-align: center;
+}
+
+/* 分类统计区域 */
+.category-stats {
+  display: grid;
+  grid-template-columns: repeat(5, 1fr);
+  gap: 1rem;
+}
+
+.stat-card {
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 8px;
+  padding: 1.5rem;
+  text-align: center;
+  transition: all 0.3s ease;
+}
+
+.stat-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+  border-color: #0ea5e9;
+}
+
+.stat-card.alert {
+  border-color: #ef4444;
+  background: linear-gradient(to right, rgba(239, 68, 68, 0.05), transparent);
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
+  70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
+  100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
+}
+
+.stat-number {
+  font-size: 2rem;
+  font-weight: 700;
+  margin-bottom: 0.5rem;
+}
+
+.stat-number.online { color: #10b981; }
+.stat-number.offline { color: #ef4444; }
+.stat-number.alert { color: #ef4444; }
+.stat-number.camera { color: #3b82f6; }
+.stat-number.sensor { color: #10b981; }
+.stat-number.weather { color: #8b5cf6; }
+.stat-number.control { color: #f59e0b; }
+
+.stat-label {
+  font-size: 0.875rem;
+  color: #94a3b8;
+  margin-bottom: 0.25rem;
+}
+
+.stat-detail {
+  font-size: 0.75rem;
+  color: #64748b;
+}
+
+.stat-detail .processed {
+  color: #10b981;
+}
+
+/* 主要内容区域 */
+.main-content {
+  display: flex;
+  flex-direction: column;
+  gap: 2rem;
+}
+
+.content-section {
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 8px;
+  padding: 1.5rem;
+  transition: all 0.3s ease;
+}
+
+.content-section:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+  border-color: #0ea5e9;
+}
+
+.section-title {
+  font-size: 1.25rem;
+  font-weight: 600;
+  margin-bottom: 0;
+  color: #f1f5f9;
+}
+
+/* 视频头部样式 */
+.video-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 1.5rem;
+}
+
+.video-header-left {
+  display: flex;
+  align-items: baseline;
+  gap: 1rem;
+}
+
+.video-count {
+  font-size: 0.875rem;
+  color: #94a3b8;
+}
+
+.video-header-right {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+}
+
+.video-nav-controls {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+}
+
+.video-nav-btn {
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(15, 23, 42, 0.8);
+  border: 1px solid #334155;
+  border-radius: 4px;
+  color: #94a3b8;
+  transition: all 0.2s ease;
+  cursor: pointer;
+}
+
+.video-nav-btn:hover:not(:disabled) {
+  background: #1e293b;
+  color: #f1f5f9;
+  border-color: #0ea5e9;
+}
+
+.video-nav-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.page-indicator {
+  padding: 0 12px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  background: rgba(15, 23, 42, 0.8);
+  border: 1px solid #334155;
+  border-radius: 4px;
+  color: #94a3b8;
+  font-size: 14px;
+}
+
+/* 视频容器样式 */
+.video-container {
+  padding: 0;
+}
+
+/* 视频网格 */
+.video-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 1rem;
+}
+
+.video-card {
+  position: relative;
+  background: #0f172a;
+  border: 1px solid #334155;
+  border-radius: 8px;
+  overflow: hidden;
+  transition: all 0.3s ease;
+}
+
+.video-card:hover {
+  border-color: #0ea5e9;
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+}
+
+/* 摄像头全屏按钮 */
+.camera-fullscreen-btn {
+  position: absolute;
+  top: 12px;
+  right: 12px;
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(0, 0, 0, 0.5);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  border-radius: 4px;
+  color: #94a3b8;
+  transition: all 0.2s ease;
+  z-index: 10;
+  cursor: pointer;
+}
+
+.camera-fullscreen-btn:hover {
+  background: rgba(0, 0, 0, 0.7);
+  color: #f1f5f9;
+  border-color: #0ea5e9;
+}
+
+.video-preview {
+  position: relative;
+  padding-top: 56.25%; /* 16:9 aspect ratio */
+  background: #0f172a;
+}
+
+.camera-placeholder {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+
+.camera-icon {
+  position: absolute;
+  width: 48px;
+  height: 48px;
+  color: #94a3b8;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+}
+
+/* 强制居中的新样式 */
+.centered-container {
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+}
+
+.centered-icon {
+  position: relative !important;
+  top: auto !important;
+  left: auto !important;
+  transform: none !important;
+  margin: auto !important;
+}
+
+.video-info-overlay {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
+  padding: 0.75rem;
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-end;
+  z-index: 5;
+}
+
+.video-details {
+  flex: 1;
+}
+
+.video-name {
+  font-weight: 500;
+  margin-bottom: 0.25rem;
+  color: #f1f5f9;
+  font-size: 0.875rem;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
+}
+
+.video-location {
+  font-size: 0.75rem;
+  color: #e2e8f0;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
+}
+
+.video-status {
+  font-size: 0.75rem;
+  padding: 4px 8px;
+  border-radius: 12px;
+  font-weight: 500;
+  white-space: nowrap;
+  backdrop-filter: blur(4px);
+}
+
+.video-status.online {
+  background-color: rgba(16, 185, 129, 0.2);
+  color: #10b981;
+  border: 1px solid rgba(16, 185, 129, 0.3);
+}
+
+.video-status.offline {
+  background-color: rgba(239, 68, 68, 0.2);
+  color: #ef4444;
+  border: 1px solid rgba(239, 68, 68, 0.3);
+}
+
+/* 设备监控头部 */
+.device-monitor-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 1.5rem;
+  gap: 2rem;
+}
+
+.device-monitor-title-section {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+  flex: 1;
+}
+
+.section-title {
+  margin: 0;
+  line-height: 1.5;
+}
+
+.filter-buttons {
+  display: flex;
+  gap: 0.5rem;
+  align-items: center;
+}
+
+.filter-divider {
+  height: 24px;
+  width: 1px;
+  background-color: #334155;
+}
+
+.type-filters {
+  display: flex;
+  gap: 0.5rem;
+}
+
+.filter-btn {
+  padding: 0.5rem 1rem;
+  border-radius: 6px;
+  font-size: 0.875rem;
+  color: #94a3b8;
+  background: transparent;
+  border: 1px solid #334155;
+  transition: all 0.2s ease;
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+  cursor: pointer;
+  height: 36px; /* 固定高度确保对齐 */
+  line-height: 1.5;
+}
+
+.filter-btn:hover {
+  background: rgba(255, 255, 255, 0.05);
+  border-color: #0ea5e9;
+}
+
+.filter-btn.active {
+  background: #0ea5e9;
+  border-color: #0ea5e9;
+  color: white;
+}
+
+.filter-count {
+  font-size: 0.75rem;
+  color: #64748b;
+}
+
+.filter-btn.active .filter-count {
+  color: rgba(255, 255, 255, 0.8);
+}
+
+.status-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  display: inline-block;
+}
+
+.status-dot.warning {
+  background-color: #f59e0b;
+}
+
+.status-dot.offline {
+  background-color: #ef4444;
+}
+
+.device-monitor-controls {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+}
+
+.sort-select {
+  background-color: #1e293b;
+  border: 1px solid #334155;
+  color: #f1f5f9;
+  padding: 0.5rem 2rem 0.5rem 1rem;
+  border-radius: 6px;
+  font-size: 0.875rem;
+  appearance: none;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='%2394a3b8'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'%3E%3C/path%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-position: right 0.75rem center;
+  background-size: 1rem;
+  height: 36px; /* 与按钮高度保持一致 */
+  line-height: 1.5;
+}
+
+.sort-select:focus {
+  outline: none;
+  border-color: #0ea5e9;
+}
+
+.device-pagination {
+  display: flex;
+  align-items: center;
+  gap: 0.5rem;
+}
+
+.page-btn {
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border-radius: 6px;
+  color: #94a3b8;
+  transition: all 0.2s ease;
+  background: #1e293b;
+  border: 1px solid #334155;
+  cursor: pointer;
+}
+
+.page-indicator {
+  padding: 0 12px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  color: #94a3b8;
+  font-size: 14px;
+  line-height: 1;
+}
+
+.page-btn:hover:not(:disabled) {
+  background: #0ea5e9;
+  border-color: #0ea5e9;
+  color: white;
+}
+
+.page-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+/* 设备网格 */
+.device-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
+  gap: 1rem;
+  padding: 0.5rem;
+  min-height: 400px;
+}
+
+@media (min-width: 1280px) {
+  .device-grid {
+    grid-template-columns: repeat(3, 1fr);
+  }
+}
+
+@media (min-width: 1536px) {
+  .device-grid {
+    grid-template-columns: repeat(4, 1fr);
+  }
+}
+
+.device-card {
+  position: relative;
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 8px;
+  padding: 1.5rem;
+  transition: all 0.3s ease;
+  min-height: 250px;
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+}
+
+/* 右上角数据按钮 */
+.data-btn {
+  position: absolute;
+  top: 1rem;
+  right: 1rem;
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #0f172a;
+  border: 1px solid #334155;
+  border-radius: 6px;
+  color: #94a3b8;
+  transition: all 0.2s ease;
+  z-index: 1;
+  cursor: pointer;
+}
+
+.data-btn:hover {
+  background: #0ea5e9;
+  border-color: #0ea5e9;
+  color: white;
+  transform: translateY(-1px);
+}
+
+.data-btn svg {
+  width: 16px;
+  height: 16px;
+}
+
+.device-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
+}
+
+.device-card.warning {
+  border-color: #ef4444;
+  background: linear-gradient(to right, rgba(239, 68, 68, 0.05), transparent);
+}
+
+.device-card.offline {
+  opacity: 0.8;
+}
+
+
+
+.device-header {
+  margin-bottom: 0.5rem;
+  padding-right: 3rem; /* 为右上角数据按钮留出空间 */
+}
+
+.device-title-row {
+  display: flex;
+  align-items: center;
+  gap: 0.75rem;
+  flex-wrap: wrap;
+}
+
+.device-name {
+  font-weight: 600;
+  color: #f1f5f9;
+  font-size: 1.125rem;
+}
+
+.device-status {
+  font-size: 0.75rem;
+  padding: 2px 8px;
+  border-radius: 12px;
+  font-weight: 500;
+}
+
+.device-status.online {
+  background-color: rgba(16, 185, 129, 0.1);
+  color: #10b981;
+}
+
+.device-status.warning {
+  background-color: rgba(245, 158, 11, 0.1);
+  color: #f59e0b;
+}
+
+.device-status.offline {
+  background-color: rgba(239, 68, 68, 0.2);
+  color: #ef4444;
+}
+
+.device-location {
+  font-size: 0.875rem;
+  color: #94a3b8;
+  margin-bottom: 1.5rem;
+}
+
+.device-metrics {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 2rem;
+  margin-bottom: 1.5rem;
+  flex: 1;
+}
+
+.metric {
+  text-align: left;
+}
+
+.metric-label {
+  font-size: 0.875rem;
+  color: #94a3b8;
+  margin-bottom: 0.25rem;
+}
+
+.metric-value {
+  font-size: 1.875rem;
+  font-weight: 500;
+  color: #f1f5f9;
+  display: flex;
+  align-items: baseline;
+}
+
+.metric-value span {
+  font-size: 1.25rem;
+  margin-left: 0.25rem;
+}
+
+.metric-value.metric-warning {
+  color: #ef4444;
+}
+
+.device-card.offline .metric-value {
+  color: #6b7280;
+}
+
+.device-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-top: auto;
+}
+
+.last-update {
+  font-size: 0.875rem;
+  color: #94a3b8;
+}
+
+.last-update.text-warning {
+  color: #ef4444;
+}
+
+.last-update.text-offline {
+  color: #6b7280;
+}
+
+
+
+.detail-btn {
+  background: transparent;
+  border: 1px solid #334155;
+  color: #f1f5f9;
+  padding: 6px 12px;
+  border-radius: 6px;
+  font-size: 0.875rem;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.detail-btn:hover {
+  border-color: #0ea5e9;
+  background-color: rgba(14, 165, 233, 0.1);
+}
+
+.detail-btn.warning {
+  border-color: #ef4444;
+  color: #ef4444;
+}
+
+.detail-btn.warning:hover {
+  background-color: #ef4444;
+  color: white;
+}
+
+
+
+/* 单个摄像头全屏模式 */
+.single-camera-fullscreen {
+  position: fixed !important;
+  top: 0 !important;
+  left: 0 !important;
+  width: 100vw !important;
+  height: 100vh !important;
+  background-color: #0f172a !important;
+  z-index: 10000 !important;
+  display: flex !important;
+  margin: 0 !important;
+  padding: 0 !important;
+}
+
+.video-area {
+  flex: 1 !important;
+  min-width: 0 !important;
+  display: flex !important;
+  flex-direction: column !important;
+  position: relative !important;
+}
+
+.fullscreen-video-container {
+  flex: 1;
+  position: relative;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #0f172a;
+}
+
+/* 左上角摄像头名称 */
+.camera-title-overlay {
+  position: absolute !important;
+  top: 20px !important;
+  left: 20px !important;
+  z-index: 10001 !important;
+  background: rgba(0, 0, 0, 0.8) !important;
+  padding: 12px 20px !important;
+  border-radius: 6px !important;
+  backdrop-filter: blur(4px) !important;
+  border: 1px solid rgba(255, 255, 255, 0.1) !important;
+}
+
+.camera-title-text {
+  color: #ffffff;
+  font-size: 1.25rem;
+  font-weight: 600;
+  margin: 0;
+  text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);
+  letter-spacing: 0.5px;
+}
+
+/* 右上角关闭按钮 */
+.close-button-overlay {
+  position: absolute !important;
+  top: 20px !important;
+  right: 20px !important;
+  z-index: 10001 !important;
+}
+
+.fullscreen-video-preview {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #0f172a;
+}
+
+.fullscreen-camera-icon {
+  width: 120px;
+  height: 120px;
+  color: #94a3b8;
+}
+
+.single-camera-fullscreen .control-panel {
+  width: 280px;
+  background: #1e293b;
+  border-left: 1px solid #334155;
+  padding: 1.5rem;
+  overflow-y: auto;
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+}
+
+.control-sections {
+  display: flex;
+  flex-direction: column;
+  gap: 1.25rem;
+  flex: 1;
+  justify-content: flex-start;
+}
+
+.control-section {
+  background: transparent;
+}
+
+.control-title {
+  font-size: 0.875rem;
+  font-weight: 500;
+  margin-bottom: 0.75rem;
+  color: #f1f5f9;
+  text-align: left;
+}
+
+/* 云台控制按钮网格 */
+.ptz-controls {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 0.5rem;
+}
+
+.ptz-btn {
+  aspect-ratio: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: #0f172a;
+  border: 1px solid #334155;
+  color: #94a3b8;
+  border-radius: 8px;
+  font-size: 1.25rem;
+  transition: all 0.2s ease;
+  cursor: pointer;
+  min-height: 44px;
+}
+
+.ptz-btn:hover {
+  background: #0ea5e9;
+  border-color: #0ea5e9;
+  color: white;
+}
+
+.ptz-btn:active {
+  transform: scale(0.95);
+}
+
+/* 统一所有控制按钮的样式 */
+.zoom-btn, .playback-btn, .other-btn {
+  aspect-ratio: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 44px;
+}
+
+.zoom-btn svg, .playback-btn svg, .other-btn svg {
+  width: 16px !important;
+  height: 16px !important;
+}
+
+/* 变倍控制 */
+.zoom-controls {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 0.5rem;
+}
+
+/* 预置位按钮网格 */
+.preset-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 0.5rem;
+}
+
+.preset-btn {
+  padding: 0.75rem;
+  background: #0f172a;
+  border: 1px solid #334155;
+  color: #94a3b8;
+  border-radius: 8px;
+  transition: all 0.2s ease;
+  text-align: center;
+  cursor: pointer;
+  font-size: 0.875rem;
+  min-height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.preset-btn:hover {
+  background: #0ea5e9;
+  border-color: #0ea5e9;
+  color: white;
+}
+
+/* 回放控制 */
+.playback-controls {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 0.5rem;
+}
+
+/* 其他控制 */
+.other-controls {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 0.5rem;
+}
+
+/* 网格全屏模式 */
+.grid-fullscreen-mode {
+  position: fixed !important;
+  top: 0 !important;
+  left: 0 !important;
+  width: 100vw !important;
+  height: 100vh !important;
+  background-color: #0f172a !important;
+  z-index: 10000 !important;
+  display: flex !important;
+  flex-direction: column !important;
+  padding: 1rem !important;
+}
+
+.fullscreen-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 1rem 0;
+  border-bottom: 1px solid #334155;
+  margin-bottom: 1rem;
+  gap: 2rem;
+}
+
+.fullscreen-title {
+  display: flex;
+  align-items: baseline;
+  gap: 1rem;
+}
+
+.fullscreen-title-text {
+  font-size: 1.5rem;
+  font-weight: 600;
+  color: #f1f5f9;
+  margin: 0;
+}
+
+.fullscreen-subtitle {
+  font-size: 0.875rem;
+  color: #94a3b8;
+}
+
+.fullscreen-close-btn {
+  width: 40px;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(15, 23, 42, 0.8);
+  border: 1px solid #334155;
+  border-radius: 6px;
+  color: #94a3b8;
+  transition: all 0.2s ease;
+  cursor: pointer;
+}
+
+.fullscreen-close-btn:hover {
+  background: #1e293b;
+  color: #f1f5f9;
+  border-color: #0ea5e9;
+}
+
+/* 全屏分页控制样式 */
+.fullscreen-pagination {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+  flex: 1;
+  justify-content: center;
+}
+
+.fullscreen-nav-btn {
+  width: 36px;
+  height: 36px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(15, 23, 42, 0.8);
+  border: 1px solid #334155;
+  border-radius: 6px;
+  color: #94a3b8;
+  transition: all 0.2s ease;
+  cursor: pointer;
+}
+
+.fullscreen-nav-btn:hover:not(:disabled) {
+  background: #1e293b;
+  color: #f1f5f9;
+  border-color: #0ea5e9;
+}
+
+.fullscreen-nav-btn:disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+}
+
+.fullscreen-page-indicator {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 0.5rem;
+  color: #94a3b8;
+  font-size: 0.875rem;
+}
+
+.fullscreen-page-dots {
+  display: flex;
+  gap: 0.5rem;
+  align-items: center;
+}
+
+.page-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background-color: #334155;
+  cursor: pointer;
+  transition: all 0.2s ease;
+}
+
+.page-dot.active {
+  background-color: #0ea5e9;
+  transform: scale(1.2);
+}
+
+.page-dot:hover {
+  background-color: #64748b;
+}
+
+/* 全屏控制区域 */
+.fullscreen-controls {
+  display: flex;
+  align-items: center;
+  gap: 1rem;
+}
+
+/* 键盘快捷键提示 */
+.keyboard-hints {
+  display: flex;
+  gap: 1rem;
+  align-items: center;
+}
+
+.hint-item {
+  display: flex;
+  align-items: center;
+  gap: 0.25rem;
+  font-size: 0.75rem;
+  color: #64748b;
+}
+
+.hint-item kbd {
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 3px;
+  color: #94a3b8;
+  font-size: 0.75rem;
+  padding: 2px 6px;
+  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+  font-family: monospace;
+  min-width: 20px;
+  text-align: center;
+}
+
+.fullscreen-video-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  grid-template-rows: repeat(2, 1fr);
+  gap: 1rem;
+  flex: 1;
+  height: calc(100vh - 120px);
+}
+
+.fullscreen-video-card {
+  position: relative;
+  background: #1e293b;
+  border: 1px solid #334155;
+  border-radius: 8px;
+  overflow: hidden;
+  transition: all 0.3s ease;
+  display: flex;
+  flex-direction: column;
+}
+
+.fullscreen-video-card:hover {
+  border-color: #0ea5e9;
+  transform: translateY(-2px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+}
+
+.fullscreen-camera-btn {
+  position: absolute;
+  top: 12px;
+  right: 12px;
+  width: 32px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(0, 0, 0, 0.5);
+  border: 1px solid rgba(255, 255, 255, 0.1);
+  border-radius: 4px;
+  color: #94a3b8;
+  transition: all 0.2s ease;
+  z-index: 10;
+  cursor: pointer;
+}
+
+.fullscreen-camera-btn:hover {
+  background: rgba(0, 0, 0, 0.7);
+  color: #f1f5f9;
+  border-color: #0ea5e9;
+}
+
+.fullscreen-video-preview {
+  flex: 1;
+  position: relative;
+  background: #0f172a;
+  display: flex;
+  flex-direction: column;
+}
+
+.fullscreen-camera-placeholder {
+  flex: 1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.fullscreen-camera-icon {
+  width: 64px;
+  height: 64px;
+  color: #94a3b8;
+}
+
+.fullscreen-video-info {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);
+  padding: 0.75rem;
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-end;
+  z-index: 5;
+}
+
+.fullscreen-video-details {
+  flex: 1;
+}
+
+.fullscreen-video-name {
+  font-weight: 500;
+  margin-bottom: 0.25rem;
+  color: #f1f5f9;
+  font-size: 0.875rem;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
+}
+
+.fullscreen-video-location {
+  font-size: 0.75rem;
+  color: #e2e8f0;
+  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
+}
+
+.fullscreen-video-status {
+  font-size: 0.75rem;
+  padding: 4px 8px;
+  border-radius: 12px;
+  font-weight: 500;
+  white-space: nowrap;
+  backdrop-filter: blur(4px);
+}
+
+.fullscreen-video-status.online {
+  background-color: rgba(16, 185, 129, 0.2);
+  color: #10b981;
+  border: 1px solid rgba(16, 185, 129, 0.3);
+}
+
+.fullscreen-video-status.offline {
+  background-color: rgba(239, 68, 68, 0.2);
+  color: #ef4444;
+  border: 1px solid rgba(239, 68, 68, 0.3);
+}
+
+/* 从网格全屏进入单摄像头全屏时的处理 */
+.grid-fullscreen-mode .single-camera-fullscreen {
+  position: fixed !important;
+  z-index: 10001 !important;
+}
+
+/* 网格全屏模式响应式设计 */
+@media (max-width: 1024px) {
+  .fullscreen-video-grid {
+    grid-template-columns: repeat(2, 1fr);
+    grid-template-rows: repeat(3, 1fr);
+  }
+  
+  .fullscreen-camera-icon {
+    width: 48px;
+    height: 48px;
+  }
+}
+
+@media (max-width: 768px) {
+  .grid-fullscreen-mode {
+    padding: 0.5rem !important;
+  }
+  
+  .fullscreen-header {
+    padding: 0.5rem 0;
+    flex-direction: column;
+    gap: 1rem;
+  }
+  
+  .fullscreen-title-text {
+    font-size: 1.25rem;
+  }
+  
+  .fullscreen-pagination {
+    order: 2;
+    gap: 0.5rem;
+  }
+  
+  .fullscreen-controls {
+    order: 3;
+    gap: 0.5rem;
+  }
+  
+  .keyboard-hints {
+    display: none;
+  }
+  
+  .fullscreen-video-grid {
+    grid-template-columns: repeat(1, 1fr);
+    grid-template-rows: repeat(6, 1fr);
+    gap: 0.5rem;
+    height: calc(100vh - 140px);
+  }
+  
+  .fullscreen-camera-icon {
+    width: 40px;
+    height: 40px;
+  }
+  
+  .fullscreen-page-indicator {
+    font-size: 0.75rem;
+  }
+  
+  .fullscreen-nav-btn {
+    width: 32px;
+    height: 32px;
+  }
+}
+
+/* 响应式设计 */
+@media (max-width: 1280px) {
+  .stats-container {
+    grid-template-columns: 1fr;
+    gap: 1.5rem;
+  }
+  
+  .category-stats {
+    grid-template-columns: repeat(4, 1fr);
+  }
+  
+  .device-monitor-header {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 1rem;
+  }
+  
+  .device-monitor-title-section {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 1rem;
+  }
+  
+  .filter-buttons {
+    flex-wrap: wrap;
+  }
+  
+  .type-filters {
+    flex-wrap: wrap;
+  }
+}
+
+@media (max-width: 768px) {
+  .control-panel {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 1rem;
+  }
+  
+  .control-left {
+    flex-direction: column;
+    gap: 1rem;
+  }
+  
+  .control-right {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 1rem;
+  }
+  
+  .form-group {
+    justify-content: space-between;
+  }
+  
+  .stats-container {
+    grid-template-columns: 1fr;
+  }
+  
+  .overview-stats {
+    grid-template-columns: repeat(3, 1fr);
+    gap: 1rem;
+  }
+  
+  .category-stats {
+    grid-template-columns: repeat(2, 1fr);
+  }
+  
+  .video-grid {
+    grid-template-columns: repeat(2, 1fr);
+  }
+  
+  .device-monitor-header {
+    gap: 0.75rem;
+  }
+  
+  .device-monitor-title-section {
+    gap: 0.75rem;
+  }
+  
+  .device-monitor-controls {
+    flex-direction: column;
+    align-items: stretch;
+    gap: 0.75rem;
+  }
+  
+  .filter-btn {
+    padding: 0.375rem 0.75rem;
+    font-size: 0.8rem;
+  }
+  
+  .device-pagination {
+    justify-content: center;
+  }
+  
+  .device-grid {
+    padding: 0;
+  }
+  
+  .device-metrics {
+    gap: 1rem;
+  }
+  
+  .metric-value {
+    font-size: 1.5rem;
+  }
+  
+  .metric-value span {
+    font-size: 1rem;
+  }
+  
+  .detail-btn {
+    width: 100%;
+    justify-content: center;
+    text-align: center;
+  }
+}
+
+/* 历史数据模态框样式 */
+.history-modal-backdrop {
+  position: fixed !important;
+  top: 0 !important; /* 从页面顶部开始覆盖 */
+  left: 200px !important; /* 考虑左侧菜单栏宽度 */
+  right: 0 !important;
+  bottom: 0 !important;
+  background-color: rgba(15, 23, 42, 0.75) !important;
+  backdrop-filter: blur(4px) !important;
+  z-index: 1000 !important;
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  padding: 1rem !important;
+  padding-top: 84px !important; /* 给顶部留出导航栏空间,但仍然覆盖 */
+  opacity: 1 !important;
+  transition: opacity 0.3s ease !important;
+  box-sizing: border-box !important;
+}
+
+/* 当侧边栏隐藏时的样式调整 */
+.hideSidebar .history-modal-backdrop {
+  left: 54px !important; /* 折叠后的侧边栏宽度 */
+}
+
+/* 移动端适配 */
+@media (max-width: 1024px) {
+  .history-modal-backdrop {
+    left: 0 !important;
+    padding-top: 50px !important;
+  }
+}
+
+.history-modal-content {
+  background-color: #0f172a !important;
+  border-radius: 8px !important;
+  width: 100% !important;
+  max-width: 1000px !important;
+  max-height: 90vh !important;
+  box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) !important;
+  border: 1px solid #334155 !important;
+  overflow: hidden !important;
+  display: flex !important;
+  flex-direction: column !important;
+}
+
+.history-modal-header {
+  display: flex !important;
+  justify-content: space-between !important;
+  align-items: center !important;
+  padding: 1rem 1.5rem !important;
+  border-bottom: 1px solid #334155 !important;
+  background: #1e293b !important;
+}
+
+.history-modal-title-section {
+  display: flex !important;
+  align-items: center !important;
+  gap: 0.75rem !important;
+}
+
+.history-modal-title {
+  font-size: 1.125rem !important;
+  font-weight: 500 !important;
+  color: #f1f5f9 !important;
+  margin: 0 !important;
+}
+
+.history-modal-close {
+  background: transparent !important;
+  border: none !important;
+  color: #94a3b8 !important;
+  cursor: pointer !important;
+  padding: 0.5rem !important;
+  transition: all 0.2s ease !important;
+  border-radius: 4px !important;
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  width: 40px !important;
+  height: 40px !important;
+}
+
+.history-modal-close:hover {
+  color: #f1f5f9 !important;
+  background: rgba(255, 255, 255, 0.1) !important;
+}
+
+.history-modal-close svg {
+  width: 20px !important;
+  height: 20px !important;
+  stroke: currentColor !important;
+  fill: none !important;
+}
+
+.history-modal-controls {
+  display: flex !important;
+  justify-content: space-between !important;
+  align-items: center !important;
+  padding: 1rem 1.5rem !important;
+  border-bottom: 1px solid #334155 !important;
+  background: #1e293b !important;
+}
+
+.time-range-buttons {
+  display: flex !important;
+  gap: 0.5rem !important;
+}
+
+.indicator-buttons {
+  display: flex !important;
+  gap: 0.5rem !important;
+}
+
+.time-range-btn, .indicator-btn {
+  padding: 0.5rem 1rem !important;
+  border-radius: 6px !important;
+  font-size: 0.875rem !important;
+  color: #94a3b8 !important;
+  background: transparent !important;
+  border: 1px solid #334155 !important;
+  transition: all 0.2s ease !important;
+  cursor: pointer !important;
+}
+
+.time-range-btn:hover, .indicator-btn:hover {
+  background: rgba(255, 255, 255, 0.05) !important;
+  border-color: #0ea5e9 !important;
+}
+
+.time-range-btn.active, .indicator-btn.active {
+  background: #0ea5e9 !important;
+  border-color: #0ea5e9 !important;
+  color: white !important;
+}
+
+.history-modal-body {
+  flex: 1 !important;
+  padding: 1.5rem !important;
+  background: #0f172a !important;
+  overflow: hidden !important;
+}
+
+.chart-container {
+  width: 100% !important;
+  height: 400px !important;
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+  background: transparent !important;
+  border-radius: 6px !important;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+  .history-modal-content {
+    margin: 0.5rem !important;
+    max-width: calc(100vw - 1rem) !important;
+    max-height: calc(100vh - 1rem) !important;
+  }
+  
+  .history-modal-controls {
+    flex-direction: column !important;
+    gap: 1rem !important;
+    align-items: stretch !important;
+  }
+  
+  .time-range-buttons, .indicator-buttons {
+    flex-wrap: wrap !important;
+    justify-content: center !important;
+  }
+  
+  .time-range-btn, .indicator-btn {
+    padding: 0.375rem 0.75rem !important;
+    font-size: 0.8rem !important;
+  }
+  
+  .chart-container {
+    height: 300px !important;
+  }
+}
+</style> 

+ 17 - 0
src/views/login-alt1.vue

@@ -0,0 +1,17 @@
+<!-- 备用登录页面 - 使用 agritech-login-bg.jpg -->
+<!-- 如需切换背景,可以将此文件重命名为 login.vue,或复制其中的背景图路径 -->
+<template>
+  <!-- 背景图选项2:agritech-login-bg.jpg -->
+  <!-- 将登录页面的背景图 URL 改为:url("../assets/images/agritech-login-bg.jpg") -->
+  
+  <!-- 背景图选项3:agriculture-tech-bg.jpg -->
+  <!-- 将登录页面的背景图 URL 改为:url("../assets/images/agriculture-tech-bg.jpg") -->
+</template>
+
+<script>
+// 这是一个背景图片选择参考文件
+// 您有3个农业科技风格的背景图可选:
+// 1. smart-farming-bg.jpg (当前使用)
+// 2. agritech-login-bg.jpg (绿色田野风格)
+// 3. agriculture-tech-bg.jpg (现代农业设备风格)
+</script>