Explorar o código

feat(设备照片): 添加图片缩放和害虫标记功能

- 实现图片的缩放和拖动查看功能
- 添加害虫标记层,显示害虫位置和名称
- 使用固定颜色标识不同害虫类型
- 优化日期选择器默认值设置
- 修复图片加载和害虫标记处理逻辑
allen hai 1 día
pai
achega
9ec3f3e0f0
Modificáronse 2 ficheiros con 288 adicións e 22 borrados
  1. 5 0
      pages/cbd/detail.vue
  2. 283 22
      pages/cbd/devicePhoto.vue

+ 5 - 0
pages/cbd/detail.vue

@@ -320,6 +320,11 @@ export default {
   display:flex;
   justify-content: space-between;
 }
+::v-deep .u-hover-class{
+  .u-calendar__content__item__inner{
+    color:#aaa !important;
+  }
+}
 .device-detail {
   display: flex;
   width: 100%;

+ 283 - 22
pages/cbd/devicePhoto.vue

@@ -31,11 +31,45 @@
         </view>
     </view>
     <!-- 主图片区域 -->
-    <view class="main-photo">
-      <image 
-        :src="currentImg.addr" 
-        class="main-photo-image"
-      />
+    <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>
 
@@ -69,14 +103,14 @@
         </view>
         <view class="pest-item" v-for="(p, i) in pest[1]" :key="i">
           <view class="pest-info">
-            <view class="pest-color" :style="{ backgroundColor: getRandomColor() }"></view>
+            <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-calendar v-model="show" :mode="mode" @change="handleChange" :max-date="maxDate" :min-date="minDate"></u-calendar>
+		<u-calendar v-model="show" :mode="mode" @change="handleChange" :max-date="maxDate" :min-date="minDate" :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>
@@ -89,6 +123,7 @@ export default {
       mode: 'range',
       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: '查看图片',
@@ -116,17 +151,24 @@ export default {
       pest_list_arr:[],
       thumbnails: [],
       currentImg:{},
+      bugMarkers: [],
+      imageWidth: 0,
+      imageHeight: 0,
+      imgOld_id:'',
+      containerHeight: '300px',
+      pestDict: {}, // 虫子字典 {id: name}
+      pestColorMap: {}, // 害虫颜色映射 {pestName: color}
     };
   },
-  onLoad(options) {
+  async onLoad(options) {
     const {id,img_id,currentYear} = options
     this.device_id = id
-    this.img_id = img_id
+    this.imgOld_id = img_id
     this.currentYear = currentYear
     this.time_begin = this.currentYear + '-01-01'
     this.time_end = this.currentYear + '-12-31'
-    this.maxDate = this.formatTime(new Date())
-    this.minDate = this.formatTime(new Date(new Date().getFullYear(), 0, 1))
+    this.maxDate = this.formatTime(this.time_end)
+    this.minDate = this.formatTime(this.time_begin)
     this.selectorRange = [];
     const nowYear = new Date().getFullYear();
     for(let i = 0;i<50;i++){
@@ -136,12 +178,17 @@ export default {
       }
       this.selectorRange.push(item);
     }
+    this.pestList = [];
+    await this.getPestDict();
     this.getPestLevelMap();
     this.getPestList();
-    this.getDevicePhotoDetails();
+    // this.getDevicePhotoDetails();
   },
   methods: {
-    confirmHandler(e){
+    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));
@@ -152,18 +199,44 @@ 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.minDate = this.formatTime(new Date(this.currentYear, 0, 1));
+      this.defaultDate = this.minDate;
       }
       this.showPicker = false;
+      this.pestList = [];
+      this.thumbnails = [];
+      await this.getPestList();
     },
-    handleChange(e){
+    async handleChange(e){
       console.log(e,'e')
       this.time_begin = e.startDate;
       this.time_begin = e.endDate;
-      this.getPestList()
+      this.pestList = [];
+      await this.getPestList()
+      this.currentImg = {};
     },
-    getRandomColor(){
-      return this.colors[Math.floor(Math.random() * this.colors.length)]
+    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 = {
@@ -190,7 +263,18 @@ export default {
         },
       });
       this.thumbnails = res?.data || [];
-      console.log(this.thumbnails,'resres2222res')
+      //如果和旧的id相同,就用旧的id不然就用第一个
+      if(this.thumbnails.find(item => item.ids == this.imgOld_id)){
+        this.img_id = this.imgOld_id
+      }else{
+        this.img_id = this.thumbnails[0].ids
+      }
+      this.getDevicePhotoDetails();
+      // if(this.img_id){
+      //   this.currentImg = this.thumbnails.find(item => item.ids == this.img_id) ||  this.thumbnails[0] || {}
+      // }else{
+      //   this.currentImg = this.thumbnails[0] || {}
+      // }
     },
     formatTime(dateString) {
       const date = new Date(dateString);
@@ -210,6 +294,22 @@ export default {
       }
       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();
@@ -230,7 +330,10 @@ export default {
           img_id: this.img_id,
         },
       });
+      console.log('获取图片详情成功:', res);
       this.currentImg = res;
+      // 处理label参数生成bugMarkers
+      this.processBugMarkers();
       const pest_list = res.pest_list
       const pestArr = new Map()
       for(let key in this.pestYype){
@@ -253,6 +356,86 @@ export default {
       }
       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('开始处理label参数:', this.currentImg.label);
+      if (!this.currentImg.label) {
+        console.log('没有label参数');
+        this.bugMarkers = [];
+        return;
+      }
+
+      // 如果没有图片尺寸,先设置默认值
+      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);
+        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
+            });
+          }
+        });
+
+        this.bugMarkers = markers;
+      } catch (error) {
+        console.error('处理label参数失败:', error);
+        this.bugMarkers = [];
+      }
+    },
     handleBack() {
       uni.navigateBack({
         delta: 1
@@ -270,6 +453,15 @@ export default {
 </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;
@@ -405,12 +597,81 @@ export default {
     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%;
-      height: 400rpx;
-      object-fit: cover;
-      border-radius: 8rpx;
+      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 {