Prechádzať zdrojové kódy

完善首页地块相关功能,新增设备相关页面

jiuling 11 mesiacov pred
rodič
commit
2f542012d2
48 zmenil súbory, kde vykonal 5337 pridanie a 1598 odobranie
  1. 2 1
      App.vue
  2. 2 4
      api/services/field.js
  3. 20 22
      main.js
  4. 1 1
      manifest.json
  5. 7 10
      package-lock.json
  6. 1 1
      package.json
  7. 37 6
      pages.json
  8. 29 24
      pages/about/index.vue
  9. 1567 1406
      pages/dashboard/index.vue
  10. 1051 0
      pages/device-list/detail-camera.vue
  11. 1179 0
      pages/device-list/detail-collector.vue
  12. 869 0
      pages/device-list/index.vue
  13. 443 3
      pages/device/index.vue
  14. 2 2
      pages/knowledge/detail.vue
  15. 4 4
      pages/knowledge/index.vue
  16. 101 103
      pages/settings/index.vue
  17. BIN
      static/icons/Voice_icon.png
  18. BIN
      static/icons/action_icon.png
  19. BIN
      static/icons/arrow_down_icon.png
  20. BIN
      static/icons/arrow_left_icon.png
  21. BIN
      static/icons/arrow_right_icon.png
  22. BIN
      static/icons/arrow_up_icon.png
  23. BIN
      static/icons/camera_icon.png
  24. BIN
      static/icons/clock_icon.png
  25. 1 0
      static/icons/device-default.png
  26. BIN
      static/icons/device_alert.png
  27. BIN
      static/icons/device_icon.png
  28. BIN
      static/icons/device_num.png
  29. BIN
      static/icons/device_offline.png
  30. BIN
      static/icons/device_online.png
  31. BIN
      static/icons/info_icon.png
  32. BIN
      static/icons/location_icon.png
  33. BIN
      static/icons/muted_icon.png
  34. BIN
      static/icons/pause_icon.png
  35. BIN
      static/icons/play_icon.png
  36. BIN
      static/icons/refresh_icon.png
  37. BIN
      static/icons/resetPTZ_icon.png
  38. BIN
      static/icons/resize_icon.png
  39. BIN
      static/icons/signal_icon.png
  40. BIN
      static/icons/success_icon.png
  41. BIN
      static/icons/unmuted_icon.png
  42. BIN
      static/icons/warning_icon.png
  43. BIN
      static/icons/zoom01_icon.png
  44. BIN
      static/icons/zoom02_icon.png
  45. 0 0
      static/images/video-placeholder.jpg
  46. 11 7
      store/index.js
  47. 3 0
      uni.scss
  48. 7 4
      utils/storage.js

+ 2 - 1
App.vue

@@ -58,8 +58,9 @@ export default {
   </view>
 </template>
 
-<style>
+<style lang="scss">
 /* 全局基础样式 */
+@import "uview-ui/index.scss";
 page {
   font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
   font-size: 28rpx;

+ 2 - 4
api/services/field.js

@@ -6,7 +6,6 @@ import {
 import storage from "@/utils/storage.js";
 const request = http.request;
 
-
 /**
  * 获取用户关联的地块列表
  * @param {number} pageNum - 页码
@@ -32,10 +31,9 @@ export function fetchUserFieldList(pageNum = 1, pageSize = 10) {
 /**
  * 获取当前登录用户默认地块
  */
-export function getUserCurrentField() {
-	const user = storage.getUserInfo();
+export function listFieldName(userId) {
 	return http.request({
-		url: `uniapp/field/current/user/${user.userId}`,
+		url: `uniapp/field/name/${userId}`,
 		method: Method.GET,
 	});
 }

+ 20 - 22
main.js

@@ -1,32 +1,30 @@
-import { createSSRApp } from 'vue'
+// main.js(Vue 2)
+import Vue from 'vue'
 import App from './App.vue'
 import * as filters from './utils/filters.js'
 import store from './store'
-// import uView from 'uview-ui'
+import uView from 'uview-ui'
 import config from '@/config/config'
 
-export function createApp() {
-  const app = createSSRApp(App)
+// 注册全局过滤器
+Object.keys(filters).forEach((key) => {
+  Vue.filter(key, filters[key])
+})
 
-  // 全局注册 filters(通过 globalProperties.$filters)
-  app.config.globalProperties.$filters = {}
-  Object.keys(filters).forEach((key) => {
-    app.config.globalProperties.$filters[key] = filters[key]
-  })
+// 全局挂载变量(通过 Vue.prototype)
+Vue.prototype.$mainColor = config.mainColor
+Vue.prototype.$lightColor = config.lightColor
+Vue.prototype.$aiderLightColor = config.aiderLightColor
 
-  // 全局配置颜色变量(替代 Vue.prototype)
-  app.config.globalProperties.$mainColor = config.mainColor
-  app.config.globalProperties.$lightColor = config.lightColor
-  app.config.globalProperties.$aiderLightColor = config.aiderLightColor
+// 使用 Vuex store
+Vue.use(store)
 
-  // 使用 Vuex store
-  app.use(store)
+// 使用 uView UI
+Vue.use(uView)
 
-  // 使用 uView UI 框架(确保已正确安装并配置)
-  // app.use(uView)
+Vue.config.productionTip = false
 
-  // SSR 挂载由框架控制,这里仅返回 app 实例
-  return {
-    app
-  }
-}
+new Vue({
+  store,
+  render: h => h(App)
+}).$mount('#app')

+ 1 - 1
manifest.json

@@ -18,5 +18,5 @@
             "urlCheck" : false
         }
     },
-    "vueVersion" : "3"
+    "vueVersion" : "2"
 }

+ 7 - 10
package-lock.json

@@ -6,14 +6,7 @@
     "": {
       "dependencies": {
         "event-source-polyfill": "^1.0.31",
-        "uview-ui": "^2.0.36"
-      }
-    },
-    "node_modules/.pnpm/uview-ui@2.0.38": {
-      "name": "uview-ui",
-      "version": "2.0.38",
-      "engines": {
-        "HBuilderX": "^3.1.0"
+        "uview-ui": "^2.0.38"
       }
     },
     "node_modules/event-source-polyfill": {
@@ -22,8 +15,12 @@
       "integrity": "sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA=="
     },
     "node_modules/uview-ui": {
-      "resolved": "node_modules/.pnpm/uview-ui@2.0.38",
-      "link": true
+      "version": "2.0.38",
+      "resolved": "https://registry.npmmirror.com/uview-ui/-/uview-ui-2.0.38.tgz",
+      "integrity": "sha512-6egHDf9lXHKpG3hEjRE0vMx4+VWwKk/ReTf5x18KrIKqdvdPRqO3+B8Unh7vYYwrIxzAWIlmhZ9RJpKI/4UqPQ==",
+      "engines": {
+        "HBuilderX": "^3.1.0"
+      }
     }
   }
 }

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "dependencies": {
     "event-source-polyfill": "^1.0.31",
-    "uview-ui": "^2.0.36"
+    "uview-ui": "^2.0.38"
   }
 }

+ 37 - 6
pages.json

@@ -1,4 +1,5 @@
 {
+
   "pages": [
    
     {
@@ -26,12 +27,39 @@
         "navigationBarTitleText": "农事活动"
       }
     },
+	    {
+	      "path": "pages/device/index",
+	      "style": {
+	        "navigationBarTitleText": "设备监测"
+	      }
+	    },
     {
-      "path": "pages/device/index",
-      "style": {
-        "navigationBarTitleText": "设备监测"
-      }
-    },
+          "path": "pages/device-list/detail-camera",
+          "style": {
+            "navigationBarTitleText": "监控设备详情",
+            "navigationBarBackgroundColor": "#ffffff",
+            "navigationBarTextStyle": "black",
+            "enablePullDownRefresh": false
+          }
+        },
+        {
+          "path": "pages/device-list/detail-collector",
+          "style": {
+            "navigationBarTitleText": "采集设备详情",
+            "navigationBarBackgroundColor": "#ffffff",
+            "navigationBarTextStyle": "black",
+            "enablePullDownRefresh": false
+          }
+        },
+		{
+		      "path": "pages/device-list/index",
+		      "style": {
+		        "navigationBarTitleText": "设备列表",
+		        "navigationBarBackgroundColor": "#ffffff",
+		        "navigationBarTextStyle": "black",
+		        "enablePullDownRefresh": false
+		      }
+		    },
     {
       "path": "pages/knowledge/index",
       "style": {
@@ -157,5 +185,8 @@
         "text": "我的"
       }
     ]
-  }
+  },
+  "easycom": {
+  		"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
+  	}
 }

+ 29 - 24
pages/about/index.vue

@@ -48,32 +48,37 @@
   </view>
 </template>
 
-<script setup>
-import { ref } from 'vue'
-
-const version = ref('1.0.0')
-
-// 复制文本
-const copyText = (text) => {
-  uni.setClipboardData({
-    data: text,
-    success: () => {
-      uni.showToast({
-        title: '已复制到剪贴板',
-        icon: 'none'
-      })
+<script>
+export default {
+  data() {
+    return {
+      version: '1.0.0'
     }
-  })
-}
-
-// 拨打电话
-const makePhoneCall = () => {
-  uni.makePhoneCall({
-    phoneNumber: '400-xxx-xxxx',
-    fail: () => {
-      copyText('400-xxx-xxxx')
+  },
+  methods: {
+    // 复制文本
+    copyText(text) {
+      uni.setClipboardData({
+        data: text,
+        success: () => {
+          uni.showToast({
+            title: '已复制到剪贴板',
+            icon: 'none'
+          })
+        }
+      })
+    },
+
+    // 拨打电话
+    makePhoneCall() {
+      uni.makePhoneCall({
+        phoneNumber: '400-xxx-xxxx',
+        fail: () => {
+          this.copyText('400-xxx-xxxx')
+        }
+      })
     }
-  })
+  }
 }
 </script>
 

+ 1567 - 1406
pages/dashboard/index.vue

@@ -1,1414 +1,1575 @@
 <template>
-  <view class="dashboard-container">
-    <!-- 顶部用户信息卡片 -->
-    <view class="user-info-card">
-      <view class="user-info">
-        <text class="greeting">您好,{{ userData.nickname }}</text>
-        <view class="plot-info">
-          <text class="plot-label">当前地块:</text>
-          <text class="plot-name">{{ userData.selectedPlot }}</text>
-          <view class="switch-plot-btn" @click="handleSwitchPlot">
-            <text>切换</text>
-          </view>
-        </view>
-      </view>
-      <view class="avatar-container" @click="navigateToProfile">
-        <view class="avatar">
-          <image :src="userData.avatar" mode="aspectFill"></image>
-        </view>
-      </view>
-    </view>
-
-    <!-- 顶部统计概览 -->
-    <view class="stats-overview">
-      <view class="alert-card" v-for="(alert, index) in alertSummaries" :key="index" @click="navigateToAlertDetail(alert.type)">
-        <view class="alert-header">
-          <view class="alert-icon-container">
-            <image class="custom-icon" :src="alert.iconSrc"></image>
-          </view>
-          <text class="alert-title">{{ alert.title }}</text>
-        </view>
-        <text class="alert-value">{{ alert.value }}</text>
-        <text class="alert-description">{{ alert.description }}</text>
-      </view>
-    </view>
-
-    <!-- 天气卡片 -->
-    <view class="card weather-card">
-      <view class="card-header">
-        <view class="title-section">
-          <view class="title-line"></view>
-          <text class="card-title">天气与预报</text>
-        </view>
-      </view>
-      <view class="weather-content">
-        <view class="current-weather">
-          <view class="weather-icon-container">
-            <view class="weather-icon">
-              <text v-if="weatherData.description === '晴朗'">☀️</text>
-              <text v-else-if="weatherData.description.includes('雨')">🌧️</text>
-              <text v-else-if="weatherData.description.includes('云')">⛅</text>
-              <text v-else>🌤️</text>
-            </view>
-          </view>
-          <view class="weather-details">
-            <text class="weather-temp">{{ weatherData.temperature }}°C</text>
-            <text class="weather-desc">{{ weatherData.description }}</text>
-          </view>
-        </view>
-        <view class="weather-metrics">
-          <view class="weather-metric">
-            <text class="metric-label">湿度</text>
-            <text class="metric-value">{{ weatherData.humidity }}%</text>
-          </view>
-          <view class="vertical-divider"></view>
-          <view class="weather-metric">
-            <text class="metric-label">风力</text>
-            <text class="metric-value">{{ weatherData.windLevel }} 级</text>
-          </view>
-          <view class="vertical-divider"></view>
-          <view class="weather-metric">
-            <text class="metric-label">降水量</text>
-            <text class="metric-value">{{ weatherData.rainfall }} mm</text>
-          </view>
-        </view>
-        <view class="weather-advice">
-          <view class="advice-header">
-            <view class="icon-tile small">
-              <u-icon name="info" color="#ffffff" size="14"></u-icon>
-            </view>
-            <text class="advice-title">今日建议:</text>
-          </view>
-          <text class="advice-content">{{ weatherData.advice }}</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 农场绩效卡片 -->
-    <view class="card farm-performance">
-      <view class="card-header">
-        <view class="title-section">
-          <view class="title-line"></view>
-          <text class="card-title">产值分析</text>
-        </view>
-        <view class="period-selector">
-          <text class="period" :class="{ active: currentPeriod === 'month' }" @click="changePeriod('month')">月</text>
-          <text class="period" :class="{ active: currentPeriod === 'quarter' }" @click="changePeriod('quarter')">季</text>
-          <text class="period" :class="{ active: currentPeriod === 'year' }" @click="changePeriod('year')">年</text>
-        </view>
-      </view>
-      <view class="performance-stats">
-        <view class="metric-column">
-          <text class="metric-value">128<text class="metric-unit">亩</text></text>
-          <text class="metric-label">管理面积总计</text>
-          <view class="growth positive">
-            <u-icon name="arrow-upward" color="#3BB44A" size="12"></u-icon>
-            <text>比上月增长12%</text>
-          </view>
-        </view>
-        <view class="metric-divider"></view>
-        <view class="metric-column">
-          <text class="metric-value">¥36,480</text>
-          <text class="metric-label">预计产值</text>
-          <view class="growth positive">
-            <u-icon name="arrow-upward" color="#3BB44A" size="12"></u-icon>
-            <text>比上月增长8.2%</text>
-          </view>
-        </view>
-      </view>
-      <view class="chart-container">
-        <view class="chart-header">
-          <text class="chart-title">产值趋势 (万元)</text>
-          <view class="chart-legend">
-            <view class="legend-item">
-              <view class="legend-color" style="background: #3BB44A;"></view>
-              <text>今年</text>
-            </view>
-            <view class="legend-item">
-              <view class="legend-color" style="background: #E0E0E0;"></view>
-              <text>去年</text>
-            </view>
-          </view>
-        </view>
-        <view class="chart-body">
-          <view class="y-axis">
-            <text v-for="(value, index) in [4, 3, 2, 1, 0]" :key="index">{{ value }}</text>
-          </view>
-          <view class="bars-container">
-            <view class="month-group" v-for="(month, index) in farmPerformanceData.months" :key="index">
-              <view class="bar-wrapper">
-                <view class="bar last-year" :style="{ height: (farmPerformanceData.lastYearValues[index] / 4) * 100 + '%' }"></view>
-                <view class="bar this-year" :style="{ height: (farmPerformanceData.thisYearValues[index] / 4) * 100 + '%' }"></view>
-              </view>
-              <text class="month-label">{{ month }}</text>
-            </view>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 监控设备概览 -->
-    <view class="card device-overview">
-      <view class="card-header">
-        <view class="title-section">
-          <view class="title-line"></view>
-          <text class="card-title">监控设备概览</text>
-        </view>
-      </view>
-      <view class="device-metrics-grid">
-        <view class="device-metric-card" v-for="(metric, index) in deviceMetrics" :key="index">
-          <view class="device-metric-header">
-            <view class="icon-tile" :style="{ background: metric.gradient }">
-              <u-icon :name="metric.icon" color="#ffffff" size="18"></u-icon>
-            </view>
-            <text class="metric-name">{{ metric.name }}</text>
-          </view>
-          <view class="device-metric-value">{{ metric.value }}</view>
-          <view class="device-metric-trend" :class="metric.trend.type">
-            <u-icon :name="metric.trend.type === 'up' ? 'arrow-upward' : 'arrow-downward'" :color="metric.trend.type === 'up' ? '#3BB44A' : '#FF5252'" size="14"></u-icon>
-            <text>{{ metric.trend.value }}</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 农业机械活动 -->
-    <view class="card machinery-activity">
-      <view class="card-header">
-        <view class="title-section">
-          <view class="title-line"></view>
-          <text class="card-title">农机作业概览</text>
-        </view>
-      </view>
-      <view class="machinery-metrics-grid">
-        <view class="machinery-metric-card" v-for="(metric, index) in machineryMetrics" :key="index">
-          <view class="machinery-metric-header">
-            <view class="icon-tile" :style="{ background: metric.gradient }">
-              <u-icon :name="metric.icon" color="#ffffff" size="18"></u-icon>
-            </view>
-            <text class="metric-name">{{ metric.name }}</text>
-          </view>
-          <view class="machinery-metric-value">{{ metric.value }}</view>
-          <view v-if="metric.trend" class="machinery-metric-trend" :class="metric.trend.type">
-            <u-icon :name="metric.trend.type === 'up' ? 'arrow-upward' : 'arrow-downward'" :color="metric.trend.type === 'up' ? '#3BB44A' : '#FF5252'" size="14"></u-icon>
-            <text>{{ metric.trend.value }}</text>
-          </view>
-          <view v-else class="machinery-metric-unit">
-            <text>{{ metric.unit }}</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 农场活动卡片 -->
-    <view class="card farm-activities">
-      <view class="card-header">
-        <view class="title-section">
-          <view class="title-line"></view>
-          <text class="card-title">农事活动</text>
-        </view>
-        <view class="action-button">
-          <text>查看全部</text>
-          <u-icon name="arrow-right" color="#ffffff" size="14"></u-icon>
-        </view>
-      </view>
-      <view class="activities-list">
-        <view class="activity-item" v-for="(activity, index) in farmData.recentActivities" :key="index">
-          <view class="activity-dot" :class="activity.status"></view>
-          <view class="activity-details">
-            <view class="activity-title-row">
-              <text class="activity-title">{{ activity.title }}</text>
-              <text class="activity-date">{{ activity.date }}</text>
-            </view>
-            <view class="activity-meta-row">
-              <text class="activity-executor">{{ activity.executor }}</text>
-              <view class="activity-action" @click.stop="navigateToActivity(activity)">
-                <text class="action-text">查看</text>
-                <u-icon name="arrow-right" color="#3BB44A" size="14"></u-icon>
-              </view>
-            </view>
-          </view>
-        </view>
-      </view>
-    </view>
-  </view>
+	<view class="dashboard-container">
+		<!-- 顶部用户信息卡片 -->
+		<view class="user-info-card">
+			<view class="user-info">
+				<text class="greeting">您好,{{ userData.nickname }}</text>
+				<view class="plot-info">
+					<text class="plot-label">当前地块:</text>
+					<text class="plot-name">{{ userData.selectedPlot }}</text>
+					<view class="switch-plot-btn" v-if="isLogin">
+						<u-picker :show="show" :columns="columns" :defaultIndex="defaultIndex" @cancel="show = false"
+							@confirm="onConfirm"></u-picker>
+						<text @click="handleSwitchPlot">切换</text>
+					</view>
+
+				</view>
+			</view>
+			<view class="avatar-container" @click="navigateToProfile">
+				<view class="avatar">
+					<image :src="userData.avatar" mode="aspectFill"></image>
+				</view>
+			</view>
+		</view>
+
+		<!-- 顶部统计概览 -->
+		<view class="stats-overview">
+			<view class="alert-card" v-for="(alert, index) in alertSummaries" :key="index"
+				@click="navigateToAlertDetail(alert.type)">
+				<view class="alert-header">
+					<view class="alert-icon-container">
+						<image class="custom-icon" :src="alert.iconSrc"></image>
+					</view>
+					<text class="alert-title">{{ alert.title }}</text>
+				</view>
+				<text class="alert-value">{{ alert.value }}</text>
+				<text class="alert-description">{{ alert.description }}</text>
+			</view>
+		</view>
+
+		<!-- 天气卡片 -->
+		<view class="card weather-card">
+			<view class="card-header">
+				<view class="title-section">
+					<view class="title-line"></view>
+					<text class="card-title">天气与预报</text>
+				</view>
+			</view>
+			<view class="weather-content">
+				<view class="current-weather">
+					<view class="weather-icon-container">
+						<view class="weather-icon">
+							<text v-if="weatherData.description === '晴朗'">☀️</text>
+							<text v-else-if="weatherData.description.includes('雨')">🌧️</text>
+							<text v-else-if="weatherData.description.includes('云')">⛅</text>
+							<text v-else>🌤️</text>
+						</view>
+					</view>
+					<view class="weather-details">
+						<text class="weather-temp">{{ weatherData.temperature }}°C</text>
+						<text class="weather-desc">{{ weatherData.description }}</text>
+					</view>
+				</view>
+				<view class="weather-metrics">
+					<view class="weather-metric">
+						<text class="metric-label">湿度</text>
+						<text class="metric-value">{{ weatherData.humidity }}%</text>
+					</view>
+					<view class="vertical-divider"></view>
+					<view class="weather-metric">
+						<text class="metric-label">风力</text>
+						<text class="metric-value">{{ weatherData.windLevel }} 级</text>
+					</view>
+					<view class="vertical-divider"></view>
+					<view class="weather-metric">
+						<text class="metric-label">降水量</text>
+						<text class="metric-value">{{ weatherData.rainfall }} mm</text>
+					</view>
+				</view>
+				<view class="weather-advice">
+					<view class="advice-header">
+						<view class="icon-tile small">
+							<u-icon name="info" color="#ffffff" size="14"></u-icon>
+						</view>
+						<text class="advice-title">今日建议:</text>
+					</view>
+					<text class="advice-content">{{ weatherData.advice }}</text>
+				</view>
+			</view>
+		</view>
+
+		<!-- 农场绩效卡片 -->
+		<view class="card farm-performance">
+			<view class="card-header">
+				<view class="title-section">
+					<view class="title-line"></view>
+					<text class="card-title">产值分析</text>
+				</view>
+				<view class="period-selector">
+					<text class="period" :class="{ active: currentPeriod === 'month' }"
+						@click="changePeriod('month')">月</text>
+					<text class="period" :class="{ active: currentPeriod === 'quarter' }"
+						@click="changePeriod('quarter')">季</text>
+					<text class="period" :class="{ active: currentPeriod === 'year' }"
+						@click="changePeriod('year')">年</text>
+				</view>
+			</view>
+			<view class="performance-stats">
+				<view class="metric-column">
+					<text class="metric-value">128<text class="metric-unit">亩</text></text>
+					<text class="metric-label">管理面积总计</text>
+					<view class="growth positive">
+						<u-icon name="arrow-upward" color="#3BB44A" size="12"></u-icon>
+						<text>比上月增长12%</text>
+					</view>
+				</view>
+				<view class="metric-divider"></view>
+				<view class="metric-column">
+					<text class="metric-value">¥36,480</text>
+					<text class="metric-label">预计产值</text>
+					<view class="growth positive">
+						<u-icon name="arrow-upward" color="#3BB44A" size="12"></u-icon>
+						<text>比上月增长8.2%</text>
+					</view>
+				</view>
+			</view>
+			<view class="chart-container">
+				<view class="chart-header">
+					<text class="chart-title">产值趋势 (万元)</text>
+					<view class="chart-legend">
+						<view class="legend-item">
+							<view class="legend-color" style="background: #3BB44A;"></view>
+							<text>今年</text>
+						</view>
+						<view class="legend-item">
+							<view class="legend-color" style="background: #E0E0E0;"></view>
+							<text>去年</text>
+						</view>
+					</view>
+				</view>
+				<view class="chart-body">
+					<view class="y-axis">
+						<text v-for="(value, index) in [4, 3, 2, 1, 0]" :key="index">{{ value }}</text>
+					</view>
+					<view class="bars-container">
+						<view class="month-group" v-for="(month, index) in farmPerformanceData.months" :key="index">
+							<view class="bar-wrapper">
+								<view class="bar last-year"
+									:style="{ height: (farmPerformanceData.lastYearValues[index] / 4) * 100 + '%' }">
+								</view>
+								<view class="bar this-year"
+									:style="{ height: (farmPerformanceData.thisYearValues[index] / 4) * 100 + '%' }">
+								</view>
+							</view>
+							<text class="month-label">{{ month }}</text>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 监控设备概览 -->
+		<view class="card device-overview">
+			<view class="card-header">
+				<view class="title-section">
+					<view class="title-line"></view>
+					<text class="card-title">监控设备概览</text>
+				</view>
+			</view>
+			<view class="device-metrics-grid">
+				<view class="device-metric-card" v-for="(metric, index) in deviceMetrics" :key="index">
+					<view class="device-metric-header">
+						<view class="icon-tile" :style="{ background: metric.gradient }">
+							<u-icon :name="metric.icon" color="#ffffff" size="18"></u-icon>
+						</view>
+						<text class="metric-name">{{ metric.name }}</text>
+					</view>
+					<view class="device-metric-value">{{ metric.value }}</view>
+					<view class="device-metric-trend" :class="metric.trend.type">
+						<u-icon :name="metric.trend.type === 'up' ? 'arrow-upward' : 'arrow-downward'"
+							:color="metric.trend.type === 'up' ? '#3BB44A' : '#FF5252'" size="14"></u-icon>
+						<text>{{ metric.trend.value }}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 农业机械活动 -->
+		<view class="card machinery-activity">
+			<view class="card-header">
+				<view class="title-section">
+					<view class="title-line"></view>
+					<text class="card-title">农机作业概览</text>
+				</view>
+			</view>
+			<view class="machinery-metrics-grid">
+				<view class="machinery-metric-card" v-for="(metric, index) in machineryMetrics" :key="index">
+					<view class="machinery-metric-header">
+						<view class="icon-tile" :style="{ background: metric.gradient }">
+							<u-icon :name="metric.icon" color="#ffffff" size="18"></u-icon>
+						</view>
+						<text class="metric-name">{{ metric.name }}</text>
+					</view>
+					<view class="machinery-metric-value">{{ metric.value }}</view>
+					<view v-if="metric.trend" class="machinery-metric-trend" :class="metric.trend.type">
+						<u-icon :name="metric.trend.type === 'up' ? 'arrow-upward' : 'arrow-downward'"
+							:color="metric.trend.type === 'up' ? '#3BB44A' : '#FF5252'" size="14"></u-icon>
+						<text>{{ metric.trend.value }}</text>
+					</view>
+					<view v-else class="machinery-metric-unit">
+						<text>{{ metric.unit }}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 农场活动卡片 -->
+		<view class="card farm-activities">
+			<view class="card-header">
+				<view class="title-section">
+					<view class="title-line"></view>
+					<text class="card-title">农事活动</text>
+				</view>
+				<view class="action-button">
+					<text>查看全部</text>
+					<u-icon name="arrow-right" color="#ffffff" size="14"></u-icon>
+				</view>
+			</view>
+			<view class="activities-list">
+				<view class="activity-item" v-for="(activity, index) in farmData.recentActivities" :key="index">
+					<view class="activity-dot" :class="activity.status"></view>
+					<view class="activity-details">
+						<view class="activity-title-row">
+							<text class="activity-title">{{ activity.title }}</text>
+							<text class="activity-date">{{ activity.date }}</text>
+						</view>
+						<view class="activity-meta-row">
+							<text class="activity-executor">{{ activity.executor }}</text>
+							<view class="activity-action" @click.stop="navigateToActivity(activity)">
+								<text class="action-text">查看</text>
+								<u-icon name="arrow-right" color="#3BB44A" size="14"></u-icon>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-import storage from "@/utils/storage.js";
-export default {
-  data() {
-    return {
-      // 用户数据
-      userData: {
-        nickname: '',
-        selectedPlot: '',
-		avatar:''
-      },
-      
-      // 农场数据
-      farmData: {
-        plotCount: 5,
-        deviceCount: 12,
-        deviceOnlineRate: 85,
-        taskCompletionRate: 76,
-        recentActivities: [
-          { title: '稻田水稻施肥', date: '2023-05-15', status: 'pending', id: 1, executor: '李四' },
-          { title: '南地块除草', date: '2023-05-12', status: 'completed', id: 2, executor: '王五' },
-          { title: '西北区域杀虫', date: '2023-05-08', status: 'completed', id: 3, executor: '张三' }
-        ]
-      },
-      
-      // 天气数据
-      weatherData: {
-        temperature: 28,
-        description: '晴朗',
-        humidity: 65,
-        windLevel: 3,
-        rainfall: 0,
-        advice: '今日适宜进行春玉米防虫作业,注意水分管理。'
-      },
-      
-      // 作物数据
-      crops: [
-        { name: '水稻', area: 48, progress: 75, icon: '🌾', bgColor: '#4CAF50' },
-        { name: '小麦', area: 36, progress: 60, icon: '🌿', bgColor: '#66BB6A' },
-        { name: '玉米', area: 32, progress: 85, icon: '🌽', bgColor: '#43A047' },
-        { name: '大豆', area: 12, progress: 40, icon: '🫘', bgColor: '#388E3C' }
-      ],
-      
-      // 监控设备指标
-      deviceMetrics: [
-        {
-          name: '在线设备',
-          value: '28',
-          icon: 'wifi',
-          gradient: 'linear-gradient(135deg, #26A69A, #00796B)',
-          trend: {
-            type: 'up',
-            value: '5.2%'
-          }
-        },
-        {
-          name: '告警设备',
-          value: '3',
-          icon: 'error-circle',
-          gradient: 'linear-gradient(135deg, #FF7043, #E64A19)',
-          trend: {
-            type: 'down',
-            value: '2.1%'
-          }
-        },
-        {
-          name: '离线设备',
-          value: '5',
-          icon: 'close-circle',
-          gradient: 'linear-gradient(135deg, #78909C, #455A64)',
-          trend: {
-            type: 'down',
-            value: '1.8%'
-          }
-        },
-        {
-          name: '数据稳定性',
-          value: '96.3%',
-          icon: 'checkmark-circle',
-          gradient: 'linear-gradient(135deg, #66BB6A, #388E3C)',
-          trend: {
-            type: 'up',
-            value: '0.5%'
-          }
-        }
-      ],
-      
-      // 农业机械指标
-      machineryMetrics: [
-        {
-          name: '今日运行时长',
-          value: '36.5',
-          unit: '小时',
-          icon: 'clock',
-          gradient: 'linear-gradient(135deg, #42A5F5, #1976D2)'
-        },
-        {
-          name: '今日作业地块',
-          value: '8',
-          unit: '块',
-          icon: 'map',
-          gradient: 'linear-gradient(135deg, #66BB6A, #388E3C)'
-        },
-        {
-          name: '今日执行任务',
-          value: '12',
-          unit: '个',
-          icon: 'calendar',
-          gradient: 'linear-gradient(135deg, #FFA726, #F57C00)'
-        },
-        {
-          name: '使用率',
-          value: '78.2%',
-          icon: 'star',
-          gradient: 'linear-gradient(135deg, #5C6BC0, #3949AB)',
-          trend: {
-            type: 'up',
-            value: '3.7%'
-          }
-        }
-      ],
-      
-      // 当前选择的周期
-      currentPeriod: 'month',
-      
-      // 警报摘要数据
-      alertSummaries: [
-        {
-          title: '设备离线',
-          value: '3 台',
-          iconSrc: '/static/icons/offline.png',
-          description: '最长离线时长:26 小时',
-          type: 'device-offline'
-        },
-        {
-          title: '虫害预警',
-          value: '稻飞虱|小地老虎',
-          iconSrc: '/static/icons/Pest_Alert.png',
-          description: '预警等级:中',
-          type: 'pest-warning'
-        },
-        {
-          title: '气象预警',
-          value: '强风(8级)',
-          iconSrc: '/static/icons/weather_risk.png',
-          description: '预计:未来 12 小时内',
-          type: 'weather-risk'
-        },
-        {
-          title: '作业延迟',
-          value: '5 项',
-          iconSrc: '/static/icons/task_delay.png',
-          description: '最长延迟:3 天',
-          type: 'task-delay'
-        }
-      ],
-      
-      // 农场绩效图表数据 - 月度数据
-      monthlyData: {
-        months: ['1月', '2月', '3月', '4月', '5月', '6月'],
-        thisYearValues: [2.8, 3.4, 2.9, 3.6, 3.8, 3.2],
-        lastYearValues: [2.5, 2.8, 2.4, 3.0, 3.2, 2.7]
-      },
-      
-      // 季度数据
-      quarterlyData: {
-        months: ['Q1', 'Q2', 'Q3', 'Q4'],
-        thisYearValues: [3.2, 3.7, 4.0, 3.5],
-        lastYearValues: [2.7, 3.3, 3.6, 3.0]
-      },
-      
-      // 年度数据
-      yearlyData: {
-        months: ['2019', '2020', '2021', '2022', '2023'],
-        thisYearValues: [2.2, 2.5, 3.0, 3.5, 3.8],
-        lastYearValues: [2.0, 2.3, 2.7, 3.2, 3.4]
-      }
-    };
-  },
-  
-  computed: {
-    // 核心统计数据
-    coreStats() {
-      return [
-        { 
-          label: '地块', 
-          value: this.farmData.plotCount, 
-          icon: 'map',
-        },
-        { 
-          label: '设备', 
-          value: this.farmData.deviceCount, 
-          icon: 'setting',
-        },
-        { 
-          label: '设备在线', 
-          value: this.farmData.deviceOnlineRate + '%', 
-          icon: 'wifi',
-        },
-        { 
-          label: '任务完成', 
-          value: this.farmData.taskCompletionRate + '%', 
-          icon: 'checkmark-circle',
-        }
-      ];
-    },
-    
-    // 根据当前周期计算要显示的数据
-    farmPerformanceData() {
-      if (this.currentPeriod === 'month') {
-        return this.monthlyData;
-      } else if (this.currentPeriod === 'quarter') {
-        return this.quarterlyData;
-      } else {
-        return this.yearlyData;
-      }
-    }
-  },
-  
-  methods: {
-    // 处理切换地块
-    handleSwitchPlot() {
-      uni.showActionSheet({
-        itemList: ['南地块', '西北区域', '稻田', '东区试验田'],
-        success: (res) => {
-          this.userData.selectedPlot = ['南地块', '西北区域', '稻田', '东区试验田'][res.tapIndex];
-        }
-      });
-    },
-
-    // 导航到个人资料
-    navigateToProfile() {
-      uni.navigateTo({
-        url: '/pages/profile/index'
-      });
-    },
-    
-    // 导航到活动详情
-    navigateToActivity(activity) {
-      uni.navigateTo({
-        url: `/pages/activity/detail?id=${activity.id}`
-      });
-    },
-    
-    // 导航到警报详情
-    navigateToAlertDetail(type) {
-      // 实现导航到警报详情页的逻辑
-      console.log(`Navigating to alert detail for type: ${type}`);
-    },
-    
-    // 切换周期
-    changePeriod(period) {
-      this.currentPeriod = period;
-    }
-  },
-  onShow(){
-	const userInfo = storage.getUserInfo()
-	console.log("userInfo",userInfo);  
-	this.userData = {
-		nickname:userInfo.nickName,
-		selectedPlot:"南地块",
-		avatar:userInfo.avatar || '/static/icons/user_icon.png'
-	}
-  },
-  
-  mounted() {
-    // 页面加载完成后的处理
-    // 这里可以添加数据加载、初始化等逻辑
-  }
-};
+	import storage from "@/utils/storage.js";
+	import {
+		listFieldName
+	} from "@/api/services/field.js"
+	export default {
+		data() {
+			return {
+				isLogin:false,
+				show: false,
+				columns: [
+					[]
+				],
+				defaultIndex: [9],
+				// 用户数据
+				userData: {
+					nickname: '',
+					selectedPlot: '',
+					avatar: '',
+				},
+
+				// 农场数据
+				farmData: {
+					plotCount: 5,
+					deviceCount: 12,
+					deviceOnlineRate: 85,
+					taskCompletionRate: 76,
+					recentActivities: [{
+							title: '稻田水稻施肥',
+							date: '2023-05-15',
+							status: 'pending',
+							id: 1,
+							executor: '李四'
+						},
+						{
+							title: '南地块除草',
+							date: '2023-05-12',
+							status: 'completed',
+							id: 2,
+							executor: '王五'
+						},
+						{
+							title: '西北区域杀虫',
+							date: '2023-05-08',
+							status: 'completed',
+							id: 3,
+							executor: '张三'
+						}
+					]
+				},
+
+				// 天气数据
+				weatherData: {
+					temperature: 28,
+					description: '晴朗',
+					humidity: 65,
+					windLevel: 3,
+					rainfall: 0,
+					advice: '今日适宜进行春玉米防虫作业,注意水分管理。'
+				},
+
+				// 作物数据
+				crops: [{
+						name: '水稻',
+						area: 48,
+						progress: 75,
+						icon: '🌾',
+						bgColor: '#4CAF50'
+					},
+					{
+						name: '小麦',
+						area: 36,
+						progress: 60,
+						icon: '🌿',
+						bgColor: '#66BB6A'
+					},
+					{
+						name: '玉米',
+						area: 32,
+						progress: 85,
+						icon: '🌽',
+						bgColor: '#43A047'
+					},
+					{
+						name: '大豆',
+						area: 12,
+						progress: 40,
+						icon: '🫘',
+						bgColor: '#388E3C'
+					}
+				],
+
+				// 监控设备指标
+				deviceMetrics: [{
+						name: '在线设备',
+						value: '28',
+						icon: 'wifi',
+						gradient: 'linear-gradient(135deg, #26A69A, #00796B)',
+						trend: {
+							type: 'up',
+							value: '5.2%'
+						}
+					},
+					{
+						name: '告警设备',
+						value: '3',
+						icon: 'error-circle',
+						gradient: 'linear-gradient(135deg, #FF7043, #E64A19)',
+						trend: {
+							type: 'down',
+							value: '2.1%'
+						}
+					},
+					{
+						name: '离线设备',
+						value: '5',
+						icon: 'close-circle',
+						gradient: 'linear-gradient(135deg, #78909C, #455A64)',
+						trend: {
+							type: 'down',
+							value: '1.8%'
+						}
+					},
+					{
+						name: '数据稳定性',
+						value: '96.3%',
+						icon: 'checkmark-circle',
+						gradient: 'linear-gradient(135deg, #66BB6A, #388E3C)',
+						trend: {
+							type: 'up',
+							value: '0.5%'
+						}
+					}
+				],
+
+				// 农业机械指标
+				machineryMetrics: [{
+						name: '今日运行时长',
+						value: '36.5',
+						unit: '小时',
+						icon: 'clock',
+						gradient: 'linear-gradient(135deg, #42A5F5, #1976D2)'
+					},
+					{
+						name: '今日作业地块',
+						value: '8',
+						unit: '块',
+						icon: 'map',
+						gradient: 'linear-gradient(135deg, #66BB6A, #388E3C)'
+					},
+					{
+						name: '今日执行任务',
+						value: '12',
+						unit: '个',
+						icon: 'calendar',
+						gradient: 'linear-gradient(135deg, #FFA726, #F57C00)'
+					},
+					{
+						name: '使用率',
+						value: '78.2%',
+						icon: 'star',
+						gradient: 'linear-gradient(135deg, #5C6BC0, #3949AB)',
+						trend: {
+							type: 'up',
+							value: '3.7%'
+						}
+					}
+				],
+
+				// 当前选择的周期
+				currentPeriod: 'month',
+
+				// 警报摘要数据
+				alertSummaries: [{
+						title: '设备离线',
+						value: '3 台',
+						iconSrc: '/static/icons/offline.png',
+						description: '最长离线时长:26 小时',
+						type: 'device-offline'
+					},
+					{
+						title: '虫害预警',
+						value: '稻飞虱|小地老虎',
+						iconSrc: '/static/icons/Pest_Alert.png',
+						description: '预警等级:中',
+						type: 'pest-warning'
+					},
+					{
+						title: '气象预警',
+						value: '强风(8级)',
+						iconSrc: '/static/icons/weather_risk.png',
+						description: '预计:未来 12 小时内',
+						type: 'weather-risk'
+					},
+					{
+						title: '作业延迟',
+						value: '5 项',
+						iconSrc: '/static/icons/task_delay.png',
+						description: '最长延迟:3 天',
+						type: 'task-delay'
+					}
+				],
+
+				// 农场绩效图表数据 - 月度数据
+				monthlyData: {
+					months: ['1月', '2月', '3月', '4月', '5月', '6月'],
+					thisYearValues: [2.8, 3.4, 2.9, 3.6, 3.8, 3.2],
+					lastYearValues: [2.5, 2.8, 2.4, 3.0, 3.2, 2.7]
+				},
+
+				// 季度数据
+				quarterlyData: {
+					months: ['Q1', 'Q2', 'Q3', 'Q4'],
+					thisYearValues: [3.2, 3.7, 4.0, 3.5],
+					lastYearValues: [2.7, 3.3, 3.6, 3.0]
+				},
+
+				// 年度数据
+				yearlyData: {
+					months: ['2019', '2020', '2021', '2022', '2023'],
+					thisYearValues: [2.2, 2.5, 3.0, 3.5, 3.8],
+					lastYearValues: [2.0, 2.3, 2.7, 3.2, 3.4]
+				}
+			};
+		},
+
+		computed: {
+			// 核心统计数据
+			coreStats() {
+				return [{
+						label: '地块',
+						value: this.farmData.plotCount,
+						icon: 'map',
+					},
+					{
+						label: '设备',
+						value: this.farmData.deviceCount,
+						icon: 'setting',
+					},
+					{
+						label: '设备在线',
+						value: this.farmData.deviceOnlineRate + '%',
+						icon: 'wifi',
+					},
+					{
+						label: '任务完成',
+						value: this.farmData.taskCompletionRate + '%',
+						icon: 'checkmark-circle',
+					}
+				];
+			},
+
+			// 根据当前周期计算要显示的数据
+			farmPerformanceData() {
+				if (this.currentPeriod === 'month') {
+					return this.monthlyData;
+				} else if (this.currentPeriod === 'quarter') {
+					return this.quarterlyData;
+				} else {
+					return this.yearlyData;
+				}
+			}
+		},
+
+		methods: {
+			loadPhots(userId) {
+				console.log("加载用户字段", userId);
+
+				listFieldName(userId)
+					.then(res => {
+						const {
+							data
+						} = res || {};
+
+						if (data?.code !== 200 || !Array.isArray(data.data)) {
+							console.warn("接口返回异常或数据格式不正确", res);
+							return;
+						}
+
+						const fieldList = data.data;
+
+						const fields = [];
+						fieldList.forEach(item => {
+							const fieldName = item?.fieldName;
+							if (fieldName && !fields.includes(fieldName)) {
+								fields.push({
+									text: fieldName,
+									value: item.id
+								});
+							}
+						});
+
+						this.columns = [fields]; // 符合 u-picker 的二维数组格式
+						storage.setCurrentUserPlotsList(fields)
+
+						console.log("字段加载完成:", this.columns);
+						
+						// 字段加载完成后立即设置默认索引
+						this.setDefaultSelectedPlot();
+					})
+					.catch(err => {
+						console.error("加载字段失败", err);
+					});
+			},
+
+			// 初始化地块列表
+			initPlotColumns() {
+				const cached = storage.getCurrentUserPlotsList();
+				if (Array.isArray(cached) && cached.length > 0) {
+					this.columns = [cached]; // 结构上是二维数组
+					console.log("从缓存加载字段:", this.columns);
+				} else {
+					this.loadPhots(this.userData.userId); // 异步方法内部要处理赋值 columns
+				}
+			},
+
+			// 设置默认选中项
+			setDefaultSelectedPlot() {
+				try {
+					const currentPlots = JSON.parse(storage.getPlots() || '{}');
+					console.log("设置默认地块", currentPlots);
+					
+					if (currentPlots?.name) {
+						this.userData.selectedPlot = currentPlots.name;
+
+						// 防止 this.columns 未定义或格式不正确
+						if (Array.isArray(this.columns) && this.columns.length > 0 && Array.isArray(this.columns[0])) {
+							const plots = this.columns[0];
+							const index = plots.findIndex(item => item.text === currentPlots.name);
+
+							if (index !== -1) {
+								this.defaultIndex = [index];
+								console.log("设置默认选中地块索引:", index, "defaultIndex:", this.defaultIndex);
+							} else {
+								console.warn("未找到匹配的地块:", currentPlots.name);
+								this.defaultIndex = [0]; // 默认选中第一项
+							}
+						} else {
+							console.warn("columns数据格式不正确:", this.columns);
+							this.defaultIndex = [0]; // 默认选中第一项
+						}
+					} else {
+						console.warn("未设置默认地块或地块名为空");
+						this.defaultIndex = [0]; // 默认选中第一项
+					}
+				} catch (error) {
+					console.error("设置默认地块时出错:", error);
+					this.defaultIndex = [0]; // 出错时默认选中第一项
+				}
+			},
+			onConfirm(e) {
+				console.log('选择了:', e);
+				this.userData.selectedPlot = e.value[0].text
+				let obj = {id:e.value[0].value,name:e.value[0].text}
+				storage.setPlots(JSON.stringify(obj))
+				this.show = false;
+			},
+			// 处理切换地块
+			handleSwitchPlot() {
+				// 在显示picker前重新设置defaultIndex
+				this.setDefaultSelectedPlot();
+				this.show = true;
+			},
+
+			// 导航到个人资料
+			navigateToProfile() {
+				uni.navigateTo({
+					url: '/pages/profile/index'
+				});
+			},
+
+			// 导航到活动详情
+			navigateToActivity(activity) {
+				uni.navigateTo({
+					url: `/pages/activity/detail?id=${activity.id}`
+				});
+			},
+
+			// 导航到警报详情
+			navigateToAlertDetail(type) {
+				// 实现导航到警报详情页的逻辑
+				console.log(`Navigating to alert detail for type: ${type}`);
+			},
+
+			// 切换周期
+			changePeriod(period) {
+				this.currentPeriod = period;
+			}
+		},
+		onShow() {
+			const userInfo = storage.getUserInfo()
+			console.log("userInfo", userInfo);
+			this.userData.nickname = userInfo.nickName || '未登录'
+			this.userData.avatar = userInfo.avatar || '/static/icons/user_icon.png'
+			this.userData.userId = userInfo.userId
+			
+			if(storage.getHasLogin()){
+				this.isLogin = true;
+				// 加载当前登录用户所属所有地块信息
+				const cached = storage.getCurrentUserPlotsList();
+				if (Array.isArray(cached) && cached.length > 0) {
+					// 如果有缓存,直接用缓存构造 columns
+					this.columns = [cached];
+					console.log("从缓存加载字段:", this.columns);
+					// 有缓存时,在columns设置后立即设置defaultIndex
+					this.setDefaultSelectedPlot();
+				} else {
+					// 无缓存时请求接口,在接口完成后会调用setDefaultSelectedPlot
+					this.loadPhots(this.userData.userId);
+				}
+				console.log("columns", this.columns);
+			}
+
+		},
+
+		mounted() {
+			
+		}
+	};
 </script>
 
 <style lang="scss" scoped>
-.dashboard-container {
-  padding: 24rpx;
-  background-color: #F6FDF9;
-  min-height: 100vh;
-}
-
-// 顶部用户信息卡片
-.user-info-card {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  background: white;
-  border-radius: 20rpx;
-  padding: 24rpx 28rpx;
-  margin-bottom: 24rpx;
-  box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
-  
-  .user-info {
-    flex: 1;
-    
-    .greeting {
-      font-size: 32rpx;
-      font-weight: 600;
-      color: #2C3E50;
-      margin-bottom: 8rpx;
-    }
-    
-    .plot-info {
-      display: flex;
-      align-items: center;
-      
-      .plot-label {
-        font-size: 24rpx;
-        color: #8C9396;
-      }
-      
-      .plot-name {
-        font-size: 24rpx;
-        color: #2C3E50;
-        margin: 0 8rpx;
-      }
-      
-      .switch-plot-btn {
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        background: linear-gradient(135deg, #3BB44A, #66CC6A);
-        border-radius: 16rpx;
-        padding: 4rpx 16rpx;
-        box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
-        
-        text {
-          font-size: 22rpx;
-          color: white;
-        }
-      }
-    }
-  }
-  
-  .avatar-container {
-    .avatar {
-      width: 80rpx;
-      height: 80rpx;
-      border-radius: 50%;
-      background-color: rgba(59, 180, 74, 0.1);
-      border: 2px solid rgba(255, 255, 255, 0.8);
-      box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
-      overflow: hidden;
-      
-      image {
-        width: 100%;
-        height: 100%;
-        object-fit: cover;
-      }
-    }
-  }
-}
-
-// Top Stats Overview
-.stats-overview {
-  display: grid;
-  grid-template-columns: repeat(2, 1fr);
-  gap: 16rpx;
-  margin-bottom: 24rpx;
-  
-  .alert-card {
-    background: white;
-    border-radius: 16rpx;
-    padding: 20rpx;
-    display: flex;
-    flex-direction: column;
-    box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
-    position: relative;
-    overflow: hidden;
-    transition: transform 0.2s, box-shadow 0.2s;
-    
-    &:active {
-      transform: translateY(2rpx);
-      box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
-    }
-    
-    &::after {
-      content: "";
-      position: absolute;
-      right: 12rpx;
-      bottom: 12rpx;
-      width: 16rpx;
-      height: 16rpx;
-      border-top: 2rpx solid #E0E0E0;
-      border-right: 2rpx solid #E0E0E0;
-      transform: rotate(45deg);
-      opacity: 0.5;
-    }
-    
-    .alert-header {
-      display: flex;
-      align-items: center;
-      margin-bottom: 12rpx;
-      
-      .alert-icon-container {
-        margin-right: 12rpx;
-        width: 40rpx;
-        height: 40rpx;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        
-        .custom-icon {
-          width: 40rpx;
-          height: 40rpx;
-          object-fit: contain;
-        }
-      }
-      
-      .alert-title {
-        font-size: 26rpx;
-        color: #333333;
-        font-weight: 600;
-      }
-    }
-    
-    .alert-value {
-      font-size: 30rpx;
-      font-weight: 700;
-      color: #3BB44A;
-      margin-bottom: 6rpx;
-      line-height: 1.2;
-      padding-left: 52rpx;
-    }
-    
-    .alert-description {
-      font-size: 22rpx;
-      color: #757575;
-      padding-left: 52rpx;
-    }
-  }
-}
-
-// Card Base Styles
-.card {
-  background: white;
-  border-radius: 20rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04), 0 1rpx 4rpx rgba(0, 0, 0, 0.02);
-  margin-bottom: 24rpx;
-  overflow: hidden;
-  
-  .card-header {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 24rpx 28rpx;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.03);
-    
-    .title-section {
-      display: flex;
-      align-items: center;
-      
-      .title-line {
-        width: 4rpx;
-        height: 28rpx;
-        border-radius: 0;
-        background: linear-gradient(180deg, #3BB44A, #66CC6A);
-        margin-right: 16rpx;
-      }
-      
-      .card-title {
-        font-size: 28rpx;
-        font-weight: 600;
-        color: #2C3E50;
-      }
-    }
-    
-    .period-selector {
-      display: flex;
-      background: rgba(0, 0, 0, 0.03);
-      border-radius: 20rpx;
-      padding: 2rpx;
-      
-      .period {
-        font-size: 22rpx;
-        color: #8C9396;
-        padding: 8rpx 16rpx;
-        border-radius: 16rpx;
-        
-        &.active {
-          background: white;
-          color: #3BB44A;
-          font-weight: 500;
-          box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
-        }
-      }
-    }
-    
-    .action-button {
-      display: flex;
-      align-items: center;
-      background: linear-gradient(135deg, #3BB44A, #66CC6A);
-      border-radius: 24rpx;
-      padding: 8rpx 16rpx;
-      box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
-      
-      text {
-        font-size: 22rpx;
-        color: white;
-        margin-right: 6rpx;
-      }
-    }
-  }
-}
-
-// Farm Performance Card
-.farm-performance {
-  .performance-stats {
-    display: flex;
-    padding: 24rpx 28rpx;
-    
-    .metric-column {
-      flex: 1;
-      display: flex;
-      flex-direction: column;
-      
-      .metric-value {
-        font-size: 48rpx;
-        font-weight: 700;
-        color: #2C3E50;
-        margin-bottom: 6rpx;
-        line-height: 1.1;
-        
-        .metric-unit {
-          font-size: 24rpx;
-          font-weight: 500;
-          color: #8C9396;
-          margin-left: 4rpx;
-        }
-      }
-      
-      .metric-label {
-        font-size: 24rpx;
-        color: #8C9396;
-        margin-bottom: 12rpx;
-      }
-      
-      .growth {
-        display: flex;
-        align-items: center;
-        font-size: 22rpx;
-        
-        &.positive {
-          color: #3BB44A;
-        }
-        
-        &.negative {
-          color: #FF5252;
-        }
-      }
-    }
-    
-    .metric-divider {
-      width: 1px;
-      background: rgba(0, 0, 0, 0.04);
-      margin: 0 28rpx;
-    }
-  }
-  
-  .chart-container {
-    padding: 24rpx 28rpx;
-    
-    .chart-header {
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      margin-bottom: 16rpx;
-      
-      .chart-title {
-        font-size: 28rpx;
-        font-weight: 600;
-        color: #2C3E50;
-      }
-      
-      .chart-legend {
-        display: flex;
-        align-items: center;
-        
-        .legend-item {
-          display: flex;
-          align-items: center;
-          margin-left: 16rpx;
-          
-          .legend-color {
-            width: 16rpx;
-            height: 16rpx;
-            border-radius: 4rpx;
-            margin-right: 8rpx;
-          }
-          
-          text {
-            font-size: 22rpx;
-            color: #8C9396;
-          }
-        }
-      }
-    }
-    
-    .chart-body {
-      display: flex;
-      height: 240rpx;
-      position: relative;
-      margin-top: 12rpx;
-      
-      .y-axis {
-        width: 40rpx;
-        height: 200rpx;
-        display: flex;
-        flex-direction: column;
-        justify-content: space-between;
-        text-align: right;
-        padding-right: 16rpx;
-        margin-bottom: 30rpx;
-        
-        text {
-          font-size: 22rpx;
-          color: #8C9396;
-        }
-      }
-      
-      .bars-container {
-        flex: 1;
-        display: flex;
-        justify-content: space-around;
-        height: 200rpx;
-        position: relative;
-        
-        &::before {
-          content: "";
-          position: absolute;
-          left: 0;
-          bottom: 0;
-          width: 100%;
-          height: 1px;
-          background-color: #E0E0E0;
-        }
-        
-        .month-group {
-          display: flex;
-          flex-direction: column;
-          align-items: center;
-          width: 14%;
-          
-          .bar-wrapper {
-            height: 200rpx;
-            width: 70%;
-            display: flex;
-            justify-content: center;
-            position: relative;
-            
-            .bar {
-              position: absolute;
-              bottom: 0;
-              width: 45%;
-              border-radius: 4rpx 4rpx 0 0;
-              transition: height 0.3s ease;
-            }
-            
-            .last-year {
-              left: 0;
-              background: #E0E0E0;
-            }
-            
-            .this-year {
-              right: 0;
-              background: #3BB44A;
-            }
-          }
-          
-          .month-label {
-            font-size: 22rpx;
-            color: #8C9396;
-            margin-top: 8rpx;
-          }
-        }
-      }
-    }
-  }
-}
-
-// Crop Portfolio
-.crop-portfolio {
-  .crop-grid {
-    padding: 16rpx;
-    display: grid;
-    grid-template-columns: repeat(2, 1fr);
-    gap: 16rpx;
-  }
-  
-  .crop-item {
-    padding: 16rpx;
-    background: rgba(0, 0, 0, 0.01);
-    border-radius: 16rpx;
-    display: flex;
-    align-items: center;
-    
-    .crop-icon {
-      width: 48rpx;
-      height: 48rpx;
-      border-radius: 12rpx;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      margin-right: 16rpx;
-      
-      .crop-icon-text {
-        font-size: 24rpx;
-      }
-    }
-    
-    .crop-details {
-      flex: 1;
-      
-      .crop-info {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        margin-bottom: 10rpx;
-        
-        .crop-name {
-          font-size: 26rpx;
-          font-weight: 600;
-          color: #2C3E50;
-        }
-        
-        .crop-area {
-          font-size: 22rpx;
-          color: #8C9396;
-        }
-      }
-      
-      .crop-stats {
-        display: flex;
-        align-items: center;
-        
-        .crop-progress-container {
-          flex: 1;
-          height: 8rpx;
-          background: rgba(0, 0, 0, 0.03);
-          border-radius: 4rpx;
-          margin-right: 12rpx;
-          overflow: hidden;
-          
-          .crop-progress {
-            height: 100%;
-            border-radius: 4rpx;
-          }
-        }
-        
-        .crop-progress-text {
-          font-size: 22rpx;
-          color: #8C9396;
-          min-width: 36rpx;
-          text-align: right;
-        }
-      }
-    }
-  }
-}
-
-// Resource Efficiency
-.resource-efficiency {
-  .resource-grid {
-    padding: 16rpx;
-    display: grid;
-    grid-template-columns: repeat(2, 1fr);
-    gap: 16rpx;
-  }
-  
-  .resource-item {
-    padding: 16rpx;
-    background: rgba(0, 0, 0, 0.01);
-    border-radius: 16rpx;
-    
-    .resource-header {
-      display: flex;
-      align-items: center;
-      margin-bottom: 12rpx;
-      
-      .icon-tile {
-        width: 28rpx;
-        height: 28rpx;
-        border-radius: 6rpx;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        margin-right: 10rpx;
-        
-        &.small {
-          width: 28rpx;
-          height: 28rpx;
-        }
-      }
-      
-      .resource-name {
-        font-size: 24rpx;
-        color: #8C9396;
-      }
-    }
-    
-    .resource-value {
-      margin-bottom: 12rpx;
-      
-      .value {
-        font-size: 36rpx;
-        font-weight: 700;
-        color: #2C3E50;
-      }
-      
-      .unit {
-        font-size: 22rpx;
-        color: #8C9396;
-        margin-left: 4rpx;
-      }
-    }
-    
-    .resource-progress-container {
-      .resource-progress-bg {
-        height: 8rpx;
-        background: rgba(0, 0, 0, 0.03);
-        border-radius: 4rpx;
-        margin-bottom: 8rpx;
-        overflow: hidden;
-        
-        .resource-progress {
-          height: 100%;
-          border-radius: 4rpx;
-        }
-      }
-      
-      .resource-efficiency {
-        font-size: 22rpx;
-        color: #8C9396;
-      }
-    }
-  }
-}
-
-// Farm Activities
-.farm-activities {
-  .activities-list {
-    padding: 8rpx 0;
-  }
-  
-  .activity-item {
-    display: flex;
-    align-items: center;
-    padding: 20rpx 28rpx;
-    border-bottom: 1px solid rgba(0, 0, 0, 0.02);
-    
-    &:last-child {
-      border-bottom: none;
-    }
-    
-    .activity-dot {
-      width: 10rpx;
-      height: 10rpx;
-      border-radius: 50%;
-      margin-right: 16rpx;
-      flex-shrink: 0;
-      
-      &.completed {
-        background: #4CAF50;
-      }
-      
-      &.pending {
-        background: #FFC107;
-      }
-      
-      &.failed {
-        background: #FF5252;
-      }
-    }
-    
-    .activity-details {
-      flex: 1;
-      
-      .activity-title-row {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        margin-bottom: 6rpx;
-        
-        .activity-title {
-          font-size: 26rpx;
-          font-weight: 500;
-          color: #2C3E50;
-        }
-        
-        .activity-date {
-          font-size: 22rpx;
-          color: #8C9396;
-        }
-      }
-      
-      .activity-meta-row {
-        display: flex;
-        justify-content: space-between;
-        align-items: center;
-        
-        .activity-executor {
-          font-size: 22rpx;
-          color: #8C9396;
-        }
-        
-        .activity-action {
-          display: flex;
-          align-items: center;
-          
-          .action-text {
-            font-size: 22rpx;
-            color: #3BB44A;
-            margin-right: 6rpx;
-          }
-        }
-      }
-    }
-  }
-}
-
-// Weather Card
-.weather-card {
-  .weather-content {
-    padding: 20rpx 28rpx;
-  }
-  
-  .current-weather {
-    display: flex;
-    align-items: center;
-    margin-bottom: 24rpx;
-    
-    .weather-icon-container {
-      margin-right: 20rpx;
-      
-      .weather-icon {
-        font-size: 60rpx;
-        line-height: 1;
-      }
-    }
-    
-    .weather-details {
-      .weather-temp {
-        font-size: 48rpx;
-        font-weight: 700;
-        color: #2C3E50;
-        line-height: 1.1;
-      }
-      
-      .weather-desc {
-        font-size: 26rpx;
-        color: #8C9396;
-      }
-    }
-  }
-  
-  .weather-metrics {
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    padding: 16rpx 24rpx;
-    margin-bottom: 24rpx;
-    border-radius: 12rpx;
-    background-color: rgba(247, 247, 247, 0.5);
-    border: 1px solid rgba(0, 0, 0, 0.03);
-    
-    .weather-metric {
-      flex: 1;
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      
-      .metric-label {
-        font-size: 22rpx;
-        color: #8C9396;
-        margin-bottom: 6rpx;
-      }
-      
-      .metric-value {
-        font-size: 28rpx;
-        font-weight: 600;
-        color: #2C3E50;
-      }
-    }
-    
-    .vertical-divider {
-      width: 1px;
-      height: 36rpx;
-      background-color: rgba(0, 0, 0, 0.05);
-    }
-  }
-  
-  .weather-advice {
-    background: rgba(59, 180, 74, 0.05);
-    border-radius: 16rpx;
-    padding: 16rpx;
-    
-    .advice-header {
-      display: flex;
-      align-items: center;
-      margin-bottom: 10rpx;
-      
-      .icon-tile {
-        width: 24rpx;
-        height: 24rpx;
-        border-radius: 50%;
-        background: linear-gradient(135deg, #3BB44A, #66CC6A);
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        margin-right: 10rpx;
-        box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
-        
-        &.small {
-          width: 24rpx;
-          height: 24rpx;
-        }
-      }
-      
-      .advice-title {
-        font-size: 24rpx;
-        font-weight: 600;
-        color: #2C3E50;
-      }
-    }
-    
-    .advice-content {
-      font-size: 24rpx;
-      color: #2C3E50;
-      line-height: 1.4;
-    }
-  }
-}
-
-// For small screens - mobile responsiveness
-@media screen and (max-width: 768px) {
-  .stats-overview {
-    grid-template-columns: repeat(2, 1fr);
-  }
-  
-  .crop-grid {
-    grid-template-columns: 1fr;
-  }
-}
-
-// 监控设备概览
-.device-overview {
-  .device-metrics-grid {
-    display: grid;
-    grid-template-columns: repeat(2, 1fr);
-    gap: 16rpx;
-    padding: 16rpx;
-  }
-  
-  .device-metric-card {
-    background: white;
-    border-radius: 16rpx;
-    padding: 20rpx;
-    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
-    border: 1px solid rgba(0, 0, 0, 0.02);
-    
-    .device-metric-header {
-      display: flex;
-      align-items: center;
-      margin-bottom: 16rpx;
-      
-      .icon-tile {
-        width: 24rpx;
-        height: 24rpx;
-        border-radius: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        margin-right: 10rpx;
-        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
-      }
-      
-      .metric-name {
-        font-size: 24rpx;
-        color: #8C9396;
-      }
-    }
-    
-    .device-metric-value {
-      font-size: 44rpx;
-      font-weight: 700;
-      color: #2C3E50;
-      margin-bottom: 12rpx;
-    }
-    
-    .device-metric-trend {
-      display: flex;
-      align-items: center;
-      font-size: 22rpx;
-      
-      &.up {
-        color: #3BB44A;
-      }
-      
-      &.down {
-        color: #FF5252;
-      }
-      
-      text {
-        margin-left: 4rpx;
-      }
-    }
-  }
-}
-
-// 农业机械活动
-.machinery-activity {
-  .machinery-metrics-grid {
-    display: grid;
-    grid-template-columns: repeat(2, 1fr);
-    gap: 16rpx;
-    padding: 16rpx;
-  }
-  
-  .machinery-metric-card {
-    background: white;
-    border-radius: 16rpx;
-    padding: 20rpx;
-    box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
-    border: 1px solid rgba(0, 0, 0, 0.02);
-    
-    .machinery-metric-header {
-      display: flex;
-      align-items: center;
-      margin-bottom: 16rpx;
-      
-      .icon-tile {
-        width: 24rpx;
-        height: 24rpx;
-        border-radius: 50%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        margin-right: 10rpx;
-        box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
-      }
-      
-      .metric-name {
-        font-size: 24rpx;
-        color: #8C9396;
-      }
-    }
-    
-    .machinery-metric-value {
-      font-size: 44rpx;
-      font-weight: 700;
-      color: #2C3E50;
-      margin-bottom: 12rpx;
-    }
-    
-    .machinery-metric-trend {
-      display: flex;
-      align-items: center;
-      font-size: 22rpx;
-      
-      &.up {
-        color: #3BB44A;
-      }
-      
-      &.down {
-        color: #FF5252;
-      }
-      
-      text {
-        margin-left: 4rpx;
-      }
-    }
-    
-    .machinery-metric-unit {
-      font-size: 22rpx;
-      color: #8C9396;
-    }
-  }
-}
-</style>
+	.dashboard-container {
+		padding: 24rpx;
+		background-color: #F6FDF9;
+		min-height: 100vh;
+	}
+
+	// 顶部用户信息卡片
+	.user-info-card {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		background: white;
+		border-radius: 20rpx;
+		padding: 24rpx 28rpx;
+		margin-bottom: 24rpx;
+		box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.04);
+
+		.user-info {
+			flex: 1;
+
+			.greeting {
+				font-size: 32rpx;
+				font-weight: 600;
+				color: #2C3E50;
+				margin-bottom: 8rpx;
+			}
+
+			.plot-info {
+				display: flex;
+				align-items: center;
+
+				.plot-label {
+					font-size: 24rpx;
+					color: #8C9396;
+				}
+
+				.plot-name {
+					font-size: 24rpx;
+					color: #2C3E50;
+					margin: 0 8rpx;
+				}
+
+				.switch-plot-btn {
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					background: linear-gradient(135deg, #3BB44A, #66CC6A);
+					border-radius: 16rpx;
+					padding: 4rpx 16rpx;
+					box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
+
+					text {
+						font-size: 22rpx;
+						color: white;
+					}
+				}
+			}
+		}
+
+		.avatar-container {
+			.avatar {
+				width: 80rpx;
+				height: 80rpx;
+				border-radius: 50%;
+				background-color: rgba(59, 180, 74, 0.1);
+				border: 2px solid rgba(255, 255, 255, 0.8);
+				box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+				overflow: hidden;
+
+				image {
+					width: 100%;
+					height: 100%;
+					object-fit: cover;
+				}
+			}
+		}
+	}
+
+	// Top Stats Overview
+	.stats-overview {
+		display: grid;
+		grid-template-columns: repeat(2, 1fr);
+		gap: 16rpx;
+		margin-bottom: 24rpx;
+
+		.alert-card {
+			background: white;
+			border-radius: 16rpx;
+			padding: 20rpx;
+			display: flex;
+			flex-direction: column;
+			box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05);
+			position: relative;
+			overflow: hidden;
+			transition: transform 0.2s, box-shadow 0.2s;
+
+			&:active {
+				transform: translateY(2rpx);
+				box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
+			}
+
+			&::after {
+				content: "";
+				position: absolute;
+				right: 12rpx;
+				bottom: 12rpx;
+				width: 16rpx;
+				height: 16rpx;
+				border-top: 2rpx solid #E0E0E0;
+				border-right: 2rpx solid #E0E0E0;
+				transform: rotate(45deg);
+				opacity: 0.5;
+			}
+
+			.alert-header {
+				display: flex;
+				align-items: center;
+				margin-bottom: 12rpx;
+
+				.alert-icon-container {
+					margin-right: 12rpx;
+					width: 40rpx;
+					height: 40rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+
+					.custom-icon {
+						width: 40rpx;
+						height: 40rpx;
+						object-fit: contain;
+					}
+				}
+
+				.alert-title {
+					font-size: 26rpx;
+					color: #333333;
+					font-weight: 600;
+				}
+			}
+
+			.alert-value {
+				font-size: 30rpx;
+				font-weight: 700;
+				color: #3BB44A;
+				margin-bottom: 6rpx;
+				line-height: 1.2;
+				padding-left: 52rpx;
+			}
+
+			.alert-description {
+				font-size: 22rpx;
+				color: #757575;
+				padding-left: 52rpx;
+			}
+		}
+	}
+
+	// Card Base Styles
+	.card {
+		background: white;
+		border-radius: 20rpx;
+		box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.04), 0 1rpx 4rpx rgba(0, 0, 0, 0.02);
+		margin-bottom: 24rpx;
+		overflow: hidden;
+
+		.card-header {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding: 24rpx 28rpx;
+			border-bottom: 1px solid rgba(0, 0, 0, 0.03);
+
+			.title-section {
+				display: flex;
+				align-items: center;
+
+				.title-line {
+					width: 4rpx;
+					height: 28rpx;
+					border-radius: 0;
+					background: linear-gradient(180deg, #3BB44A, #66CC6A);
+					margin-right: 16rpx;
+				}
+
+				.card-title {
+					font-size: 28rpx;
+					font-weight: 600;
+					color: #2C3E50;
+				}
+			}
+
+			.period-selector {
+				display: flex;
+				background: rgba(0, 0, 0, 0.03);
+				border-radius: 20rpx;
+				padding: 2rpx;
+
+				.period {
+					font-size: 22rpx;
+					color: #8C9396;
+					padding: 8rpx 16rpx;
+					border-radius: 16rpx;
+
+					&.active {
+						background: white;
+						color: #3BB44A;
+						font-weight: 500;
+						box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
+					}
+				}
+			}
+
+			.action-button {
+				display: flex;
+				align-items: center;
+				background: linear-gradient(135deg, #3BB44A, #66CC6A);
+				border-radius: 24rpx;
+				padding: 8rpx 16rpx;
+				box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
+
+				text {
+					font-size: 22rpx;
+					color: white;
+					margin-right: 6rpx;
+				}
+			}
+		}
+	}
+
+	// Farm Performance Card
+	.farm-performance {
+		.performance-stats {
+			display: flex;
+			padding: 24rpx 28rpx;
+
+			.metric-column {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+
+				.metric-value {
+					font-size: 48rpx;
+					font-weight: 700;
+					color: #2C3E50;
+					margin-bottom: 6rpx;
+					line-height: 1.1;
+
+					.metric-unit {
+						font-size: 24rpx;
+						font-weight: 500;
+						color: #8C9396;
+						margin-left: 4rpx;
+					}
+				}
+
+				.metric-label {
+					font-size: 24rpx;
+					color: #8C9396;
+					margin-bottom: 12rpx;
+				}
+
+				.growth {
+					display: flex;
+					align-items: center;
+					font-size: 22rpx;
+
+					&.positive {
+						color: #3BB44A;
+					}
+
+					&.negative {
+						color: #FF5252;
+					}
+				}
+			}
+
+			.metric-divider {
+				width: 1px;
+				background: rgba(0, 0, 0, 0.04);
+				margin: 0 28rpx;
+			}
+		}
+
+		.chart-container {
+			padding: 24rpx 28rpx;
+
+			.chart-header {
+				display: flex;
+				justify-content: space-between;
+				align-items: center;
+				margin-bottom: 16rpx;
+
+				.chart-title {
+					font-size: 28rpx;
+					font-weight: 600;
+					color: #2C3E50;
+				}
+
+				.chart-legend {
+					display: flex;
+					align-items: center;
+
+					.legend-item {
+						display: flex;
+						align-items: center;
+						margin-left: 16rpx;
+
+						.legend-color {
+							width: 16rpx;
+							height: 16rpx;
+							border-radius: 4rpx;
+							margin-right: 8rpx;
+						}
+
+						text {
+							font-size: 22rpx;
+							color: #8C9396;
+						}
+					}
+				}
+			}
+
+			.chart-body {
+				display: flex;
+				height: 240rpx;
+				position: relative;
+				margin-top: 12rpx;
+
+				.y-axis {
+					width: 40rpx;
+					height: 200rpx;
+					display: flex;
+					flex-direction: column;
+					justify-content: space-between;
+					text-align: right;
+					padding-right: 16rpx;
+					margin-bottom: 30rpx;
+
+					text {
+						font-size: 22rpx;
+						color: #8C9396;
+					}
+				}
+
+				.bars-container {
+					flex: 1;
+					display: flex;
+					justify-content: space-around;
+					height: 200rpx;
+					position: relative;
+
+					&::before {
+						content: "";
+						position: absolute;
+						left: 0;
+						bottom: 0;
+						width: 100%;
+						height: 1px;
+						background-color: #E0E0E0;
+					}
 
+					.month-group {
+						display: flex;
+						flex-direction: column;
+						align-items: center;
+						width: 14%;
+
+						.bar-wrapper {
+							height: 200rpx;
+							width: 70%;
+							display: flex;
+							justify-content: center;
+							position: relative;
+
+							.bar {
+								position: absolute;
+								bottom: 0;
+								width: 45%;
+								border-radius: 4rpx 4rpx 0 0;
+								transition: height 0.3s ease;
+							}
+
+							.last-year {
+								left: 0;
+								background: #E0E0E0;
+							}
+
+							.this-year {
+								right: 0;
+								background: #3BB44A;
+							}
+						}
+
+						.month-label {
+							font-size: 22rpx;
+							color: #8C9396;
+							margin-top: 8rpx;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Crop Portfolio
+	.crop-portfolio {
+		.crop-grid {
+			padding: 16rpx;
+			display: grid;
+			grid-template-columns: repeat(2, 1fr);
+			gap: 16rpx;
+		}
+
+		.crop-item {
+			padding: 16rpx;
+			background: rgba(0, 0, 0, 0.01);
+			border-radius: 16rpx;
+			display: flex;
+			align-items: center;
+
+			.crop-icon {
+				width: 48rpx;
+				height: 48rpx;
+				border-radius: 12rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				margin-right: 16rpx;
+
+				.crop-icon-text {
+					font-size: 24rpx;
+				}
+			}
+
+			.crop-details {
+				flex: 1;
+
+				.crop-info {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					margin-bottom: 10rpx;
+
+					.crop-name {
+						font-size: 26rpx;
+						font-weight: 600;
+						color: #2C3E50;
+					}
+
+					.crop-area {
+						font-size: 22rpx;
+						color: #8C9396;
+					}
+				}
+
+				.crop-stats {
+					display: flex;
+					align-items: center;
+
+					.crop-progress-container {
+						flex: 1;
+						height: 8rpx;
+						background: rgba(0, 0, 0, 0.03);
+						border-radius: 4rpx;
+						margin-right: 12rpx;
+						overflow: hidden;
+
+						.crop-progress {
+							height: 100%;
+							border-radius: 4rpx;
+						}
+					}
+
+					.crop-progress-text {
+						font-size: 22rpx;
+						color: #8C9396;
+						min-width: 36rpx;
+						text-align: right;
+					}
+				}
+			}
+		}
+	}
+
+	// Resource Efficiency
+	.resource-efficiency {
+		.resource-grid {
+			padding: 16rpx;
+			display: grid;
+			grid-template-columns: repeat(2, 1fr);
+			gap: 16rpx;
+		}
+
+		.resource-item {
+			padding: 16rpx;
+			background: rgba(0, 0, 0, 0.01);
+			border-radius: 16rpx;
+
+			.resource-header {
+				display: flex;
+				align-items: center;
+				margin-bottom: 12rpx;
+
+				.icon-tile {
+					width: 28rpx;
+					height: 28rpx;
+					border-radius: 6rpx;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					margin-right: 10rpx;
+
+					&.small {
+						width: 28rpx;
+						height: 28rpx;
+					}
+				}
+
+				.resource-name {
+					font-size: 24rpx;
+					color: #8C9396;
+				}
+			}
+
+			.resource-value {
+				margin-bottom: 12rpx;
+
+				.value {
+					font-size: 36rpx;
+					font-weight: 700;
+					color: #2C3E50;
+				}
+
+				.unit {
+					font-size: 22rpx;
+					color: #8C9396;
+					margin-left: 4rpx;
+				}
+			}
+
+			.resource-progress-container {
+				.resource-progress-bg {
+					height: 8rpx;
+					background: rgba(0, 0, 0, 0.03);
+					border-radius: 4rpx;
+					margin-bottom: 8rpx;
+					overflow: hidden;
+
+					.resource-progress {
+						height: 100%;
+						border-radius: 4rpx;
+					}
+				}
+
+				.resource-efficiency {
+					font-size: 22rpx;
+					color: #8C9396;
+				}
+			}
+		}
+	}
+
+	// Farm Activities
+	.farm-activities {
+		.activities-list {
+			padding: 8rpx 0;
+		}
+
+		.activity-item {
+			display: flex;
+			align-items: center;
+			padding: 20rpx 28rpx;
+			border-bottom: 1px solid rgba(0, 0, 0, 0.02);
+
+			&:last-child {
+				border-bottom: none;
+			}
+
+			.activity-dot {
+				width: 10rpx;
+				height: 10rpx;
+				border-radius: 50%;
+				margin-right: 16rpx;
+				flex-shrink: 0;
+
+				&.completed {
+					background: #4CAF50;
+				}
+
+				&.pending {
+					background: #FFC107;
+				}
+
+				&.failed {
+					background: #FF5252;
+				}
+			}
+
+			.activity-details {
+				flex: 1;
+
+				.activity-title-row {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+					margin-bottom: 6rpx;
+
+					.activity-title {
+						font-size: 26rpx;
+						font-weight: 500;
+						color: #2C3E50;
+					}
+
+					.activity-date {
+						font-size: 22rpx;
+						color: #8C9396;
+					}
+				}
+
+				.activity-meta-row {
+					display: flex;
+					justify-content: space-between;
+					align-items: center;
+
+					.activity-executor {
+						font-size: 22rpx;
+						color: #8C9396;
+					}
+
+					.activity-action {
+						display: flex;
+						align-items: center;
+
+						.action-text {
+							font-size: 22rpx;
+							color: #3BB44A;
+							margin-right: 6rpx;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	// Weather Card
+	.weather-card {
+		.weather-content {
+			padding: 20rpx 28rpx;
+		}
+
+		.current-weather {
+			display: flex;
+			align-items: center;
+			margin-bottom: 24rpx;
+
+			.weather-icon-container {
+				margin-right: 20rpx;
+
+				.weather-icon {
+					font-size: 60rpx;
+					line-height: 1;
+				}
+			}
+
+			.weather-details {
+				.weather-temp {
+					font-size: 48rpx;
+					font-weight: 700;
+					color: #2C3E50;
+					line-height: 1.1;
+				}
+
+				.weather-desc {
+					font-size: 26rpx;
+					color: #8C9396;
+				}
+			}
+		}
+
+		.weather-metrics {
+			display: flex;
+			justify-content: space-between;
+			align-items: center;
+			padding: 16rpx 24rpx;
+			margin-bottom: 24rpx;
+			border-radius: 12rpx;
+			background-color: rgba(247, 247, 247, 0.5);
+			border: 1px solid rgba(0, 0, 0, 0.03);
+
+			.weather-metric {
+				flex: 1;
+				display: flex;
+				flex-direction: column;
+				align-items: center;
+
+				.metric-label {
+					font-size: 22rpx;
+					color: #8C9396;
+					margin-bottom: 6rpx;
+				}
+
+				.metric-value {
+					font-size: 28rpx;
+					font-weight: 600;
+					color: #2C3E50;
+				}
+			}
+
+			.vertical-divider {
+				width: 1px;
+				height: 36rpx;
+				background-color: rgba(0, 0, 0, 0.05);
+			}
+		}
+
+		.weather-advice {
+			background: rgba(59, 180, 74, 0.05);
+			border-radius: 16rpx;
+			padding: 16rpx;
+
+			.advice-header {
+				display: flex;
+				align-items: center;
+				margin-bottom: 10rpx;
+
+				.icon-tile {
+					width: 24rpx;
+					height: 24rpx;
+					border-radius: 50%;
+					background: linear-gradient(135deg, #3BB44A, #66CC6A);
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					margin-right: 10rpx;
+					box-shadow: 0 2rpx 8rpx rgba(59, 180, 74, 0.25);
+
+					&.small {
+						width: 24rpx;
+						height: 24rpx;
+					}
+				}
+
+				.advice-title {
+					font-size: 24rpx;
+					font-weight: 600;
+					color: #2C3E50;
+				}
+			}
+
+			.advice-content {
+				font-size: 24rpx;
+				color: #2C3E50;
+				line-height: 1.4;
+			}
+		}
+	}
+
+	// For small screens - mobile responsiveness
+	@media screen and (max-width: 768px) {
+		.stats-overview {
+			grid-template-columns: repeat(2, 1fr);
+		}
+
+		.crop-grid {
+			grid-template-columns: 1fr;
+		}
+	}
+
+	// 监控设备概览
+	.device-overview {
+		.device-metrics-grid {
+			display: grid;
+			grid-template-columns: repeat(2, 1fr);
+			gap: 16rpx;
+			padding: 16rpx;
+		}
+
+		.device-metric-card {
+			background: white;
+			border-radius: 16rpx;
+			padding: 20rpx;
+			box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
+			border: 1px solid rgba(0, 0, 0, 0.02);
+
+			.device-metric-header {
+				display: flex;
+				align-items: center;
+				margin-bottom: 16rpx;
+
+				.icon-tile {
+					width: 24rpx;
+					height: 24rpx;
+					border-radius: 50%;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					margin-right: 10rpx;
+					box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+				}
+
+				.metric-name {
+					font-size: 24rpx;
+					color: #8C9396;
+				}
+			}
+
+			.device-metric-value {
+				font-size: 44rpx;
+				font-weight: 700;
+				color: #2C3E50;
+				margin-bottom: 12rpx;
+			}
+
+			.device-metric-trend {
+				display: flex;
+				align-items: center;
+				font-size: 22rpx;
+
+				&.up {
+					color: #3BB44A;
+				}
+
+				&.down {
+					color: #FF5252;
+				}
+
+				text {
+					margin-left: 4rpx;
+				}
+			}
+		}
+	}
+
+	// 农业机械活动
+	.machinery-activity {
+		.machinery-metrics-grid {
+			display: grid;
+			grid-template-columns: repeat(2, 1fr);
+			gap: 16rpx;
+			padding: 16rpx;
+		}
+
+		.machinery-metric-card {
+			background: white;
+			border-radius: 16rpx;
+			padding: 20rpx;
+			box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.03);
+			border: 1px solid rgba(0, 0, 0, 0.02);
+
+			.machinery-metric-header {
+				display: flex;
+				align-items: center;
+				margin-bottom: 16rpx;
+
+				.icon-tile {
+					width: 24rpx;
+					height: 24rpx;
+					border-radius: 50%;
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					margin-right: 10rpx;
+					box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.08);
+				}
+
+				.metric-name {
+					font-size: 24rpx;
+					color: #8C9396;
+				}
+			}
+
+			.machinery-metric-value {
+				font-size: 44rpx;
+				font-weight: 700;
+				color: #2C3E50;
+				margin-bottom: 12rpx;
+			}
+
+			.machinery-metric-trend {
+				display: flex;
+				align-items: center;
+				font-size: 22rpx;
+
+				&.up {
+					color: #3BB44A;
+				}
+
+				&.down {
+					color: #FF5252;
+				}
+
+				text {
+					margin-left: 4rpx;
+				}
+			}
+
+			.machinery-metric-unit {
+				font-size: 22rpx;
+				color: #8C9396;
+			}
+		}
+	}
+</style>

+ 1051 - 0
pages/device-list/detail-camera.vue

@@ -0,0 +1,1051 @@
+<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>
+      
+      <view class="device-meta-row">
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <image src="/static/icons/device_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
+          </view>
+          <text class="meta-label">设备编号:</text>
+          <text class="meta-value">{{ deviceInfo.deviceId }}</text>
+        </view>
+        
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <image src="/static/icons/location_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
+          </view>
+          <text class="meta-label">安装位置:</text>
+          <text class="meta-value">{{ deviceInfo.location }}</text>
+        </view>
+        
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <image src="/static/icons/clock_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
+          </view>
+          <text class="meta-label">最近更新:</text>
+          <text class="meta-value">{{ deviceInfo.lastUpdate }}</text>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 视频预览区域 -->
+    <view class="video-section">
+      <view class="video-container">
+        <image v-if="!isPlaying" src="/static/images/video-placeholder.jpg" mode="aspectFill" class="video-placeholder"></image>
+        <video 
+          v-else
+          id="videoPlayer" 
+          :src="deviceInfo.streamUrl"
+          class="video-player"
+          object-fit="cover"
+          autoplay
+          :controls="false"
+          :show-center-play-btn="false"
+          :show-fullscreen-btn="false"
+          :show-play-btn="false"
+          :enable-progress-gesture="false"
+          @error="handleVideoError"
+        ></video>
+        
+        <!-- 视频控制层 -->
+        <view class="video-controls">
+          <view class="control-row top-controls">
+            <view class="signal-indicator">
+              <image src="/static/icons/signal_icon.png" mode="aspectFit" style="width: 16px; height: 16px;"></image>
+              <text class="signal-text">信号良好</text>
+            </view>
+            
+            <view class="fullscreen-button" @click="toggleFullscreen">
+              <image src="/static/icons/resize_icon.png" mode="aspectFit" style="width: 20px; height: 20px;"></image>
+            </view>
+          </view>
+          
+          <view class="control-row center-controls">
+            <view v-if="!isPlaying" class="play-button" @click="togglePlayState">
+              <image src="/static/icons/play_icon.png" mode="aspectFit" style="width: 32px; height: 32px;"></image>
+            </view>
+            <view v-else class="center-button-container" @click="togglePlayState">
+              <view class="pause-icon">
+                <image src="/static/icons/pause_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+              </view>
+            </view>
+          </view>
+          
+          <view class="control-row bottom-controls">
+            <view class="video-time">{{ currentTime }}</view>
+          </view>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 云台控制区域 -->
+    <view class="ptz-section">
+      <view class="section-title">云台控制</view>
+      
+      <view class="ptz-container">
+        <!-- 圆形云台控制 -->
+        <view class="ptz-circle-container">
+          <!-- 上箭头 -->
+          <view class="ptz-arrow ptz-up" @touchstart="controlPTZ('up', true)" @touchend="controlPTZ('up', false)">
+            <image src="/static/icons/arrow_up_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+          
+          <!-- 左箭头 -->
+          <view class="ptz-arrow ptz-left" @touchstart="controlPTZ('left', true)" @touchend="controlPTZ('left', false)">
+            <image src="/static/icons/arrow_left_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+          
+          <!-- 中心拍照按钮 -->
+          <view class="ptz-center" @click="takeScreenshot">
+            <image src="/static/icons/camera_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+          
+          <!-- 右箭头 -->
+          <view class="ptz-arrow ptz-right" @touchstart="controlPTZ('right', true)" @touchend="controlPTZ('right', false)">
+            <image src="/static/icons/arrow_right_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+          
+          <!-- 下箭头 -->
+          <view class="ptz-arrow ptz-down" @touchstart="controlPTZ('down', true)" @touchend="controlPTZ('down', false)">
+            <image src="/static/icons/arrow_down_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+        </view>
+        
+        <!-- 底部操作按钮 -->
+        <view class="ptz-bottom-controls">
+          <view class="ptz-bottom-button" @touchstart="controlZoom('out', true)" @touchend="controlZoom('out', false)">
+            <image src="/static/icons/zoom01_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+          
+          <view class="ptz-bottom-button" @click="resetPTZ">
+            <image src="/static/icons/resetPTZ_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+          
+          <view class="ptz-bottom-button" @touchstart="controlZoom('in', true)" @touchend="controlZoom('in', false)">
+            <image src="/static/icons/zoom02_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </view>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 快捷功能按钮 -->
+    <view class="quick-actions">
+      <view class="action-button" @click="toggleVoiceIntercom">
+        <view class="action-icon">
+          <image src="/static/icons/Voice_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+        </view>
+        <text class="action-text">语音对讲</text>
+      </view>
+      
+      <view class="action-button" @click="toggleMute">
+        <view class="action-icon">
+          <image v-if="isMuted" src="/static/icons/muted_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          <image v-else src="/static/icons/unmuted_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+        </view>
+        <text class="action-text">{{ isMuted ? '取消静音' : '静音' }}</text>
+      </view>
+      
+      <view class="action-button" @click="navigateToHistory">
+        <view class="action-icon">
+          <image src="/static/icons/action_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+        </view>
+        <text class="action-text">视频回看</text>
+      </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" 
+          :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">
+            <image v-if="item.level === 'high'" src="/static/icons/warning_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+            <image v-else-if="item.level === 'medium'" src="/static/icons/info_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+            <image v-else src="/static/icons/success_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </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: 'DEV1001',
+        name: '监控设备-1',
+        status: 'online',
+        location: '西区B2地块',
+        lastUpdate: '5分钟前',
+        streamUrl: 'https://demo-rtsp-server-2h4n.onrender.com/stream.mp4',
+        alertCount: 3
+      },
+      isPlaying: false,
+      isMuted: false,
+      isRecording: false,
+      isFullscreen: false,
+      isVoiceActive: false,
+      isGridView: false,
+      isZoomMode: false,
+      currentTime: '14:30:25',
+      // 模拟历史录像数据
+      recordHistory: [
+        { id: 1, startTime: '今天 12:30', duration: '00:15:30', url: '' },
+        { id: 2, startTime: '今天 10:15', duration: '00:05:22', url: '' },
+        { id: 3, startTime: '昨天 18:45', duration: '00:30:10', url: '' },
+        { id: 4, startTime: '昨天 14:20', duration: '00:10:05', url: '' }
+      ],
+      // 模拟告警数据
+      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' }
+      ],
+      videoContext: null
+    }
+  },
+  
+  computed: {
+    // 获取所有未处理的告警
+    getUnhandledAlerts() {
+      return this.alertHistory.filter(alert => alert.status === '未处理');
+    }
+  },
+  
+  onReady() {
+    // 获取视频实例
+    this.videoContext = uni.createVideoContext('videoPlayer')
+    
+    // 设置页面标题
+    uni.setNavigationBarTitle({
+      title: this.deviceInfo.name
+    })
+    
+    // 模拟更新时间
+    this.startTimeUpdate()
+  },
+  
+  onLoad(options) {
+    // 如果有传入设备ID,则获取设备信息
+    if (options && options.id) {
+      this.fetchDeviceInfo(options.id)
+    }
+  },
+  
+  methods: {
+    // 获取设备信息
+    fetchDeviceInfo(deviceId) {
+      // 这里应该是API请求,暂时用模拟数据
+      console.log('获取设备信息:', deviceId)
+      // 模拟异步获取数据
+      setTimeout(() => {
+        // 实际应该是API请求结果
+      }, 500)
+    },
+    
+    // 播放/暂停切换
+    togglePlayState() {
+      if (!this.isPlaying) {
+        // 从未播放状态切换到播放状态
+        this.isPlaying = true;
+        
+        // 短暂延迟确保视频元素已加载
+        setTimeout(() => {
+          if (this.videoContext) {
+            this.videoContext.play();
+            // 振动反馈
+            uni.vibrateShort();
+          }
+        }, 300);
+      } else {
+        // 从播放状态切换到暂停状态
+        if (this.videoContext) {
+          this.videoContext.pause();
+          
+          // 在页面上显示暂停状态
+          uni.showToast({
+            title: '视频已暂停',
+            icon: 'none',
+            duration: 1500
+          });
+        }
+      }
+    },
+    
+    // 静音切换
+    toggleMute() {
+      this.isMuted = !this.isMuted
+      if (this.videoContext) {
+        if (this.isMuted) {
+          this.videoContext.mute()
+        } else {
+          this.videoContext.unmute()
+        }
+      }
+    },
+    
+    // 全屏切换
+    toggleFullscreen() {
+      if (!this.isPlaying) {
+        // 如果视频未播放,先开始播放
+        this.togglePlayState();
+        // 延迟执行全屏操作,等待视频元素加载
+        setTimeout(() => {
+          if (this.videoContext) {
+            this.videoContext.requestFullScreen();
+            this.isFullscreen = true;
+          }
+        }, 500);
+      } else if (this.videoContext) {
+        if (!this.isFullscreen) {
+          this.videoContext.requestFullScreen();
+        } else {
+          this.videoContext.exitFullScreen();
+        }
+        this.isFullscreen = !this.isFullscreen;
+      }
+    },
+    
+    // 切换九宫格视图
+    toggleGridView() {
+      this.isGridView = !this.isGridView
+      uni.showToast({
+        title: this.isGridView ? '切换到多画面模式' : '切换到单画面模式',
+        icon: 'none'
+      })
+    },
+    
+    // 截图
+    takeScreenshot() {
+      // 模拟截图功能
+      uni.showLoading({
+        title: '截图中...'
+      })
+      
+      setTimeout(() => {
+        uni.hideLoading()
+        uni.showToast({
+          title: '截图已保存',
+          icon: 'success'
+        })
+      }, 1000)
+    },
+    
+    // 语音对讲
+    toggleVoiceIntercom() {
+      this.isVoiceActive = !this.isVoiceActive
+      
+      if (this.isVoiceActive) {
+        uni.showToast({
+          title: '语音对讲已开启',
+          icon: 'none'
+        })
+      } else {
+        uni.showToast({
+          title: '语音对讲已关闭',
+          icon: 'none'
+        })
+      }
+    },
+    
+    // 云台控制
+    controlPTZ(direction, isStart) {
+      // 模拟云台控制
+      if (isStart) {
+        console.log(`开始控制云台: ${direction}`)
+        uni.vibrateShort() // 短震动反馈
+      } else {
+        console.log(`停止控制云台: ${direction}`)
+      }
+    },
+    
+    // 云台复位
+    resetPTZ() {
+      console.log('云台复位')
+      uni.vibrateShort() // 短震动反馈
+      uni.showToast({
+        title: '云台已复位',
+        icon: 'none'
+      })
+    },
+    
+    // 变焦控制
+    controlZoom(type, isStart) {
+      // 模拟变焦控制
+      if (isStart) {
+        console.log(`开始控制变焦: ${type}`)
+        uni.vibrateShort() // 短震动反馈
+      } else {
+        console.log(`停止控制变焦: ${type}`)
+      }
+    },
+    
+    // 切换缩放模式
+    toggleZoom() {
+      this.isZoomMode = !this.isZoomMode
+      uni.showToast({
+        title: this.isZoomMode ? '进入缩放模式' : '退出缩放模式',
+        icon: 'none'
+      })
+    },
+    
+    // 添加预设位
+    addPreset() {
+      uni.showModal({
+        title: '添加预设位',
+        content: '是否保存当前位置为预设位?',
+        success: (res) => {
+          if (res.confirm) {
+            uni.showToast({
+              title: '预设位已保存',
+              icon: 'success'
+            })
+          }
+        }
+      })
+    },
+    
+    // 处理告警点击
+    handleAlert(item) {
+      uni.navigateTo({
+        url: `/pages/alerts/alert-detail?alertId=${item.id}&deviceId=${this.deviceInfo.deviceId}`
+      })
+    },
+    
+    // 跳转到历史视频页面
+    navigateToHistory() {
+      uni.navigateTo({
+        url: `/pages/video/history?deviceId=${this.deviceInfo.deviceId}`
+      })
+    },
+    
+    // 处理视频错误
+    handleVideoError(e) {
+      console.error('视频播放错误:', e)
+      uni.showToast({
+        title: '视频播放出错,请稍后再试',
+        icon: 'none'
+      })
+    },
+    
+    // 更新时间
+    startTimeUpdate() {
+      // 模拟时间更新
+      setInterval(() => {
+        const now = new Date()
+        const hours = String(now.getHours()).padStart(2, '0')
+        const minutes = String(now.getMinutes()).padStart(2, '0')
+        const seconds = String(now.getSeconds()).padStart(2, '0')
+        this.currentTime = `${hours}:${minutes}:${seconds}`
+      }, 1000)
+    }
+  }
+}
+</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: 26rpx 30rpx 30rpx;
+  margin: 20rpx 30rpx 20rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.06);
+}
+
+.device-info-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 24rpx;
+}
+
+.device-name-container {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+}
+
+.device-name {
+  font-size: 34rpx;
+  color: #333333;
+  font-weight: 600;
+  margin-bottom: 10rpx;
+}
+
+.status-tag {
+  padding: 4rpx 12rpx 4rpx 24rpx;
+  border-radius: 30rpx;
+  font-size: 22rpx;
+  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: 24rpx;
+}
+
+.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;
+}
+
+.device-meta-item {
+  display: flex;
+  align-items: center;
+  font-size: 26rpx;
+  margin-top: 18rpx;
+}
+
+.meta-icon {
+  width: 36rpx;
+  height: 36rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 8rpx;
+}
+
+.meta-label {
+  color: #999999;
+  min-width: 140rpx;
+  font-size: 26rpx;
+}
+
+.meta-value {
+  color: #333333;
+  flex: 1;
+  font-size: 26rpx;
+}
+
+/* 视频预览区域 */
+.video-section {
+  margin: 0 30rpx 20rpx;
+}
+
+.video-container {
+  position: relative;
+  width: 100%;
+  height: 420rpx; /* 16:9比例 */
+  background-color: #000000;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.1);
+}
+
+.video-player, .video-placeholder {
+  width: 100%;
+  height: 100%;
+}
+
+.video-controls {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  justify-content: space-between;
+  padding: 20rpx;
+  box-sizing: border-box;
+  background: linear-gradient(to bottom, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0) 30%, rgba(0,0,0,0) 70%, rgba(0,0,0,0.4) 100%);
+}
+
+.control-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+
+.top-controls {
+  height: 80rpx;
+}
+
+.center-controls {
+  height: 120rpx;
+  justify-content: center;
+  align-items: center;
+}
+
+.bottom-controls {
+  height: 80rpx;
+}
+
+.signal-indicator {
+  display: flex;
+  align-items: center;
+  color: #FFFFFF;
+  font-size: 24rpx;
+  background-color: rgba(0, 0, 0, 0.5);
+  padding: 8rpx 16rpx;
+  border-radius: 30rpx;
+}
+
+.signal-indicator svg {
+  margin-right: 8rpx;
+}
+
+.signal-text {
+  font-weight: 500;
+}
+
+.fullscreen-button {
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #FFFFFF;
+  background-color: rgba(0, 0, 0, 0.5);
+  border-radius: 50%;
+  transition: all 0.2s;
+}
+
+.fullscreen-button:active {
+  background-color: rgba(76, 175, 80, 0.7);
+  transform: scale(0.9);
+}
+
+.video-time {
+  color: #FFFFFF;
+  font-size: 26rpx;
+  background-color: rgba(0, 0, 0, 0.5);
+  padding: 6rpx 16rpx;
+  border-radius: 30rpx;
+  font-weight: 500;
+}
+
+.play-button {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  background-color: rgba(255, 255, 255, 0.9);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.5);
+  transition: all 0.2s;
+  transform: scale(1);
+}
+
+.play-button:active {
+  transform: scale(0.92);
+  background-color: rgba(255, 255, 255, 1);
+}
+
+.center-button-container {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.pause-icon {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  background-color: rgba(0, 0, 0, 0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  opacity: 0;
+  transition: opacity 0.3s;
+}
+
+.center-button-container:active .pause-icon {
+  opacity: 1;
+  background-color: rgba(76, 175, 80, 0.7);
+}
+
+/* 云台控制区域 */
+.ptz-section {
+  margin: 0 30rpx 20rpx;
+  background-color: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+}
+
+.section-title {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #333333;
+  margin-bottom: 20rpx;
+  padding: 0 10rpx;
+  display: flex;
+  align-items: center;
+}
+
+.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;
+}
+
+.ptz-container {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 0;
+}
+
+.ptz-circle-container {
+  position: relative;
+  width: 300rpx;
+  height: 300rpx;
+  margin: 20rpx 0;
+}
+
+.ptz-arrow {
+  position: absolute;
+  width: 70rpx;
+  height: 70rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #F0F9F0;
+  border-radius: 50%;
+  color: #3BB44A;
+  font-size: 36rpx;
+  transition: all 0.2s;
+  box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.15);
+}
+
+.ptz-up {
+  top: 0;
+  left: 50%;
+  transform: translateX(-50%);
+}
+
+.ptz-down {
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+}
+
+.ptz-left {
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.ptz-right {
+  right: 0;
+  top: 50%;
+  transform: translateY(-50%);
+}
+
+.ptz-center {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 90rpx;
+  height: 90rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #F0F9F0;
+  border-radius: 50%;
+  color: #3BB44A;
+  font-size: 36rpx;
+  transition: all 0.2s;
+  box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.15);
+}
+
+.ptz-arrow:active, .ptz-center:active {
+  background-color: #3BB44A;
+  transform-origin: center;
+}
+
+.ptz-arrow:active svg path, .ptz-center:active svg path {
+  fill: #FFFFFF;
+}
+
+.ptz-up:active {
+  transform: scale(0.92) translateX(-50%);
+}
+
+.ptz-down:active {
+  transform: scale(0.92) translateX(-50%);
+}
+
+.ptz-left:active {
+  transform: scale(0.92) translateY(-50%);
+}
+
+.ptz-right:active {
+  transform: scale(0.92) translateY(-50%);
+}
+
+.ptz-center:active {
+  transform: translate(-50%, -50%) scale(0.92);
+}
+
+.ptz-bottom-controls {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 300rpx;
+  margin-top: 30rpx;
+}
+
+.ptz-bottom-button {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  background-color: #F0F9F0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #3BB44A;
+  font-size: 32rpx;
+  font-weight: 500;
+  transition: all 0.2s;
+  box-shadow: 0 2rpx 10rpx rgba(76, 175, 80, 0.1);
+}
+
+.ptz-bottom-button:active {
+  background-color: #3BB44A;
+  color: #FFFFFF;
+  transform: scale(0.92);
+}
+
+.ptz-bottom-button:active svg path {
+  fill: #FFFFFF;
+}
+
+/* 快捷功能按钮 */
+.quick-actions {
+  display: flex;
+  justify-content: space-evenly;
+  margin: 0 30rpx 20rpx;
+  background-color: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 24rpx 30rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+}
+
+.action-button {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 160rpx;
+}
+
+.action-icon {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  background-color: #F0F9F0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #3BB44A;
+  margin-bottom: 12rpx;
+  transition: all 0.2s;
+  box-shadow: 0 4rpx 12rpx rgba(76, 175, 80, 0.1);
+}
+
+.action-icon:active {
+  background-color: #3BB44A;
+  transform: scale(0.92);
+}
+
+.action-icon:active svg path {
+  fill: #FFFFFF;
+}
+
+.action-text {
+  font-size: 24rpx;
+  color: #666666;
+  text-align: center;
+}
+
+/* 告警信息列表 */
+.alerts-section {
+  margin: 0 30rpx;
+  background-color: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
+}
+
+.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 8rpx rgba(0, 0, 0, 0.05);
+  position: relative;
+}
+
+.alert-urgent {
+  background-color: #FEF3F3;
+  border-left: 4rpx solid #F56C6C;
+}
+
+.alert-warning {
+  background-color: #FFF8E6;
+  border-left: 4rpx solid #E6A23C;
+}
+
+.alert-info {
+  background-color: #F2FAF5;
+  border-left: 4rpx solid #67C23A;
+}
+
+.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;
+}
+</style> 

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

@@ -0,0 +1,1179 @@
+<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">
+          <image src="/static/icons/refresh_icon.png" mode="aspectFit" style="width: 22px; height: 22px;"></image>
+        </view>
+      </view>
+      
+      <view class="device-meta-row">
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <image src="/static/icons/device_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
+          </view>
+          <text class="meta-label">设备编号:</text>
+          <text class="meta-value">{{ deviceInfo.deviceId }}</text>
+        </view>
+        
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <image src="/static/icons/location_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
+          </view>
+          <text class="meta-label">安装位置:</text>
+          <text class="meta-value">{{ deviceInfo.location }}</text>
+        </view>
+        
+        <view class="device-meta-item">
+          <view class="meta-icon">
+            <image src="/static/icons/clock_icon.png" mode="aspectFit" style="width: 36rpx; height: 36rpx;"></image>
+          </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 data-title">
+        <text>气象数据</text>
+      </view>
+      
+      <view class="data-grid">
+        <!-- 气温 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(weatherData.temperature) > 35 || parseFloat(weatherData.temperature) < 5
+              }">{{ weatherData.temperature }}</text>
+              <text class="data-unit">℃</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time">{{ weatherData.updateTime || '2分钟前' }}</text>
+        </view>
+        
+        <!-- 湿度 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(weatherData.humidity) > 85
+              }">{{ weatherData.humidity }}</text>
+              <text class="data-unit">%</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time">{{ weatherData.updateTime || '2分钟前' }}</text>
+        </view>
+        
+        <!-- 降雨量 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-alert': parseFloat(weatherData.rainfall) > 10
+              }">{{ weatherData.rainfall }}</text>
+              <text class="data-unit">mm</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time">{{ weatherData.updateTime || '2分钟前' }}</text>
+        </view>
+        
+        <!-- 风向 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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">{{ weatherData.updateTime || '2分钟前' }}</text>
+        </view>
+        
+        <!-- 风速 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-alert': parseFloat(weatherData.windSpeed) > 8,
+                'value-warning': parseFloat(weatherData.windSpeed) > 5 && parseFloat(weatherData.windSpeed) <= 8
+              }">{{ 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">{{ weatherData.updateTime || '2分钟前' }}</text>
+        </view>
+        
+        <!-- 气压 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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">{{ weatherData.updateTime || '2分钟前' }}</text>
+        </view>
+        
+        <!-- 光照 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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">{{ weatherData.updateTime || '2分钟前' }}</text>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 采集数据展示区域 - 土壤墒情 -->
+    <view class="data-section" v-if="deviceInfo.deviceType === 'soil'">
+      <view class="section-title soil-title">
+        <text>土壤数据</text>
+      </view>
+      
+      <view class="data-grid">
+        <!-- 土壤温度 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(soilData.temperature) > 30 || parseFloat(soilData.temperature) < 5
+              }">{{ soilData.temperature }}</text>
+              <text class="data-unit">℃</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time">{{ soilData.updateTime || '5分钟前' }}</text>
+        </view>
+        
+        <!-- 土壤湿度 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(soilData.moisture) < 20,
+                'value-alert': parseFloat(soilData.moisture) < 10
+              }">{{ soilData.moisture }}</text>
+              <text class="data-unit">%</text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time">{{ soilData.updateTime || '5分钟前' }}</text>
+        </view>
+        
+        <!-- 氮含量 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(soilData.nitrogen) < 120
+              }">{{ 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">{{ soilData.updateTime || '5分钟前' }}</text>
+        </view>
+        
+        <!-- 磷含量 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(soilData.phosphorus) < 30
+              }">{{ 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">{{ soilData.updateTime || '5分钟前' }}</text>
+        </view>
+        
+        <!-- 钾含量 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(soilData.potassium) < 150
+              }">{{ 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">{{ soilData.updateTime || '5分钟前' }}</text>
+        </view>
+        
+        <!-- 土壤电导率 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-alert': parseFloat(soilData.conductivity) > 0.8
+              }">{{ 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">{{ soilData.updateTime || '5分钟前' }}</text>
+        </view>
+        
+        <!-- PH值 -->
+        <view class="data-item" hover-class="data-item-hover">
+          <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,
+                'value-warning': parseFloat(soilData.ph) < 5.5 || parseFloat(soilData.ph) > 8.5,
+                'value-alert': parseFloat(soilData.ph) < 4.5 || parseFloat(soilData.ph) > 9
+              }">{{ soilData.ph }}</text>
+              <text class="data-unit"></text>
+            </template>
+            <text v-else class="data-value no-data">暂无数据</text>
+          </view>
+          <text class="data-time">{{ 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">
+            <image v-if="item.level === 'high'" src="/static/icons/warning_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+            <image v-else-if="item.level === 'medium'" src="/static/icons/info_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+            <image v-else src="/static/icons/success_icon.png" mode="aspectFit" style="width: 24px; height: 24px;"></image>
+          </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: 24rpx;
+  padding: 30rpx 24rpx;
+  box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05);
+  border: 1rpx solid rgba(210, 237, 217, 0.5);
+}
+
+.section-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333333;
+  margin-bottom: 20rpx;
+  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: linear-gradient(to bottom, #4CAF50, #81C784);
+  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: 16rpx 0;
+  width: 100%;
+  margin: 0 -12rpx;
+}
+
+.data-item {
+  width: calc(50% - 24rpx);
+  margin: 0 12rpx 24rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-start;
+  justify-content: space-between;
+  background: linear-gradient(to bottom right, #F9FFFB, #FFFFFF);
+  border-radius: 24rpx;
+  padding: 28rpx 24rpx;
+  box-sizing: border-box;
+  box-shadow: 0 6rpx 18rpx rgba(0, 0, 0, 0.06), inset 0 1rpx 3rpx rgba(255, 255, 255, 0.8);
+  position: relative;
+  border: 1rpx solid rgba(210, 237, 217, 0.6);
+  transition: all 0.25s ease;
+  overflow: hidden;
+  min-height: 180rpx;
+}
+
+.data-item::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 8rpx;
+  height: 100%;
+  background: linear-gradient(to bottom, #4CAF50, #81C784);
+  opacity: 0.9;
+  border-radius: 4rpx 0 0 4rpx;
+}
+
+.data-item-hover {
+  transform: scale(1.02);
+  box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.08), inset 0 1rpx 3rpx rgba(255, 255, 255, 0.8);
+  background: linear-gradient(to bottom right, #F7FFF9, #FFFFFF);
+}
+
+.data-label {
+  font-size: 26rpx;
+  color: #666666;
+  margin-bottom: 22rpx;
+  font-weight: 500;
+  align-self: flex-start;
+}
+
+.data-value-container {
+  display: flex;
+  align-items: flex-end;
+  width: 100%;
+  margin-bottom: 18rpx;
+}
+
+.data-value {
+  font-size: 40rpx;
+  color: #222222;
+  font-weight: 700;
+  line-height: 1;
+}
+
+.data-value.no-data {
+  font-size: 28rpx;
+  color: #999999;
+  font-weight: normal;
+}
+
+.data-unit {
+  font-size: 24rpx;
+  color: #999999;
+  margin-left: 6rpx;
+  padding-bottom: 6rpx;
+  align-self: flex-end;
+}
+
+.data-time {
+  font-size: 20rpx;
+  color: #AAAAAA;
+  width: 100%;
+  text-align: right;
+}
+
+/* 数据更新动画 */
+@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;
+}
+
+/* 异常值样式 */
+.value-warning {
+  color: #E6A23C !important;
+}
+
+.value-alert {
+  color: #F56C6C !important;
+}
+
+/* 点击波纹效果(可选)*/
+@keyframes ripple {
+  0% {
+    transform: scale(0);
+    opacity: 0.6;
+  }
+  100% {
+    transform: scale(2);
+    opacity: 0;
+  }
+}
+
+.data-item::after {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background-color: rgba(76, 175, 80, 0.1);
+  border-radius: 24rpx;
+  opacity: 0;
+  transition: opacity 0.3s;
+  z-index: -1;
+}
+
+.data-item-hover::after {
+  opacity: 1;
+}
+
+/* 告警信息列表 */
+.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 spin {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+.refresh-btn.refreshing {
+  animation: spin 1.2s linear infinite;
+}
+
+/* 土壤数据标题样式调整 */
+.soil-title, .data-title {
+  display: flex;
+  align-items: center;
+  position: relative;
+  padding-left: 24rpx;
+}
+
+.soil-title::before, .data-title::before {
+  content: '';
+  position: absolute;
+  left: 0;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 8rpx;
+  height: 36rpx;
+  background: linear-gradient(to bottom, #4CAF50, #81C784);
+  border-radius: 4rpx;
+}
+
+.soil-title text, .data-title text {
+  margin-left: 16rpx;
+  font-weight: 600;
+}
+</style> 

+ 869 - 0
pages/device-list/index.vue

@@ -0,0 +1,869 @@
+<template>
+  <view class="container">
+    <!-- 搜索区域 -->
+    <view class="search-section">
+      <view class="search-box" :class="{ 'search-focus': isSearchFocused }">
+        <view class="search-icon">
+          <text class="iconfont icon-search"></text>
+        </view>
+        <input 
+          class="search-input" 
+          type="text" 
+          v-model="searchKeyword" 
+          placeholder="搜索设备名称 / 编号"
+          confirm-type="search"
+          @confirm="handleSearch"
+          @focus="isSearchFocused = true"
+          @blur="isSearchFocused = false"
+        />
+        <view class="clear-icon" v-if="searchKeyword" @click="handleClearSearch">
+          <text class="iconfont icon-close"></text>
+        </view>
+      </view>
+    </view>
+    
+    <!-- 状态筛选区域 -->
+    <view class="filter-section">
+      <view 
+        class="filter-item" 
+        :class="{ active: statusFilter === 'all' }" 
+        @click="setStatusFilter('all')"
+      >
+        全部
+      </view>
+      <view 
+        class="filter-item" 
+        :class="{ active: statusFilter === 'online' }" 
+        @click="setStatusFilter('online')"
+      >
+        <view class="filter-dot online-dot"></view>
+        在线 ({{ onlineCount }})
+      </view>
+      <view 
+        class="filter-item" 
+        :class="{ active: statusFilter === 'offline' }" 
+        @click="setStatusFilter('offline')"
+      >
+        <view class="filter-dot offline-dot"></view>
+        离线 ({{ offlineCount }})
+      </view>
+    </view>
+    
+    <!-- 设备列表区域 -->
+    <scroll-view 
+      scroll-y 
+      class="device-list" 
+      @scrolltolower="loadMore"
+      :scroll-with-animation="true"
+      :enable-back-to-top="true"
+      :refresher-enabled="true"
+      :refresher-threshold="80"
+      :refresher-triggered="isRefreshing"
+      @refresherrefresh="handleRefresh"
+      @refresherrestore="isRefreshing = false"
+    >
+      <!-- 无数据提示 -->
+      <view v-if="filteredDeviceList.length === 0 && !isLoading" class="empty-tips">
+        <view class="empty-icon">
+          <text class="iconfont icon-empty"></text>
+        </view>
+        <text class="empty-text">{{ searchKeyword ? '未找到匹配的设备' : '暂无设备' }}</text>
+        <view v-if="searchKeyword" class="empty-action" @click="handleClearSearch">
+          <text>清除搜索条件</text>
+        </view>
+      </view>
+      
+      <!-- 首次加载中状态 -->
+      <view v-if="isLoading && deviceList.length === 0" class="loading-container">
+        <view class="loading-spinner"></view>
+        <text class="loading-text">加载中...</text>
+      </view>
+      
+      <!-- 设备列表 -->
+      <view 
+        v-for="(item, index) in filteredDeviceList" 
+        :key="index"
+        class="device-card"
+        :class="{
+          'has-alert': item.alarmCount > 0,
+          'is-offline': item.status === 'offline'
+        }"
+        hover-class="device-card-hover"
+        hover-stay-time="70"
+        @click="navigateToDetail(item)"
+      >
+        <!-- 设备基本信息 -->
+        <view class="device-info">
+          <view class="device-icon-wrapper">
+            <!-- 告警角标 -->
+            <view v-if="item.alarmCount > 0" class="alarm-badge">
+              {{ item.alarmCount }}
+            </view>
+            
+            <view class="device-icon-container" :class="{'offline-icon': item.status === 'offline'}">
+              <image :src="getDeviceIcon(item.type)" mode="aspectFit" class="device-icon"></image>
+            </view>
+          </view>
+          
+          <view class="device-meta">
+            <view class="device-name-row">
+              <text class="device-name" :class="{'offline-text': item.status === 'offline'}">{{ item.name }}</text>
+              <view 
+                class="status-tag" 
+                :class="item.status === 'online' ? 'status-online' : 'status-offline'"
+              >
+                <text class="status-dot" :class="{'offline-dot': item.status === 'offline'}"></text>
+                {{ item.status === 'online' ? '在线' : '离线' }}
+              </view>
+            </view>
+            
+            <view class="device-id">
+              <text class="id-label">设备编号:</text>
+              <text class="id-value">{{ item.code }}</text>
+            </view>
+            
+            <view class="device-location">
+              <text class="location-label">安装位置:</text>
+              <text class="location-value">{{ item.location }}</text>
+            </view>
+          </view>
+        </view>
+        
+        <!-- 底部信息栏 -->
+        <view class="device-footer">
+          <text class="update-time">{{ item.updateTime }}</text>
+          <view class="device-actions">
+            <text class="iconfont icon-right"></text>
+          </view>
+        </view>
+      </view>
+      
+      <!-- 加载更多提示 -->
+      <view v-if="filteredDeviceList.length > 0" class="load-more">
+        <view class="load-more-content" v-if="loadMoreStatus === 'loading'">
+          <view class="loading-icon"></view>
+          <text>正在加载...</text>
+        </view>
+        <view class="load-more-content" v-if="loadMoreStatus === 'nomore'">
+          <text>没有更多了</text>
+        </view>
+        <view class="load-more-content" v-if="loadMoreStatus === 'loadmore'" @click="loadMore">
+          <text>点击加载更多</text>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      deviceType: '',
+      searchKeyword: '',
+      isSearchFocused: false,
+      statusFilter: 'all', // 'all', 'online', 'offline'
+      deviceList: [],
+      page: 1,
+      limit: 10,
+      loadMoreStatus: 'loading', // 'loadmore', 'loading', 'nomore'
+      isLoading: true,
+      isRefreshing: false
+    }
+  },
+  
+  computed: {
+    // 过滤后的设备列表
+    filteredDeviceList() {
+      let result = this.deviceList;
+      
+      // 按状态筛选
+      if (this.statusFilter !== 'all') {
+        result = result.filter(device => device.status === this.statusFilter);
+      }
+      
+      // 按关键词搜索
+      if (this.searchKeyword) {
+        const keyword = this.searchKeyword.toLowerCase();
+        result = result.filter(device => 
+          device.name.toLowerCase().includes(keyword) || 
+          device.code.toLowerCase().includes(keyword)
+        );
+      }
+      
+      return result;
+    },
+    
+    // 在线设备数量
+    onlineCount() {
+      return this.deviceList.filter(device => device.status === 'online').length;
+    },
+    
+    // 离线设备数量
+    offlineCount() {
+      return this.deviceList.filter(device => device.status === 'offline').length;
+    }
+  },
+  
+  onLoad(options) {
+    // 获取路由参数
+    if (options.type) {
+      this.deviceType = options.type;
+      this.setPageTitle();
+    }
+    
+    // 加载设备数据
+    this.loadDeviceData();
+  },
+  
+  methods: {
+    // 设置页面标题
+    setPageTitle() {
+      const titleMap = {
+        'monitor': '监控设备列表',
+        'sensor': '采集设备列表',
+        'control': '控制设备列表',
+        'irrigation': '灌溉设备列表',
+        'tractor': '农机设备列表'
+      };
+      
+      const title = titleMap[this.deviceType] || '设备列表';
+      uni.setNavigationBarTitle({
+        title: title
+      });
+    },
+    
+    // 获取设备图标
+    getDeviceIcon(type) {
+      const iconMap = {
+        'monitor': '/static/icons/camera.png',
+        'sensor': '/static/icons/sensor.png',
+        'control': '/static/icons/control.png',
+        'irrigation': '/static/icons/water.png',
+        'tractor': '/static/icons/tractor.png'
+      };
+      
+      return iconMap[type] || '/static/icons/device-default.png';
+    },
+    
+    // 设置状态筛选
+    setStatusFilter(status) {
+      this.statusFilter = status;
+    },
+    
+    // 加载设备数据
+    loadDeviceData() {
+      this.isLoading = true;
+      
+      // 模拟API请求数据
+      setTimeout(() => {
+        // 这里应该是真实的API请求
+        // 模拟一些设备数据用于展示
+        const newDevices = this.generateMockDevices();
+        this.deviceList = [...this.deviceList, ...newDevices];
+        
+        if (this.deviceList.length >= 30) {
+          this.loadMoreStatus = 'nomore';
+        } else {
+          this.loadMoreStatus = 'loadmore';
+        }
+        
+        this.isLoading = false;
+        this.isRefreshing = false;
+      }, 1000);
+    },
+    
+    // 生成模拟设备数据
+    generateMockDevices() {
+      const devices = [];
+      const locations = ['东区A1地块', '西区B2地块', '南区C3地块', '北区D4地块'];
+      const updateTimes = ['刚刚更新', '1分钟前更新', '5分钟前更新', '10分钟前更新', '1小时前更新'];
+      
+      // 根据当前页码和限制数量生成对应数量的模拟数据
+      const startIndex = (this.page - 1) * this.limit;
+      for (let i = 0; i < this.limit; i++) {
+        const index = startIndex + i;
+        
+        // 如果已经生成了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: 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)],
+          alarmCount: Math.random() > 0.7 ? Math.floor(Math.random() * 3) + 1 : 0 // 30% 概率有告警
+        });
+      }
+      
+      return devices;
+    },
+    
+    // 获取设备类型名称
+    getDeviceTypeName(type) {
+      const nameMap = {
+        'monitor': '监控设备',
+        'sensor': '采集设备',
+        'control': '控制设备',
+        'irrigation': '灌溉设备',
+        'tractor': '农机设备'
+      };
+      
+      return nameMap[type] || '未知设备';
+    },
+    
+    // 处理搜索
+    handleSearch() {
+      // 执行搜索逻辑
+      console.log('搜索关键词:', this.searchKeyword);
+    },
+    
+    // 处理清空搜索
+    handleClearSearch() {
+      this.searchKeyword = '';
+    },
+    
+    // 加载更多数据
+    loadMore() {
+      if (this.loadMoreStatus !== 'nomore') {
+        this.loadMoreStatus = 'loading';
+        this.page += 1;
+        this.loadDeviceData();
+      }
+    },
+    
+    // 跳转到设备详情页
+    navigateToDetail(device) {
+      // 根据设备类型跳转到不同的详情页
+      let url = '';
+      
+      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}`;
+      }
+      
+      uni.navigateTo({
+        url: url
+      });
+    },
+    
+    // 处理刷新
+    handleRefresh() {
+      this.isRefreshing = true;
+      this.page = 1;
+      this.deviceList = [];
+      this.loadDeviceData();
+    }
+  }
+}
+</script>
+
+<style scoped>
+/* 图标字体 */
+@font-face {
+  font-family: "iconfont";
+  src: url('data:font/woff2;charset=utf-8;base64,d09GMgABAAAAAAOMAAsAAAAACFAAAAM+AAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDMgqDGIJmATYCJAMUCwwABCAFhGcHRhvqBsgOJaDgwABgBNFYPPy3H/3OSB4kWdI1Pb2QBw8gSpZIsxBFsTCNTIm//9dv/7FzF1E1S9ysIZWDPNdPJdSYbfLy/+fpTTMwKn5ZZ3OImpRUTZ8FHAcYFrLCBqyAHZD3CP9I09oXUgNcDSEAS8oGR507tO6KhRQTNQGQyeNGD8PCkVATUkFYsMgGcuqQ5YiFWruCPAVYnn9f/KGFBQxrghRq0KjzAXS5TP3SHp3ZmXUyEIfTAYDvDEiQARSQiaJjIjKaZiDJ+hpQYnY1i8tK1ZfWl83/fxYLm8TS//EADBBU6C9ghIBwJXxprxqwhC8d6x9AvAB4BcARgGWgRlWY71Cn5I5XvJ5Ot5sXy91Ud9Oj1nXnfbK9vK09bVsrGq2rrB1sxMcbxZmXntbdI8u6cRGhGy9r92Hj7hCjx1G6Dd9G9N6sbSvwVZiw+/hC0D7WFi5tbcltVyPOdqm41HkQr9T0bKFa2zrLLVeKJufZy7vn7aXtY+3XMvl65fJ7wnHT7ZJWozcvrHC5XNnTGy5/0NvX1xdSu8W9Ml0ePXEqZ3Jm0r6SLSdPbJnc27Mm05YsrLjfI8Ypvb5dL5pAj9m5c3Tzw+UvtK/SzO/Vv+Dt/9Lfq3+Xr30Hf9pz0JsnGgsPDBWHv+v12l5zGsE7Pz9A2d0W9WJ2dUZ/a/PkxQW58o/asvHDfyvJEw39G/5zf2p9OmZFYYGKoioVKPzPpwQA0+d2xv7C/aL/Dx/m7QfkjDLZL8AH4F3p+8PBrGIuqFLmM3uqfI6+YxAFCRZWAQ9LM8Q3JuRm2/bCgWEDCgzzIFgrEYk0AxgciQCDtQxgSRr76yYgfkIiHoB0FxGEEKfAEOQMJCHukYj0CQyjPsMQ0oJgyQD6hI6DL3FILJJLiqDqQs9kbYnLnTRuoA+5j7HsWmUJG2QdvdPrY51xR0WpwQZZIJvsGJ8OWYgVpomO28rIDCGTHHGJpqFe7xSPlUXs04FBiQV1FxSCqhboGVlrEldXpOGP94G4Hwydnaj6DjJBj1dvVtZizE8WlMYD9rHWaVsLsmF6QhZEmMDsIk1wOZWLgSHSSTkEJzGNQvvLc3HLFVXz9euBBWkm+YbLZKGH3uISrbIXRuNaAA==');
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 24rpx;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-search:before {
+  content: "\e6e1";
+  font-size: 32rpx;
+}
+
+.icon-close:before {
+  content: "\e6a7";
+  font-size: 28rpx;
+}
+
+.icon-right:before {
+  content: "\e6a3";
+  font-size: 28rpx;
+}
+
+.icon-empty:before {
+  content: "\e6a9";
+  font-size: 80rpx;
+}
+
+
+
+/* 容器样式 */
+.container {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  background-color: #F9FCFA;
+  padding-bottom: 20rpx;
+}
+
+/* 搜索区域 */
+.search-section {
+  padding: 20rpx 30rpx;
+  background-color: #FFFFFF;
+  margin-bottom: 2rpx;
+  width: 100%;
+  box-sizing: border-box;
+  position: relative;
+  z-index: 5;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.03);
+}
+
+.search-box {
+  display: flex;
+  align-items: center;
+  background-color: #F7F7F7;
+  height: 80rpx;
+  border-radius: 40rpx;
+  padding: 0 24rpx;
+  width: 100%;
+  box-sizing: border-box;
+  border: 2rpx solid transparent;
+  transition: all 0.3s ease;
+}
+
+.search-box.search-focus {
+  border-color: #4CAF50;
+  background-color: #FFFFFF;
+  box-shadow: 0 0 10rpx rgba(76, 175, 80, 0.1);
+}
+
+.search-icon {
+  color: #4CAF50;
+  width: 60rpx;
+  display: flex;
+  justify-content: center;
+}
+
+.search-input {
+  flex: 1;
+  height: 80rpx;
+  font-size: 28rpx;
+  color: #333333;
+}
+
+.clear-icon {
+  width: 60rpx;
+  display: flex;
+  justify-content: center;
+  color: #999;
+}
+
+/* 状态筛选区域 */
+.filter-section {
+  display: flex;
+  padding: 24rpx 30rpx;
+  background-color: #FFFFFF;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.03);
+  position: relative;
+  z-index: 4;
+}
+
+.filter-item {
+  display: flex;
+  align-items: center;
+  font-size: 28rpx;
+  color: #666666;
+  margin-right: 30rpx;
+  padding: 12rpx 20rpx;
+  border-radius: 30rpx;
+  transition: all 0.2s ease;
+  position: relative;
+}
+
+.filter-item.active {
+  background-color: #F0F9F0;
+  color: #4CAF50;
+  font-weight: 500;
+}
+
+
+
+.filter-dot {
+  width: 14rpx;
+  height: 14rpx;
+  border-radius: 50%;
+  margin-right: 10rpx;
+}
+
+.online-dot {
+  background-color: #4CAF50;
+  box-shadow: 0 0 6rpx rgba(76, 175, 80, 0.5);
+}
+
+.offline-dot {
+  background-color: #F56C6C;
+  box-shadow: 0 0 6rpx rgba(245, 108, 108, 0.5);
+}
+
+/* 设备列表区域 */
+.device-list {
+  flex: 1;
+  padding: 0 30rpx;
+  box-sizing: border-box;
+  width: 100%;
+  position: relative;
+  z-index: 3;
+}
+
+/* 空数据提示 */
+.empty-tips {
+  padding: 120rpx 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.empty-icon {
+  color: #DDDDDD;
+  margin-bottom: 20rpx;
+}
+
+.empty-text {
+  font-size: 28rpx;
+  color: #999999;
+  margin-bottom: 20rpx;
+}
+
+.empty-action {
+  font-size: 26rpx;
+  color: #4CAF50;
+  padding: 12rpx 30rpx;
+  border-radius: 30rpx;
+  background-color: rgba(76, 175, 80, 0.1);
+}
+
+/* 首次加载中状态 */
+.loading-container {
+  padding: 80rpx 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.loading-spinner {
+  width: 60rpx;
+  height: 60rpx;
+  border: 4rpx solid #E0E0E0;
+  border-top: 4rpx solid #4CAF50;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+  margin-bottom: 20rpx;
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: #999999;
+}
+
+/* 设备卡片 */
+.device-card {
+  position: relative;
+  background-color: #FFFFFF;
+  border-radius: 24rpx;
+  padding: 28rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.05);
+  transition: all 0.25s ease;
+  width: 100%;
+  box-sizing: border-box;
+  border: 1rpx solid rgba(0, 0, 0, 0.05);
+  overflow: hidden;
+}
+
+.device-card::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 6rpx;
+  height: 100%;
+  background: linear-gradient(to bottom, #66CC6A, #3BB44A);
+  opacity: 0;
+  transition: opacity 0.3s ease;
+}
+
+.device-card:active::before {
+  opacity: 1;
+}
+
+.device-card-hover {
+  background-color: #f2fef6;
+  box-shadow: 0 10rpx 25rpx rgba(59, 180, 74, 0.1);
+  transform: translateY(-3rpx);
+}
+
+.device-card.has-alert {
+  box-shadow: 0 6rpx 20rpx rgba(245, 108, 108, 0.08);
+  border: 1rpx solid rgba(245, 108, 108, 0.08);
+}
+
+.device-card.has-alert::before {
+  background: linear-gradient(to bottom, #FF8F8F, #F56C6C);
+}
+
+.device-card.is-offline {
+  opacity: 0.9;
+}
+
+.device-card.is-offline.device-card-hover {
+  background-color: #f5f5f5;
+  box-shadow: 0 10rpx 25rpx rgba(0, 0, 0, 0.05);
+}
+
+/* 告警角标 */
+.alarm-badge {
+  position: absolute;
+  top: -8rpx;
+  right: -8rpx;
+  min-width: 36rpx;
+  height: 36rpx;
+  border-radius: 18rpx;
+  background-color: #F56C6C;
+  color: #FFFFFF;
+  font-size: 22rpx;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 0 8rpx;
+  z-index: 3;
+  box-shadow: 0 3rpx 8rpx rgba(245, 108, 108, 0.3);
+  animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+  }
+  50% {
+    transform: scale(1.1);
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+
+/* 设备基本信息 */
+.device-info {
+  display: flex;
+  margin-bottom: 24rpx;
+  width: 100%;
+}
+
+.device-icon-wrapper {
+  position: relative;
+  margin-right: 24rpx;
+  flex-shrink: 0;
+}
+
+.device-icon-container {
+  width: 96rpx;
+  height: 96rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #66CC6A 0%, #3BB44A 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  box-shadow: 0 6rpx 16rpx rgba(59, 180, 74, 0.2);
+  transition: all 0.3s ease;
+}
+
+.device-icon-container.offline-icon {
+  background: linear-gradient(135deg, #AAB2BD 0%, #656D78 100%);
+  box-shadow: 0 6rpx 16rpx rgba(101, 109, 120, 0.2);
+}
+
+.device-icon {
+  width: 52rpx;
+  height: 52rpx;
+  filter: brightness(0) invert(1);
+}
+
+.device-meta {
+  flex: 1;
+  width: calc(100% - 120rpx);
+  overflow: hidden;
+}
+
+.device-name-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 14rpx;
+  width: 100%;
+}
+
+.device-name {
+  font-size: 34rpx;
+  font-weight: 600;
+  color: #333333;
+  max-width: 65%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  transition: color 0.3s ease;
+}
+
+.device-name.offline-text {
+  color: #656D78;
+}
+
+.status-tag {
+  padding: 6rpx 16rpx 6rpx 32rpx;
+  border-radius: 8rpx;
+  font-size: 24rpx;
+  font-weight: 500;
+  flex-shrink: 0;
+  border: 1rpx solid;
+  position: relative;
+  overflow: hidden;
+}
+
+.status-online {
+  background-color: rgba(76, 175, 80, 0.1);
+  color: #4CAF50;
+  border-color: rgba(76, 175, 80, 0.3);
+}
+
+.status-dot {
+  position: absolute;
+  width: 8rpx;
+  height: 8rpx;
+  background-color: #4CAF50;
+  border-radius: 50%;
+  top: 50%;
+  left: 16rpx;
+  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);
+}
+
+.status-offline {
+  background-color: rgba(245, 108, 108, 0.1);
+  color: #F56C6C;
+  border-color: rgba(245, 108, 108, 0.3);
+  padding-left: 32rpx;
+}
+
+@keyframes blink {
+  0% {
+    opacity: 0.4;
+  }
+  50% {
+    opacity: 1;
+  }
+  100% {
+    opacity: 0.4;
+  }
+}
+
+.device-id, .device-location {
+  display: flex;
+  font-size: 26rpx;
+  margin-top: 10rpx;
+  color: #666666;
+  width: 100%;
+  overflow: hidden;
+}
+
+.id-label, .location-label {
+  color: #999999;
+  margin-right: 8rpx;
+  flex-shrink: 0;
+}
+
+.id-value, .location-value {
+  color: #666666;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+/* 底部信息栏 */
+.device-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 18rpx;
+  border-top: 1rpx solid #F2F2F2;
+}
+
+.update-time {
+  font-size: 24rpx;
+  color: #999999;
+}
+
+.device-actions {
+  display: flex;
+  align-items: center;
+  color: #CCCCCC;
+}
+
+/* 加载更多区域 */
+.load-more {
+  padding: 20rpx 0 40rpx;
+}
+
+.load-more-content {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 60rpx;
+  font-size: 24rpx;
+  color: #999999;
+}
+
+.loading-icon {
+  width: 30rpx;
+  height: 30rpx;
+  margin-right: 10rpx;
+  border: 2rpx solid #E0E0E0;
+  border-top: 2rpx solid #4CAF50;
+  border-radius: 50%;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+
+</style> 

+ 443 - 3
pages/device/index.vue

@@ -1,15 +1,455 @@
 <template>
   <view class="container">
-    设备监测页面(待开发)
+    <!-- 地块信息 -->
+    <view class="plot-section">
+      <text class="plot-text">当前查看地块:</text>
+      <text class="plot-name">{{ currentPlot }}</text>
+    </view>
+    
+    <!-- 顶部区域:概览统计 -->
+    <view class="overview-section">
+      <view class="stat-item">
+        <view class="stat-icon-wrapper">
+          <image src="/static/icons/device_num.png" mode="aspectFit" class="stat-icon-img"></image>
+        </view>
+        <text class="stat-value">{{ totalDevices }}</text>
+        <text class="stat-label">总数</text>
+      </view>
+      
+      <view class="stat-item">
+        <view class="stat-icon-wrapper online">
+          <image src="/static/icons/device_online.png" mode="aspectFit" class="stat-icon-img"></image>
+        </view>
+        <text class="stat-value online">{{ onlineDevices }}</text>
+        <text class="stat-label">在线</text>
+      </view>
+      
+      <view class="stat-item">
+        <view class="stat-icon-wrapper offline">
+          <image src="/static/icons/device_offline.png" mode="aspectFit" class="stat-icon-img"></image>
+        </view>
+        <text class="stat-value offline">{{ offlineDevices }}</text>
+        <text class="stat-label">离线</text>
+      </view>
+      
+      <view class="stat-item">
+        <view class="stat-icon-wrapper alert">
+          <image src="/static/icons/device_alert.png" mode="aspectFit" class="stat-icon-img"></image>
+        </view>
+        <text class="stat-value alert">{{ alertDevices }}</text>
+        <text class="stat-label">告警</text>
+      </view>
+    </view>
+    
+    <!-- 设备网格布局 -->
+    <view class="device-grid">
+      <view 
+        v-for="(item, index) in deviceList" 
+        :key="index" 
+        @click="navigateToDeviceList(item.type)"
+        class="device-card"
+        :class="{'has-alert': item.alerts > 0}"
+        hover-class="device-card-hover"
+      >
+        <!-- 告警角标 -->
+        <view v-if="item.alerts > 0" class="alert-badge">{{ item.alerts }}</view>
+        
+        <!-- 设备图标 -->
+        <view class="device-icon-container">
+          <image :src="item.icon" mode="aspectFit" class="device-icon"></image>
+        </view>
+        
+        <!-- 设备名称 -->
+        <text class="device-name">{{ item.name }}</text>
+        
+        <!-- 设备状态信息区域 -->
+        <view class="device-status-container">
+          <!-- 在线状态 -->
+          <view class="status-info">
+            <view class="status-dot online-dot"></view>
+            <text class="status-text">在线</text>
+            <text class="status-num online-num">{{ item.online }}</text>
+          </view>
+          
+          <!-- 离线状态 -->
+          <view class="status-info">
+            <view class="status-dot offline-dot"></view>
+            <text class="status-text">离线</text>
+            <text class="status-num offline-num">{{ item.offline }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
   </view>
 </template>
 
-<script setup>
+<script>
+export default {
+  data() {
+    return {
+      deviceList: [
+        { 
+          name: '监控设备', 
+          online: 8, 
+          offline: 2, 
+          type: 'monitor',
+          icon: '/static/icons/camera.png',
+          alerts: 1,
+          onlineRate: 80
+        },
+        { 
+          name: '采集设备', 
+          online: 14, 
+          offline: 1, 
+          type: 'sensor',
+          icon: '/static/icons/sensor.png',
+          alerts: 2,
+          onlineRate: 93
+        },
+        { 
+          name: '控制设备', 
+          online: 10, 
+          offline: 1, 
+          type: 'control',
+          icon: '/static/icons/control.png',
+          alerts: 0,
+          onlineRate: 91
+        },
+        { 
+          name: '灌溉设备', 
+          online: 6, 
+          offline: 0, 
+          type: 'irrigation',
+          icon: '/static/icons/water.png',
+          alerts: 0,
+          onlineRate: 100
+        },
+        { 
+          name: '农机设备', 
+          online: 3, 
+          offline: 1, 
+          type: 'tractor',
+          icon: '/static/icons/tractor.png',
+          alerts: 0,
+          onlineRate: 75
+        }
+      ],
+      currentPlot: '东区智慧农场'
+    }
+  },
+  
+  computed: {
+    totalDevices() {
+      return this.deviceList.reduce((sum, device) => sum + device.online + device.offline, 0)
+    },
+    
+    onlineDevices() {
+      return this.deviceList.reduce((sum, device) => sum + device.online, 0)
+    },
+    
+    offlineDevices() {
+      return this.deviceList.reduce((sum, device) => sum + device.offline, 0)
+    },
+    
+    alertDevices() {
+      return this.deviceList.reduce((sum, device) => sum + device.alerts, 0)
+    }
+  },
+  
+  onLoad() {
+    // 页面加载时获取设备数据
+    this.fetchDeviceData()
+  },
+  
+  methods: {
+    // 获取设备数据
+    fetchDeviceData() {
+      // 实际开发中替换为API请求
+      // 模拟异步请求
+      setTimeout(() => {
+        // 这里只是示例,实际开发中应从API获取数据
+        console.log('设备数据加载完成')
+        this.calculateOnlineRates()
+      }, 500)
+    },
+    
+    // 计算在线率
+    calculateOnlineRates() {
+      this.deviceList.forEach(item => {
+        const total = item.online + item.offline
+        if (total > 0) {
+          item.onlineRate = Math.round((item.online / total) * 100)
+        } else {
+          item.onlineRate = 0
+        }
+      })
+    },
+    
+    // 跳转到对应设备列表页面
+    navigateToDeviceList(type) {
+      uni.navigateTo({
+        url: '/pages/device-list/index?type=' + type
+      })
+    },
+    
+    // 切换地块
+    changePlot() {
+      // 暂时仅展示UI,后续实现地块切换逻辑
+      uni.showToast({
+        title: '地块切换功能开发中',
+        icon: 'none'
+      })
+    }
+  },
+
+  // 页面导航配置
+  onShow() {
+    uni.setNavigationBarTitle({
+      title: '设备中心'
+    })
+  }
+}
 </script>
 
 <style scoped>
+
+
 .container {
-  padding: 40rpx;
+  padding: 30rpx;
+  background-color: #F9FCFA;
+  min-height: 100vh;
+  box-sizing: border-box;
+}
+
+/* 地块选择区域 */
+.plot-section {
+  background-color: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 26rpx 30rpx;
+  margin-bottom: 30rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.04);
+}
+
+.plot-text {
+  font-size: 28rpx;
+  color: #606060;
+}
+
+.plot-name {
   font-size: 32rpx;
+  color: #333333;
+  font-weight: 600;
+  margin: 0 12rpx;
+}
+
+/* 顶部概览统计区域 */
+.overview-section {
+  background-color: #F2F7F3;
+  border-radius: 20rpx;
+  padding: 36rpx 40rpx;
+  margin-bottom: 34rpx;
+  display: flex;
+  justify-content: space-between;
+  box-shadow: 0 6rpx 16rpx rgba(0, 0, 0, 0.03);
+}
+
+.stat-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+}
+
+.stat-icon-wrapper {
+  width: 90rpx;
+  height: 90rpx;
+  border-radius: 50%;
+  background-color: #F0F0F0;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-bottom: 14rpx;
+  box-shadow: 0 6rpx 12rpx rgba(0, 0, 0, 0.04);
+}
+
+.stat-icon-wrapper.online {
+  background: linear-gradient(135deg, #E8F5EA, #D6ECD8);
+}
+
+.stat-icon-wrapper.offline {
+  background: linear-gradient(135deg, #FDEAEA, #FDDCDC);
+}
+
+.stat-icon-wrapper.alert {
+  background: linear-gradient(135deg, #FFF6E5, #FFEFD0);
+}
+
+.stat-icon-img {
+  width: 48rpx;
+  height: 48rpx;
+  display: block;
+}
+
+.stat-value {
+  font-size: 38rpx;
+  color: #333333;
+  font-weight: 600;
+  margin: 8rpx 0;
+  line-height: 1.2;
+}
+
+.stat-value.online {
+  color: #4CAF50;
+}
+
+.stat-value.offline {
+  color: #F44336;
+}
+
+.stat-value.alert {
+  color: #FF9800;
+}
+
+.stat-label {
+  font-size: 24rpx;
+  color: #888888;
+  font-weight: 400;
+}
+
+/* 设备网格区域 */
+.device-grid {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  width: 100%;
+}
+
+.device-card {
+  width: 48%;
+  border-radius: 24rpx;  /* 加大卡片圆角 */
+  background-color: #FFFFFF;
+  box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.05);  /* 柔和阴影 */
+  padding: 30rpx 0 24rpx 0;
+  margin-bottom: 30rpx;  /* 增加卡片间距 */
+  position: relative;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  transition: all 0.25s ease;
+}
+
+.device-card-hover {
+  background-color: #f2fef6;
+  box-shadow: 0 12rpx 32rpx rgba(59, 180, 74, 0.1);
+  transform: translateY(-3rpx);
+}
+
+.device-card.has-alert {
+  box-shadow: 0 10rpx 30rpx rgba(245, 108, 108, 0.08);
+}
+
+/* 告警角标 */
+.alert-badge {
+  position: absolute;
+  top: 16rpx;  /* 6px */
+  right: 16rpx;  /* 6px */
+  width: 40rpx;  /* 20px */
+  height: 40rpx;  /* 20px */
+  border-radius: 50%;
+  background-color: #F56C6C;
+  color: white;
+  font-size: 24rpx;  /* 12px */
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 8rpx rgba(245, 108, 108, 0.3);
+  z-index: 2;
+}
+
+/* 设备图标 */
+.device-icon-container {
+  width: 72rpx;  /* 36px */
+  height: 72rpx;  /* 36px */
+  border-radius: 50%;
+  background: linear-gradient(135deg, #66CC6A 0%, #3BB44A 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 18rpx;
+  box-shadow: 0 6rpx 12rpx rgba(59, 180, 74, 0.15);
+}
+
+.device-icon {
+  width: 40rpx;
+  height: 40rpx;
+  filter: brightness(0) invert(1);
+}
+
+/* 设备名称 */
+.device-name {
+  font-size: 32rpx;  /* 16px */
+  color: #333333;
+  font-weight: 600;
+  text-align: center;
+  margin-bottom: 24rpx;
+  padding: 0 20rpx;
+}
+
+/* 设备状态信息区域 */
+.device-status-container {
+  width: 100%;
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  padding: 16rpx 0;
+  border-top: 1rpx solid #EEEEEE;
+}
+
+/* 状态信息项 */
+.status-info {
+  display: flex;
+  align-items: center;
+  margin: 0 16rpx;
+}
+
+/* 状态指示点 */
+.status-dot {
+  width: 8rpx;
+  height: 8rpx;
+  border-radius: 50%;
+  margin-right: 6rpx;
+}
+
+.online-dot {
+  background-color: #4CAF50;
+}
+
+.offline-dot {
+  background-color: #F56C6C;
+}
+
+/* 状态文本 */
+.status-text {
+  font-size: 26rpx;
+  color: #999999;
+  margin-right: 6rpx;
+}
+
+/* 状态数字 */
+.status-num {
+  font-size: 26rpx;
+  font-weight: 600;
+}
+
+.online-num {
+  color: #4CAF50;
+}
+
+.offline-num {
+  color: #F56C6C;
 }
 </style>

+ 2 - 2
pages/knowledge/detail.vue

@@ -32,7 +32,7 @@
           indicator-color="rgba(255,255,255,0.5)"
           @change="handleSwiperChange">
           <!-- 有实际图片时展示 -->
-          <swiper-item v-for="(slide, index) in articleImages" :key="'img-' + index" v-if="articleImages.length > 0">
+          <swiper-item v-for="(slide, index) in articleImages" :key="slide.id" v-if="articleImages.length > 0">
             <view class="swiper-item-container" :style="{ backgroundColor: slide.color || '#4CAF50' }">
               <image class="swiper-image" :src="slide.url" mode="aspectFill" v-if="slide.url"></image>
               <view class="pattern-overlay"></view>
@@ -44,7 +44,7 @@
           </swiper-item>
           
           <!-- 没有图片时使用生成的轮播图 -->
-          <swiper-item v-for="(slide, index) in carouselImages" :key="'gen-' + index" v-if="articleImages.length === 0">
+          <swiper-item v-for="(slide, index) in carouselImages" :key="slide.id" v-if="articleImages.length === 0">
             <view class="swiper-item-container" :style="{ backgroundColor: slide.color || '#4CAF50' }">
               <view class="pattern-overlay"></view>
               <view class="agri-icon" v-html="slide.icon"></view>

+ 4 - 4
pages/knowledge/index.vue

@@ -249,8 +249,8 @@ export default {
           }});
 		console.log("result",result);
         // 请求成功
-        if (result.data.code === 200 && result.data.data) {
-          const newData = result.data.data;
+        if (result.data.code === 200 && result.data.rows) {
+          const newData = result.data.rows;
           
           // 如果是刷新或第一页,则替换列表
           if (refresh || this.techPage === 1) {
@@ -308,8 +308,8 @@ export default {
         })
         
         // 请求成功
-        if (result.data.code === 200 && result.data.data) {
-          const newData = result.data.data;
+        if (result.data.code === 200 && result.data.rows) {
+          const newData = result.data.rows;
           
           // 如果是刷新或第一页,则替换列表
           if (refresh || this.policyPage === 1) {

+ 101 - 103
pages/settings/index.vue

@@ -61,117 +61,115 @@
   </view>
 </template>
 
-<script setup>
-import { ref } from 'vue'
-import { onShow } from '@dcloudio/uni-app'
-
-// 通知设置状态
-const notifications = ref({
-  system: true,
-  crop: true,
-  weather: true
-})
-
-// 缓存大小
-const cacheSize = ref('0.00MB')
-
-// 版本号
-const version = ref('1.0.0')
-
-// 获取缓存大小
-const getCacheSize = () => {
-  // 实际项目中需要调用相关API获取缓存大小
-  uni.getStorageInfo({
-    success: (res) => {
-      const size = (res.currentSize / 1024).toFixed(2)
-      cacheSize.value = size + 'MB'
+<script>
+export default {
+  data() {
+    return {
+      // 通知设置状态
+      notifications: {
+        system: true,
+        crop: true,
+        weather: true
+      },
+      // 缓存大小
+      cacheSize: '0.00MB',
+      // 版本号
+      version: '1.0.0'
     }
-  })
-}
-
-// 清除缓存
-const handleClearCache = () => {
-  uni.showModal({
-    title: '提示',
-    content: '确定要清除缓存吗?',
-    success: (res) => {
-      if (res.confirm) {
-        uni.clearStorage({
-          success: () => {
-            uni.showToast({
-              title: '清除成功',
-              icon: 'success'
+  },
+
+  onShow() {
+    this.getCacheSize()
+    this.loadNotificationSettings()
+  },
+
+  methods: {
+    // 获取缓存大小
+    getCacheSize() {
+      uni.getStorageInfo({
+        success: (res) => {
+          const size = (res.currentSize / 1024).toFixed(2)
+          this.cacheSize = size + 'MB'
+        }
+      })
+    },
+
+    // 清除缓存
+    handleClearCache() {
+      uni.showModal({
+        title: '提示',
+        content: '确定要清除缓存吗?',
+        success: (res) => {
+          if (res.confirm) {
+            uni.clearStorage({
+              success: () => {
+                uni.showToast({
+                  title: '清除成功',
+                  icon: 'success'
+                })
+                this.getCacheSize()
+              }
             })
-            getCacheSize()
           }
+        }
+      })
+    },
+
+    // 检查更新
+    checkUpdate() {
+      uni.showLoading({ title: '检查更新中...' })
+      setTimeout(() => {
+        uni.hideLoading()
+        uni.showToast({
+          title: '已是最新版本',
+          icon: 'none'
         })
+      }, 1500)
+    },
+
+    // 通知设置处理函数
+    handleSystemNotification(e) {
+      this.notifications.system = e.detail.value
+      this.saveNotificationSettings()
+    },
+    handleCropNotification(e) {
+      this.notifications.crop = e.detail.value
+      this.saveNotificationSettings()
+    },
+    handleWeatherNotification(e) {
+      this.notifications.weather = e.detail.value
+      this.saveNotificationSettings()
+    },
+
+    // 保存通知设置
+    saveNotificationSettings() {
+      uni.setStorageSync('notifications', this.notifications)
+    },
+
+    // 加载通知设置
+    loadNotificationSettings() {
+      const savedSettings = uni.getStorageSync('notifications')
+      if (savedSettings) {
+        this.notifications = savedSettings
       }
+    },
+
+    // 页面跳转
+    navigateToAboutUs() {
+      uni.navigateTo({ url: '/pages/about/index' })
+    },
+    navigateToPrivacy() {
+      uni.navigateTo({ url: '/pages/privacy/index' })
+    },
+
+    // 拨打客服电话
+    handleContact() {
+      uni.makePhoneCall({
+        phoneNumber: '400-xxx-xxxx' // 替换为实际的客服电话
+      })
     }
-  })
-}
-
-// 检查更新
-const checkUpdate = () => {
-  // 实际项目中需要调用后端API检查更新
-  uni.showLoading({ title: '检查更新中...' })
-  setTimeout(() => {
-    uni.hideLoading()
-    uni.showToast({
-      title: '已是最新版本',
-      icon: 'none'
-    })
-  }, 1500)
-}
-
-// 通知设置处理函数
-const handleSystemNotification = (e) => {
-  notifications.value.system = e.detail.value
-  saveNotificationSettings()
-}
-
-const handleCropNotification = (e) => {
-  notifications.value.crop = e.detail.value
-  saveNotificationSettings()
-}
-
-const handleWeatherNotification = (e) => {
-  notifications.value.weather = e.detail.value
-  saveNotificationSettings()
-}
-
-// 保存通知设置
-const saveNotificationSettings = () => {
-  uni.setStorageSync('notifications', notifications.value)
-}
-
-// 加载通知设置
-const loadNotificationSettings = () => {
-  const savedSettings = uni.getStorageSync('notifications')
-  if (savedSettings) {
-    notifications.value = savedSettings
   }
 }
-
-// 页面跳转
-const navigateToAboutUs = () => {
-  uni.navigateTo({ url: '/pages/about/index' })
-}
-
-const navigateToPrivacy = () => {
-  uni.navigateTo({ url: '/pages/privacy/index' })
-}
-
-const handleContact = () => {
-  uni.makePhoneCall({
-    phoneNumber: '400-xxx-xxxx' // 替换为实际的客服电话
-  })
-}
-
-// 页面显示时加载数据
-onShow(() => {
-  getCacheSize()
-  loadNotificationSettings()
-})
 </script>
 
 <style lang="scss">

BIN
static/icons/Voice_icon.png


BIN
static/icons/action_icon.png


BIN
static/icons/arrow_down_icon.png


BIN
static/icons/arrow_left_icon.png


BIN
static/icons/arrow_right_icon.png


BIN
static/icons/arrow_up_icon.png


BIN
static/icons/camera_icon.png


BIN
static/icons/clock_icon.png


+ 1 - 0
static/icons/device-default.png

@@ -0,0 +1 @@
+ 

BIN
static/icons/device_alert.png


BIN
static/icons/device_icon.png


BIN
static/icons/device_num.png


BIN
static/icons/device_offline.png


BIN
static/icons/device_online.png


BIN
static/icons/info_icon.png


BIN
static/icons/location_icon.png


BIN
static/icons/muted_icon.png


BIN
static/icons/pause_icon.png


BIN
static/icons/play_icon.png


BIN
static/icons/refresh_icon.png


BIN
static/icons/resetPTZ_icon.png


BIN
static/icons/resize_icon.png


BIN
static/icons/signal_icon.png


BIN
static/icons/success_icon.png


BIN
static/icons/unmuted_icon.png


BIN
static/icons/warning_icon.png


BIN
static/icons/zoom01_icon.png


BIN
static/icons/zoom02_icon.png


+ 0 - 0
static/images/video-placeholder.jpg


+ 11 - 7
store/index.js

@@ -1,8 +1,12 @@
-import { createStore } from 'vuex';
-import storage from '@/utils/storage';
+// store/index.js (Vue 2)
+import Vue from 'vue'
+import Vuex from 'vuex'
+import storage from '@/utils/storage'
 
-const store = createStore({
-  state: () => ({
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+  state: {
     isShowToast: false, // 是否在展示Toast中
     remark: [], // 填写订单备注
     shareLink: "", // 分享链接
@@ -13,7 +17,7 @@ const store = createStore({
     uuid: storage.getUuid(),
     token: "",
     userName: "",
-  }),
+  },
 
   mutations: {
     login(state, userInfo) {
@@ -36,6 +40,6 @@ const store = createStore({
   actions: {
     // 可在此添加异步登录逻辑
   },
-});
+})
 
-export default store;
+export default store

+ 3 - 0
uni.scss

@@ -0,0 +1,3 @@
+/* 页面左右间距 */
+@import "uview-ui/theme.scss";
+

+ 7 - 4
utils/storage.js

@@ -2,6 +2,7 @@ let isDev = process.env.NODE_ENV === "development";
 
 const UUID = isDev ? "uuid_key_dev" : "uuid_key";
 const CURRENT_PLOT = isDev ? "current_plot_dev" : "current_plot";
+const CURRENT_USER_PLOTS_LIST = isDev ? "user_plots_list_dev" : "user_plots_list";
 const HAS_LOGIN = isDev ? "has_login_key_dev" : "has_login_key";
 const ACCESS_TOKEN = isDev ? "access_token_key_dev" : "access_token_key";
 const REFRESH_TOKEN = isDev ? "refresh_token_key_dev" : "refresh_token_key";
@@ -64,6 +65,12 @@ export default {
 	setUserInfo(val) {
 		uni.setStorageSync(USER_INFO, val);
 	},
+	setCurrentUserPlotsList(val){
+		uni.setStorageSync(CURRENT_USER_PLOTS_LIST,val)
+	},
+	getCurrentUserPlotsList(){
+		return uni.getStorageSync(CURRENT_USER_PLOTS_LIST);
+	},
 	// 获取用户信息
 	getUserInfo() {
 		return uni.getStorageSync(USER_INFO);
@@ -102,10 +109,6 @@ export default {
 	getAccessToken() {
 		return uni.getStorageSync(ACCESS_TOKEN);
 	},
-	// 后退购物车
-	setCartBackbtn(val) {
-		uni.setStorageSync(CART_BACKBTN, val);
-	},
 
 	// 删除token
 	removeAccessToken() {