Jelajahi Sumber

feat(devicePhoto): 实现带虫害标注的图片渲染功能

重构了虫害标记的展示逻辑,新增隐藏画布绘制带标注的合成图片,替换原有的DOM叠加标记方案。优化了图片容器高度的动态计算逻辑,根据图片实际宽高比自适应调整显示高度。修复了标记缩放比例的参数微调,提升标注显示准确性。
allen 3 hari lalu
induk
melakukan
9a5c2f87a1
1 mengubah file dengan 130 tambahan dan 36 penghapusan
  1. 130 36
      pages/sy/devicePhoto.vue

+ 130 - 36
pages/sy/devicePhoto.vue

@@ -39,40 +39,28 @@
           scale="true"
           scale-min="1"
           scale-max="5"
+          :style="{ height: containerHeight }"
         >
           <view class="image-container">
-            <!-- 原图 -->
             <image
-              :src="currentImg.addr"
+              :src="canvasImagePath || 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>
 
+    <!-- 隐藏画布,用于绘制标注图 -->
+    <canvas
+      canvas-id="annotatedCanvas"
+      :style="{ width: canvasWidth + 'px', height: canvasHeight + 'px' }"
+      style="position: fixed; left: -9999px; top: -9999px;"
+    ></canvas>
+
     <!-- 缩略图预览 -->
     <view class="thumbnail-preview">
       <view  class="thumbnail-scroll">
@@ -179,6 +167,9 @@ export default {
       imageWidth: 0,
       imageHeight: 0,
       imgOld_id:'',
+      canvasImagePath: '',
+      canvasWidth: 375,
+      canvasHeight: 300,
       containerHeight: '300px',
       device_type_id:'',
       cmd:'sy',
@@ -370,8 +361,37 @@ export default {
         },
       });
       this.currentImg = res;
+      this.canvasImagePath = '';
+
+      // 获取图片信息和本地路径
+      let localImagePath = '';
+      try {
+        const imageInfo = await new Promise((resolve, reject) => {
+          uni.getImageInfo({
+            src: res.addr,
+            success: resolve,
+            fail: reject
+          });
+        });
+        this.imageWidth = imageInfo.width;
+        this.imageHeight = imageInfo.height;
+        localImagePath = imageInfo.path;
+
+        // 计算容器高度
+        const systemInfo = uni.getSystemInfoSync();
+        const screenWidth = systemInfo.windowWidth;
+        const containerWidth = screenWidth - 24;
+        const displayHeight = (containerWidth / this.imageWidth) * this.imageHeight;
+        this.containerHeight = displayHeight + 'px';
+      } catch(e) {
+        console.error('获取图片信息失败:', e);
+        this.imageWidth = 1000;
+        this.imageHeight = 1000;
+      }
+
       // 处理label参数生成bugMarkers
       this.processBugMarkers();
+
       const pest_list = res.pest_list
       const pestArr = new Map()
       const pesAlltList = []
@@ -406,23 +426,23 @@ export default {
       if (this.currentImg.is_mark != 0) {
         this.processBugMarkers();
       }
+
+      // 绘制标注图片(异步,不阻塞)
+      if (localImagePath) {
+        this.drawAnnotatedImage(localImagePath);
+      }
+
       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();
+      // 仅在容器高度未计算时更新
+      if (!this.containerHeight || this.containerHeight === '300px') {
+        const systemInfo = uni.getSystemInfoSync();
+        const screenWidth = systemInfo.windowWidth;
+        const containerWidth = screenWidth - 24;
+        const displayHeight = (containerWidth / e.detail.width) * e.detail.height;
+        this.containerHeight = displayHeight + 'px';
+      }
     },
     
     processBugMarkers() {
@@ -491,7 +511,7 @@ export default {
         // this.pestList = [];
         const markers = [];
         // 根据原图宽度计算缩放比例
-        let scaleRatio = 3.06;
+        let scaleRatio = 3.07;
         // if (this.imageWidth >= 5000) {
         //   scaleRatio = 4;
         // } else if (this.imageWidth >= 4000) {
@@ -541,6 +561,80 @@ export default {
         this.bugMarkers = markers;
       }
     },
+    async drawAnnotatedImage(localImagePath) {
+      if (!localImagePath || !this.bugMarkers.length) {
+        return;
+      }
+
+      try {
+        // 计算画布尺寸(匹配显示区域)
+        const sysInfo = uni.getSystemInfoSync();
+        const displayWidth = sysInfo.windowWidth - 24;
+        const displayHeight = (displayWidth / this.imageWidth) * this.imageHeight;
+
+        this.canvasWidth = Math.round(displayWidth);
+        this.canvasHeight = Math.round(displayHeight);
+
+        // 等待画布尺寸更新
+        await this.$nextTick();
+
+        const ctx = uni.createCanvasContext('annotatedCanvas', this);
+
+        // 绘制原始图片
+        ctx.drawImage(localImagePath, 0, 0, this.canvasWidth, this.canvasHeight);
+
+        // 绘制虫子标记
+        this.bugMarkers.forEach(marker => {
+          const x = (marker.x / 100) * this.canvasWidth;
+          const y = (marker.y / 100) * this.canvasHeight;
+          const w = (marker.width / 100) * this.canvasWidth;
+          const h = (marker.height / 100) * this.canvasHeight;
+
+          // 绘制矩形框
+          ctx.setStrokeStyle(marker.color);
+          ctx.setLineWidth(1);
+          ctx.strokeRect(x, y, w, h);
+
+          // 绘制标签(上方优先,超出画布上边界时放到框下方)
+          const pestName = this.getPestName(marker.id);
+          const fontSize = 12;
+          ctx.setFontSize(fontSize);
+          ctx.setFillStyle(marker.color);
+          if (y - fontSize - 4 < 0) {
+            ctx.fillText(pestName, x + 4, y + h + fontSize + 4);
+          } else {
+            ctx.fillText(pestName, x + 4, y - 4);
+          }
+        });
+
+        // 提交绘制
+        await new Promise((resolve) => {
+          ctx.draw(false, () => {
+            setTimeout(resolve, 300);
+          });
+        });
+
+        // 转为临时图片
+        const tempFile = await new Promise((resolve, reject) => {
+          uni.canvasToTempFilePath({
+            canvasId: 'annotatedCanvas',
+            x: 0,
+            y: 0,
+            width: this.canvasWidth,
+            height: this.canvasHeight,
+            destWidth: this.canvasWidth * 2,
+            destHeight: this.canvasHeight * 2,
+            success: resolve,
+            fail: reject
+          }, this);
+        });
+
+        this.canvasImagePath = tempFile.tempFilePath;
+      } catch (error) {
+        console.error('绘制标注图片失败:', error);
+        this.canvasImagePath = '';
+      }
+    },
     handleBack() {
       uni.navigateBack({
         delta: 1