Bladeren bron

设备(摄像头,采集设备)详情页

yawuga 11 maanden geleden
bovenliggende
commit
618fbd5f64
4 gewijzigde bestanden met toevoegingen van 1642 en 229 verwijderingen
  1. 9 0
      pages.json
  2. 437 227
      pages/device-list/detail-camera.vue
  3. 1180 0
      pages/device-list/detail-collector.vue
  4. 16 2
      pages/device-list/index.vue

+ 9 - 0
pages.json

@@ -15,6 +15,15 @@
         "enablePullDownRefresh": false
       }
     },
+    {
+      "path": "pages/device-list/detail-collector",
+      "style": {
+        "navigationBarTitleText": "采集设备详情",
+        "navigationBarBackgroundColor": "#ffffff",
+        "navigationBarTextStyle": "black",
+        "enablePullDownRefresh": false
+      }
+    },
     {
       "path": "pages/activity/index",
       "style": {

File diff suppressed because it is too large
+ 437 - 227
pages/device-list/detail-camera.vue


+ 1180 - 0
pages/device-list/detail-collector.vue

@@ -0,0 +1,1180 @@
+<template>
+  <view class="container">
+    <!-- 设备头部信息区域 -->
+    <view class="device-header">
+      <view class="device-info-row">
+        <view class="device-name-container">
+          <text class="device-name">{{ deviceInfo.name }}</text>
+          <view 
+            class="status-tag" 
+            :class="deviceInfo.status === 'online' ? 'status-online' : 'status-offline'"
+          >
+            <view class="status-dot" :class="{'offline-dot': deviceInfo.status === 'offline'}"></view>
+            {{ deviceInfo.status === 'online' ? '在线' : '离线' }}
+          </view>
+        </view>
+        
+        <view class="refresh-btn" :class="{'refreshing': isRefreshing}" @tap="refreshData">
+          <svg width="22" height="22" viewBox="0 0 24 24">
+            <path fill="#3BB44A" d="M17.65,6.35C16.2,4.9,14.21,4,12,4c-4.42,0-7.99,3.58-7.99,8s3.57,8,7.99,8c3.73,0,6.84-2.55,7.73-6h-2.08c-0.82,2.33-3.04,4-5.65,4c-3.31,0-6-2.69-6-6s2.69-6,6-6c1.66,0,3.14,0.69,4.22,1.78L13,11h7V4L17.65,6.35z"/>
+          </svg>
+        </view>
+      </view>
+      
+      <view class="device-meta-row">
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <svg width="16" height="16" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M22,3H2C0.9,3,0,3.9,0,5v14c0,1.1,0.9,2,2,2h20c1.1,0,1.99-0.9,1.99-2L24,5C24,3.9,23.1,3,22,3z M9,17H6.5v-2.5h2.5V17z M9,13H6.5v-2.5h2.5V13z M9,9H6.5V6.5h2.5V9z M13.5,17H11v-2.5h2.5V17z M13.5,13H11v-2.5h2.5V13z M13.5,9H11V6.5h2.5V9z M18,17h-2.5v-2.5H18V17z M18,13h-2.5v-2.5H18V13z M18,9h-2.5V6.5H18V9z"/>
+            </svg>
+          </view>
+          <text class="meta-label">设备编号:</text>
+          <text class="meta-value">{{ deviceInfo.deviceId }}</text>
+        </view>
+        
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <svg width="16" height="16" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M12,2C8.13,2,5,5.13,5,9c0,5.25,7,13,7,13s7-7.75,7-13C19,5.13,15.87,2,12,2z M12,11.5c-1.38,0-2.5-1.12-2.5-2.5s1.12-2.5,2.5-2.5s2.5,1.12,2.5,2.5S13.38,11.5,12,11.5z"/>
+            </svg>
+          </view>
+          <text class="meta-label">安装位置:</text>
+          <text class="meta-value">{{ deviceInfo.location }}</text>
+        </view>
+        
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <svg width="16" height="16" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M11.99,2C6.47,2,2,6.48,2,12s4.47,10,9.99,10C17.52,22,22,17.52,22,12S17.52,2,11.99,2z M12,20c-4.42,0-8-3.58-8-8s3.58-8,8-8s8,3.58,8,8S16.42,20,12,20z M12.5,7H11v6l5.25,3.15l0.75-1.23l-4.5-2.67V7z"/>
+            </svg>
+          </view>
+          <text class="meta-label">最近更新:</text>
+          <text class="meta-value">{{ deviceInfo.lastUpdate }}</text>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 采集数据展示区域 - 气象站 -->
+    <view class="data-section" v-if="deviceInfo.deviceType === 'weather'">
+      <view class="section-title">
+        <text>气象数据</text>
+      </view>
+      
+      <view class="data-grid">
+        <!-- 气温 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !weatherData.temperature}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M15,13V5c0-1.66-1.34-3-3-3S9,3.34,9,5v8c-1.21,0.91-2,2.37-2,4c0,2.76,2.24,5,5,5s5-2.24,5-5C17,15.37,16.21,13.91,15,13z M11,5c0-0.55,0.45-1,1-1s1,0.45,1,1v3h-2V5z M12,19.5c-1.38,0-2.5-1.12-2.5-2.5c0-0.94,0.53-1.76,1.31-2.17L11.5,14.5v-4h1v4l0.69,0.33c0.78,0.41,1.31,1.23,1.31,2.17C14.5,18.38,13.38,19.5,12,19.5z"/>
+            </svg>
+          </view>
+          <text class="data-label">气温</text>
+          <view class="data-value-container">
+            <template v-if="weatherData.temperature">
+              <text class="data-value" :class="{'updated': updatedFields.weather && updatedFields.weather.temperature}">{{ weatherData.temperature }}</text>
+              <text class="data-unit">℃</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="weatherData.temperature">{{ weatherData.updateTime || '2分钟前更新' }}</text>
+        </view>
+        
+        <!-- 湿度 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !weatherData.humidity}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M12,2c-5.33,4.55-8,8.48-8,11.8c0,4.98,3.8,8.2,8,8.2s8-3.22,8-8.2C20,10.48,17.33,6.55,12,2z M12,20c-3.35,0-6-2.57-6-6.2c0-2.34,1.95-5.44,6-9.14c4.05,3.7,6,6.79,6,9.14C18,17.43,15.35,20,12,20z M7.83,14c0.37,0,0.67,0.26,0.74,0.62c0.41,2.22,2.28,2.98,3.64,2.87c0.43-0.02,0.79,0.32,0.79,0.75c0,0.4-0.32,0.73-0.72,0.75c-2.13,0.13-4.62-1.09-5.19-4.12C7.01,14.42,7.37,14,7.83,14z"/>
+            </svg>
+          </view>
+          <text class="data-label">湿度</text>
+          <view class="data-value-container">
+            <template v-if="weatherData.humidity">
+              <text class="data-value" :class="{'updated': updatedFields.weather && updatedFields.weather.humidity}">{{ weatherData.humidity }}</text>
+              <text class="data-unit">%</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="weatherData.humidity">{{ weatherData.updateTime || '2分钟前更新' }}</text>
+        </view>
+        
+        <!-- 降雨量 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !weatherData.rainfall}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M12.01,6c2.61,0,4.89,1.86,5.4,4.43l0.3,1.5l1.52,0.11c1.56,0.11,2.78,1.41,2.78,2.96c0,1.65-1.35,3-3,3h-13c-2.21,0-4-1.79-4-4c0-2.05,1.53-3.76,3.56-3.97l1.07-0.11l0.5-0.95C8.08,7.14,9.95,6,12.01,6 M12,4C9.2,4,6.78,5.64,5.67,8.04C2.68,8.16,0.24,10.7,0.24,13.76c0,3.08,2.49,5.58,5.56,5.58h13.19c2.49,0,4.51-2.03,4.51-4.53c0-2.34-1.77-4.25-4.05-4.5C18.4,6.75,15.49,4,12,4L12,4z M14,11l-2-2l-2,2H8.5l3.5,3.5l3.5-3.5H14z"/>
+            </svg>
+          </view>
+          <text class="data-label">降雨量</text>
+          <view class="data-value-container">
+            <template v-if="weatherData.rainfall">
+              <text class="data-value" :class="{'updated': updatedFields.weather && updatedFields.weather.rainfall}">{{ weatherData.rainfall }}</text>
+              <text class="data-unit">mm</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="weatherData.rainfall">{{ weatherData.updateTime || '2分钟前更新' }}</text>
+        </view>
+        
+        <!-- 风向 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !weatherData.windDirection}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M4,10H3L4,6L5,10H4z M22,10v2h-8v9l-2-1.6V19H9v1.4L7,22v-2.5c0-5.2,0-9.5,0-9.5H6v-2h16z M17,12v-1.5C17,9.7,16.3,9,15.5,9S14,9.7,14,10.5V12H17z M9,7.5C9,9.43,7.43,11,5.5,11S2,9.43,2,7.5S3.57,4,5.5,4S9,5.57,9,7.5z M6.5,7.5C6.5,8.33,6.02,9,5.5,9S4.5,8.33,4.5,7.5S4.98,6,5.5,6S6.5,6.67,6.5,7.5z"/>
+            </svg>
+          </view>
+          <text class="data-label">风向</text>
+          <view class="data-value-container">
+            <template v-if="weatherData.windDirection">
+              <text class="data-value" :class="{'updated': updatedFields.weather && updatedFields.weather.windDirection}">{{ weatherData.windDirection }}</text>
+              <text class="data-unit"></text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="weatherData.windDirection">{{ weatherData.updateTime || '2分钟前更新' }}</text>
+        </view>
+        
+        <!-- 风速 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !weatherData.windSpeed}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M17.66,4.53L18.11,4.5c2.05,0.14,3.74,1.86,3.74,3.93c0,1.66-1.04,3.12-2.6,3.7H14v-2h5.56c0.47,0,0.87-0.39,0.87-0.89c0-0.5-0.4-0.89-0.87-0.89H8.5c-0.64,0-1.22,0.18-1.72,0.49c-0.53-0.34-1.14-0.5-1.66-0.51c-0.35,0-0.69,0.04-1.02,0.13c-0.97,0.37-1.69,1.33-1.69,2.38c0,1.44,1.22,2.61,2.72,2.61H9v2H5.14c-2.36,0-4.26-1.88-4.26-4.24c0-1.75,0.91-3.17,2.62-3.88C3.99,5.01,4.81,4.5,5.78,4.5c1.17,0,2.29,0.58,2.96,1.59c0.51-0.25,1.11-0.41,1.76-0.41h5.77L17.66,4.53z M21.11,14.5H17v-2h4.11c2.01,0,3.89,1.63,3.89,3.83c0,1.56-0.88,2.87-2.27,3.44c-0.38,0.14-0.77,0.23-1.21,0.23c-0.84,0-1.75-0.35-2.43-1.14l1.58-1.23c0.27,0.37,0.66,0.57,1.08,0.57c0.76,0,1.38-0.64,1.38-1.43C23.11,15.33,22.2,14.5,21.11,14.5z M13,18H4.69c-2.06,0-3.69-1.69-3.69-3.72c0-1.17,0.58-2.27,1.5-2.97C3.14,11.37,4,12.23,5,12.72V14h7v2H8v2h5V18z"/>
+            </svg>
+          </view>
+          <text class="data-label">风速</text>
+          <view class="data-value-container">
+            <template v-if="weatherData.windSpeed">
+              <text class="data-value" :class="{'updated': updatedFields.weather && updatedFields.weather.windSpeed}">{{ weatherData.windSpeed }}</text>
+              <text class="data-unit">m/s</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="weatherData.windSpeed">{{ weatherData.updateTime || '2分钟前更新' }}</text>
+        </view>
+        
+        <!-- 气压 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !weatherData.pressure}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8c0-4.41,3.59-8,8-8s8,3.59,8,8C20,16.41,16.41,20,12,20z M13,8h-2v2h-2v2h2v2h2v-2h2v-2h-2V8z M14.5,14.5v-1h-1v1H14.5z M9.5,14.5v-1h-1v1H9.5z M14.5,10.5V10h-1v0.5H14.5z M9.5,10.5V10h-1v0.5H9.5z"/>
+            </svg>
+          </view>
+          <text class="data-label">气压</text>
+          <view class="data-value-container">
+            <template v-if="weatherData.pressure">
+              <text class="data-value" :class="{'updated': updatedFields.weather && updatedFields.weather.pressure}">{{ weatherData.pressure }}</text>
+              <text class="data-unit">hPa</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="weatherData.pressure">{{ weatherData.updateTime || '2分钟前更新' }}</text>
+        </view>
+        
+        <!-- 光照 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !weatherData.illumination}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M6.76,4.84l-1.8-1.79-1.41,1.41l1.79,1.79L6.76,4.84z M4,10.5h-2v2h2V10.5z M13,0.55h-2V3.5h2V0.55z M20.45,4.46l-1.41-1.41-1.79,1.79l1.41,1.41L20.45,4.46z M17.24,18.16l1.79,1.8l1.41-1.41l-1.8-1.79L17.24,18.16z M20,10.5v2h2v-2H20z M12,5.5c-3.31,0-6,2.69-6,6s2.69,6,6,6s6-2.69,6-6S15.31,5.5,12,5.5z M11,22.45h2v-2.95h-2V22.45z M3.55,18.54l1.41,1.41l1.79-1.8l-1.41-1.41L3.55,18.54z"/>
+            </svg>
+          </view>
+          <text class="data-label">光照</text>
+          <view class="data-value-container">
+            <template v-if="weatherData.illumination">
+              <text class="data-value" :class="{'updated': updatedFields.weather && updatedFields.weather.illumination}">{{ weatherData.illumination }}</text>
+              <text class="data-unit">lux</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="weatherData.illumination">{{ weatherData.updateTime || '2分钟前更新' }}</text>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 采集数据展示区域 - 土壤墒情 -->
+    <view class="data-section" v-if="deviceInfo.deviceType === 'soil'">
+      <view class="section-title">
+        <text>土壤数据</text>
+      </view>
+      
+      <view class="data-grid">
+        <!-- 土壤温度 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !soilData.temperature}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M15,13V5c0-1.66-1.34-3-3-3S9,3.34,9,5v8c-1.21,0.91-2,2.37-2,4c0,2.76,2.24,5,5,5s5-2.24,5-5C17,15.37,16.21,13.91,15,13z M12,20c-1.65,0-3-1.35-3-3c0-1.12,0.61-2.1,1.5-2.61V5c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5v9.38c0.9,0.52,1.5,1.49,1.5,2.62C15,18.65,13.65,20,12,20z"/>
+            </svg>
+          </view>
+          <text class="data-label">土壤温度</text>
+          <view class="data-value-container">
+            <template v-if="soilData.temperature">
+              <text class="data-value" :class="{'updated': updatedFields.soil && updatedFields.soil.temperature}">{{ soilData.temperature }}</text>
+              <text class="data-unit">℃</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="soilData.temperature">{{ soilData.updateTime || '5分钟前更新' }}</text>
+        </view>
+        
+        <!-- 土壤湿度 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !soilData.moisture}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M12,2c-5.33,4.55-8,8.48-8,11.8c0,4.98,3.8,8.2,8,8.2s8-3.22,8-8.2C20,10.48,17.33,6.55,12,2z M12,20c-3.35,0-6-2.57-6-6.2c0-2.34,1.95-5.44,6-9.14c4.05,3.7,6,6.79,6,9.14C18,17.43,15.35,20,12,20z"/>
+            </svg>
+          </view>
+          <text class="data-label">土壤湿度</text>
+          <view class="data-value-container">
+            <template v-if="soilData.moisture">
+              <text class="data-value" :class="{'updated': updatedFields.soil && updatedFields.soil.moisture}">{{ soilData.moisture }}</text>
+              <text class="data-unit">%</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="soilData.moisture">{{ soilData.updateTime || '5分钟前更新' }}</text>
+        </view>
+        
+        <!-- 氮含量 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !soilData.nitrogen}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M12,2L4.5,20.29l0.71,0.71L12,18l6.79,3l0.71-0.71L12,2z M12,15.3L7.46,16.97L12,6.69l4.54,10.28L12,15.3z"/>
+            </svg>
+          </view>
+          <text class="data-label">氮含量</text>
+          <view class="data-value-container">
+            <template v-if="soilData.nitrogen">
+              <text class="data-value" :class="{'updated': updatedFields.soil && updatedFields.soil.nitrogen}">{{ soilData.nitrogen }}</text>
+              <text class="data-unit">mg/kg</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="soilData.nitrogen">{{ soilData.updateTime || '5分钟前更新' }}</text>
+        </view>
+        
+        <!-- 磷含量 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !soilData.phosphorus}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M11,15H6.83l3.59-3.59L9,10l-6,6l6,6l1.41-1.41L6.83,17H11c4.42,0,8-3.58,8-8V3h-2v6C17,12.42,14.42,15,11,15z"/>
+            </svg>
+          </view>
+          <text class="data-label">磷含量</text>
+          <view class="data-value-container">
+            <template v-if="soilData.phosphorus">
+              <text class="data-value" :class="{'updated': updatedFields.soil && updatedFields.soil.phosphorus}">{{ soilData.phosphorus }}</text>
+              <text class="data-unit">mg/kg</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="soilData.phosphorus">{{ soilData.updateTime || '5分钟前更新' }}</text>
+        </view>
+        
+        <!-- 钾含量 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !soilData.potassium}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M19,3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h14c1.1,0,2-0.9,2-2V5C21,3.9,20.1,3,19,3z M13.97,7.17l3.92,7.85C17.96,15.31,18.18,16,18.5,16c0.44,0,0.75-0.53,0.5-1l-4-8h-2l-2,4h-1.5L11,7H8.5L7,11H5.5l1-2l-2-4h10.72C16.42,5,16.97,6.17,13.97,7.17z"/>
+            </svg>
+          </view>
+          <text class="data-label">钾含量</text>
+          <view class="data-value-container">
+            <template v-if="soilData.potassium">
+              <text class="data-value" :class="{'updated': updatedFields.soil && updatedFields.soil.potassium}">{{ soilData.potassium }}</text>
+              <text class="data-unit">mg/kg</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="soilData.potassium">{{ soilData.updateTime || '5分钟前更新' }}</text>
+        </view>
+        
+        <!-- 土壤电导率 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !soilData.conductivity}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M14.69,6.63L14.69,6.63c-0.35-0.35-0.92-0.35-1.27,0l-6.75,6.73c-0.35,0.35-0.35,0.92,0,1.27l0,0c0.35,0.35,0.92,0.35,1.27,0l6.75-6.73C15.04,7.55,15.04,6.98,14.69,6.63z M16.21,15.83l1.56-1.56c0.2-0.2,0.2-0.51,0-0.71c-0.2-0.2-0.51-0.2-0.71,0l-1.56,1.56c-0.2,0.2-0.2,0.51,0,0.71C15.7,16.02,16.02,16.02,16.21,15.83z M12.41,14.27c-0.2-0.2-0.51-0.2-0.71,0l-1.56,1.56c-0.2,0.2-0.2,0.51,0,0.71c0.2,0.2,0.51,0.2,0.71,0l1.56-1.56C12.61,14.78,12.61,14.47,12.41,14.27z M19,6.5C19,4.01,16.99,2,14.5,2c-1.82,0-3.53,1.15-4.27,2.64c-0.4-0.19-0.86-0.3-1.33-0.3C7.17,4.35,6,5.53,6,7c0,0.6,0.13,1.22,0.38,1.79C4.88,9.15,4,10.67,4,12.43C4,15.52,6.49,18,9.56,18h10.81c0.44,0,0.69-0.54,0.39-0.85l-1.56-1.56c-0.2-0.2-0.51-0.2-0.71,0c-0.2,0.2-0.2,0.51,0,0.71l0.63,0.63H9.56c-2.52,0-4.56-2.05-4.56-4.57c0-1.37,0.61-2.64,1.67-3.51L7.4,8.47c0.33,0.48,0.8,0.8,1.35,0.95C8.24,8.67,8,7.86,8,7c0-0.54,0.42-0.98,0.95-1c0.36-0.01,0.71,0.08,1.03,0.28l0.56,0.29l0.15-0.62C11.13,4.39,12.74,3,14.5,3C16.43,3,18,4.57,18,6.5v0.58c0,0.17,0.13,0.29,0.29,0.29c0.51,0.04,1.1,0.24,1.53,0.55C19.37,7.35,19,6.42,19,6.5z"/>
+            </svg>
+          </view>
+          <text class="data-label">电导率</text>
+          <view class="data-value-container">
+            <template v-if="soilData.conductivity">
+              <text class="data-value" :class="{'updated': updatedFields.soil && updatedFields.soil.conductivity}">{{ soilData.conductivity }}</text>
+              <text class="data-unit">μS/cm</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="soilData.conductivity">{{ soilData.updateTime || '5分钟前更新' }}</text>
+        </view>
+        
+        <!-- PH值 -->
+        <view class="data-item">
+          <view class="data-icon" :class="{'no-data': !soilData.ph}">
+            <svg width="28" height="28" viewBox="0 0 24 24">
+              <path fill="#3BB44A" d="M12,2c-5.33,4.55-8,8.48-8,11.8c0,4.98,3.8,8.2,8,8.2s8-3.22,8-8.2C20,10.48,17.33,6.55,12,2z M8.83,16.29c-1.03-1.46-1.74-3.39-1.26-5.37c0.17-0.71,0.49-1.34,0.9-1.92c0.17-0.24,0.35-0.47,0.55-0.67c0.18-0.21,0.38-0.36,0.58-0.54c0.72-0.49,1.45-0.89,2.1-1.39c0.35-0.22,0.7-0.43,1.05-0.61c0.43-0.23,0.93-0.35,1.26-0.68c0.21-0.21,0.35-0.47,0.45-0.77c0.1-0.28,0.17-0.64,0.25-0.91C14.84,3.16,14.99,3,15.16,3c0.55,0,0.59,0.41,0.61,0.44l0.05,0.15c0.04,0.11,0.21,0.71,0.21,1.03c0,0.53-0.23,0.94-0.44,1.31c-0.52,0.88-1.31,1.25-1.92,1.84c-0.36,0.38-0.66,0.85-0.91,1.34c-0.27,0.5-0.47,1.07-0.49,1.66c-0.02,0.42,0.05,0.86,0.27,1.26c0.23,0.43,0.65,0.65,1.06,0.65c0.71,0,1.24-0.46,1.58-0.97c0.46-0.66,0.74-1.48,0.95-2.27c0.11-0.46,0.18-0.93,0.23-1.35c0.14-1.16,0.27-2.25,0.95-2.33c0.17-0.02,0.3,0.01,0.42,0.12c0.32,0.29,0.31,1.11,0.32,1.38c0,0.51-0.01,1.19-0.14,1.83c-0.07,0.32-0.17,0.64-0.28,0.95c-0.19,0.48-0.4,0.95-0.66,1.34C15.85,12.85,15.29,13.16,14.7,13.29z M12,19.8c-3.46,0-6-2.55-6-6c0-0.34,0.03-0.67,0.08-1l0.93,0.7c0.19,0.47,0.55,0.96,1.28,1.23c0.67,0.25,1.37,0.05,1.86-0.33c0.27-0.21,0.48-0.46,0.65-0.71c0.62-0.95,0.89-2.85,0.62-4.04C11.32,9.32,11.17,9.05,11,8.88c0.03-0.06,0.13-0.16,0.3-0.22c0.37-0.14,0.64,0.11,0.82,0.37c0.28,0.42,0.46,0.82,0.55,1.24c0.02,0.08,0.04,0.18,0.05,0.24c0.12,0.52,0.33,1.72-0.25,3.07c-0.52,1.2-1.45,2.25-2.92,2.25c-1.66,0-2.52-1.24-2.67-2.03c-0.19-1.01,0.04-2.33,0.55-3.37c0.44-0.89,1-1.69,1.68-2.35c0.82-0.79,1.73-1.45,2.73-1.99c0.29-0.16,0.6-0.32,0.84-0.44c-0.14,0.45-0.37,0.76-0.87,1.07c-0.54,0.36-1.17,0.67-1.71,1.01c-0.65,0.36-1.26,0.76-1.81,1.18c-0.38,0.28-0.78,0.59-1.15,0.97c-1.1,1.06-1.84,2.23-2.2,3.41c-0.27,0.86-0.37,1.75-0.29,2.6c0.09,0.92,0.38,1.81,0.88,2.58c0.91,1.38,2.45,2.28,4.28,2.28c4.12,0,6-4.27,6-7c0-0.45-0.04-0.85-0.12-1.22c-0.08-0.36-0.21-0.69-0.4-0.97c0,0,0.18-0.07,0.35,0c0.23,0.09,0.39,0.29,0.52,0.67c0.18,0.56,0.25,1.12,0.25,1.68C18,17.41,15.4,19.8,12,19.8z"/>
+            </svg>
+          </view>
+          <text class="data-label">PH值</text>
+          <view class="data-value-container">
+            <template v-if="soilData.ph">
+              <text class="data-value" :class="{'updated': updatedFields.soil && updatedFields.soil.ph}">{{ soilData.ph }}</text>
+              <text class="data-unit"></text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time" v-if="soilData.ph">{{ soilData.updateTime || '5分钟前更新' }}</text>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 告警信息列表 -->
+    <view class="alerts-section">
+      <view class="section-title">
+        <text>告警信息</text>
+        <view class="alert-badge" v-if="getUnhandledAlerts.length > 0">{{ getUnhandledAlerts.length }}</view>
+      </view>
+      
+      <view class="alerts-list" v-if="getUnhandledAlerts.length > 0">
+        <view 
+          v-for="(item, index) in getUnhandledAlerts.slice(0, 3)" 
+          :key="index"
+          class="alert-item"
+          :class="{
+            'alert-urgent': item.level === 'high', 
+            'alert-warning': item.level === 'medium', 
+            'alert-info': item.level === 'low'
+          }"
+        >
+          <view class="alert-item-icon">
+            <svg v-if="item.level === 'high'" width="24" height="24" viewBox="0 0 24 24">
+              <path fill="#F56C6C" d="M12,2L1,21h22L12,2z M12,6l7.53,13H4.47L12,6z M11,10v4h2v-4H11z M11,16v2h2v-2H11z" />
+            </svg>
+            <svg v-else-if="item.level === 'medium'" width="24" height="24" viewBox="0 0 24 24">
+              <path fill="#E6A23C" d="M11,15h2v2h-2V15z M11,7h2v6h-2V7z M11.99,2C6.47,2,2,6.48,2,12s4.47,10,9.99,10C17.52,22,22,17.52,22,12S17.52,2,11.99,2z M12,20c-4.42,0-8-3.58-8-8s3.58-8,8-8s8,3.58,8,8S16.42,20,12,20z" />
+            </svg>
+            <svg v-else width="24" height="24" viewBox="0 0 24 24">
+              <path fill="#67C23A" d="M11,7h2v2h-2V7z M11,11h2v6h-2V11z M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M12,20c-4.41,0-8-3.59-8-8c0-4.41,3.59-8,8-8s8,3.59,8,8C20,16.41,16.41,20,12,20z" />
+            </svg>
+          </view>
+          
+          <view class="alert-item-info">
+            <text class="alert-item-type">{{ item.type }}</text>
+            <text class="alert-item-level">
+              {{ item.level === 'high' ? '紧急' : item.level === 'medium' ? '警告' : '提示' }}
+            </text>
+          </view>
+          
+          <view class="alert-item-time">{{ item.time }}</view>
+        </view>
+      </view>
+      
+      <view v-else class="empty-alert">
+        <text class="empty-text">暂无告警信息</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      deviceInfo: {
+        deviceId: 'COL1002',
+        name: '采集设备-1',
+        status: 'online',
+        location: '西区B2地块',
+        lastUpdate: '5分钟前',
+        deviceType: 'weather' // 'weather' 或 'soil'
+      },
+      
+      // 气象站数据
+      weatherData: {
+        temperature: '26.5',
+        humidity: '68',
+        rainfall: '0.0',
+        windDirection: '东北',
+        windSpeed: '3.2',
+        pressure: '1013.5',
+        illumination: '45000',
+        updateTime: '2分钟前更新'
+      },
+      
+      // 土壤墒情数据
+      soilData: {
+        temperature: '22.3',
+        moisture: '35.8',
+        nitrogen: '138.5',
+        phosphorus: '42.7',
+        potassium: '185.2',
+        conductivity: '0.35',
+        ph: '6.8',
+        updateTime: '5分钟前更新'
+      },
+      
+      // 模拟告警数据
+      alertHistory: [
+        { id: 1, time: '今天 13:05', type: '数据异常', status: '未处理', level: 'high' },
+        { id: 2, time: '今天 09:30', type: '设备离线', status: '未处理', level: 'medium' },
+        { id: 3, time: '昨天 15:45', type: '通信错误', status: '已处理', level: 'low' },
+        { id: 4, time: '昨天 10:20', type: '电量不足', status: '未处理', level: 'low' }
+      ],
+      
+      // 刷新状态
+      isRefreshing: false,
+      
+      // 数值更新动画
+      updatedFields: {
+        weather: {},
+        soil: {}
+      }
+    }
+  },
+  
+  computed: {
+    // 获取所有未处理的告警
+    getUnhandledAlerts() {
+      return this.alertHistory.filter(alert => alert.status === '未处理');
+    }
+  },
+  
+  onReady() {
+    // 设置页面标题
+    uni.setNavigationBarTitle({
+      title: this.deviceInfo.name
+    })
+  },
+  
+  onLoad(options) {
+    // 如果有传入设备ID,则获取设备信息
+    if (options && options.id) {
+      let deviceId = options.id;
+      let deviceCode = options.code || '';
+      
+      // 如果有传入设备编码,优先使用编码判断设备类型
+      if (deviceCode) {
+        this.fetchDeviceInfoWithCode(deviceId, deviceCode);
+      } else {
+        this.fetchDeviceInfo(deviceId);
+      }
+    }
+  },
+  
+  methods: {
+    // 获取设备信息(带编码)
+    fetchDeviceInfoWithCode(deviceId, deviceCode) {
+      // 这里应该是API请求,暂时用模拟数据
+      console.log('获取设备信息:', deviceId, deviceCode);
+      
+      // 模拟异步获取数据
+      setTimeout(() => {
+        // 根据设备编码判断是气象站还是土壤墒情
+        if (deviceCode.startsWith('W')) {
+          this.deviceInfo.deviceType = 'weather';
+          
+          // 保存旧数据用于比较
+          const oldData = JSON.parse(JSON.stringify(this.weatherData));
+          
+          // 模拟更新气象数据
+          this.weatherData = {
+            temperature: (Math.random() * 10 + 20).toFixed(1),
+            humidity: (Math.random() * 30 + 50).toFixed(0),
+            rainfall: Math.random() < 0.3 ? (Math.random() * 5).toFixed(1) : '0.0',
+            windDirection: ['东北', '东南', '西北', '西南', '东', '南', '西', '北'][Math.floor(Math.random() * 8)],
+            windSpeed: (Math.random() * 5 + 1).toFixed(1),
+            pressure: (Math.random() * 20 + 1000).toFixed(1),
+            illumination: Math.floor(Math.random() * 50000 + 10000).toString(),
+            updateTime: '刚刚更新'
+          };
+          
+          // 检查哪些字段发生了变化
+          this.updatedFields.weather = {};
+          Object.keys(this.weatherData).forEach(key => {
+            if (key !== 'updateTime' && this.weatherData[key] !== oldData[key]) {
+              this.updatedFields.weather[key] = true;
+              
+              // 1秒后清除动画标记
+              setTimeout(() => {
+                this.$set(this.updatedFields.weather, key, false);
+              }, 1000);
+            }
+          });
+          
+        } else if (deviceCode.startsWith('S')) {
+          this.deviceInfo.deviceType = 'soil';
+          
+          // 保存旧数据用于比较
+          const oldData = JSON.parse(JSON.stringify(this.soilData));
+          
+          // 模拟更新土壤数据
+          this.soilData = {
+            temperature: (Math.random() * 10 + 18).toFixed(1),
+            moisture: (Math.random() * 30 + 20).toFixed(1),
+            nitrogen: (Math.random() * 100 + 100).toFixed(1),
+            phosphorus: (Math.random() * 50 + 20).toFixed(1),
+            potassium: (Math.random() * 100 + 150).toFixed(1),
+            conductivity: (Math.random() * 0.5 + 0.2).toFixed(2),
+            ph: (Math.random() * 2 + 5.5).toFixed(1),
+            updateTime: '刚刚更新'
+          };
+          
+          // 检查哪些字段发生了变化
+          this.updatedFields.soil = {};
+          Object.keys(this.soilData).forEach(key => {
+            if (key !== 'updateTime' && this.soilData[key] !== oldData[key]) {
+              this.updatedFields.soil[key] = true;
+              
+              // 1秒后清除动画标记
+              setTimeout(() => {
+                this.$set(this.updatedFields.soil, key, false);
+              }, 1000);
+            }
+          });
+          
+        } else {
+          // 如果编码无法判断类型,使用原有判断逻辑
+          this.fetchDeviceInfo(deviceId);
+          return;
+        }
+        
+        // 更新设备基本信息
+        this.deviceInfo.deviceId = deviceCode;
+        this.deviceInfo.name = `采集设备-${deviceCode.slice(-4)}`;
+        this.deviceInfo.lastUpdate = '刚刚';
+        
+        // 实际应该是API请求结果
+      }, 500);
+    },
+    
+    // 获取设备信息
+    fetchDeviceInfo(deviceId) {
+      // 这里应该是API请求,暂时用模拟数据
+      console.log('获取设备信息:', deviceId)
+      
+      // 模拟异步获取数据
+      setTimeout(() => {
+        // 根据设备ID判断是气象站还是土壤墒情
+        if (deviceId.startsWith('W') || deviceId.includes('weather')) {
+          this.deviceInfo.deviceType = 'weather';
+          
+          // 保存旧数据用于比较
+          const oldData = JSON.parse(JSON.stringify(this.weatherData));
+          
+          // 模拟更新气象数据
+          this.weatherData = {
+            temperature: (Math.random() * 10 + 20).toFixed(1),
+            humidity: (Math.random() * 30 + 50).toFixed(0),
+            rainfall: Math.random() < 0.3 ? (Math.random() * 5).toFixed(1) : '0.0',
+            windDirection: ['东北', '东南', '西北', '西南', '东', '南', '西', '北'][Math.floor(Math.random() * 8)],
+            windSpeed: (Math.random() * 5 + 1).toFixed(1),
+            pressure: (Math.random() * 20 + 1000).toFixed(1),
+            illumination: Math.floor(Math.random() * 50000 + 10000).toString(),
+            updateTime: Math.floor(Math.random() * 10 + 1) + '分钟前更新'
+          };
+          
+          // 检查哪些字段发生了变化
+          this.updatedFields.weather = {};
+          Object.keys(this.weatherData).forEach(key => {
+            if (key !== 'updateTime' && this.weatherData[key] !== oldData[key]) {
+              this.updatedFields.weather[key] = true;
+              
+              // 1秒后清除动画标记
+              setTimeout(() => {
+                this.$set(this.updatedFields.weather, key, false);
+              }, 1000);
+            }
+          });
+          
+        } else if (deviceId.startsWith('S') || deviceId.includes('soil')) {
+          this.deviceInfo.deviceType = 'soil';
+          
+          // 保存旧数据用于比较
+          const oldData = JSON.parse(JSON.stringify(this.soilData));
+          
+          // 模拟更新土壤数据
+          this.soilData = {
+            temperature: (Math.random() * 10 + 18).toFixed(1),
+            moisture: (Math.random() * 30 + 20).toFixed(1),
+            nitrogen: (Math.random() * 100 + 100).toFixed(1),
+            phosphorus: (Math.random() * 50 + 20).toFixed(1),
+            potassium: (Math.random() * 100 + 150).toFixed(1),
+            conductivity: (Math.random() * 0.5 + 0.2).toFixed(2),
+            ph: (Math.random() * 2 + 5.5).toFixed(1),
+            updateTime: Math.floor(Math.random() * 10 + 1) + '分钟前更新'
+          };
+          
+          // 检查哪些字段发生了变化
+          this.updatedFields.soil = {};
+          Object.keys(this.soilData).forEach(key => {
+            if (key !== 'updateTime' && this.soilData[key] !== oldData[key]) {
+              this.updatedFields.soil[key] = true;
+              
+              // 1秒后清除动画标记
+              setTimeout(() => {
+                this.$set(this.updatedFields.soil, key, false);
+              }, 1000);
+            }
+          });
+          
+        } else {
+          // 如果ID无法判断类型,根据偶数/奇数ID模拟不同类型
+          const idNum = parseInt(deviceId.replace(/\D/g, ''));
+          this.deviceInfo.deviceType = idNum % 2 === 0 ? 'weather' : 'soil';
+          
+          if (this.deviceInfo.deviceType === 'weather') {
+            // 模拟更新气象数据
+            this.weatherData = {
+              temperature: (Math.random() * 10 + 20).toFixed(1),
+              humidity: (Math.random() * 30 + 50).toFixed(0),
+              rainfall: Math.random() < 0.3 ? (Math.random() * 5).toFixed(1) : '0.0',
+              windDirection: ['东北', '东南', '西北', '西南', '东', '南', '西', '北'][Math.floor(Math.random() * 8)],
+              windSpeed: (Math.random() * 5 + 1).toFixed(1),
+              pressure: (Math.random() * 20 + 1000).toFixed(1),
+              illumination: Math.floor(Math.random() * 50000 + 10000).toString(),
+              updateTime: Math.floor(Math.random() * 10 + 1) + '分钟前更新'
+            };
+          } else {
+            // 模拟更新土壤数据
+            this.soilData = {
+              temperature: (Math.random() * 10 + 18).toFixed(1),
+              moisture: (Math.random() * 30 + 20).toFixed(1),
+              nitrogen: (Math.random() * 100 + 100).toFixed(1),
+              phosphorus: (Math.random() * 50 + 20).toFixed(1),
+              potassium: (Math.random() * 100 + 150).toFixed(1),
+              conductivity: (Math.random() * 0.5 + 0.2).toFixed(2),
+              ph: (Math.random() * 2 + 5.5).toFixed(1),
+              updateTime: Math.floor(Math.random() * 10 + 1) + '分钟前更新'
+            };
+          }
+        }
+        
+        // 更新设备基本信息
+        this.deviceInfo.deviceId = deviceId;
+        this.deviceInfo.name = `采集设备-${deviceId.slice(-4)}`;
+        this.deviceInfo.lastUpdate = (Math.floor(Math.random() * 5) + 1) + '分钟前';
+        
+        // 实际应该是API请求结果
+      }, 500)
+    },
+    
+    // 刷新数据
+    refreshData() {
+      if (this.isRefreshing) return;
+      
+      this.isRefreshing = true;
+      
+      uni.showLoading({
+        title: '数据刷新中...'
+      });
+      
+      setTimeout(() => {
+        if (this.deviceInfo.deviceType === 'weather') {
+          // 保存旧数据用于比较
+          const oldData = JSON.parse(JSON.stringify(this.weatherData));
+          
+          // 模拟更新气象数据
+          this.weatherData = {
+            temperature: (Math.random() * 10 + 20).toFixed(1),
+            humidity: (Math.random() * 30 + 50).toFixed(0),
+            rainfall: Math.random() < 0.3 ? (Math.random() * 5).toFixed(1) : '0.0',
+            windDirection: ['东北', '东南', '西北', '西南', '东', '南', '西', '北'][Math.floor(Math.random() * 8)],
+            windSpeed: (Math.random() * 5 + 1).toFixed(1),
+            pressure: (Math.random() * 20 + 1000).toFixed(1),
+            illumination: Math.floor(Math.random() * 50000 + 10000).toString(),
+            updateTime: '刚刚更新'
+          };
+          
+          // 检查哪些字段发生了变化
+          this.updatedFields.weather = {};
+          Object.keys(this.weatherData).forEach(key => {
+            if (key !== 'updateTime' && this.weatherData[key] !== oldData[key]) {
+              this.updatedFields.weather[key] = true;
+              
+              // 1秒后清除动画标记
+              setTimeout(() => {
+                this.$set(this.updatedFields.weather, key, false);
+              }, 1000);
+            }
+          });
+          
+        } else {
+          // 保存旧数据用于比较
+          const oldData = JSON.parse(JSON.stringify(this.soilData));
+          
+          // 模拟更新土壤数据
+          this.soilData = {
+            temperature: (Math.random() * 10 + 18).toFixed(1),
+            moisture: (Math.random() * 30 + 20).toFixed(1),
+            nitrogen: (Math.random() * 100 + 100).toFixed(1),
+            phosphorus: (Math.random() * 50 + 20).toFixed(1),
+            potassium: (Math.random() * 100 + 150).toFixed(1),
+            conductivity: (Math.random() * 0.5 + 0.2).toFixed(2),
+            ph: (Math.random() * 2 + 5.5).toFixed(1),
+            updateTime: '刚刚更新'
+          };
+          
+          // 检查哪些字段发生了变化
+          this.updatedFields.soil = {};
+          Object.keys(this.soilData).forEach(key => {
+            if (key !== 'updateTime' && this.soilData[key] !== oldData[key]) {
+              this.updatedFields.soil[key] = true;
+              
+              // 1秒后清除动画标记
+              setTimeout(() => {
+                this.$set(this.updatedFields.soil, key, false);
+              }, 1000);
+            }
+          });
+        }
+        
+        this.deviceInfo.lastUpdate = '刚刚';
+        
+        uni.hideLoading();
+        uni.showToast({
+          title: '数据已更新',
+          icon: 'success'
+        });
+        
+        // 停止刷新动画
+        setTimeout(() => {
+          this.isRefreshing = false;
+        }, 500);
+      }, 1500);
+    }
+  }
+}
+</script>
+
+<style>
+/* 基础样式 */
+.container {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  background-color: #F8FCF9;
+  padding-bottom: 30rpx;
+}
+
+/* 设备头部样式 */
+.device-header {
+  background-color: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin: 30rpx 30rpx 30rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+  border: 1rpx solid rgba(210, 237, 217, 0.5);
+}
+
+.device-info-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 28rpx;
+}
+
+.device-name-container {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.refresh-btn {
+  width: 48rpx;
+  height: 48rpx;
+  background-color: rgba(76, 175, 80, 0.1);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 12rpx;
+  transition: transform 0.3s ease;
+}
+
+.refresh-btn:active {
+  transform: rotate(180deg);
+}
+
+.device-name {
+  font-size: 36rpx;
+  color: #333333;
+  font-weight: 600;
+  margin-bottom: 12rpx;
+}
+
+.status-tag {
+  padding: 6rpx 16rpx 6rpx 28rpx;
+  border-radius: 30rpx;
+  font-size: 24rpx;
+  font-weight: 500;
+  flex-shrink: 0;
+  position: relative;
+  overflow: hidden;
+}
+
+.status-online {
+  background-color: rgba(76, 175, 80, 0.1);
+  color: #3BB44A;
+}
+
+.status-offline {
+  background-color: rgba(245, 108, 108, 0.1);
+  color: #F56C6C;
+  padding-left: 28rpx;
+}
+
+.status-dot {
+  position: absolute;
+  width: 6rpx;
+  height: 6rpx;
+  background-color: #3BB44A;
+  border-radius: 50%;
+  top: 50%;
+  left: 12rpx;
+  transform: translateY(-50%);
+  box-shadow: 0 0 4rpx rgba(76, 175, 80, 0.8);
+  animation: blink 1.5s infinite;
+  display: inline-block;
+}
+
+.status-dot.offline-dot {
+  background-color: #F56C6C;
+  box-shadow: 0 0 4rpx rgba(245, 108, 108, 0.8);
+}
+
+@keyframes blink {
+  0% {
+    opacity: 0.4;
+  }
+  50% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0.4;
+  }
+}
+
+.device-meta-row {
+  display: flex;
+  flex-direction: column;
+  background-color: #F9FCFA;
+  padding: 20rpx 24rpx;
+  border-radius: 16rpx;
+  border: 1rpx solid rgba(210, 237, 217, 0.8);
+}
+
+.device-meta-item {
+  display: flex;
+  align-items: center;
+  font-size: 28rpx;
+  margin-top: 18rpx;
+}
+
+.device-meta-item:first-child {
+  margin-top: 0;
+}
+
+.meta-icon {
+  width: 36rpx;
+  height: 36rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 12rpx;
+}
+
+.meta-label {
+  color: #777777;
+  min-width: 140rpx;
+  font-size: 28rpx;
+}
+
+.meta-value {
+  color: #333333;
+  flex: 1;
+  font-size: 28rpx;
+  font-weight: 500;
+}
+
+/* 数据展示区域 */
+.data-section {
+  margin: 0 30rpx 30rpx;
+  background-color: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 30rpx 24rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+  border: 1rpx solid rgba(210, 237, 217, 0.5);
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333333;
+  margin-bottom: 30rpx;
+  padding: 0 10rpx 16rpx;
+  display: flex;
+  align-items: center;
+  position: relative;
+  border-bottom: 2rpx solid rgba(59, 180, 74, 0.1);
+}
+
+.section-title::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 8rpx;
+  height: 32rpx;
+  background-color: #3BB44A;
+  border-radius: 4rpx;
+  margin-right: 10rpx;
+}
+
+.section-title text {
+  margin-left: 20rpx;
+}
+
+.alert-badge {
+  background-color: #F56C6C;
+  color: #FFFFFF;
+  font-size: 22rpx;
+  border-radius: 30rpx;
+  padding: 2rpx 12rpx;
+  margin-left: 12rpx;
+  font-weight: normal;
+  min-width: 32rpx;
+  text-align: center;
+}
+
+.data-grid {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 10rpx 0;
+  width: 100%;
+  margin: 0 -10rpx;
+}
+
+.data-item {
+  width: calc(50% - 20rpx);
+  margin: 0 10rpx 20rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background-color: #F6FCF7;
+  border-radius: 16rpx;
+  padding: 24rpx 16rpx;
+  box-sizing: border-box;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+  position: relative;
+  border: 1rpx solid rgba(210, 237, 217, 0.6);
+  transition: all 0.25s ease;
+  overflow: hidden;
+}
+
+.data-item::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 6rpx;
+  height: 100%;
+  background: linear-gradient(to bottom, #4CAF50, #8BC34A);
+  opacity: 0.8;
+}
+
+.data-item:active {
+  transform: translateY(2rpx);
+  box-shadow: 0 1rpx 6rpx rgba(0, 0, 0, 0.1);
+  border-color: rgba(76, 175, 80, 0.8);
+}
+
+.data-icon {
+  width: 40rpx;
+  height: 40rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 16rpx;
+  background-color: rgba(76, 175, 80, 0.08);
+  border-radius: 50%;
+  padding: 20rpx;
+  transition: transform 0.3s ease;
+}
+
+.data-icon:active {
+  transform: scale(1.1);
+}
+
+.data-icon.no-data {
+  opacity: 0.5;
+}
+
+.data-label {
+  font-size: 26rpx;
+  color: #555555;
+  margin-bottom: 12rpx;
+  font-weight: 500;
+  text-align: center;
+}
+
+.data-value-container {
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+
+.data-value {
+  font-size: 36rpx;
+  color: #333333;
+  font-weight: 600;
+}
+
+.data-value.no-data {
+  font-size: 26rpx;
+  color: #999999;
+  font-weight: normal;
+}
+
+.data-unit {
+  font-size: 22rpx;
+  color: #888888;
+  margin-left: 4rpx;
+  padding-bottom: 6rpx;
+}
+
+.data-time {
+  font-size: 20rpx;
+  color: #AAAAAA;
+  margin-top: 8rpx;
+  text-align: right;
+  width: 100%;
+}
+
+/* 告警信息列表 */
+.alerts-section {
+  margin: 0 30rpx 30rpx;
+  background-color: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 30rpx 24rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+  border: 1rpx solid rgba(210, 237, 217, 0.5);
+}
+
+.alerts-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.alert-item {
+  display: flex;
+  align-items: center;
+  padding: 24rpx 20rpx;
+  border-radius: 12rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
+  position: relative;
+  border: 1rpx solid transparent;
+  transition: transform 0.2s ease;
+}
+
+.alert-item:active {
+  transform: scale(0.98);
+}
+
+.alert-urgent {
+  background-color: #FEF3F3;
+  border-left: 4rpx solid #F56C6C;
+  border-color: rgba(245, 108, 108, 0.2);
+}
+
+.alert-warning {
+  background-color: #FFF8E6;
+  border-left: 4rpx solid #E6A23C;
+  border-color: rgba(230, 162, 60, 0.2);
+}
+
+.alert-info {
+  background-color: #F2FAF5;
+  border-left: 4rpx solid #67C23A;
+  border-color: rgba(103, 194, 58, 0.2);
+}
+
+.alert-item-icon {
+  width: 50rpx;
+  height: 50rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+}
+
+.alert-item-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.alert-item-type {
+  font-size: 28rpx;
+  color: #333333;
+  font-weight: 500;
+  margin-bottom: 8rpx;
+}
+
+.alert-item-level {
+  font-size: 24rpx;
+  color: #999999;
+}
+
+.alert-item-time {
+  font-size: 24rpx;
+  color: #999999;
+  margin-left: 16rpx;
+  min-width: 100rpx;
+  text-align: right;
+}
+
+.empty-alert {
+  padding: 60rpx 0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.empty-text {
+  font-size: 28rpx;
+  color: #999999;
+}
+
+/* 数据更新动画 */
+@keyframes fadeNumberChange {
+  0% {
+    opacity: 0.6;
+    transform: scale(0.95);
+  }
+  50% {
+    opacity: 1;
+    transform: scale(1.05);
+  }
+  100% {
+    opacity: 1;
+    transform: scale(1);
+  }
+}
+
+.data-value.updated {
+  animation: fadeNumberChange 0.8s ease;
+}
+
+/* 刷新按钮旋转动画 */
+@keyframes spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+.refresh-btn.refreshing {
+  animation: spin 1.2s linear infinite;
+}
+</style> 

+ 16 - 2
pages/device-list/index.vue

@@ -287,11 +287,22 @@ export default {
         // 如果已经生成了30条数据,则停止
         if (index >= 30) break;
         
+        // 对于采集设备类型,生成随机的气象或土壤设备
+        let deviceType = this.deviceType;
+        let deviceCode = `DEV${String(index + 1001).padStart(4, '0')}`;
+        
+        // 如果是采集设备,随机生成气象站或土壤墒情设备
+        if (this.deviceType === 'sensor') {
+          // 随机分配采集设备子类型:气象站或土壤墒情
+          const sensorSubType = Math.random() > 0.5 ? 'weather' : 'soil';
+          deviceCode = sensorSubType === 'weather' ? `W${deviceCode}` : `S${deviceCode}`;
+        }
+        
         devices.push({
           id: `device-${index + 1}`,
           name: `${this.getDeviceTypeName(this.deviceType)}-${index + 1}`,
-          code: `DEV${String(index + 1001).padStart(4, '0')}`,
-          type: this.deviceType || 'default',
+          code: deviceCode,
+          type: deviceType,
           status: Math.random() > 0.3 ? 'online' : 'offline', // 70% 概率在线
           location: locations[Math.floor(Math.random() * locations.length)],
           updateTime: updateTimes[Math.floor(Math.random() * updateTimes.length)],
@@ -342,6 +353,9 @@ export default {
       
       if (device.type === 'monitor') {
         url = `/pages/device-list/detail-camera?id=${device.id}`;
+      } else if (device.type === 'sensor') {
+        // 采集设备跳转到采集设备详情页,同时传递设备编码,便于判断设备子类型
+        url = `/pages/device-list/detail-collector?id=${device.id}&code=${device.code}`;
       } else {
         // 其他类型设备暂时使用通用详情页
         url = `/pages/device-detail/index?id=${device.id}&type=${device.type}`;

Some files were not shown because too many files changed in this diff