Browse Source

Merge branch 'master' of http://code.nyzhwlw.com:10202/yf_lj/bigDataApp

leo 2 ngày trước cách đây
mục cha
commit
6500e66ae7
41 tập tin đã thay đổi với 4926 bổ sung169 xóa
  1. 2 2
      manifest.json
  2. 24 0
      pages.json
  3. 133 0
      pages/bzy/components/DeviceCard.vue
  4. 48 0
      pages/bzy/components/DeviceControl.vue
  5. 1197 0
      pages/bzy/components/deviceData.vue
  6. 221 0
      pages/bzy/components/floating-button.vue
  7. 216 0
      pages/bzy/components/pestArchive.vue
  8. 120 0
      pages/bzy/components/pestDiscern.vue
  9. 416 0
      pages/bzy/components/pestEchart.vue
  10. 218 0
      pages/bzy/components/photoImage.vue
  11. 670 0
      pages/bzy/detail.vue
  12. 532 0
      pages/bzy/deviceControl.vue
  13. 819 0
      pages/bzy/devicePhoto.vue
  14. 37 13
      pages/cb/bzy/equip-set/equip-set.vue
  15. 22 11
      pages/cb/equip-detail/equip-detail.vue
  16. BIN
      pages/cbd/assets/cbd.png
  17. BIN
      pages/cbd/assets/copy.png
  18. BIN
      pages/cbd/assets/edit.png
  19. BIN
      pages/cbd/assets/editBorder.png
  20. BIN
      pages/cbd/assets/editIcon.png
  21. BIN
      pages/cbd/assets/general.png
  22. BIN
      pages/cbd/assets/offline.png
  23. BIN
      pages/cbd/assets/online.png
  24. BIN
      pages/cbd/assets/photoBorder.png
  25. BIN
      pages/cbd/assets/photoIcon.png
  26. BIN
      pages/cbd/assets/serviceIcon.png
  27. BIN
      pages/cbd/assets/setting.png
  28. BIN
      pages/cbd/assets/settingBorder.png
  29. BIN
      pages/cbd/assets/settingIcon.png
  30. BIN
      pages/cbd/assets/sim.png
  31. BIN
      pages/cbd/assets/simBorder.png
  32. BIN
      pages/cbd/assets/simIcon.png
  33. 3 4
      pages/cbd/components/DeviceCard.vue
  34. 19 29
      pages/cbd/components/deviceData.vue
  35. 1 2
      pages/cbd/components/photoImage.vue
  36. 7 12
      pages/cbd/detail.vue
  37. 145 68
      pages/cbd/devicePhoto.vue
  38. 73 25
      pages/equipList2/index.vue
  39. 1 1
      pages/equipMange/index/index.vue
  40. 1 1
      pages/index/developing.vue
  41. 1 1
      pages/server/index.vue

+ 2 - 2
manifest.json

@@ -2,8 +2,8 @@
     "name" : "云飞智控",
     "appid" : "__UNI__DBA6730",
     "description" : "",
-    "versionName" : "1.15.13",
-    "versionCode" : 11513,
+    "versionName" : "1.15.14",
+    "versionCode" : 11514,
     "transformPx" : false,
     "sassImplementationName" : "node-sass",
     /* 5+App特有相关 */

+ 24 - 0
pages.json

@@ -197,6 +197,30 @@
       }
     },
     {
+      "path": "pages/bzy/detail",
+      "style": {
+        "navigationBarTitleText": "设备详情",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/bzy/devicePhoto",
+      "style": {
+        "navigationBarTitleText": "查看图片",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/bzy/deviceControl",
+      "style": {
+        "navigationBarTitleText": "设备控制",
+        "enablePullDownRefresh": false,
+        "navigationStyle": "custom"
+      }
+    },
+    {
       "path": "pages/my/index/index",
       "style": {
         "navigationBarTitleText": "个人中心",

+ 133 - 0
pages/bzy/components/DeviceCard.vue

@@ -0,0 +1,133 @@
+<template>
+  <view class="device-card__body">
+    <view class="device-card">
+      <view :class=" dataSource.status == 1 ?'online-status':'offline-status'">{{ dataSource.status === 1 ? '在线' : '离线' }}</view>
+      <view class="device-card__title">设备ID</view>
+      <view class="device-card__content">
+        <text class="device-id-text">{{ dataSource.id  }}</text>
+        <image class="copy-icon" :src="copy" @click="copyDeviceId"/>
+      </view>
+    </view>
+    <view class="device-card">
+      <view class="device-card__title">上报时间</view>
+      <view class="device-card__content">{{ dataSource.uptime | timeFormat()}}</view>
+    </view>
+    <view class="device-card">
+      <view class="device-card__title">设备位置</view>
+      <view class="device-card__content address">{{ dataSource.address || '-' }}</view>
+    </view>
+  </view>
+</template>
+<script>
+export default {
+  props: {
+    dataSource: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      copy:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/copy.png',
+      title: ''
+    }
+  },
+  methods: {
+    copyDeviceId() {
+      uni.setClipboardData({
+        data: this.dataSource.id,
+        success: () => {
+          uni.showToast({
+            title: '复制成功',
+            icon: 'success'
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.device-card__body {
+  display: flex;
+  padding: 16px 12px;
+  flex-direction: column;
+  align-items: flex-start;
+  gap: 8px;
+  border-radius: 8px;
+  background: linear-gradient(180deg, #EFF 0%, #FFF 23.56%);
+  margin-top: 34rpx;
+  position: relative;
+  .device-card {
+    display: flex;
+    gap: 16px;
+    .device-card__title {
+      width: 120rpx;
+      color: #4E5969;
+      font-size: 14px;
+      font-style: normal;
+      font-weight: 400;
+      text-align: right;
+      display: flex;
+      align-items: center;
+      justify-content: flex-end;
+    }
+    .online-status {
+      width: 136rpx;
+      height: 56rpx;
+      line-height: 56rpx;
+      position: absolute;
+      text-align: center;
+      top: 0;
+      right: 0;
+      background: url('https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/online1.png') no-repeat center center;
+      background-size: 100% 100%;
+      color: #0bbc58;
+      font-family: "Source Han Sans CN VF";
+      font-size: 28rpx;
+    }
+    .offline-status{
+      width: 136rpx;
+      height: 56rpx;
+      line-height: 56rpx;
+      position: absolute;
+      text-align: center;
+      top: 0;
+      right: 0;
+      background: url('https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/offline.png') no-repeat center center;
+      background-size: 100% 100%;
+      color: #ff3546;
+      font-family: "Source Han Sans CN VF";
+      font-size: 28rpx;
+    }
+    .device-card__content {
+      color: #4E5969;
+      font-size: 14px;
+      font-style: normal;
+      font-weight: 400;
+      display: flex;
+      align-items: center;
+      gap: 8rpx;
+      .device-id-text {
+        flex: 1;
+      }
+      .copy-icon {
+        font-family: 'iconfont';
+        font-size: 32rpx;
+        color: #86909C;
+        cursor: pointer;
+        width: 28rpx;
+        height: 28rpx;
+        &:active {
+          opacity: 0.6;
+        }
+      }
+    }
+    .address {
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+    }
+  }
+}
+</style>

+ 48 - 0
pages/bzy/components/DeviceControl.vue

@@ -0,0 +1,48 @@
+<template>
+  <view class="device-control-container">
+    <view class="device-status">
+      <view class="status-item">
+        <text class="status-label">环境温度</text>
+        <text class="status-value">{{ deviceStatus.temp }}°C</text>
+      </view>
+      <view class="status-item">
+        <text class="status-label">环境湿度</text>
+        <text class="status-value">{{ deviceStatus.humidity }}%</text>
+      </view>
+      <view class="status-item">
+        <text class="status-label">环境光照</text>
+        <text class="status-value">{{ deviceStatus.light }}Lux</text>
+      </view>
+      <view class="status-item">
+        <text class="status-label">环境湿度</text>
+        <text class="status-value">{{ deviceStatus.moisture }}%</text>
+      </view>
+      <view class="status-item">
+        <text class="status-label">环境湿度</text>
+        <text class="status-value">{{ deviceStatus.pest }}%</text>
+      </view>
+    </view>
+  </view>
+</template>
+<script>
+export default {
+  data() {
+    return {
+      deviceStatus: {
+        temp: 25,
+        humidity: 50,
+        light: 800,
+        moisture: 300,
+        pest: 10
+      }
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+.device-control-container {
+  padding: 24rpx;
+  background-color: #F7F8FA;
+  border-radius: 12rpx;
+}
+</style>

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 1197 - 0
pages/bzy/components/deviceData.vue


+ 221 - 0
pages/bzy/components/floating-button.vue

@@ -0,0 +1,221 @@
+<template>
+	<view
+		class="floating-container"
+		:style="{ top: position.y + 'px', left: position.x + 'px' }"
+		@touchstart="onTouchStart"
+		@touchmove="onTouchMove"
+		@touchend="onTouchEnd"
+		@click="onClick"
+	>
+		<view class="floating-icon" :class="{ expanded: isExpanded }">
+			<view class="icon-content">
+				<slot name="icon">
+					<text class="default-icon">+</text>
+				</slot>
+			</view>
+			<view v-if="isExpanded" class="expanded-content">
+				<slot name="expanded">
+				</slot>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'FloatingButton',
+	props: {
+		// 初始位置距离右边的距离
+		right: {
+			type: Number,
+			default: 20
+		},
+		// 初始位置距离底部的距离
+		bottom: {
+			type: Number,
+			default: 100
+		},
+		// 按钮大小
+		size: {
+			type: Number,
+			default: 50
+		},
+		// 是否可拖拽
+		draggable: {
+			type: Boolean,
+			default: true
+		}
+	},
+	data() {
+		return {
+			position: {
+				x: 0,
+				y: 0
+			},
+			isDragging: false,
+			isExpanded: false,
+			startX: 0,
+			startY: 0,
+			lastX: 0,
+			lastY: 0,
+			clickStartTime: 0,
+			hasMoved: false
+		};
+	},
+	mounted() {
+		this.initPosition();
+	},
+	methods: {
+		// 初始化位置
+		initPosition() {
+			const systemInfo = uni.getSystemInfoSync();
+			const screenWidth = systemInfo.windowWidth;
+			const screenHeight = systemInfo.windowHeight;
+
+			this.position = {
+				x: screenWidth - this.right - this.size,
+				y: screenHeight - this.bottom - this.size
+			};
+		},
+
+		// 触摸开始
+		onTouchStart(e) {
+			if (!this.draggable) return;
+
+			const touch = e.touches[0];
+			this.startX = touch.clientX;
+			this.startY = touch.clientY;
+			this.lastX = this.position.x;
+			this.lastY = this.position.y;
+			this.isDragging = true;
+			this.hasMoved = false;
+			this.clickStartTime = Date.now();
+		},
+
+		// 触摸移动
+		onTouchMove(e) {
+			if (!this.draggable || !this.isDragging) return;
+
+			e.preventDefault();
+
+			const touch = e.touches[0];
+			const deltaX = touch.clientX - this.startX;
+			const deltaY = touch.clientY - this.startY;
+
+			// 如果移动超过5像素,认为是拖拽操作
+			if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
+				this.hasMoved = true;
+			}
+
+			const systemInfo = uni.getSystemInfoSync();
+			const screenWidth = systemInfo.windowWidth;
+			const screenHeight = systemInfo.windowHeight;
+
+			// 计算新位置,限制在屏幕范围内
+			let newX = this.lastX + deltaX;
+			let newY = this.lastY + deltaY;
+
+			// 边界限制
+			newX = Math.max(0, Math.min(newX, screenWidth - this.size));
+			newY = Math.max(0, Math.min(newY, screenHeight - this.size));
+
+			this.position = {
+				x: newX,
+				y: newY
+			};
+		},
+
+		// 触摸结束
+		onTouchEnd(e) {
+			if (!this.draggable) return;
+
+			this.isDragging = false;
+
+			// 自动吸附到屏幕边缘
+			this.snapToEdge();
+		},
+
+		// 吸附到屏幕左右边缘
+		snapToEdge() {
+			const systemInfo = uni.getSystemInfoSync();
+			const screenWidth = systemInfo.windowWidth;
+
+			// 判断当前在左半屏还是右半屏
+			const centerX = this.position.x + this.size / 2;
+
+			if (centerX < screenWidth / 2) {
+				// 吸附到左边
+				this.position.x = 10;
+			} else {
+				// 吸附到右边
+				this.position.x = screenWidth - this.size - 10;
+			}
+		},
+
+		// 点击事件
+		onClick(e) {
+			// 如果发生了拖拽移动,不触发点击事件
+			const clickDuration = Date.now() - this.clickStartTime;
+
+			if (this.hasMoved || clickDuration > 300) {
+				return;
+			}
+
+			// 切换展开/收起状态
+			this.isExpanded = !this.isExpanded;
+
+			// 触发父组件事件
+			this.$emit(this.isExpanded ? 'expand' : 'collapse', {
+				expanded: this.isExpanded
+			});
+		},
+
+		// 手动展开
+		expand() {
+			this.isExpanded = true;
+		},
+
+		// 手动收起
+		collapse() {
+			this.isExpanded = false;
+		}
+	}
+};
+</script>
+
+<style scoped>
+.floating-container {
+	position: fixed;
+	z-index: 9999;
+	width: 50px;
+	height: 50px;
+}
+.expanded-content{
+	position: absolute;
+}
+.floating-icon {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	transition: all 0.3s ease;
+}
+
+.icon-content {
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 50%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	transition: all 0.3s ease;
+}
+
+.default-icon {
+	font-size: 24px;
+	color: #ffffff;
+	font-weight: bold;
+}
+
+</style>

+ 216 - 0
pages/bzy/components/pestArchive.vue

@@ -0,0 +1,216 @@
+<template>
+  <view class="pest-archive" v-if="pest_info.name">
+    <view class="pest-archive__header">
+      <view class="pest-archive__title">虫害档案</view>
+    </view>
+
+      <view
+        class="pest-card"
+        @tap="handleCardClick()"
+      >
+        <view class="pest-card__image">
+          <image :src="pest_info.img_urls" mode="aspectFill" class="pest-img"></image>
+        </view>
+        <view class="pest-card__info">
+          <view class="pest-card__top">
+            <view class="pest-card__name">
+              名称
+            </view>
+            <view class="pest-card__status">
+              {{ pest_info.name || '未知' }}
+            </view>
+          </view>
+          <view class="pest-card__top">
+            <view class="pest-card__name">
+              害虫信息
+            </view>
+            <view class="pest-card__status" style="height:200rpx;overflow: auto;">
+              {{ pest_info.prevention || '未知' }}
+            </view>
+          </view>
+        </view>
+      </view>
+      <u-popup v-model="showDetail" mode="bottom" border-radius="32">
+        <view class="pest-card__detail">
+          <view class="pest-card__image">
+            <image :src="pest_info.img_urls" mode="aspectFill" class="pest-img"></image>
+          </view>
+          <view>
+            {{ pest_info.name || '未知' }}
+          </view>
+          <view>
+            {{ pest_info.prevention || '未知' }}
+          </view>
+        </view>
+      </u-popup>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    scrollHeight: {
+      type: String,
+      default: '500rpx'
+    },
+    pest_info: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      showDetail: false,
+    }
+  },
+  methods: {
+    showPestDetail(){
+      this.showDetail = true;
+    },
+    handleCardClick() {
+      this.showPestDetail()
+      this.$emit('click', this.pest_info)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.pest-archive {
+  width: 100%;
+  padding: 32rpx;
+  margin-top: 24rpx;
+  box-sizing: border-box;
+  background: #fff;
+  border-radius: 16rpx;
+}
+
+.pest-archive__header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 24rpx;
+}
+
+.pest-archive__title {
+  font-size: 32rpx;
+  color: #042118;
+  font-family: 'Source Han Sans CN VF';
+  font-weight: 500;
+}
+
+.pest-archive__add {
+  width: 56rpx;
+  height: 56rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #00D26A 0%, #00B55A 100%);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 12rpx rgba(0, 210, 106, 0.3);
+
+  .add-icon {
+    font-size: 40rpx;
+    color: #fff;
+    line-height: 1;
+    font-weight: 300;
+  }
+}
+
+.pest-card__detail{
+  height: 70vh;
+  padding: 40rpx;
+}
+.pest-archive__list {
+  width: 100%;
+}
+
+.pest-card {
+  display: flex;
+  padding: 24rpx;
+  margin-bottom: 16rpx;
+  background: #F7F8F9;
+  border-radius: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.04);
+
+  &:last-child {
+    margin-bottom: 0;
+  }
+
+  &:active {
+    opacity: 0.8;
+  }
+}
+
+.pest-card__image {
+  width: 200rpx;
+  height: 200rpx;
+  border-radius: 12rpx;
+  overflow: hidden;
+  flex-shrink: 0;
+  margin-right: 20rpx;
+  background: #e0e0e0;
+
+  .pest-img {
+    width: 100%;
+    height: 100%;
+  }
+}
+
+.pest-card__info {
+  padding: 4rpx 0;
+}
+
+.pest-card__top {
+  display: flex;
+}
+
+.pest-card__name {
+  font-size: 26rpx;
+  color: #999999;
+  font-family: "Source Han Sans CN VF";
+  font-weight: 400;
+  width: 110rpx;
+}
+
+.pest-card__status {
+  width: 270rpx;
+  color: #303133;
+  font-size: 26srpx;
+  font-weight: 500;
+
+  &.status--handled {
+    background: rgba(0, 210, 106, 0.15);
+    color: #00B55A;
+  }
+
+  &.status--handling {
+    background: rgba(255, 149, 0, 0.15);
+    color: #FF9500;
+  }
+
+  &.status--pending {
+    background: rgba(255, 53, 70, 0.15);
+    color: #FF3546;
+  }
+}
+
+.pest-card__time {
+  font-size: 24rpx;
+  color: #86909C;
+  margin-top: 8rpx;
+}
+
+.pest-card__desc {
+  font-size: 26rpx;
+  color: #4E5969;
+  margin-top: 12rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  line-clamp: 2;
+  -webkit-box-orient: vertical;
+  line-height: 1.5;
+}
+</style>

+ 120 - 0
pages/bzy/components/pestDiscern.vue

@@ -0,0 +1,120 @@
+<template>
+  <view class="pest-discern" v-if="pests.length">
+    <view class="pest-discern__header">
+      <view class="pest-discern__title">
+        虫害识别
+      </view>
+    </view>
+    <view class="pest-discern__content">
+      <view class="pest-discern__item" v-for="(item,index) in pests" :key="index">
+        <view class="pest-discern__item-title">
+          {{ item.name }}
+        </view>
+        <view class="pest-discern__item-content">
+          <u-line-progress :active-color="item.color" :percent="item.percent" :show-percent="false" height="8"></u-line-progress>
+        </view>
+        <view class="pest-discern__item-count">
+          {{ item.count }}
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+<script>
+export default {
+  props: {
+    total: {
+      type: Number,
+      default: 0
+    },
+    pest_order: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  watch:{
+    pest_order:{
+      handler(val){
+        this.pests = [];
+        let index = 0;
+        for(let key in val){
+          index++;
+          this.pests.push({
+            color: this.colors[index],
+            name: key,
+            count: val[key],
+            percent: (val[key] / this.total) * 100
+          })
+        }
+      },
+      deep: true
+    }
+  },
+  data() {
+    return {
+      colors:[
+        '#FF5951',
+        '#66EDED',
+        '#E67B3E',
+        '#6DE28B',
+        '#FFC97A',
+        '#E7EB4B',
+        '#1561F3',
+        '#FA73F5',
+        '#159AFF',
+        '#FA73F5'
+      ],
+      pests: []
+    }
+  }
+}
+</script>
+<style lang="scss" scoped>
+.pest-discern {
+  width: 100%;
+  padding: 32rpx;
+  box-sizing: border-box;
+  background: #fff;
+  border-radius: 16rpx;
+}
+.pest-discern__header {
+  padding: 0rpx 0rpx 20rpx 0rpx;
+  font-size: 32rpx;
+  color: #042118;
+  font-family: 'Source Han Sans CN VF';
+  font-weight: 700;
+}
+.pest-discern__title {
+  font-size: 28rpx;
+  color: #042118;
+  font-family: 'Source Han Sans CN VF';
+  font-weight: 500;
+}
+.pest-discern__item {
+  display: flex;
+  width: 100%;
+  margin-bottom: 20rpx;
+  align-items: center;
+  .pest-discern__item-title{
+    width: 120rpx;
+    font-size: 28rpx;
+    color: #042118;
+    font-family: 'Source Han Sans CN VF';
+    font-weight: 400;
+    // 超出隐藏
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .pest-discern__item-content{
+    flex: 1;
+    margin: 0 20rpx;
+  }
+  .pest-discern__item-count{
+    width: 80rpx;
+    font-size: 28rpx;
+    color: #042118;
+    text-align: right;
+  }
+}
+</style>

+ 416 - 0
pages/bzy/components/pestEchart.vue

@@ -0,0 +1,416 @@
+<template>
+  <view class="pest-echart" v-show="tabs.length">
+    <view class="pest-echart__header">
+      <view class="tab-list">
+        <view
+          v-for="(tab, index) in tabs"
+          :key="index"
+          class="tab-item"
+          :class="{ active: activeTab === index }"
+          @click="switchTab(index)"
+        >
+          {{ tab.name }}
+        </view>
+      </view>
+    </view>
+
+    <view class="pest-echart__content" v-show="dayData.length">
+      <!-- 三个关键时期 -->
+      <view class="period-section">
+        <view class="period-item">
+          <view class="period-label">始见期</view>
+          <view class="period-value">{{ periodData.firstDate }}</view>
+        </view>
+        <view class="period-item">
+          <view class="period-label">高峰期</view>
+          <view class="period-value">{{ periodData.peakDate }}</view>
+        </view>
+        <view class="period-item">
+          <view class="period-label">终见期</view>
+          <view class="period-value">{{ periodData.lastDate }}</view>
+        </view>
+      </view>
+
+      <!-- 图表区域 -->
+      <view class="chart-container">
+        <!-- <canvas
+          canvas-id="pestChart"
+          id="pestChart"
+          class="charts"
+          :style="{'width':cWidth*pixelRatio+'px','height':cHeight*pixelRatio+'px', 'transform': 'scale('+(1/pixelRatio)+')','margin-left':-cWidth*(pixelRatio-1)/2+'px','margin-top':-cHeight*(pixelRatio-1)/2+'px'}"
+          @touchstart="touchChart($event)"
+          @touchmove="moveChart($event)"
+          @touchend="touchEndChart($event)"
+          disable-scroll=true
+        ></canvas> -->
+        <qiun-data-charts type="line" :chartData="chartData" :opts="opts" :canvas2d="true" :inScrollView="true" :ontouch="true" />
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import uCharts from '../../../components/js_sdk/u-charts/u-charts/u-charts.js';
+
+let chartInstance = null;
+
+export default {
+  name: 'PestEchart',
+  props:{
+    pest_order:{
+      type: Object,
+      default: () => ({})
+    },
+    endDate: {
+      type: String,
+      default: ''
+    },
+    d_id: {
+      type: String | Number,
+      default: ''
+    },
+    day:{
+      type: Array,
+      default: () => []
+    },
+    pest:{
+      type: Array,
+      default: () => []
+    },
+  },
+  data() {
+    return {
+      tabs: [],
+      currentPest:'',
+      activeTab: 0,
+      dayData:[],
+      // 三个关键时期数据
+      periodData: {
+        firstDate: '-',
+        peakDate: '-',
+        lastDate: '-'
+      },
+      chartData: {},
+      // canvas 尺寸配置
+      cWidth: 650,
+      cHeight: 400,
+      pixelRatio: 1,
+      opts: {
+        type: 'line',
+        xAxis: {
+          disableGrid: true,
+          itemCount: 3,
+          scrollShow: true
+        },
+        yAxis: {
+          disableGrid: false,
+          gridType: 'dash',
+          gridColor: '#E5E5E5',
+          splitNumber: 5,
+          min: 0,
+          minInterval: 1,
+          axisLine: true,
+          data: [{
+            min: 0,
+            toFixed: 0
+          }]
+        },
+        extra: {
+          line: {
+            type: 'curve'
+          },
+          tooltip: {
+            format: {
+              name: '',
+              value: (val) => Math.round(val)
+            }
+          }
+        },
+        legend: {
+        },
+        enableScroll: true
+      },
+    };
+  },
+  watch:{
+    pest_order:{
+      handler(val){
+        this.tabs = [];
+        for(let key in val){
+          this.tabs.push({
+            name: key,
+          })
+        }
+        const name = this.tabs[0]?.name;
+        this.currentPest = name;
+        if(this.currentPest){
+          this.getPestNameDetail(name);
+          this.setChartData();
+        }
+      },
+      deep: true
+    },
+    d_id:{
+      handler(val){
+        val && this.setChartData();
+      },
+      deep: true
+    },
+    endDate:{
+      handler(val){
+        val && this.setChartData();
+      },
+      deep: true
+    },
+    day:{
+      handler(){
+        this.initChart();
+      },
+      deep: true,
+      immediate: true
+    },
+  },
+  mounted() {
+    this.cWidth = uni.upx2px(650);
+    this.cHeight = uni.upx2px(400);
+    this.pixelRatio = uni.getSystemInfoSync().pixelRatio;
+  },
+  methods: {
+    async getPestNameDetail(name){
+      try{
+        const res = await this.$myRequest({
+          url: '/api/pest_name_detail',
+          method: 'POST',
+          data: {
+            name
+          },
+        });
+        this.deviceInfo = res
+        this.$emit('getInfo',res)
+      }catch(err){
+        this.$emit('getInfo',{})
+      }
+    },
+    async setChartData(){
+      if(!this.currentPest){
+        return;
+      }
+      if(!this.d_id || !this.endDate){
+        return;
+      }
+     const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.cbd_analysis.pest_predict_time',
+        method: 'POST',
+        data: {
+          model:'B',
+          d_id: this.d_id,
+          year: this.endDate.split('-')[0],
+          pest: this.currentPest,
+        },
+      });
+      this.periodData = {
+        firstDate: res[0][0],
+        peakDate: res[1][0],
+        lastDate: res[2][0],
+      }
+    },
+    initChart() {
+      this.$nextTick(() => {
+        this.updateChartsData(0);
+      });
+    },
+    drawChart(index) {
+      const dayData = this.day || [];
+      this.dayData = dayData;
+      const pestData = this.pest[index];
+      const ctx = uni.createCanvasContext('pestChart', this);
+      chartInstance = new uCharts({
+        context: ctx,
+        type: 'line',
+        fontSize: 11,
+        legend: {
+          show: false
+        },
+        background: '#FFFFFF',
+        pixelRatio: this.pixelRatio,
+        animation: true,
+        dataLabel: false,
+        categories: dayData,
+        series: [{
+          name: '',
+          data: pestData
+        }],
+        color: ['#0085FF'],
+        xAxis: {
+          disableGrid: false,
+          boundaryGap: 'justify',
+          axisLine: true,
+          lineColor: '#CCCCCC',
+          fontColor: '#999999'
+        },
+        yAxis: {
+          min: 0,
+          minInterval: 1,
+          splitNumber: 4,
+          axisLine: true,
+          lineColor: '#CCCCCC',
+          fontColor: '#999999',
+          gridType: 'dash',
+          gridColor: '#E5E5E5'
+        },
+        width: this.cWidth * this.pixelRatio,
+        height: this.cHeight * this.pixelRatio,
+        extra: {
+          line: {
+            type: 'curve',
+            width: 2,
+            activeType: 'hollow'
+          },
+          tooltip: {
+            showBox: true,
+            bgOpacity: 0.7
+          }
+        }
+      });
+    },
+    touchChart(e) {
+      if (chartInstance) {
+        chartInstance.scrollStart(e);
+      }
+    },
+    moveChart(e) {
+      if (chartInstance) {
+        chartInstance.scroll(e);
+      }
+    },
+    touchEndChart(e) {
+      if (chartInstance) {
+        chartInstance.scrollEnd(e);
+        chartInstance.showToolTip(e, {
+          format: function(item, category) {
+            return category + ' ' + item.name + ':' + item.data
+          }
+        });
+      }
+    },
+    switchTab(index) {
+      this.activeTab = index;
+      const name = this.tabs[index]?.name;
+      this.currentPest = name
+      this.getPestNameDetail(name);
+      this.setChartData();
+      this.$nextTick(() => {
+        this.updateChartsData(index);
+      });
+    },
+    updateChartsData(index){
+      const dayData = this.day || [];
+      this.dayData = dayData;
+      const pestData = this.pest[index];
+      const lineData = {
+        categories: dayData,
+        series: [
+          {
+            name: '',
+            data: pestData
+          }
+        ]
+      };
+      
+      this.chartData = lineData;
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.pest-echart {
+  margin-top: 24rpx;
+  &__header{
+    margin-bottom: 24rpx;
+  }
+}
+
+.tab-list {
+  width: 100%;
+  overflow-x: scroll;
+  white-space: nowrap;
+  overflow-y: hidden;
+  box-sizing: border-box;
+  -ms-overflow-style: none;
+  scrollbar-width: none;
+}
+
+.tab-item {
+  margin-right: 16rpx;
+  font-size: 28rpx;
+  font-family: 'Source Han Sans CN VF', sans-serif;
+  font-weight: 500;
+  color: #999999;
+  display: inline-block;
+  box-sizing: border-box;
+  background: #ffffff;
+  font-family: "Source Han Sans CN VF";
+  font-size: 28rpx;
+  padding: 10rpx 32rpx;
+  border-radius: 8rpx;
+  &:last-child {
+    margin-right: 0;
+  }
+  &.active {
+    color: #0bbc58;
+    font-weight: 700;
+  }
+  &:first-child.active {
+    color: #0bbc58;
+  }
+}
+
+.pest-echart__content {
+  background: #FFFFFF;
+  border-radius: 16rpx;
+  padding: 0 32rpx 32rpx;
+}
+
+.period-section {
+  display: flex;
+  justify-content: space-between;
+  padding: 24rpx;
+  background: #ffffff;
+  border-radius: 12rpx;
+}
+
+.period-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.period-label {
+  font-size: 28rpx;
+  color: #303133;
+  font-family: 'Source Han Sans CN VF', sans-serif;
+  font-weight: 700;
+  margin-bottom: 8rpx;
+}
+
+.period-value {
+  font-size: 24rpx;
+  color: #999999;
+  font-family: 'Source Han Sans CN VF', sans-serif;
+  font-weight: 400;
+}
+
+.chart-container {
+  position: relative;
+  width: 100%;
+  height: 400rpx;
+  border-radius: 12rpx;
+}
+
+.charts {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 218 - 0
pages/bzy/components/photoImage.vue

@@ -0,0 +1,218 @@
+<template>
+  <view class="photo-image">
+    <view
+      class="photo-image__tabs"
+      v-if="disableShow"
+    >
+      <view
+        class="photo-image__tab"
+        v-for="(pest, index) in pestList"
+        :key="index"
+        :class="{ active: activeIndexList.includes(index) }"
+        @click="changeTab(index)"
+      >
+        <text class="tab-text">{{ pest.pest_name }}({{ pest.pest_num }})</text>
+      </view>
+    </view>
+    <view class="photo-image__content" v-if="images.length">
+      <view class="photo-image__grid">
+        <view class="photo-image__item" v-for="(item, index) in images" :key="index" @click="handleClick(item)">
+          <view class="photo-image__item-img-container">
+            <image
+              class="photo-image__item-img"
+              :src="item.addr"
+              mode="aspectFill"
+            />
+          </view>
+          <view class="photo-image__item-time">
+            {{ formatDate(item.addtime) }}
+          </view>
+        </view>
+      </view>
+    </view>
+    <view v-else>
+      <u-empty text="暂无图片"></u-empty>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props:{
+    disableShow:{
+      type: Boolean,
+      default: true
+    },
+    pestList:{
+      type: Array,
+      default: () => []
+    },
+    images:{
+      type: Array,
+      default: () => []
+    },
+    deviceInfo:{
+      type: Object,
+      default: () => ({})
+    },
+    currentYear:{
+      type: String | Number,
+      default: ''
+    },
+  },
+  data() {
+    return {
+      activeIndexList:[],
+      activePest:[],
+      activeTab: 0,
+      tabs: ['全部', '稻飞虱', '水龟虫', '蝼蛄', '系统','全部', '稻飞虱', '水龟虫', '蝼蛄', '系统'],
+    };
+  },
+  methods: {
+    changeTab(index) {
+      if(this.activeIndexList.includes(index)){
+        this.activePest = this.activePest.filter(item => item !== this.pestList[index])
+        this.activeIndexList = this.activeIndexList.filter(item => item !== index)
+      }else{
+        this.activeIndexList.push(index)
+        this.activePest.push(this.pestList[index])
+      }
+      this.$emit('changeTab', this.activePest)
+    },
+    // 格式化时间
+    formatDate(dateString) {
+      const date = new Date(dateString*1000);
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hour = String(date.getHours()).padStart(2, '0');
+      const minute = String(date.getMinutes()).padStart(2, '0');
+      const second = String(date.getSeconds()).padStart(2, '0');
+      return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
+    },
+    handleClick(item) {
+      uni.navigateTo({
+        url: '/pages/bzy/devicePhoto?device_id=' + item?.device_id + '&addtime=' + item?.addtime + '&img_id=' + item?.id + '&id=' + this.deviceInfo.id + '&currentYear=' + this.currentYear
+      })
+    }
+  },
+};
+</script>
+
+<style lang="scss" scoped>
+.photo-image {
+  width: 100%;
+  box-sizing: border-box;
+
+  &__header {
+    margin-bottom: 32rpx;
+    white-space: nowrap;
+
+    &::-webkit-scrollbar {
+      display: none;
+    }
+  }
+
+  &__tabs {
+    box-sizing: border-box;
+    width: 100%;
+    overflow-x: auto;
+    overflow-y: hidden;
+    white-space: nowrap;
+    // 去掉滚动条
+    -ms-overflow-style: none;
+    scrollbar-width: none;
+  }
+
+  .photo-image__content{
+    padding: 30rpx;
+    background: #ffffff;
+    border-radius: 16rpx;
+    .photo-image__grid{
+      display: grid;
+      grid-template-columns: repeat(3, 1fr);
+      gap: 20rpx;
+    }
+  }
+  &__tab {
+    position: relative;
+    display: inline-block;
+    box-sizing: border-box;
+    margin-right: 16rpx;
+    background: #ffffff;
+    color: #999999;
+    font-family: "Source Han Sans CN VF";
+    font-size: 28rpx;
+    padding: 10rpx 32rpx;
+    border-radius: 8rpx;
+    &:last-child {
+      margin-right: 0;
+    }
+
+    .tab-text {
+      margin-right: 8rpx;
+    }
+
+    .tab-check {
+      color: #4CAF50;
+      font-weight: bold;
+    }
+
+    &.active {
+      color: #0bbc58;
+    }
+
+    .tab-underline {
+      position: absolute;
+      bottom: 0;
+      left: 0;
+      right: 0;
+      height: 4rpx;
+      background: #4CAF50;
+      border-radius: 2rpx;
+    }
+  }
+  &__item {
+    position: relative;
+    width: 100%;
+    padding: 0rpx;
+    box-sizing: border-box;
+    display: inline-block;
+    overflow: hidden;
+    border:2rpx solid #e4e7ed;
+    border-radius: 16rpx;
+    &-img-container {
+      width: 100%;
+      padding-bottom: 100%;
+      position: relative;
+      border-radius: 16rpx;
+      overflow: hidden;
+      background-color: #f5f5f5;
+    }
+
+    &-img {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+    }
+
+    &-time {
+      position: absolute;
+      bottom: 0rpx;
+      width: 100%;
+      text-align: center;
+      padding: 0;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      white-space: nowrap;
+      margin-top: 12rpx;
+      font-size: 22rpx;
+      color: #fff;
+      text-align: center;
+      background-color: rgba(0, 0, 0, 0.5);
+    }
+  }
+}
+</style>

+ 670 - 0
pages/bzy/detail.vue

@@ -0,0 +1,670 @@
+<template>
+  <view class="device-detail">
+    <view class="device-detail__header">
+      <u-icon
+        size="36"
+        class="arrow-left"
+        name="arrow-left"
+        @click="handleBack"
+      ></u-icon>
+      {{ deviceInfo.name }}
+    </view>
+    <view class="operation" @click.stop="isShowOperation = !isShowOperation">操作</view>
+    <view class="operation-container" v-if="isShowOperation" @click="closeOperationHandler">
+      <view class="operation-background"></view>
+      <view class="operation-content">
+        <view class="operation-item" v-if="isShowPhoto" @click="handlePhotoClick">
+          <image :src="photoIcon" class="operation-icon"></image>
+          拍照
+        </view>
+        <!-- <view class="operation-item" @click="handleServiceClick">
+          <image :src="serviceIcon" class="operation-icon"></image>
+          维修
+        </view> -->
+        <view class="operation-item" @click="handleSettingClick">
+          <image :src="settingIcon" class="operation-icon"></image>
+          设置
+        </view>
+        <view class="operation-item" @click="handleSimClick">
+          <image :src="simIcon" class="operation-icon"></image>
+          SIM卡
+        </view>
+        <view class="operation-item" @click="modification">
+          <image :src="editIcon" class="operation-icon"></image>
+          修改
+        </view>
+      </view>
+    </view>
+    <view class="device-detail__body">
+      <DeviceCard
+        :dataSource="deviceInfo"
+        :collectTime="time"
+        :title="deviceInfo.name"
+        :deviceType="deviceType"
+      />
+      <view class="tabs">
+        <view class="tab-container" v-if="isShowTab">
+          <view class="tab-item" :class="activeTab === 'viewImage'?'active':''" @click="handleTabClick('viewImage')">
+            查看图片
+          </view>
+          <view class="tab-item" :class="activeTab === 'deviceData'?'active':''" @click="handleTabClick('deviceData')">
+            设备数据
+          </view>
+        </view>
+        <view class="select-timer-container">
+          <view class="select-year">
+            <view class="select-year-item" @click="showPicker = true">
+              {{ currentYear }}
+              <u-icon name="arrow-down" size="18" class="arrow-down"></u-icon>
+            </view>
+          </view>
+          <view class="tabs-timer-container" @click="showCalendar">
+            <view class="tabs-timer-item">
+              {{startDate}}
+            </view>
+            至
+            <view class="tabs-timer-item">
+              {{endDate}}
+            </view>
+            <u-icon name="calendar" size="36" class="calendar-icon"></u-icon>
+          </view>
+        </view>
+      </view>
+      <view v-if="activeTab === 'viewImage'">
+        <photoImage
+          :images="imageList"
+          :pestList="pestList"
+          :disableShow="disableShow"
+          @changeTab="changeTab"
+          :currentYear="currentYear"
+          :deviceInfo="deviceInfo"
+        />
+      </view>
+      <view v-if="activeTab === 'deviceData'">
+        <DeviceData
+          :deviceStatic="deviceStatic"
+          :deviceInfo="deviceInfo"
+          :polylineList="polylineList"
+          :deviceHistoryList="deviceHistoryList"
+          :totalPage="totalPage"
+          :currentPage="page"
+          :page_size="page_size"
+          @prevPage="prevPage"
+          @nextPage="nextPage"
+        />
+      </view>
+    </view>
+    <u-calendar v-model="show" :mode="mode" range-color="#999" btn-type="success" active-bg-color="#0bbc58" range-bg-color="rgba(11,188,88,0.13)" @change="handleChange" :max-date="maxDate" :min-date="minDate"></u-calendar>
+    <u-picker v-model="showPicker" mode="selector" :range="selectorRange" range-key="id" :default-selector="[0]" @confirm="confirmHandler"></u-picker>
+  </view>
+</template>
+<script>
+import DeviceCard from './components/DeviceCard.vue';
+import PestDiscern from './components/pestDiscern.vue';
+import PestEchart from './components/pestEchart.vue';
+import PestArchive from './components/pestArchive.vue';
+import photoImage from './components/photoImage.vue';
+import DeviceData from './components/deviceData.vue';
+
+export default {
+  components: {
+    DeviceCard,
+    PestDiscern,  
+    PestEchart,
+    PestArchive,
+    photoImage,
+    DeviceData,
+  },
+  data(){
+    return {
+      isShowTab:false,
+      showPicker: false,
+      disableShow: false,
+      isShowOperation: false,
+      photoIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/photoIcon.png',
+      editIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/editIcon.png',
+      serviceIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/serviceIcon.png',
+      simIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/simIcon.png',
+      settingIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/settingIcon.png',
+      totalPage:0,
+      currentYear: new Date().getFullYear(),
+      selectorRange: [],
+      maxDate: this.formatDate(new Date()),
+      minDate: this.formatDate(new Date(new Date().getFullYear(), 0, 1)),
+      show: false,
+      showSim: false,
+      // 当前日期向前推30天 格式2026-01-01 月份和日期小于10的时候前面加个0
+      startDate: this.formatDate(new Date(new Date().getTime() - 30 * 24 * 60 * 60 * 1000)),
+      endDate: this.formatDate(new Date()),
+      mode: 'range',
+      imageList: [],
+      pest_order:{},
+      deviceInfo: {},
+      pestInfo: {},
+      time: '',
+      activeTab: 'viewImage',
+      deviceType: '',
+      location: '',
+      total: 0,
+      day: [],
+      pest: [],
+      page:1,
+      is_pest:'',
+      page_size:24,
+      pestList:[],
+      pest_names:'',
+      pests:[],
+      polylineList:[],
+      isShowPhoto:false,
+      deviceHistoryList:[],
+      deviceStatic:{}
+    }
+  },
+  onLoad(options){
+    this.deviceInfo = JSON.parse(options.info);
+    const newVal = this.deviceInfo;
+    if (newVal.device_model == '11'){
+      this.isShowPhoto = true
+    } else if(newVal.device_model == '12'){
+      this.isShowPhoto = false
+    } else if(newVal.device_model == '13'){
+      this.isShowPhoto = true
+    } else if(newVal.device_model == '14'){
+      this.isShowPhoto = true
+    } else if(newVal.device_model == '15'){
+      this.isShowPhoto = false
+    } else{
+      this.isShowPhoto = true
+    }
+    // this.getPestAnalysis();
+    this.isShow();
+    const currentYear = new Date().getFullYear();
+    this.selectorRange = [];
+    for(let i = 0;i<50;i++){
+      const item = {
+        label: currentYear - i,
+        id: currentYear - i
+      }
+      this.selectorRange.push(item);
+    }
+  },
+  methods: {
+    handleSimClick(){
+      // this.showSim = true;
+      //`/pages/deviceDetails/weatherStation/${type}?deviceInfo=${encodeURIComponent(JSON.stringify(this.deviceInfo))}`
+      uni.navigateTo({
+        url:
+          '/pages/deviceDetails/weatherStation/simDetail?deviceInfo=' +
+          encodeURIComponent(JSON.stringify(this.deviceInfo))
+      }); 
+    },
+		modification() {
+      uni.navigateTo({
+        url:
+          '/pages/equipList/seabox/modification?data=' +
+          JSON.stringify(this.deviceInfo) +
+          '&id=' +
+          this.deviceInfo.type,
+      }); 
+    },
+    async handlePhotoClick(){
+       const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.send_control.admin_device_control',
+        method: 'POST',
+        data: {
+          device_type_id: this.deviceInfo.type,
+          d_id: this.deviceInfo.d_id,
+          cmd: 'takephoto',
+        },
+      });
+      if(res){
+        this.$u.toast('拍照成功')
+      }
+    },
+    handleServiceClick(){
+      uni.navigateTo({
+        url: '/pages/afterSale/addafter?d_id=' + this.deviceInfo.d_id +'&device_id='+this.deviceInfo.id + '&device_type=' + this.deviceInfo.type,
+      })
+    },
+    handleSettingClick(){
+      uni.navigateTo({
+        url: '/pages/bzy/deviceControl?deviceId=' + this.deviceInfo.id + '&d_id=' + this.deviceInfo.d_id,
+      });
+    },
+    closeOperationHandler(){
+      this.isShowOperation = false;
+    },
+    isShow(){
+      // disable == 0 或者 device_model == 15 表示不可以查看
+      if(!this.deviceInfo.d_id){
+        return false;
+      }
+      let showStatus = true;
+      if(this.deviceInfo.device_model == 15 || this.deviceInfo.device_model == 12){
+        showStatus = false;
+      }
+      if(showStatus){
+        this.activeTab = 'viewImage';
+      }else{
+        this.activeTab = 'deviceData';
+      }
+      this.initAction();
+      this.isShowTab = showStatus;
+      if(this.deviceInfo.disable == 0){
+        this.disableShow = false;
+        if(showStatus){
+          this.activeTab = 'viewImage';
+          this.handleTabClick('viewImage');
+        }
+      }else{
+        this.disableShow = true;
+      }
+    },
+    prevPage(e){
+      if(e == 1){
+        return
+      }
+      this.page = e-=1;
+      this.getDeviceHistoryData();
+    },
+    nextPage(e){
+      if(e * this.page_size >= this.totalPage){
+        return
+      }
+      this.page = e+=1;
+      this.getDeviceHistoryData();
+    },
+    changeTab(pestList){
+      let pest_names = pestList.map(item => item.pest_name);
+      this.pest_names = pest_names.join(',');
+      this.initImageList();
+    },
+    confirmHandler(e){
+      this.currentYear = this.selectorRange[e].id;
+      if(this.currentYear == new Date().getFullYear()){
+        // 结束日期为this.endDate的月份和日期加上选择的年份
+        const timeDate = this.currentYear + '-' + this.endDate.split('-')[1] + '-' + this.endDate.split('-')[2];
+        this.endDate = this.formatDate(new Date(timeDate));
+        // 开始日期为结束日期前30天
+        this.startDate = this.formatDate(new Date(new Date(this.endDate).getTime() - 30 * 24 * 60 * 60 * 1000));
+        this.maxDate = this.formatDate(new Date());
+        this.minDate = this.formatDate(new Date(new Date().getFullYear(), 0, 1));
+      }else{
+        // 结束日期为this.endDate的月份和日期加上选择的年份
+        const timeDate = this.currentYear + '-' + this.endDate.split('-')[1] + '-' + this.endDate.split('-')[2];
+        this.endDate = this.formatDate(new Date(timeDate));
+        // 开始日期为结束日期前30天
+        this.startDate = this.formatDate(new Date(new Date(this.endDate).getTime() - 30 * 24 * 60 * 60 * 1000));
+        this.maxDate = this.formatDate(new Date(this.currentYear, 11, 31));
+        this.minDate = this.formatDate(new Date(this.currentYear, 0, 1));
+      }
+      this.initAction();
+      this.showPicker = false;
+    },
+    getInfo(info){
+      this.pestInfo = info;
+    },
+    // 格式化日期为YYYY-MM-DD格式,月份和日期小于10时前面加0
+    formatDate(date) {
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+    formatTime(date){
+      const time = new Date(date).getTime() / 1000;
+      return time.toFixed(0);
+    },
+    handleChange(e){
+      this.startDate = e.startDate;
+      this.endDate = e.endDate;
+      this.initAction();
+    },
+    showCalendar(){
+      this.show = true;
+    },
+    handleBack() {
+      uni.navigateBack({
+        delta: 1
+      });
+    },
+    initAction(){
+      this.pest_order = {}
+      if(this.activeTab === 'viewImage'){
+        // this.initPest();
+        this.initImageList();
+      }else if(this.activeTab === 'deviceData'){
+        // this.getDeviceData();
+        this.getPolylineData();
+        this.getDeviceHistoryData();
+      }
+    },
+    handleTabClick(tab) {
+      this.activeTab = tab;
+      this.initAction();
+    },
+    async getDeviceData(){
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.worm_lamp.device_status_data',
+        method: 'POST',
+        data: {
+          device_id: this.deviceInfo.id,
+        },
+      });
+      this.deviceStatic = res;
+    },
+    async initImageList(){
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.forecast_system.equip_photo',
+        method: 'POST',
+        data: {
+          page:this.page,
+          page_number:this.page_size,
+          device_id: this.deviceInfo.id,
+          ret:'list',
+          time_begin: this.formatTime(this.formatDate(new Date(this.startDate)) + ' 00:00:00'),// 格式化开始时间YYYY-MM-DD HH:MM:SS
+          time_end: this.formatTime(this.formatDate(new Date(this.endDate)) + ' 23:59:59'),// 格式化结束时间YYYY-MM-DD HH:MM:SS
+        },
+      });
+      const data = res?.data || [];
+      this.imageList = data
+    },
+    async initPest(){
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.new_cbd.pest_type_list',
+        method: 'POST',
+        data:{
+          page:1,
+          page_size:999999,
+          device_id: this.deviceInfo.id,
+          identify_model: 'B',
+          time_begin: this.formatTime(this.formatDate(new Date(this.startDate)) + ' 00:00:00'),// 格式化开始时间YYYY-MM-DD HH:MM:SS
+          time_end: this.formatTime(this.formatDate(new Date(this.endDate)) + ' 23:59:59'),// 格式化结束时间YYYY-MM-DD HH:MM:SS
+        },
+      });
+      const data = res?.data || [];
+      this.pestList = data
+    },
+    async getPolylineData(){
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.worm_lamp.device_polyline_data',
+        method: 'POST',
+        data: {
+          device_type_id: this.deviceInfo.type_id,
+          d_id: this.deviceInfo.d_id,
+          start_time: new Date(this.startDate).getTime()/1000,// 转成毫秒
+          end_time: new Date(this.endDate).getTime()/1000,// 转成毫秒
+        },
+      });
+      const data = res || [];
+      this.polylineList = data
+    },
+    async getDeviceHistoryData(){
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.worm_lamp.device_history_data',
+        method: 'POST',
+        data: {
+          device_type_id: this.deviceInfo.type_id,
+          device_id: this.deviceInfo.id,
+          start_time: new Date(this.startDate).getTime()/1000,
+          end_time: new Date(this.endDate).getTime()/1000,
+          page: this.page,
+          page_size: this.page_size,
+        },
+      });
+      const data = res?.data || [];
+      this.totalPage = res.counts;
+      this.deviceHistoryList = data
+    },
+    async getPestAnalysis(){
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.cbd_analysis.analysis_pest_result',
+        method: 'POST',
+        data: {
+        d_id: this.deviceInfo.d_id,
+        start: this.startDate,
+        end: this.endDate,
+        model: 'B'
+        },
+      });
+      const pest_order = res?.pest_order;
+      this.getInfo({});
+      let total = 0;
+      this.pests = [];
+      for(let key in pest_order){
+        total += pest_order[key] || 0;
+        this.pests.push({
+          name: key,
+          percent: (pest_order[key] / total) * 100
+        })
+      }
+      this.pest_order = pest_order;
+      this.day = res?.day || [];
+      const pest = res?.pest || [];
+      this.pest = pest;
+      // pest.forEach(p =>{
+      //   for(let i = 0;i< p.length;i++){
+      //     this.pest.push(p[i]);
+      //   }
+      // })
+      this.total = total;
+    }
+  }
+}
+</script>
+<style scoped lang="scss">
+::v-deep .u-calendar__action{
+  display:flex;
+  justify-content: space-between;
+}
+::v-deep .u-hover-class{
+  .u-calendar__content__item__inner{
+    color:#aaa !important;
+  }
+}
+.operation-container{
+  position: fixed;
+  right: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 99;
+}
+.operation{
+  position: fixed;
+  top: 260rpx;
+  right: 0;
+  z-index: 999;
+  width:48rpx;
+  height: 100rpx;
+  line-height: 50rpx;
+  border-radius: 8px 0 0 8px;
+  border-top: 4rpx solid #FFF;
+  border-bottom: 4rpx solid #FFF;
+  border-left: 4rpx solid #FFF;
+  background: #dddfe6a3;
+  color: #515153;
+  text-align: center;
+  font-family: "Source Han Sans CN VF";
+  font-size: 24rpx;
+  font-weight: 500;
+  writing-mode: vertical-rl;
+}
+.operation-background{
+  position: fixed;
+  right: 0;
+  top: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 998;
+  background: #00000040;
+}
+.operation-content{
+  position: fixed;
+  top: 240rpx;
+  right: 80rpx;
+  height: 126rpx;
+  display: flex;
+  padding: 16rpx 32rpx;
+  align-items: center;
+  gap: 64rpx;
+  z-index: 999;
+  border-radius: 32rpx;
+  border: 2rpx solid #FFF;
+  background: #FFF;
+  backdrop-filter: blur(8rpx);
+  .operation-item{
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    color: #333333;
+    text-align: center;
+    font-family: "Source Han Sans CN VF";
+    font-size: 24rpx;
+    font-weight: 400;
+  }
+  .operation-icon{
+    width: 58rpx;
+    height: 58rpx;
+  }
+}
+.device-detail {
+  display: flex;
+  width: 100%;
+  height: calc(100vh - 112rpx);
+  padding-top: 112rpx;
+  flex-direction: column;
+  align-items: center;
+  overflow-y: scroll;
+  
+  background: linear-gradient(180deg, #ffffff00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
+  .device-detail__header {
+    width: 100%;
+    font-size: 28rpx;
+    color: #999;
+    color: #042118;
+    font-family: 'Source Han Sans CN VF';
+    font-weight: 700;
+    position: relative;
+    text-align: center;
+    .arrow-left {
+      position: absolute;
+      left: 32rpx;
+      margin-right: 12rpx;
+    }
+  }
+  .device-detail__body {
+    width: calc(100% - 64rpx);
+    margin: 0 auto;
+    border-radius: 16rpx;
+    overflow-x: hidden;
+    overflow-y: auto;
+    // 隐藏滚动条
+    -ms-overflow-style: none;
+    scrollbar-width: none;
+  }
+  .tab-content{
+    width: 100%;
+    padding-bottom: 32rpx;
+  }
+  .tabs {
+    background: #ffffff;
+    margin: 24rpx 0;
+    border-radius: 16rpx;
+    padding: 16rpx 0;
+    padding-top: 0;
+    .tab-container{
+      display: flex;
+      width: 100%;
+      height: 88rpx;
+      line-height: 88rpx;
+      text-align: center;
+      font-size: 28rpx;
+      font-weight: 700;
+      color: #042118;
+      font-family: 'Source Han Sans CN VF';
+    }
+    .select-timer-container{
+      display:flex;
+      align-items: center;
+      padding-left: 32rpx;
+      .select-year{
+        width: 110rpx;
+        text-align: center;
+        height: 64rpx;
+        border-radius: 32rpx;
+        font-family: 'Source Han Sans CN VF';
+        line-height: 64rpx;
+        background: #F1F4F8;
+        padding: 0 32rpx;
+        .select-year-item{
+          color: #656565;
+          font-size: 24rpx;
+          display: flex;
+          align-items: center;
+          justify-content: space-around;
+        }
+      }
+    }
+    .tabs-timer-container{
+      display: flex;
+      align-items: center;
+      width: calc(100% - 256rpx);
+      height: 64rpx;
+      line-height: 64rpx;
+      text-align: center;
+      font-size: 24rpx;
+      font-weight: 500;
+      font-family: 'Source Han Sans CN VF';
+      border-radius: 32rpx;
+      background: #F1F4F8;
+      padding: 0 32rpx;
+      color: #656565;
+      margin: 16rpx;
+      position: relative;
+      .tabs-timer-item{
+        width: 42%;
+        color: #656565;
+        text-align: center;
+        font-family: "Source Han Sans CN VF";
+        font-size: 24rpx;
+        font-weight: 400;
+      }
+      .calendar-icon{
+        margin-left: 24rpx;
+      }
+    }
+    .tab-item {
+      flex: 1;
+      color: #999999;
+      text-align: center;
+      font-family: "Source Han Sans CN VF";
+      font-size: 28rpx;
+      font-weight: 400;
+    }
+    .active{
+      position: relative;
+      color: #303133;
+      text-align: center;
+      font-family: "Source Han Sans CN VF";
+      font-size: 28rpx;
+      font-weight: 700;
+      &::after {
+        content: '';
+        position: absolute;
+        bottom: 0;
+        left: 50%;
+        -webkit-transform: translateX(-50%);
+        transform: translateX(-50%);
+        width: 36rpx;
+        height: 36rpx;
+        border: 6rpx solid #0bbc58;
+        border-radius: 50%;
+        border-color: transparent;
+        border-bottom-color: #0bbc58;
+      }
+    }
+  }
+}
+</style>

+ 532 - 0
pages/bzy/deviceControl.vue

@@ -0,0 +1,532 @@
+<template>
+  <view class="device-detail">
+    <view class="device-detail__header">
+      <u-icon
+        size="36"
+        class="arrow-left"
+        name="arrow-left"
+        @click="handleBack"
+      ></u-icon>
+      {{ title }}
+    </view>
+    <view class="device-detail__body">
+      <view class="tabs">
+        <view class="tab-container">
+          <view class="tab-item" :class="activeTab === 'pestAnalysis'?'active':''" @click="handleTabClick('pestAnalysis')">
+            管理员
+          </view>
+          <view class="tab-item" :class="activeTab === 'viewImage'?'active':''" @click="handleTabClick('viewImage')">
+            设置
+          </view>
+        </view>
+      </view>
+      <view class="device-detail-content" v-if="activeTab === 'pestAnalysis'">
+        <view class="device-detail-item">
+          <text class="device-detail-label">联网模块</text>
+          <view class="device-detail-btn-container">
+            <view class="device-detail-btn" @click="setDeviceContorl('dtu_update')">升级</view>
+            <view class="device-detail-btn" @click="setDeviceContorl('dtu_reboot')">重启</view>
+            <!-- <view class="device-detail-btn" @click="showMqttConfig">MQTT配置</view> -->
+          </view>
+        </view>
+        <view class="device-detail-item">
+          <text class="device-detail-label">板子设置</text>
+          <view class="device-detail-btn-container">
+            <!-- <view class="device-detail-btn">查看原始IMEI</view> -->
+            <view class="device-detail-btn" @click="showMqttConfig('imei')">更改IMEI</view>
+          </view>
+        </view>
+        <view class="device-detail-item">
+          <text class="device-detail-label">强制操作</text>
+          <view class="device-detail-btn-container">
+            <view class="device-detail-btn force-btn" @click="setDeviceContorl('takephoto')">拍照</view>
+            <view class="device-detail-btn force-btn" @click="setDeviceContorl('update')">升级</view>
+            <view class="device-detail-btn force-btn" @click="setDeviceContorl('reboot')">重启</view>
+            <view class="device-detail-btn force-btn" @click="setDeviceContorl('autotakephoto')">对焦拍照</view>
+            <view class="device-detail-btn force-btn" @click="setDeviceContorl('turn')">转仓</view>
+          </view>
+        </view>
+      </view>
+      <view class="device-detail-content" style="padding-bottom: 50rpx" v-if="activeTab === 'viewImage'">
+        <view class="device-detail-viewImage">
+          <u-icon name="clock" color="#0bbc58" size="46"></u-icon>
+          <text class="device-detail-label" style="margin-left: 10rpx">
+            采集开始和关闭时间
+          </text>
+          <view class="clear-btn" @click="clearTime">清空</view>
+        </view>
+        <view class="time-container" v-for="(item,index) in coll_time" :key="index">
+          <view class="start-time" @click="showPickerHandler(index,'start_time')">{{item.start_time_label || '开始时间'}}</view>-
+          <view class="end-time" @click="showPickerHandler(index,'end_time')">{{item.end_time_label || '结束时间'}}
+          </view><u-icon name="clock" color="#4E5969" size="26" style="padding-right: 20rpx"></u-icon>
+        </view>
+        <view class="device-detail-viewImage">
+          <text class="device-detail-label">高温保护阀值(℃)
+          {{ equipContrlForm.tph }}</text>
+        </view>
+        <u-slider v-model="equipContrlForm.tph" style="width:100%" :min="50" :max="70" active-color="#0bbc58"></u-slider>
+        <view class="device-detail-viewImage">
+          <text class="device-detail-label">低温保护阀值(℃)
+          {{ equipContrlForm.tpl }}</text>
+        </view>
+        <u-slider v-model="equipContrlForm.tpl" style="width:100%" :min="-30" :max="20" active-color="#0bbc58"></u-slider>
+        <view class="device-detail-viewImage">
+          <text class="device-detail-label">数据上传间隔(min)
+          {{ equipContrlForm.datt }}</text>
+        </view>
+        <u-slider v-model="equipContrlForm.datt" style="width:100%;" :max="60" :min="10" active-color="#0bbc58"></u-slider>
+      </view>
+    </view>
+    <view class="device-detail-btn-footer" v-if="activeTab === 'viewImage'">
+      <view class="device-detail-btn" @click="saveSettings">保存</view>
+    </view>
+    <u-popup v-model="show" mode="bottom">
+      <view class="device-detail-content" style="height: 50vh">
+        <view class="device-detail-item">
+          <text class="device-detail-label">请输入imei</text>
+          <u-input v-model="equipContrlForm.imei" placeholder="请输入imei"></u-input>
+        </view>
+      </view>
+      <view class="device-detail-btn-footer">
+        <view class="device-detail-btn" @click="setDeviceContorl('imei')">保存</view>
+      </view>
+		</u-popup>
+		<u-select v-model="showPicker" :list="timeList" @confirm="confirmPicker"></u-select>
+  </view>
+</template>
+<script>
+
+export default {
+  data(){
+    return {
+      showPicker: false,
+      value: 10,
+      timeList: [],
+      imageList: [],
+      coll_time:[{
+        start_time: '',
+        start_time_label: '',
+        end_time: '',
+        end_time_label: '',
+      },{
+        start_time: '',
+        start_time_label: '',
+        end_time: '',
+        end_time_label: '',
+      },{
+        start_time: '',
+        start_time_label: '',
+        end_time: '',
+        end_time_label: '',
+      }],
+      equipContrlForm: {},
+      time: '',
+      activeTab: 'pestAnalysis',
+      title: '设置控制',
+      deviceType: '',
+      location: '',
+      d_id: '',
+      show: false,
+      checked: false,
+      currentIndex: -1,
+      currentType: ''
+    }
+  },
+  onLoad(options){
+    this.d_id = options.d_id;
+    this.getControlDeviceConfigInfo();
+    this.timeList = [];
+    for(let i = 0;i<24;i++){
+      this.timeList.push({
+        value:i,
+        label:i < 10 ? '0' + i + ':00' : i + ':00'
+      })
+    }
+  },
+  methods: {
+    clearTime(){
+      this.coll_time.forEach(item => {
+        item.start_time = '';
+        item.start_time_label = '';
+        item.end_time = '';
+        item.end_time_label = '';
+      })
+    },
+    confirmPicker(e){
+      const value = e[0].value;
+      const label = e[0].label;
+      if(this.currentType == 'end_time'){
+        if(this.coll_time[this.currentIndex]['start_time'] > value){
+          uni.showToast({
+            title: '结束时间不能早于开始时间',
+            icon: 'none',
+          })
+          return
+        }
+      }
+      if(this.currentType == 'start_time' && this.currentIndex > 0){
+        if(this.coll_time[this.currentIndex-1]['end_time'] > value){
+          uni.showToast({
+            title: '开始时间不能早于前面采集时间的结束时间',
+            icon: 'none',
+          })
+          return
+        }
+      }
+      if(this.currentType == 'start_time' && this.coll_time[this.currentIndex]['end_time'] !== ''){
+        if(value > this.coll_time[this.currentIndex]['end_time']){
+          uni.showToast({
+            title: '开始时间不能晚于结束时间',
+            icon: 'none',
+          })
+          return
+        }
+      }
+      this.coll_time[this.currentIndex][this.currentType] = value;
+      this.coll_time[this.currentIndex][this.currentType + '_label'] = label;
+    },
+    showPickerHandler(index,type){
+      if(type == 'end_time'){
+        if(this.coll_time[index]['start_time'] === ''){
+          uni.showToast({
+            title: '请先选择开始时间',
+            icon: 'none',
+          })
+          return
+        }
+      }
+      if(index != 0){
+        if(this.coll_time[index-1]['start_time_label'] === '' || this.coll_time[index-1]['end_time_label'] === ''){
+          uni.showToast({
+            title: '请先选择前面采集时间的开始和结束时间',
+            icon: 'none',
+          })
+          return
+        }
+      }
+      this.showPicker = true;
+      this.currentIndex = index
+      this.currentType = type
+    },
+    closePicker(){
+      this.showPicker = false;
+      this.currentType = ''
+      this.currentIndex = -1
+    },
+    handleBack() {
+      uni.navigateBack({
+        delta: 1
+      });
+    },
+    handleTabClick(tab) {
+      this.activeTab = tab;
+    },
+    showMqttConfig(){
+      this.show = true;
+    },
+    closeMqttConfig(){
+      this.show = false;
+    },
+    async setDeviceContorl(type){
+      const data = {
+        device_type_id: 3,
+        d_id: this.d_id,
+        cmd: type,
+      }
+      if(type === 'imei'){
+        data.imei = this.equipContrlForm.imei
+      }else{
+        delete data.imei
+      }
+      this.$myRequest({
+        url: '/api/api_gateway?method=forecast.send_control.admin_device_control',
+        method: 'POST',
+        data
+      }).then(res => {
+        this.closeMqttConfig()
+        if (res) {
+          uni.showToast({
+            title: '设备控制修改成功!',
+            icon: 'success',
+          });
+        } else {
+          uni.showToast({
+            title: '设备控制修改失败',
+            icon: 'error',
+          });
+        }
+      });
+    },
+    saveSettings(){
+      let newForm = Object.assign({}, this.equipContrlForm) // 深拷贝
+      newForm.st = newForm.st + ''
+      newForm.et = newForm.et + ''
+      newForm.st && newForm.st.slice(0, 2).charAt(0) != '0'
+        ? newForm.st.slice(0, 2)
+        : newForm.st.slice(1, 2)
+      newForm.et =
+        newForm.et && newForm.et.slice(0, 2).charAt(0) != '0'
+          ? newForm.et.slice(0, 2)
+          : newForm.et.slice(1, 2)
+
+      for (let k in newForm) {
+        if (typeof newForm[k] === 'number') {
+          newForm[k] = newForm[k] + ''
+        }
+      }
+      newForm.st = newForm.st.replace(':00', '')
+      console.log(this.coll_time,'coll_timecoll_timecoll_time')
+      const coll_time = [];
+      for(let i = 0;i< this.coll_time.length;i++){
+        const coll_item = this.coll_time[i];
+        if(String(coll_item.start_time) && String(coll_item.end_time)){
+          coll_time.push(String(coll_item.start_time) + '-' + String(coll_item.end_time))
+        }
+      }
+      newForm.coll_time = coll_time
+      this.$myRequest({
+        url: '/api/api_gateway?method=forecast.send_control.device_control',
+        method: 'POST',
+        data: {
+          device_type_id: 3,
+          d_id: this.d_id,
+          config: JSON.stringify(newForm)
+        }
+      }).then(res => {
+        if (res) {
+          // 设备控制修改成功
+          uni.showToast({
+            title: '设备控制修改成功!',
+            icon: 'success',
+          });
+        } else {
+          uni.showToast({
+            title: '设备控制修改失败',
+            icon: 'error',
+          });
+        }
+      })
+    },
+    async getControlDeviceConfigInfo(){
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.send_control.device_control_info',
+        method: 'POST',
+        data: {
+          d_id: this.d_id,
+          cmd:'paramconf'
+        },
+      });
+      this.equipContrlForm = res
+      const coll_time = res?.coll_time || [];
+      for(let i = 0;i< coll_time.length;i++){
+        const coll_item = coll_time[i];
+        const start_time = coll_item.split('-')[0];
+        const end_time = coll_item.split('-')[1];
+        this.coll_time[i]['start_time'] = start_time;
+        this.coll_time[i]['end_time'] = end_time;
+        this.coll_time[i]['start_time_label'] = start_time >= 10 ? start_time + ':00' : '0' + start_time + ':00';
+        this.coll_time[i]['end_time_label'] = end_time >= 10 ? end_time + ':00' : '0' + end_time + ':00';
+      }
+      console.log(this.coll_time,'coll_timecoll_timecoll_time')
+      this.equipContrlForm.tph = Number(this.equipContrlForm.tph || 50)
+      this.equipContrlForm.tpl = Number(this.equipContrlForm.tpl || 0)
+    },
+  }
+}
+</script>
+<style scoped lang="scss">
+.device-detail {
+  display: flex;
+  width: 100%;
+  height: calc(100vh - 112rpx);
+  padding-top: 112rpx;
+  flex-direction: column;
+  align-items: center;
+  background: linear-gradient(180deg, #ffffff00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
+  .device-detail__header {
+    width: 100%;
+    font-size: 28rpx;
+    color: #999;
+    color: #042118;
+    font-family: 'Source Han Sans CN VF';
+    font-weight: 700;
+    position: relative;
+    text-align: center;
+    .arrow-left {
+      position: absolute;
+      left: 32rpx;
+      margin-right: 12rpx;
+    }
+  }
+  .time-container{
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: #999999;
+    border-radius: 8rpx;
+    background:#F6F8FC;
+    gap: 12rpx;
+    .start-time{
+      width: 250rpx;
+      height: 50rpx;
+      line-height: 50rpx;
+      text-align: left;
+      font-size: 24rpx;
+      color: #999999;
+      font-family: 'Source Han Sans CN VF';
+      border-radius: 16rpx;
+      padding: 0 24rpx;
+    }
+    .end-time{
+      width: 200rpx;
+      height: 50rpx;
+      line-height: 50rpx;
+      text-align: left;
+      font-size: 24rpx;
+      color: #999999;
+      font-family: 'Source Han Sans CN VF';
+      border-radius: 16rpx;
+      padding: 0 24rpx;
+    }
+  }
+  .device-detail__body {
+    width: calc(100% - 64rpx);
+    margin: 0 auto;
+    border-radius: 16rpx;
+    overflow-x: hidden;
+    overflow-y: auto;
+    // 隐藏滚动条
+    -ms-overflow-style: none;
+    scrollbar-width: none;
+  }
+  .tabs {
+    margin: 24rpx 0;
+    border-radius: 16rpx;
+    padding: 16rpx 0;
+    padding-top: 0;
+    .tab-container{
+      display: flex;
+      width: 100%;
+      height: 88rpx;
+      line-height: 88rpx;
+      text-align: center;
+      font-size: 28rpx;
+      color: #042118;
+      font-family: 'Source Han Sans CN VF';
+    }
+    .tab-item {
+      margin-right: 40rpx;
+      color:#999999;
+    }
+    .active{
+      position: relative;
+      color: #0bbc58;
+      text-align: center;
+      font-family: "Source Han Sans CN VF";
+      font-size: 28rpx;
+      font-weight: 700;
+      &::after {
+        content: '';
+        position: absolute;
+        bottom: 10rpx;
+        left: 50%;
+        transform: translateX(-50%);
+        width: 100%;
+        height: 36rpx;
+        border-bottom: 6rpx solid #0bbc58;
+      }
+    }
+  }
+  .device-detail-content{
+    display: flex;
+    padding: 24rpx 32rpx;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 20rpx;
+    border-radius: 16rpx;
+    background: #FFF;
+    .device-detail-item{
+      .device-detail-label{
+        color: #303133;
+        font-family: "Source Han Sans CN VF";
+        font-size: 28rpx;
+        font-weight: 400;
+      }
+      .device-detail-btn-container{
+        display: flex;
+        gap: 24rpx;
+        margin-top: 12rpx;
+      }
+      .device-detail-btn{
+        display: flex;
+        padding: 10rpx 16rpx;
+        justify-content: center;
+        align-items: center;
+        gap: 16rpx;
+        border-radius: 16rpx;
+        background: #0bbc58;
+        color: #ffffff;
+        font-family: "Source Han Sans CN VF";
+        font-size: 24rpx;
+        font-weight: 400;
+      }
+      .force-btn{
+        background: #FB4E52;
+      }
+    }
+    .device-detail-viewImage{
+      width: 100%;
+      display: flex;
+      align-items: center;
+      padding-bottom: 20rpx;
+      position: relative;
+      .clear-btn{
+        position: absolute;
+        right: 0rpx;
+        border: 1rpx solid #0bbc58;
+        border-radius: 8rpx;
+        padding: 4rpx 12rpx;
+        font-size: 24rpx;
+        color: #0bbc58;
+        font-family: 'Source Han Sans CN VF';
+        font-weight: 400;
+      }
+    }
+  }
+  .device-detail-btn-footer{
+    position: fixed;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    height: 112rpx;
+    line-height: 112rpx;
+    text-align: center;
+    font-size: 28rpx;
+    font-weight: 700;
+    color: #042118;
+    font-family: 'Source Han Sans CN VF';
+    background: #ffffff;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .device-detail-btn{
+      display: flex;
+      height: 80rpx;
+      width: 90%;
+      margin: 0 auto;
+      padding: 0rpx 20rpx;
+      justify-content: center;
+      align-items: center;
+      gap: 8rpx;
+      border-radius: 16rpx;
+      background:#0bbc58;
+      color: #ffffff;
+      text-align: center;
+      font-family: "Source Han Sans CN VF";
+      font-size: 14px;
+      font-style: normal;
+      font-weight: 500;
+    }
+  }
+}
+</style>

+ 819 - 0
pages/bzy/devicePhoto.vue

@@ -0,0 +1,819 @@
+<template>
+  <view class="device-photo-page">
+    <!-- 顶部导航栏 -->
+    <view class="device-detail__header">
+      <u-icon
+        size="36"
+        class="arrow-left"
+        name="arrow-left"
+        @click="handleBack"
+      ></u-icon>
+      {{ title }}
+    </view>
+
+    <!-- 日期选择器 -->
+     <view class="date-container">
+        <!-- <view class="select-year">
+          <view class="select-year-item" @click="showPicker = true">
+            {{ currentYear }}
+            <u-icon name="arrow-down" size="18" class="arrow-down"></u-icon>
+          </view>
+        </view> -->
+        <view class="date-picker" @click="show = true">
+          <view class="date-input">
+            <text class="date-label">{{ time_begin || '-' }}</text>
+          </view>
+          <!-- <view class="date-separator">-</view>
+          <view class="date-input">
+            <text class="date-label">{{ time_end || '-' }}</text>
+          </view> -->
+          <u-icon name="calendar" class="calendar"></u-icon>
+        </view>
+    </view>
+    <!-- 主图片区域 -->
+    <view class="main-photo" v-if="currentImg.addr">
+      <movable-area class="movable-area" :style="{ height: containerHeight }">
+        <movable-view
+          class="movable-view"
+          direction="all"
+          scale="true"
+          scale-min="1"
+          scale-max="5"
+        >
+          <view class="image-container">
+            <!-- 原图 -->
+            <image
+              :src="currentImg.addr"
+              class="main-photo-image"
+              mode="widthFix"
+              @load="onImageLoad"
+            />
+            <!-- 虫子标记层 -->
+            <view class="bug-markers" v-if="bugMarkers.length > 0">
+              <view
+                v-for="(marker, index) in bugMarkers"
+                :key="index"
+                class="bug-marker"
+                :style="{
+                  left: marker.x + '%',
+                  top: marker.y + '%',
+                  width: marker.width + '%',
+                  height: marker.height + '%',
+                  borderColor: marker.color
+                }"
+              >
+                <view class="bug-label" :style="{ color: marker.color }">
+                  {{ getPestName(marker.id) }}
+                </view>
+              </view>
+            </view>
+          </view>
+        </movable-view>
+      </movable-area>
+      <view class="photo-timestamp">{{ formatDate(currentImg.addtime) }}</view>
+    </view>
+
+    <!-- 缩略图预览 -->
+    <view class="thumbnail-preview">
+      <view  class="thumbnail-scroll">
+        <view 
+          class="thumbnail-item" 
+          v-for="(item, index) in thumbnails" 
+          :key="index"
+          :class="{ active: img_id == item.ids }"
+          @click="selectThumbnail(item)"
+        >
+          <image 
+            :src="item.addr" 
+            class="thumbnail-image"
+          />
+        </view>
+      </view>
+    </view>
+
+    <!-- 识别结果 -->
+    <view class="recognition-result" v-if="pestList.length">
+      <view class="result-title">当前图片识别结果</view>
+      <!-- 一类害虫 -->
+      <view class="pest-category" v-for="(pest,index) in pestList" :key="index">
+        <view class="category-header">
+          <text class="category-title">{{ pest[0] }}</text>
+          <text class="category-count">数量</text>
+        </view>
+        <view class="pest-item" v-for="(p, i) in pest[1]" :key="i">
+          <view class="pest-info">
+            <view class="pest-color" :style="{ backgroundColor: getPestColor(p.pest_name) }"></view>
+            <text class="pest-name">{{ p.pest_name }}</text>
+          </view>
+          <text class="pest-count">{{ p.pest_num }}</text>
+        </view>
+      </view>
+    </view>
+    <u-empty v-else text="暂无数据"></u-empty>
+		<u-calendar 
+      v-model="show"
+      :mode="mode"
+      @change="handleChange"
+      range-color="#999"
+      btn-type="success"
+      active-bg-color="#0bbc58"
+      range-bg-color="rgba(11,188,88,0.13)"
+      :defaultDate="defaultDate"
+    ></u-calendar>
+		<u-picker v-model="showPicker" mode="selector" :range="selectorRange" range-key="id" :default-selector="[0]" @confirm="confirmHandler"></u-picker>
+  </view>
+</template>
+
+<script>
+export default {
+  data() {
+    return {
+      show: false,
+      mode: 'date',
+      maxDate: this.formatTime(new Date()),
+      minDate: this.formatTime(new Date(new Date().getFullYear(), 0, 1)),
+      defaultDate: this.formatTime(new Date(new Date().getFullYear(), 0, 1)),
+      showPicker: false,
+      selectorRange: [],
+      title: '查看图片',
+      currentThumbnail: 2,
+      pestYype:{},
+      device_id: '',
+      time_begin: this.formatTime(new Date(new Date().getFullYear(), 0, 1)),
+      time_end: this.formatTime(new Date()),
+      img_id: '',
+      pest_list:[],
+      pestList:[],
+      colors:[
+        '#FF5951',
+        '#66EDED',
+        '#E67B3E',
+        '#6DE28B',
+        '#FFC97A',
+        '#E7EB4B',
+        '#1561F3',
+        '#FA73F5',
+        '#159AFF',
+        '#FA73F5'
+      ],
+      currentYear:'',
+      pest_list_arr:[],
+      thumbnails: [],
+      addtime:'',
+      currentImg:{},
+      bugMarkers: [],
+      imageWidth: 0,
+      imageHeight: 0,
+      imgOld_id:'',
+      containerHeight: '300px',
+      pestDict: {}, // 虫子字典 {id: name}
+      pestColorMap: {}, // 害虫颜色映射 {pestName: color}
+    };
+  },
+  async onLoad(options) {
+    const {id,img_id,currentYear,addtime} = options
+    this.device_id = id
+    this.imgOld_id = img_id
+    this.img_id = img_id
+    this.currentYear = currentYear
+    this.addtime = this.formatTime(Number(addtime)*1000);
+    this.time_begin = this.addtime;
+    this.defaultDate = this.addtime;
+    this.time_end = this.addtime;
+    this.selectorRange = [];
+    const nowYear = new Date().getFullYear();
+    for(let i = 0;i<50;i++){
+      const item = {
+        label: nowYear - i,
+        id: nowYear - i
+      }
+      this.selectorRange.push(item);
+    }
+    this.pestList = [];
+    // await this.getPestDict();
+    // this.getPestLevelMap();
+    this.getDevicePhotoDetails();
+    // this.getDevicePhotoDetails();
+  },
+  methods: {
+    getPestName(id){
+      return this.pestDict[id] || id;
+    },
+    async confirmHandler(e){
+      this.currentYear = this.selectorRange[e].id;
+      if(this.currentYear == new Date().getFullYear()){
+        this.time_begin = this.formatTime(new Date(new Date().getFullYear(), 0, 1));
+        this.time_end = this.formatTime(new Date());
+        this.maxDate = this.formatTime(new Date());
+        this.minDate = this.formatTime(new Date(new Date().getFullYear(), 0, 1));
+      }else{
+        this.time_begin = this.formatTime(new Date(this.currentYear, 0, 1));
+        this.time_end = this.formatTime(new Date(this.currentYear, 11, 31));
+        this.maxDate = this.formatTime(new Date(this.currentYear, 11, 31));
+      this.minDate = this.formatTime(new Date(this.currentYear, 0, 1));
+      this.defaultDate = this.minDate;
+      }
+      this.showPicker = false;
+      this.pestList = [];
+      this.thumbnails = [];
+      await this.getDevicePhotoDetails();
+    },
+    async handleChange(e){
+      console.log(e,'e')
+      this.time_begin = e.result;
+      this.time_end = e.result;
+      this.pestList = [];
+      await this.getDevicePhotoDetails()
+      this.currentImg = {};
+    },
+    getPestColor(pestName) {
+      // 如果该害虫已有颜色,直接返回
+      if (this.pestColorMap[pestName]) {
+        return this.pestColorMap[pestName];
+      }
+      // 为新害虫分配颜色
+      const usedColors = Object.values(this.pestColorMap);
+      // 找一个未使用的颜色
+      let color;
+      for (const c of this.colors) {
+        if (!usedColors.includes(c)) {
+          color = c;
+          break;
+        }
+      }
+      // 如果所有颜色都用完了,使用哈希值确定颜色
+      if (!color) {
+        const hash = pestName.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);
+        color = this.colors[hash % this.colors.length];
+      }
+      this.pestColorMap[pestName] = color;
+      return color;
+    },
+     getLevelDisplayName(level) {
+      const levelMap = {
+        '-1': '其他害虫',
+        1: '一类害虫',
+        2: '二类害虫',
+        3: '三类害虫',
+        4: '四类害虫',
+        5: '五类害虫'
+      };
+      return levelMap[String(level)] || `${level}`;
+    },
+    formatTime(dateString) {
+      const date = new Date(dateString);
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      return `${year}-${month}-${day}`;
+    },
+    async getPestLevelMap() {
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.new_cbd.pest_level_map',
+        method: 'POST',
+      });
+      const pestYype = {}
+      for(let key in res){
+        pestYype[this.getLevelDisplayName(key)] = res[key]
+      }
+      this.pestYype = pestYype
+    },
+    // 获取虫子字典
+    async getPestDict() {
+      try {
+        const res = await this.$myRequest({
+          url: '/api/api_gateway?method=forecast.pest_info.pest_dict',
+          method: 'POST',
+          data:{
+            type_name: '1'
+          }
+        });
+        this.pestDict = res || {};
+      } catch (error) {
+        console.error('获取虫子字典失败:', error);
+        this.pestDict = {};
+      }
+    },
+    formatDate(dateString) {
+      const date = new Date(dateString*1000);
+      const year = date.getFullYear();
+      const month = String(date.getMonth() + 1).padStart(2, '0');
+      const day = String(date.getDate()).padStart(2, '0');
+      const hour = String(date.getHours()).padStart(2, '0');
+      const minute = String(date.getMinutes()).padStart(2, '0');
+      const second = String(date.getSeconds()).padStart(2, '0');
+      return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
+    },
+    async getDevicePhotoDetails() {
+      const res = await this.$myRequest({
+        url: '/api/api_gateway?method=forecast.forecast_system.bzy_photo_detail',
+        method: 'POST',
+        data: {
+          cmd:'bzy',
+          img_id: this.img_id,
+        },
+      });
+      this.currentImg = res;
+      // 处理label参数生成bugMarkers
+      this.processBugMarkers();
+      const pest_list = res.pest_list || [];
+      const pestArr = new Map()
+      const pesAlltList = []
+      for(let key in this.pestYype){
+        for(let j in this.pestYype[key]){
+          const pestName = this.pestYype[key][j]
+          pesAlltList.push(pestName)
+          pest_list?.forEach(item => {
+            if(item.pest_name == pestName){
+              if(pestArr.has(key)){
+                pestArr.get(key).push(item)
+              }else{
+                pestArr.set(key, [item])
+              }
+            }
+          })
+        }
+      }
+      pest_list.forEach(item => {
+        if(!pesAlltList.includes(item.pest_name)){
+          pestArr.get('其他害虫').push(item)
+        }
+      })
+      const pestList = []
+      for(let key of pestArr){
+        pestList.push(key)
+      }
+      this.pestList = pestList
+    },
+    onImageLoad(e) {
+      this.imageWidth = e.detail.width;
+      this.imageHeight = e.detail.height;
+
+      // 计算容器显示高度(根据屏幕宽度和图片宽高比)
+      const systemInfo = uni.getSystemInfoSync();
+      const screenWidth = systemInfo.windowWidth;
+      const containerWidth = screenWidth - 24; // 减去左右margin (24rpx ≈ 12px)
+      const displayHeight = (containerWidth / this.imageWidth) * this.imageHeight;
+
+      this.containerHeight = displayHeight + 'px';
+      console.log('图片尺寸:', this.imageWidth, this.imageHeight, '容器高度:', this.containerHeight);
+
+      // 图片加载完成后处理bugMarkers
+      this.processBugMarkers();
+    },
+    
+    processBugMarkers() {
+      console.log('22222', this.currentImg);
+      if(this.currentImg.is_mark){
+        if(!this.currentImg.mark){
+          this.bugMarkers = [];
+          return;
+        }
+      }else{
+        if(!this.currentImg.label){
+          this.bugMarkers = [];
+          return;
+        }
+      }
+      // 如果没有图片尺寸,先设置默认值
+      if (!this.imageWidth || !this.imageHeight) {
+        console.log('没有图片尺寸,使用默认值');
+        this.imageWidth = 1000;
+        this.imageHeight = 1000;
+      }
+
+      try {
+        // 处理label参数,将单引号替换为双引号
+        let labelStr = this.currentImg.label;
+        let label
+        if(labelStr){
+          labelStr = labelStr?.replace(/'/g, '"');
+          console.log(labelStr,'labelStrlabelStr')
+          label = JSON.parse(labelStr);
+        }
+        const markers = [];
+
+        // 根据原图宽度计算缩放比例
+        let scaleRatio;
+        if (this.imageWidth >= 5000) {
+          scaleRatio = 1;
+        } else if (this.imageWidth >= 4000) {
+          scaleRatio = 1;
+        } else {
+          scaleRatio = 1;
+        }
+        console.log('原图宽度:', this.imageWidth, '缩放比例:', scaleRatio);
+        const mark = this.currentImg.mark || []
+        if(this.currentImg.is_mark){
+          mark.forEach(item=>{
+            const x = (item.startX * scaleRatio / this.imageWidth) * 100;
+            const y = (item.startY * scaleRatio / this.imageHeight) * 100;
+            const width = (item.width * scaleRatio / this.imageWidth) * 100;
+            const height = (item.height * scaleRatio / this.imageHeight) * 100;
+            markers.push({
+              id: item.text, // 显示虫子名字
+              x,
+              y,
+              width,
+              height,
+              color:this.getPestColor(item.text)
+            });
+          })
+        }else{
+          label?.forEach(item => {
+            for (let key in item) {
+              const [x1, y1, x2, y2] = item[key];
+              // 使用缩放比例计算标记位置和尺寸
+              const x = (x1 * scaleRatio / this.imageWidth) * 100;
+              const y = (y1 * scaleRatio / this.imageHeight) * 100;
+              const width = ((x2 - x1) * scaleRatio / this.imageWidth) * 100;
+              const height = ((y2 - y1) * scaleRatio / this.imageHeight) * 100;
+
+              // 从字典中获取虫子名字(尝试字符串和数字两种类型)
+              const pestName = this.pestDict[key] || this.pestDict[String(key)] || key;
+              const color = this.getPestColor(pestName);
+
+              markers.push({
+                id: pestName, // 显示虫子名字
+                x,
+                y,
+                width,
+                height,
+                color
+              });
+            }
+          });
+        }
+        console.log(markers,'markersmarkers')
+        this.bugMarkers = markers;
+      } catch (error) {
+        console.error('处理label参数失败:', error);
+        this.bugMarkers = [];
+      }
+    },
+    handleBack() {
+      uni.navigateBack({
+        delta: 1
+      });
+    },
+    goBack() {
+      uni.navigateBack();
+    },
+    selectThumbnail(item) {
+      this.img_id = item.ids;
+      this.getDevicePhotoDetails();
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+::v-deep .u-calendar__action{
+  display:flex;
+  justify-content: space-between;
+}
+::v-deep .u-hover-class{
+  .u-calendar__content__item__inner{
+    color:#aaa !important;
+  }
+}
+.device-photo-page {
+  background-color: #F5F5F5;
+  min-height: 100vh;
+  width: 100%;
+  padding-top: 112rpx;
+  background: linear-gradient(180deg, #ffffff00 0%, #F5F6FA 23.64%, #F5F6FA 100%), linear-gradient(102deg, #BFEADD 6.77%, #B8F1E7 40.15%, #B9EEF5 84.02%);
+  .device-detail__header {
+    width: 100%;
+    font-size: 28rpx;
+    color: #999;
+    color: #042118;
+    font-family: 'Source Han Sans CN VF';
+    font-weight: 700;
+    position: relative;
+    text-align: center;
+    .arrow-left {
+      position: absolute;
+      left: 32rpx;
+      margin-right: 12rpx;
+    }
+  }
+
+  /* 顶部导航栏 */
+  .nav-bar {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 24rpx;
+    background-color: #E6F7EF;
+
+    .nav-left {
+      .back-icon {
+        font-size: 36rpx;
+        color: #042118;
+      }
+    }
+
+    .nav-title {
+      font-size: 32rpx;
+      font-weight: 700;
+      color: #042118;
+    }
+
+    .nav-right {
+      display: flex;
+      align-items: center;
+
+      .more-icon {
+        font-size: 32rpx;
+        color: #042118;
+        margin-right: 24rpx;
+      }
+
+      .eye-icon {
+        font-size: 32rpx;
+      }
+    }
+  }
+  .date-container{
+    padding: 0 20rpx;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    .select-year{
+      width: 150rpx;
+      height: 80rpx;
+      display: flex;
+      align-items: center;
+      justify-content: space-around;
+      color: #656565;
+      font-size: 26rpx;
+      line-height: 80rpx;
+      text-align: center;
+      border-radius: 48rpx;
+      background-color: #FFFFFF;
+      margin-right: 10rpx;
+      padding: 0 26rpx;
+      .select-year-item{
+        width: 100%;
+        display: flex;
+        align-items: center;
+        justify-content: space-around;
+      }
+    }
+  }
+  /* 日期选择器 */
+  .date-picker {
+    display: flex;
+    align-items: center;
+    padding: 20rpx 24rpx;
+    background-color: #FFFFFF;
+    margin-bottom: 24rpx;
+    width: 100%;
+    margin:20rpx auto;
+    border-radius: 48rpx;
+    position: relative;
+    .date-input {
+      display: flex;
+      flex-direction: column;
+      width: 100%;
+      text-align: center;
+      .date-label {
+        font-size: 24rpx;
+        color: #999999;
+        margin-bottom: 8rpx;
+      }
+
+      .date-value {
+        font-size: 28rpx;
+        color: #042118;
+        font-weight: 500;
+      }
+    }
+    .calendar {
+      position: absolute;
+      right: 24rpx;
+      top: 50%;
+      transform: translateY(-50%);
+      font-size: 32rpx;
+      color: #999999;
+    }
+    .date-separator {
+      margin: 0 24rpx;
+      font-size: 28rpx;
+      color: #999999;
+    }
+  }
+
+  /* 主图片区域 */
+  .main-photo {
+    position: relative;
+    margin: 0 24rpx 24rpx;
+    background-color: #FFFFFF;
+    border-radius: 16rpx;
+    padding: 0;
+    overflow: hidden;
+    .movable-area {
+      width: 100%;
+      overflow: hidden;
+    }
+
+    .movable-view {
+      width: 100%;
+    }
+
+    .image-container {
+      position: relative;
+      width: 100%;
+    }
+
+    .main-photo-image {
+      width: 100%;
+      display: block;
+    }
+
+    /* 虫子标记层 */
+    .bug-markers {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      pointer-events: none;
+    }
+
+    /* 虫子标记 */
+    .bug-marker {
+      position: absolute;
+      border: 2rpx solid;
+      border-radius: 4rpx;
+    }
+
+    /* 虫子标记标签 */
+    .bug-label {
+      position: absolute;
+      top: -24rpx;
+      left: 0;
+      padding: 2rpx 6rpx;
+      border-radius: 4rpx;
+      font-size: 20rpx;
+      color: white;
+      white-space: nowrap;
+    }
+
+    /* 虫子标记层 */
+    .bug-markers {
+      position: absolute;
+      top: 0;
+      left: 0;
+      width: 100%;
+      height: 100%;
+      pointer-events: none;
+    }
+
+    /* 虫子标记 */
+    .bug-marker {
+      position: absolute;
+      border: 2rpx solid;
+      border-radius: 4rpx;
+      pointer-events: auto;
+    }
+
+    /* 虫子标记标签 */
+    .bug-label {
+      position: absolute;
+      top: -30rpx;
+      left: 0;
+      padding: 4rpx 8rpx;
+      border-radius: 4rpx;
+      font-size: 20rpx;
+      font-weight: bold;
+    }
+
+    .photo-timestamp {
+      position: absolute;
+      bottom: 0rpx;
+      left: 0rpx;
+      width:100%;
+      background-color: rgba(0, 0, 0, 0.6);
+      color: #FFFFFF;
+      padding: 8rpx 16rpx;
+      border-radius: 8rpx;
+      font-size: 24rpx;
+    }
+  }
+
+  /* 缩略图预览 */
+  .thumbnail-preview {
+    margin: 0 24rpx 24rpx;
+
+    .thumbnail-scroll {
+      gap: 16rpx;
+      padding-bottom: 16rpx;
+      box-sizing: border-box;
+      white-space: nowrap;
+      overflow-x: auto;
+      overflow-y: hidden;
+      width: 100%;
+      // 隐藏滚动条
+      -ms-overflow-style: none;
+      scrollbar-width: none;
+      .thumbnail-item {
+        width: 120rpx;
+        height: 120rpx;
+        border-radius: 8rpx;
+        overflow: hidden;
+        border: 2rpx solid transparent;
+        display: inline-block;
+        box-sizing: border-box;
+        margin-right: 10rpx;
+        &.active {
+          border-color: #0bbc58;
+        }
+
+        .thumbnail-image {
+          width: 100%;
+          height: 100%;
+          object-fit: cover;
+        }
+      }
+    }
+  }
+
+  /* 识别结果 */
+  .recognition-result {
+    margin: 0 24rpx 24rpx;
+    background-color: #FFFFFF;
+    border-radius: 16rpx;
+    padding: 24rpx;
+
+    .result-title {
+      font-size: 28rpx;
+      font-weight: 700;
+      color: #042118;
+      margin-bottom: 24rpx;
+    }
+
+    /* 害虫类别 */
+    .pest-category {
+      margin-bottom: 32rpx;
+      border: 1px solid #E4E7ED;
+      border-radius: 16rpx;
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      .category-header {
+        display: flex;
+        justify-content: space-between;
+        margin-bottom: 16rpx;
+        background: #F6F8FC;
+        padding: 18rpx 24rpx;
+        .category-title {
+          font-size: 24rpx;
+          font-weight: 500;
+          color: #042118;
+        }
+
+        .category-count {
+          font-size: 24rpx;
+          color: #999999;
+        }
+      }
+
+      .pest-item {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 12rpx;
+        padding: 6rpx 24rpx;
+        &:last-child {
+          margin-bottom: 0;
+        }
+
+        .pest-info {
+          display: flex;
+          align-items: center;
+
+          .pest-color {
+            width: 16rpx;
+            height: 16rpx;
+            border-radius: 50%;
+            margin-right: 12rpx;
+          }
+
+          .pest-name {
+            font-size: 24rpx;
+            color: #042118;
+          }
+        }
+
+        .pest-count {
+          font-size: 24rpx;
+          color: #042118;
+        }
+      }
+    }
+  }
+}
+</style>

+ 37 - 13
pages/cb/bzy/equip-set/equip-set.vue

@@ -20,7 +20,7 @@
 				<u-select v-model="on_off_show" mode="single-column" :list="on_off_list" @confirm="confirm($event,'on_off')"></u-select>
 			</view>
 		</view>
-		<view class="tit">
+		<view class="tit" v-if="device_model == 0">
 			载玻片滴液时间
 		</view>
 		<view class="uni-list-cell" @click="selectFun('drop_time')">
@@ -28,10 +28,10 @@
 			<view class="arrow"></view>
 			<u-select v-model="drop_time_show" mode="single-column" :list="drop_time_List" @confirm="confirm($event,'drop')"></u-select>
 		</view>
-		<view class="tit">
+		<view class="tit" v-if="device_model == 0">
 			孢子培养时间(h)
 		</view>
-		<view class="">
+		<view class="" v-if="device_model == 0">
 			<slider :value="setFrom.cul_time" show-value="true" :min="1" :max="24" @change="sliderChange($event,'cul_time')"  block-color="#57C878" activeColor="#57C878" step="1" />
 		</view>
 		<view class="tit">
@@ -41,6 +41,18 @@
 			<slider :value="setFrom.set_temp" show-value="true" :min="10" :max="40" @change="sliderChange($event,'set_temp')"  block-color="#57C878" activeColor="#57C878" step="1" />
 		</view>
 		<view class="tit">
+			高温保护阈值(℃)
+		</view>
+		<view class="">
+			<slider :value="setFrom.tph" show-value="true" :min="50" :max="70" @change="sliderChange($event,'tph')"  block-color="#57C878" activeColor="#57C878" step="1" />
+		</view>
+		<view class="tit">
+			低温保护阈值(℃)
+		</view>
+		<view class="">
+			<slider :value="setFrom.tpl" show-value="true" :min="-30" :max="20" @change="sliderChange($event,'tpl')"  block-color="#57C878" activeColor="#57C878" step="1" />
+		</view>
+		<view class="tit">
 			数据上传时间间隔(min)
 		</view>
 		<view class="">
@@ -106,27 +118,30 @@
 				}],
 				drop_time_show:false,
 				drop_time_List:[],
-			    setFrom: {
+			  setFrom: {
+					tph: 0, //高温保护阈值
+					tpl: 0, //低温保护阈值
 					drop_time: "1", //载玻片滴液时间
 					cul_time: 1, //孢子培养时间
 					set_temp: 10, //保温仓设定温度
 					datt: 10, //数据上传时间间隔(h)-m
 					coll_time: [], //采集开启和关闭时间
-				 },
+				},
+				device_model:'',
 				coll_time: {
-			        time01: "",
-			        time02: "",
-			        time03: "",
-			        time04: "",
-			        time05: "",
-			        time06: "",
+					time01: "",
+					time02: "",
+					time03: "",
+					time04: "",
+					time05: "",
+					time06: "",
 					value01: "",
 					value02: "",
 					value03: "",
 					value04: "",
 					value05: "",
 					value06: "",
-			      },
+			  },
 				timeList:[] ,
 				collShow:false,
 				timeType:'',
@@ -134,7 +149,8 @@
 			}
 		},
 		onLoad(option){
-			this.d_id=option.d_id
+			this.d_id = option.d_id
+			this.device_model = option.device_model
 			this.equipOperation()
 			this.equipSet()
 			uni.getStorage({
@@ -160,6 +176,8 @@
 				this.setFrom.cul_time=res.cul_time
 				this.setFrom.set_temp=res.set_temp
 				this.setFrom.datt=res.datt
+				this.setFrom.tph=res.tph
+				this.setFrom.tpl=res.tpl
 				let coll_time=res.coll_time 
 				for (let i in coll_time) {
 					if (i == 0) {
@@ -268,6 +286,12 @@
 					case 'datt':
 					  this.setFrom.datt=e.detail.value;
 					  break;
+					case 'tph':
+					  this.setFrom.tph=e.detail.value;
+					  break;
+					case 'tpl':
+					  this.setFrom.tpl=e.detail.value;
+					  break;
 				}
 			},
 			selectTime(a){

+ 22 - 11
pages/cb/equip-detail/equip-detail.vue

@@ -26,10 +26,11 @@
       </view>
       <view class=""> 设备地址:{{ equipInfo.address || city }} </view>
       <view v-if="type == 7" @click="setTime(equipInfo.d_id)">
-        <text space="emsp">载玻片、培养液更换时间</text>
+        <text space="emsp" v-if="equipInfo.device_model == 0">载玻片、培养液更换时间</text>
+				<text space="emsp" v-else>载玻带更换时间</text>
         <u-icon name="edit-pen" color="#f0ad4e" size="28"></u-icon>
       </view>
-      <view v-if="type == 7">
+      <view v-if="type == 7 && equipInfo.device_model == 0">
         <text space="emsp">
           <span
             :class="{
@@ -63,7 +64,7 @@
       </view>
       <u-popup v-model="setTimeShow" mode="center" width="600rpx">
         <u-field
-          label="载玻片更换时间"
+          :label="equipInfo.device_model == 0?'载玻片更换时间':'载玻带更换时间'"
           placeholder="选择日期"
           label-width="240"
           required
@@ -75,6 +76,7 @@
         >
         </u-field>
         <u-field
+          v-if="equipInfo.device_model == 0"
           label="培养液更换时间"
           placeholder="选择日期"
           label-width="240"
@@ -784,7 +786,9 @@ export default {
           '&device_type=' +
           this.type +
           '&disable=' +
-          this.equipInfo.disable,
+          this.equipInfo.disable +
+          '&device_model=' +
+          this.equipInfo.device_model,
       });
     },
     partClicks() {
@@ -834,21 +838,28 @@ export default {
     async setTimeSubmit() {
       if (!this.glass_slide_time) {
         this.glassErr = '请填写载玻片更换时间';
+        return
       }
-      if (!this.cultivate_time) {
+      if (!this.cultivate_time && this.equipInfo.device_model == 0) {
         this.culErr = '请填写培养液更换时间';
         return;
       }
+      let params = {
+        device_type_id: this.type,
+        d_id: this.equipInfo.d_id,
+        glass_slide_time: glass,
+        cultivate_time: cultivate,
+      }
+      if(this.equipInfo.device_model == 0){
+        params.cultivate_time = cultivate
+      }else{
+        delete params.cultivate_time
+      }
       let glass = parseInt(new Date(this.glass_slide_time).getTime() / 1000);
       let cultivate = parseInt(new Date(this.cultivate_time).getTime() / 1000);
       const res = await this.$myRequest({
         url: '/api/api_gateway?method=device.device_manage.updata_spore_time',
-        data: {
-          device_type_id: this.type,
-          d_id: this.equipInfo.d_id,
-          glass_slide_time: glass,
-          cultivate_time: cultivate,
-        },
+        data: params,
       });
       if (res) {
         this.$refs.toast.show({

BIN
pages/cbd/assets/cbd.png


BIN
pages/cbd/assets/copy.png


BIN
pages/cbd/assets/edit.png


BIN
pages/cbd/assets/editBorder.png


BIN
pages/cbd/assets/editIcon.png


BIN
pages/cbd/assets/general.png


BIN
pages/cbd/assets/offline.png


BIN
pages/cbd/assets/online.png


BIN
pages/cbd/assets/photoBorder.png


BIN
pages/cbd/assets/photoIcon.png


BIN
pages/cbd/assets/serviceIcon.png


BIN
pages/cbd/assets/setting.png


BIN
pages/cbd/assets/settingBorder.png


BIN
pages/cbd/assets/settingIcon.png


BIN
pages/cbd/assets/sim.png


BIN
pages/cbd/assets/simBorder.png


BIN
pages/cbd/assets/simIcon.png


+ 3 - 4
pages/cbd/components/DeviceCard.vue

@@ -19,7 +19,6 @@
   </view>
 </template>
 <script>
-import copy from '../assets/copy.png';
 export default {
   props: {
     dataSource: {
@@ -29,7 +28,7 @@ export default {
   },
   data() {
     return {
-      copy,
+      copy:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/copy.png',
       title: ''
     }
   },
@@ -81,7 +80,7 @@ export default {
       text-align: center;
       top: 0;
       right: 0;
-      background: url('../assets/online.png') no-repeat center center;
+      background: url('https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/online1.png') no-repeat center center;
       background-size: 100% 100%;
       color: #0bbc58;
       font-family: "Source Han Sans CN VF";
@@ -95,7 +94,7 @@ export default {
       text-align: center;
       top: 0;
       right: 0;
-      background: url('../assets/offline.png') no-repeat center center;
+      background: url('https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/offline.png') no-repeat center center;
       background-size: 100% 100%;
       color: #ff3546;
       font-family: "Source Han Sans CN VF";

+ 19 - 29
pages/cbd/components/deviceData.vue

@@ -36,8 +36,8 @@
             <text class="data-label">上仓门</text>
           </view>
           <view class="data-item">
-            <text class="data-value">{{ deviceStatic.dver }}</text>
-            <text class="data-label">设备版本</text>
+            <text class="data-value">{{ deviceStatic.tph || '' }}℃</text>
+            <text class="data-label">高温限值</text>
           </view>
         </view>
         <view class="data-column-left">
@@ -54,8 +54,8 @@
             <text class="data-label">下仓门</text>
           </view>
           <view class="data-item">
-            <text class="data-value">{{ deviceStatic.tph || '' }}℃</text>
-            <text class="data-label">高温限值</text>
+            <text class="data-value">{{ deviceStatic.dver }}</text>
+            <text class="data-label">设备版本</text>
           </view>
         </view>
         <view class="data-column-right">
@@ -240,16 +240,8 @@
 </template>
 
 <script>
-import setting from '../assets/setting.png';
-import edit from '../assets/edit.png';
-import sim from '../assets/sim.png';
 import Circulation from '../../../static/js/equipState_dict.json';
 import floatButton from './floating-button.vue';
-import general from '../assets/general.png';
-import editBorder from '../assets/editBorder.png';
-import settingBorder from '../assets/settingBorder.png';
-import simBorder from '../assets/simBorder.png';
-import photoBorder from '../assets/photoBorder.png';
 let chartInstance = null;
 
 export default {
@@ -289,18 +281,19 @@ export default {
   },
   data() {
     return {
-      setting,
-      general,
-      editBorder,
-      settingBorder,
-      photoBorder,
-      simBorder,
+      setting:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/setting.png',
+      general:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/general.png',
+      editBorder:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/editBorder.png',
+      settingBorder:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/settingBorder.png',
+      simBorder:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/simBorder.png',
+      devImage:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/image/cbd.png',
+      photoBorder:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/photoBorder.png',
       devImage:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/image/cbd.png',
       baseDevice:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/image',
       devImg:'',
       activeTab: 0,
-      edit,
-      sim,
+      edit:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/edit.png',
+      sim:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/sim.png',
       show:false,
       isShowPhoto:false,
       show1:true,
@@ -361,6 +354,9 @@ export default {
       return Math.ceil(this.totalPage / this.page_size)
     },
     formatDevImg() {
+      if(this.devImg == 'lowcbd'){
+        return 'https://s3.hnyfwlw.com/webstaticimg/bigdata_pc/menu/cbd.png';
+      }
       return `https://s3.hnyfwlw.com/webstaticimg/bigdata_app/image/${this.devImg}.png`;
     },
   },
@@ -393,6 +389,9 @@ export default {
         } else if(newVal.device_model == '15'){
           this.devImg = 'gkcbd1'
           this.isShowPhoto = false
+        } else if(newVal.device_model == '16'){
+          this.devImg = 'lowcbd'
+          this.isShowPhoto = true
         } else{
           this.devImg = 'cbd'
           this.isShowPhoto = true
@@ -1129,10 +1128,6 @@ export default {
             display: flex;
             align-items: center;
 
-            &.even {
-              background: #FAFAFA;
-            }
-
             .body-cell.fixed {
               font-size: 24rpx;
               font-family: 'Source Han Sans CN VF', sans-serif;
@@ -1188,11 +1183,6 @@ export default {
             border-bottom: 1rpx solid #F5F5F5;
             height: 80rpx;
             align-items: center;
-
-            &.even {
-              background: #FAFAFA;
-            }
-
             .body-cell {
               min-width: 140rpx;
               font-size: 24rpx;

+ 1 - 2
pages/cbd/components/photoImage.vue

@@ -180,7 +180,7 @@ export default {
     box-sizing: border-box;
     display: inline-block;
     overflow: hidden;
-    border:2rpx solid #000;
+    border:2rpx solid #e4e7ed;
     border-radius: 16rpx;
     &-img-container {
       width: 100%;
@@ -205,7 +205,6 @@ export default {
       width: 100%;
       text-align: center;
       padding: 0;
-      border-radius: 16rpx;
       overflow: hidden;
       text-overflow: ellipsis;
       white-space: nowrap;

+ 7 - 12
pages/cbd/detail.vue

@@ -128,11 +128,6 @@ import PestEchart from './components/pestEchart.vue';
 import PestArchive from './components/pestArchive.vue';
 import photoImage from './components/photoImage.vue';
 import DeviceData from './components/deviceData.vue';
-import photoIcon from './assets/photoIcon.png';
-import editIcon from './assets/editIcon.png';
-import serviceIcon from './assets/serviceIcon.png';
-import simIcon from './assets/simIcon.png';
-import settingIcon from './assets/settingIcon.png';
 
 export default {
   components: {
@@ -149,11 +144,11 @@ export default {
       showPicker: false,
       disableShow: false,
       isShowOperation: false,
-      photoIcon,
-      editIcon,
-      serviceIcon,
-      simIcon,
-      settingIcon,
+      photoIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/photoIcon.png',
+      editIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/editIcon.png',
+      serviceIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/serviceIcon.png',
+      simIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/simIcon.png',
+      settingIcon:'https://s3.hnyfwlw.com/webstaticimg/bigdata_app/newImg/home/settingIcon.png',
       totalPage:0,
       currentYear: new Date().getFullYear(),
       selectorRange: [],
@@ -292,14 +287,14 @@ export default {
       if(e == 1){
         return
       }
-      this.page = e;
+      this.page = e-=1;
       this.getDeviceHistoryData();
     },
     nextPage(e){
       if(e * this.page_size >= this.totalPage){
         return
       }
-      this.page = e;
+      this.page = e+=1;
       this.getDeviceHistoryData();
     },
     changeTab(pestList){

+ 145 - 68
pages/cbd/devicePhoto.vue

@@ -95,17 +95,30 @@
     <view class="recognition-result" v-if="pestList.length">
       <view class="result-title">当前图片识别结果</view>
       <!-- 一类害虫 -->
-      <view class="pest-category" v-for="(pest,index) in pestList" :key="index">
-        <view class="category-header">
-          <text class="category-title">{{ pest[0] }}</text>
-          <text class="category-count">数量</text>
+      <view v-if="is_mark == 0">
+        <view class="pest-category" v-for="(pest,index) in pestList" :key="index">
+          <view class="category-header">
+            <text class="category-title">{{ pest[0] }}</text>
+            <text class="category-count">数量</text>
+          </view>
+          <view class="pest-item" v-for="(p, i) in pest[1]" :key="i">
+            <view class="pest-info">
+              <view class="pest-color" :style="{ backgroundColor: getPestColor(p.pest_name) }"></view>
+              <text class="pest-name">{{ p.pest_name }}</text>
+            </view>
+            <text class="pest-count">{{ p.pest_num }}</text>
+          </view>
         </view>
-        <view class="pest-item" v-for="(p, i) in pest[1]" :key="i">
-          <view class="pest-info">
-            <view class="pest-color" :style="{ backgroundColor: getPestColor(p.pest_name) }"></view>
-            <text class="pest-name">{{ p.pest_name }}</text>
+      </view>
+      <view v-else>
+        <view class="pest-category" v-for="(pest,index) in pestList" :key="index">
+          <view class="pest-item">
+            <view class="pest-info">
+              <view class="pest-color" :style="{ backgroundColor: getPestColor(pest[1].text) }"></view>
+              <text class="pest-name">{{ pest[1].text }}</text>
+            </view>
+            <text class="pest-count">{{ pest[1].value }}</text>
           </view>
-          <text class="pest-count">{{ p.pest_num }}</text>
         </view>
       </view>
     </view>
@@ -190,13 +203,16 @@ export default {
     }
     this.pestList = [];
     await this.getPestDict();
-    this.getPestLevelMap();
-    this.getPestList();
+    await this.getPestLevelMap();
+    await this.getPestList();
     // this.getDevicePhotoDetails();
   },
   methods: {
     getPestName(id){
-      return this.pestDict[id] || id;
+      if(this.is_mark == 0){
+        return this.pestDict[id] || id;
+      }
+      return id;
     },
     async confirmHandler(e){
       this.currentYear = this.selectorRange[e].id;
@@ -209,8 +225,8 @@ export default {
         this.time_begin = this.formatTime(new Date(this.currentYear, 0, 1));
         this.time_end = this.formatTime(new Date(this.currentYear, 11, 31));
         this.maxDate = this.formatTime(new Date(this.currentYear, 11, 31));
-      this.minDate = this.formatTime(new Date(this.currentYear, 0, 1));
-      this.defaultDate = this.minDate;
+        this.minDate = this.formatTime(new Date(this.currentYear, 0, 1));
+        this.defaultDate = this.minDate;
       }
       this.showPicker = false;
       this.pestList = [];
@@ -364,13 +380,20 @@ export default {
       }
       pest_list.forEach(item => {
         if(!pesAlltList.includes(item.pest_name)){
-          pestArr.get('其他害虫').push(item)
+          if(pestArr.has('其他害虫')){
+            pestArr.get('其他害虫').push(item)
+          }else{
+            pestArr.set('其他害虫', [item])
+          }
         }
       })
       const pestList = []
       for(let key of pestArr){
         pestList.push(key)
       }
+      if (this.currentImg.is_mark != 0) {
+        this.processBugMarkers();
+      }
       this.pestList = pestList
     },
     onImageLoad(e) {
@@ -391,66 +414,120 @@ export default {
     },
     
     processBugMarkers() {
-      console.log('开始处理label参数:', this.currentImg.label);
-      if (!this.currentImg.label) {
-        console.log('没有label参数');
-        this.bugMarkers = [];
-        return;
-      }
+      if (this.currentImg.is_mark === 0) {
+        if (!this.currentImg.label) {
+          console.log('没有label参数');
+          this.bugMarkers = [];
+          return;
+        }
 
-      // 如果没有图片尺寸,先设置默认值
-      if (!this.imageWidth || !this.imageHeight) {
-        console.log('没有图片尺寸,使用默认值');
-        this.imageWidth = 1000;
-        this.imageHeight = 1000;
-      }
+        // 如果没有图片尺寸,先设置默认值
+        if (!this.imageWidth || !this.imageHeight) {
+          console.log('没有图片尺寸,使用默认值');
+          this.imageWidth = 1000;
+          this.imageHeight = 1000;
+        }
 
-      try {
-        // 处理label参数,将单引号替换为双引号
-        let labelStr = this.currentImg.label;
-        labelStr = labelStr.replace(/'/g, '"');
-        const label = JSON.parse(labelStr);
+        try {
+          // 根据原图宽度计算缩放比例
+          let scaleRatio;
+          if (this.imageWidth >= 5000) {
+            scaleRatio = 1;
+          } else if (this.imageWidth >= 4000) {
+            scaleRatio = 1;
+          } else {
+            scaleRatio = 1;
+          }
+          // 处理label参数,将单引号替换为双引号
+          let labelStr = this.currentImg.label;
+          labelStr = labelStr.replace(/'/g, '"');
+          console.log(labelStr,'labelStrlabelStr')
+          const label = JSON.parse(labelStr);
+          const markers = [];
+
+          console.log('原图宽度:', this.imageWidth, '缩放比例:', scaleRatio);
+
+          label.forEach(item => {
+            for (let key in item) {
+              const [x1, y1, x2, y2] = item[key];
+              // 使用缩放比例计算标记位置和尺寸
+              const x = (x1 * scaleRatio / this.imageWidth) * 100;
+              const y = (y1 * scaleRatio / this.imageHeight) * 100;
+              const width = ((x2 - x1) * scaleRatio / this.imageWidth) * 100;
+              const height = ((y2 - y1) * scaleRatio / this.imageHeight) * 100;
+
+              // 从字典中获取虫子名字(尝试字符串和数字两种类型)
+              const pestName = this.pestDict[key] || this.pestDict[String(key)] || key;
+              const color = this.getPestColor(pestName);
+
+              markers.push({
+                id: pestName, // 显示虫子名字
+                x,
+                y,
+                width,
+                height,
+                color
+              });
+            }
+          });
+          console.log(markers,'markersmarkers')
+          this.bugMarkers = markers;
+        } catch (error) {
+          console.error('处理label参数失败:', error);
+          this.bugMarkers = [];
+        }
+      } else {
+        this.pestList = [];
         const markers = [];
-
         // 根据原图宽度计算缩放比例
-        let scaleRatio;
-        if (this.imageWidth >= 5000) {
-          scaleRatio = 1;
-        } else if (this.imageWidth >= 4000) {
-          scaleRatio = 1;
-        } else {
-          scaleRatio = 1;
-        }
-        console.log('原图宽度:', this.imageWidth, '缩放比例:', scaleRatio);
-
-        label.forEach(item => {
-          for (let key in item) {
-            const [x1, y1, x2, y2] = item[key];
-            // 使用缩放比例计算标记位置和尺寸
-            const x = (x1 * scaleRatio / this.imageWidth) * 100;
-            const y = (y1 * scaleRatio / this.imageHeight) * 100;
-            const width = ((x2 - x1) * scaleRatio / this.imageWidth) * 100;
-            const height = ((y2 - y1) * scaleRatio / this.imageHeight) * 100;
-
-            // 从字典中获取虫子名字(尝试字符串和数字两种类型)
-            const pestName = this.pestDict[key] || this.pestDict[String(key)] || key;
-            const color = this.getPestColor(pestName);
-
-            markers.push({
-              id: pestName, // 显示虫子名字
-              x,
-              y,
-              width,
-              height,
-              color
-            });
+        let scaleRatio = 4;
+        // if (this.imageWidth >= 5000) {
+        //   scaleRatio = 4;
+        // } else if (this.imageWidth >= 4000) {
+        //   scaleRatio = 1;
+        // } else {
+        //   scaleRatio = 2.5;
+        // }
+        const pestArr = new Map()
+        this.currentImg.mark.map((item, index) => {
+          if(pestArr.has(item.text)){
+            pestArr.set(item.text,{
+              value: pestArr.get(item.text).value+=1,
+              text: item.text
+            })
+          }else{
+            pestArr.set(item.text, {
+              value: 1,
+              text: item.text
+            })
+          }
+          const pestList = []
+          for(let key of pestArr){
+            pestList.push(key)
           }
+          this.pestList = pestList
+          this.is_mark = 1
+          const { startX, startY,text } = item;
+          // 使用缩放比例计算标记位置和尺寸
+          const x = (startX * scaleRatio / this.imageWidth) * 100;
+          const y = (startY * scaleRatio / this.imageHeight) * 100;
+          const width = (item.width * scaleRatio / this.imageWidth) * 100;
+          const height = (item.height * scaleRatio / this.imageHeight) * 100;
+
+          // 从字典中获取虫子名字(尝试字符串和数字两种类型)
+          const pestName = text;
+          const color = this.getPestColor(text);
+
+          markers.push({
+            id: pestName, // 显示虫子名字
+            x,
+            y,
+            width,
+            height,
+            color
+          });
         });
-
         this.bugMarkers = markers;
-      } catch (error) {
-        console.error('处理label参数失败:', error);
-        this.bugMarkers = [];
       }
     },
     handleBack() {

+ 73 - 25
pages/equipList2/index.vue

@@ -64,17 +64,23 @@
                 <span class="title">{{ (item.name || '--' )}}</span>
                 <span class="sub-title" @click.stop="modification(item)">信息修改</span>
               </p>
-							<p class="online-status">
+							<p class="online-status"
+                v-if="item.status == 1 || item.is_online" >
 								<image
-                  v-if="item.status == 1 || item.is_online" 
-                  :src="$imageURL+'/bigdata_app/newImg/home/online.png'" 
+                  :src="$imageURL+'/bigdata_app/newImg/home/online1.png'" 
                   mode=""
                 ></image>
+								<text class="status-text-online">在线</text>
+							</p>
+							<p
+                v-else
+								class="online-status"
+							>
 								<image
-                  v-else
-                  :src="$imageURL+'/bigdata_app/newImg/home/outline.png'"
+                  :src="$imageURL+'/bigdata_app/newImg/home/offline.png'"
                   mode=""
                 ></image>
+								<text class="status-text-offline">离线</text>
 							</p>
 						</view>
 						<view class="list_item_text">
@@ -193,12 +199,15 @@
 					this.pur_id = this.menuList.find(item => item.pur_id == this.pur_id)?.pur_id || '';
 					this.current = this.menuList.findIndex(item => item.pur_id == this.pur_id);
 				}
-        if(this.list[0].purview_name != '全部'){
-          this.list.unshift({
-            device_type_id: '',
-            purview_name: '全部'
-          })
-        }
+				
+				if(this.list.length > 1){
+					if(this.list[0].purview_name != '全部'){
+						this.list.unshift({
+							device_type_id: '',
+							purview_name: '全部'
+						})
+					}
+				}
         this.type_id = this.list[0].device_type_id;
         this.initPage();
       },
@@ -234,12 +243,20 @@
 			},
 			change(index,itemData) {
         this.list = this.menuList[index].children || []
-        if(this.list[0].purview_name != '全部'){
-          this.list.unshift({
-            device_type_id: '',
-            purview_name: '全部'
-          })
-        }
+				const list = [];
+				this.list.forEach(item=>{
+					if(item.url){
+						list.push(item)
+					}
+				})
+				if(list.length > 1){
+					if(this.list[0].purview_name != '全部'){
+						this.list.unshift({
+							device_type_id: '',
+							purview_name: '全部'
+						})
+					}
+				}
         this.current = index;
         const item = this.list[0];
         this.pur_id = itemData.pur_id || '';
@@ -282,13 +299,14 @@
         this.currents = 0;
         this.page = 1;
 				Debounce(() => {
-					this.eqlist(true);
+					this.eqlist();
 				}, 500)();
         this.width = 0;
       },
 			historys(item) {
 				const type_id = item.type_id;
 				item.pur_id = this.pur_id;
+				console.log(type_id,'type_idtype_idtype_id')
 				switch (type_id) {
 					// 水肥新设备
 					case 22:
@@ -396,9 +414,16 @@
 					case 7:
 						item.addtime = item.uptime;
 						item.type = item.type_id;
+						
+						if(item.pur_id == 458){
+							uni.navigateTo({
+								url: '../bzy/detail?info=' + JSON.stringify(item),
+							});
+					 }else{
 						uni.navigateTo({
 							url: '../cb/equip-detail/equip-detail?info=' + JSON.stringify(item),
 						});
+					}
 						break;
 					case 8:
 						uni.navigateTo({
@@ -632,8 +657,10 @@
 		},
 		onLoad() {
 			const that = this;
-			uni.$on('purId', (purId) => {
-				that.pur_id = purId;
+			uni.$on('purId', (item) => {
+				that.pur_id = item.purId;
+				that.menu = item.menu;
+				that.device_model = item.device_model;
 			});
 			uni.$on('refreshData', (refreshData) => {
 				this.eqlist();
@@ -700,8 +727,8 @@
 	}
 
 	.inputs {
-		width: 65%;
-		margin-left: 12rpx;
+		width: calc(100% - 72rpx);
+		margin-left: 36rpx;
 
 		/deep/.u-content {
 			background-color: #fff !important;
@@ -849,6 +876,7 @@
 			position: relative;
 			background-color: #ffffff;
 			border-radius: 16rpx;
+			position: relative;
 			.list_item_top {
 				.p1 {
 					width: 86%;
@@ -885,7 +913,8 @@
 				position: absolute;
 				top:0rpx;
 				text-align: center;
-				right: 0rpx;
+				right: 8rpx;
+				top: 2rpx;
 				width: 76px;
 				height: 28px;
 				// line-height: 28px;
@@ -893,10 +922,29 @@
 				
 				// border: 1px solid #ffffff;
 				image{
-					width: 152rpx;
+					width: 140rpx;
+					height: 56rpx;
+				}
+				.status-text-online{
+					color: #0BBC58;
+					position: absolute;
+					top: 0rpx;
+					right: 0rpx;
+					width: 120rpx;
+					height: 56rpx;
+					line-height: 56rpx;
+					font-size: 26rpx;
+				}
+				.status-text-offline{
+					color: #FB4E52;
+					position: absolute;
+					top: 0rpx;
+					right: 0rpx;
+					width: 120rpx;
 					height: 56rpx;
+					line-height: 56rpx;
+					font-size: 26rpx;
 				}
-	
 			}
 			// .p2 {
 					

+ 1 - 1
pages/equipMange/index/index.vue

@@ -46,7 +46,7 @@
           ></image>
           <p class="userlist-li-city">{{ item.username }}</p>
           <p class="userlist-li-eamil">{{ item.mobile }}</p>
-          <view class="loginbox">
+          <view class="loginbox" v-if="item.device_level != 1">
             <p class="loginp" @click="userloginbtn(item)">一键登录</p>
             <!-- <p class="logininfo" @click="userOperation(item)">查看详情</p> -->
           </view>

+ 1 - 1
pages/index/developing.vue

@@ -69,7 +69,7 @@ export default {
     padding: 16rpx 32rpx;
     background: rgba(255, 255, 255, 0.25);
     border-radius: 40rpx;
-	z-index: 10;
+	  z-index: 10;
     backdrop-filter: blur(10rpx);
 
     .back-icon {

+ 1 - 1
pages/server/index.vue

@@ -141,7 +141,7 @@ export default {
 					url: '/pages/equipList2/index',
           success: () => {
             setTimeout(() => {
-              uni.$emit('purId',purId);
+              uni.$emit('purId',{purId,menu:item.children[0]?.menu || '',device_model:item.children[0]?.device_model || ''});
             }, 50);
           }
 				})