Ver Fonte

百色果园信息

shuhao há 1 dia atrás
pai
commit
41839d67b4

+ 29 - 19
src/views/home/album_compoents/albumCarousel.vue

@@ -1,15 +1,18 @@
 <template>
-  <el-dialog
-      v-model="dialogVisible"
-      width="60%"
-      height="80%"
-      class="picture-preview-wrap v-dialog"
-      :show-close="false"
-      top="10vh"
-      append-to-body
-  >
-    <album-carousel-item v-if="images" :farmId="farmId" :images="images" :lock="lock"></album-carousel-item>
-  </el-dialog>
+  <div>
+      <el-dialog
+        v-model="dialogVisible"
+        width="60%"
+        height="80%"
+        class="picture-preview-wrap v-dialog"
+        :show-close="false"
+        top="10vh"
+        append-to-body
+    >
+      <album-carousel-item v-if="images" :farmId="farmId" :images="images" :lock="lock"></album-carousel-item>
+    </el-dialog>
+  </div>
+
 </template>
 
 <script setup>
@@ -26,23 +29,30 @@ const images = ref(null);
 const dialogVisible = ref(false)
 // 获取当前日期
 const currentDate = new Date();
-// 获取当前日期的前一个月
+// 获取当前日期的前一
 const startDate = new Date(currentDate);
-startDate.setMonth(currentDate.getMonth() - 1);
+startDate.setFullYear(currentDate.getFullYear() - 1);
 
 // 格式化日期
 const formattedStartDate = dateFormat(startDate, 'YY-mm-dd');
 const formattedEndDate = dateFormat(currentDate, 'YY-mm-dd');
 
-eventBus.on("click:point",function({name}){
-  let params = {sampleId: name,farmId: farmId.value, startDate: formattedStartDate, endDate: formattedEndDate}
-  VE_API.miniimage.list(params).then(res => {
-    if(res.code === 0){
+async function clickPoint({sampleId,farmId,regionId}) {
+  let params = {sampleId,farmId, startDate: formattedStartDate, endDate: formattedEndDate}
+  let res = await VE_API.miniimage.list(params)
+  if(res.code === 0){
+    try {
+      // 现有的逻辑代码
       images.value = res.data
       dialogVisible.value = true
+    } catch (error) {
+      console.error("处理点击事件时出错: ", error);
+      // 这里可以添加额外的错误处理逻辑,比如显示错误信息给用户
     }
-  })
-})
+  }
+}
+
+eventBus.on("click:point",clickPoint)
 
 
 

+ 1 - 5
src/views/home/album_compoents/albumCarouselItem.vue

@@ -2,7 +2,7 @@
   <div class="carousel-container">
     <!-- 图片列表 -->
     <div class="carousel-wrapper"  :style="carouselStyle">
-      <photo-provider class="provider" v-if="images" :photo-closable="true" @visibleChange="handleVisibleChange">
+      <photo-provider v-if="images" :photo-closable="true" @visibleChange="handleVisibleChange">
         <template  v-for="(photo, index) in images"
                    :key="photo.id">
           <album-draw-box  :farmId="farmId" :photo="photo" :current="currentIndex" :index="index" :length="images.length"
@@ -123,16 +123,12 @@ const clearAndRestartTimer = () => {
 .carousel-container {
   position: relative;
   width: 100%;
-  height: 100%;
   overflow: hidden;
   margin: 0 auto;
   .carousel-wrapper {
     display: flex;
     transition: transform 0.5s ease;
     width: 100%;
-    .provider{
-      display: flex;
-    }
   }
   .blur-bg {
     position: absolute;

+ 90 - 116
src/views/home/album_compoents/albumDrawBox.vue

@@ -1,7 +1,7 @@
 <template>
   <photo-consumer
       class="carousel-item"
-      :src="watermark || base_img_url2 + photo.filename + resize"
+      :src="watermark || base_img_url2 + (photo.resFilename ? photo.resFilename : photo.filename) + resize"
   >
     <img v-if="Math.abs(current - index) < 3" crossorigin="anonymous" @load="drawWatermark($event)" loading="lazy" :src="watermark || (base_img_url2 + (photo.resFilename ? photo.resFilename : photo.filename) + resize)" style="width: 100%;" />
     <canvas  ref="canvasRef" style="position: absolute;"></canvas>
@@ -18,12 +18,15 @@
 <script setup>
 import { ref, onMounted, onBeforeUnmount, defineProps } from "vue";
 import { base_img_url2 } from "@/api/config";
-import {imageCache} from "./cacheImg.js"
+import {imageCache,loadImage} from "./cacheImg.js"
 import {dateFormat} from "@/utils/date_util.js"
-const resize = "?x-oss-process=image/resize,p_80/format,webp/quality,q_40";
+import {pointToFormat} from "@/views/util.js"
+import {drawTextInRect, drawBorderImageInRect, drawImageInRect, drawRectInRect, drawHorizontalTextList} from "./utils"
+const resize = "?x-oss-process=image/resize,p_30/format,webp/quality,q_40";
 
 const canvasRef = ref(null);
 const watermark = ref(null)
+const baseMapBig = ref(false)
 
 const props = defineProps({
   farmId:{
@@ -48,18 +51,38 @@ const props = defineProps({
   }
 })
 let img = null;
-function drawWatermark(event) {
+let ctx = null;
+let data = {year:props.photo.uploadDate.substring(0,4),
+  monthDay:dateFormat(new Date(props.photo.uploadDate),'mm/dd'),
+  address:props.photo.district.replaceAll("\"","") + props.photo.gardenName,
+  tempImg:imageCache.get("temp"),temp:"10°C-20°C",wendu:"适宜",
+  feiniao:imageCache.get("feiniao"),
+  baseMap:imageCache.get("base_map_"+props.photo.treeId),
+  fusheImg:imageCache.get("fushe"),fushe:"光照优",
+  shiduImg:imageCache.get("shidu"),shidu:"湿度适宜",
+  text:"病害风险,及时喷药",
+  shotCode:props.photo.shotCode,
+  treeCode:props.photo.treeCode,
+  pingzhong:props.photo.pingzhong,
+  uploadDate:props.photo.uploadDate,
+}
+
+async function drawWatermark(event) {
   img = event.target
+  await loadImage(props.photo.baseMap,"base_map_"+props.photo.treeId)
+  data.baseMap = imageCache.get("base_map_"+props.photo.treeId)
   if(!watermark.value){
     let param = {farmId:props.farmId, date: props.photo.uploadDate}
     let weather = null
-    VE_API.miniimage.getByFarmIdAndDate(param).then((res)=>{
+    VE_API.image.findSuitabilityByPoint(param).then((res)=>{
       if(res.code === 0){
         weather = res.data
         drawWatermark2(img,weather)
       }else{
         drawWatermark2(img,null)
       }
+    }).catch((err)=>{
+      console.error(err)
     })
   }
 }
@@ -69,135 +92,58 @@ function drawWatermark2(img,weather) {
   let scale = 3
   canvas.width = img.width * scale;
   canvas.height = img.height * scale;
-  const ctx = canvas.getContext('2d');
+  ctx = canvas.getContext('2d');
   ctx.scale(scale, scale)
   ctx.drawImage(img, 0, 0, img.width, img.height);
-  drawBottom(ctx, img.width, img.height, weather)
+  drawBottom(img.width, img.height, weather)
   watermark.value = canvas.toDataURL();
 }
 
-let data = {year:props.photo.uploadDate.substring(0,4),
-  monthDay:dateFormat(new Date(props.photo.uploadDate),'mm/dd'),
-  address:props.photo.district.replaceAll("\"","") + props.photo.gardenName,
-  tempImg:imageCache.get("temp"),temp:"20°C",
-  feiniao:imageCache.get("feiniao"),
-  fusheImg:imageCache.get("fushe"),fushe:"光照优",
-  shiduImg:imageCache.get("shidu"),shidu:"湿度适宜",
-  shotCode:props.photo.shotCode
-}
+
 // console.log(data)
-const drawBottom = (ctx, imgWidth, imgHeight, weather) => {
-  if(weather){
-    data["temp"] = weather.tempMin+"°C"
-    let fushe = "光照优"
-    if(weather.uvIndex > 5){
-      fushe = "辐射过高"
-    }else if(weather.uvIndex < 1){
-      fushe = "光照差"
-    }
-    data["fushe"] = fushe
-    let shidu = "湿度适宜"
-    if(weather.humidity < 10){
-      shidu = "干旱缺水"
-    }
-    data["shidu"] = shidu
+const drawBottom = (imgWidth, imgHeight, weather) => {
+  if (weather) {
+    data["temp"] = weather.tempMin + "°C" + "-" + weather.tempMax + "°C" + " " + weather.tempSuitability
+    data["fushe"] = "光照"+weather.vindexSuitability
+    data["shidu"] = "湿度"+weather.humiditySuitability
   }
-  // 设置遮罩的高度为imgHeight的1/6
-  const maskHeight = imgHeight / 4;
-
-  // 绘制黑色半透明遮罩
-  ctx.fillStyle = "rgba(0, 0, 0, 0.5)"; // 黑色,50%透明度
-  ctx.fillRect(0, imgHeight - maskHeight, imgWidth, maskHeight);
-
   // 设置文本样式
-  ctx.fillStyle = "white"; // 文本颜色为白色
   ctx.font = "8px Arial";
   ctx.textAlign = "left"; // 设置为左对齐
+  let imgRect = { x: 0, y: 0, width: imgWidth, height: imgHeight}
+  // 绘制头部黑色半透明遮罩
+  let topRect = drawRectInRect(ctx,imgRect, 0, 0, 70, 10,"rgba(0, 0, 0, 0.6)")
+  // 绘制黑色半透明遮罩
+  let bottomRect = drawRectInRect(ctx,imgRect, 0, 5/6 * 100, 100, 1/6 * 100,"rgba(0, 0, 0, 0.6)")
 
-  // 从遮罩的底部开始绘制文本
-  let currentY = imgHeight - maskHeight + 10; // 字段的起始Y坐标
-  let currentX = 10; // 字段的起始X坐标
-
-  // 绘制年份
-  const field1 = `${data.year}`;
-  ctx.fillText(field1, currentX, currentY+10);
-  currentY += 5;
-  // 计算下划线的起始和结束坐标
-  const underlineY = currentY + 10; // 下划线Y坐标稍微低于文本
-  const underlineWidth = ctx.measureText(field1).width; // 下划线的宽度与文本一致
-  ctx.strokeStyle = "white"; // 下划线颜色为白色
-  ctx.lineWidth = 1; // 下划线的宽度
-  ctx.beginPath();
-  ctx.moveTo(currentX, underlineY); // 下划线起始点
-  ctx.lineTo(currentX + underlineWidth, underlineY); // 下划线结束点
-  ctx.stroke(); // 绘制下划线
-
-  // 绘制日期
-  currentY += 20;
-  currentX += -3
+  drawHorizontalTextList(ctx, topRect, '#ffffff90',[data.treeCode, "蓬径:5m", "高度:3m", "高产树",data.pingzhong],
+      40, 0,
+      65 , 0,"|",0,
+      30,"#ffffff50",1,2)
   ctx.fillStyle = "white"; // 文本颜色为白色
-  ctx.font = "20px Arial";
-  ctx.textAlign = "left"; // 设置为左对齐
-  const field2 = `${data.monthDay}`;
-  ctx.fillText(field2, currentX, currentY + 10);
-
-  // 绘制一个圆点
-  currentX += 60
-  ctx.beginPath();
-  ctx.arc(currentX, imgHeight - maskHeight + (maskHeight / 2 + 1), 2, 0, Math.PI * 2); // 绘制圆形
-  ctx.fill(); // 填充圆形
-
-  // 绘制地址
-  currentY += -10;
-  currentX += 10
-  ctx.font = "8px Arial";
-  const field3 = `${data.address}`;
-  ctx.fillText(field3, currentX, currentY );
-
   // 绘制温度
-  currentY += 8;
-  currentX += -6
-  ctx.drawImage(data.tempImg, currentX + 2, currentY + 1, 13, 13);
-  const field4 = `${data.temp}`;
-  currentX += 15
-  currentY += 12
-  ctx.fillText(field4, currentX, currentY);
-
+  let startXPercent = 1;
+  drawImageInRect(ctx, bottomRect, data.tempImg, startXPercent, 5+10, 5, 30)
+  drawTextInRect(ctx, bottomRect,`${data.temp}`,startXPercent + 4, 28+10, 20)
   // 绘制湿度
-  currentX += 20
-  currentY += -12
-  ctx.drawImage(data.shiduImg, currentX +2, currentY + 1, 13, 13);
-  currentX += 16
-  currentY += 12
-  ctx.font = "8px Arial";
-  const field5 = `${data.shidu}`;
-  ctx.fillText(field5, currentX, currentY);
-
+  drawImageInRect(ctx, bottomRect, data.shiduImg, startXPercent+26, 7 + 10, 4, 30)
+  drawTextInRect(ctx, bottomRect,`${data.shidu}`,startXPercent + 31, 28 + 10, 20)
   // 绘制辐射
-  currentX += 32
-  currentY += -12
-  ctx.drawImage(data.fusheImg, currentX + 2, currentY + 1, 13, 13);
-  currentX += 16
-  currentY += 12
-  ctx.font = "8px Arial";
-  const field6 = `${data.fushe}`;
-  ctx.fillText(field6, currentX, currentY);
+  drawImageInRect(ctx, bottomRect, data.fusheImg, startXPercent+26 + 18, 7 + 10, 5, 30)
+  drawTextInRect(ctx, bottomRect,`${data.fushe}`,startXPercent+31 + 18, 28 + 10, 20)
+  //绘制日期信息
+  ctx.fillStyle = "#FFFFFF";
+  drawTextInRect(ctx, bottomRect,`${formatDate(new Date(data.uploadDate))}`,startXPercent +1.7, 75, 24)
+  //绘制位置信息
+  ctx.fillStyle = "#FFFFFF90";
+  drawTextInRect(ctx, bottomRect,`${data.treeCode}_S3_SCS3-3_D0P0G1`,startXPercent +13, 75, 18)
 
-  // 绘制文本信息
-  currentX =imgWidth - 40;
-  currentY =imgHeight - 40;
-  ctx.drawImage(data.feiniao, currentX, currentY, 25, 25);
-  ctx.fillText(data.shotCode, currentX - 10, currentY + 35);
+  if(data.baseMap){
+    drawBorderImageInRect(ctx, imgRect, data.baseMap, 2/3*100, 2/3*100,
+        1/3*100, 1/3*100, 5, 5)
+  }
 }
 
-//绘制文字
-const drawText = (ctx, textObject) => {
-  const { x, y, text, color } = textObject;
-  ctx.fillStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`;
-  ctx.font = 'normal 12px sans-serif';
-  const textHeight = parseInt(ctx.font, 10); // 获取字体大小计算文本的实际高度
-  ctx.fillText(text, x, y + textHeight); // 将y调整为y + textHeight  // 在指定位置(左上角)绘制文本
-};
 
 const showTagBox = ref(true); // 控制 tag-box 的显示状态
 const hideTagBox = (event) => {
@@ -205,6 +151,15 @@ const hideTagBox = (event) => {
   showTagBox.value = false; // 隐藏 tag-box
 };
 
+const formatDate = (date) => {
+  const year = date.getFullYear();
+  const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,需要加1
+  const day = String(date.getDate()).padStart(2, '0');
+  return `${(year+"").substring(2)}${month}${day}`;
+};
+
+
+
 
 </script>
 
@@ -304,5 +259,24 @@ canvas {
   transform: translateY(-50%);
 }
 
+.floating-img {
+  position: absolute;
+  bottom: 0;
+  right: 0;
+  width: auto !important;
+  height: 25% !important;
+}
+.floating-img-big {
+  position: fixed !important;
+  z-index: 99999 !important;
+  top: 50% !important;
+  left: 50% !important;
+  width: auto !important;
+  height: 100% !important;
+  transform: translate(-50%, -50%) !important;
+}
+
+
+
 
 </style>

+ 12 - 1
src/views/home/album_compoents/cacheImg.js

@@ -12,6 +12,7 @@ function loadImage(url, key) {
         // 如果缓存中没有,则创建一个新的图片对象
         const img = new Image();
         img.src = url;
+        img.crossOrigin = 'anonymous';
 
         img.onload = () => {
             // 图片加载完成后,将其存入缓存
@@ -59,6 +60,16 @@ loadImage(require('@/assets/watermark/temp.png'), 'temp')
         console.error('图片加载失败', error);
     });
 
-export { imageCache };
+loadImage("https://birdseye-img.sysuimars.com/birdseye-look-mini/base_map/90378.jpg", 'base_map')
+    .then((img) => {
+        // 在这里使用加载完成的图片
+        // console.log('图片加载成功', img);
+    })
+    .catch((error) => {
+        console.error('图片加载失败', error);
+    });
+
+
+export { imageCache ,loadImage};
 
 

+ 54 - 28
src/views/home/album_compoents/detailDailog.vue

@@ -1,6 +1,6 @@
 <template>
-    <el-dialog v-model="winDialogVisible" lock-scroll modal-class="album-detail-modal" :showClose="false" width="90%" align-center>
-        <div>
+    <el-dialog v-model="winDialogVisible" lock-scroll modal-class="album-detail-modal" :showClose="false" width="86%" align-center @close="closeDialog">
+        <div class="detail-log">
             <div class="congratulation-wrap">
                 <div class="congratulation-box">
                     <div class="win-des">
@@ -9,45 +9,32 @@
                     <div class="album-detail-box">
                         <div class="detail-title">{{ dialogData.farmWorkName }}</div>
                         <div class="detail-desc-box">
-                            <div class="desc-item">
+                            <div class="desc-item" v-if="dialogData?.conditionList && dialogData.conditionList.length">
                                 <span class="item-name">触发条件</span>
-                                {{ dialogData.condition }}
+                                {{ dialogData.condition || dialogData.conditionList[0].name + dialogData.conditionList[0].value }}
                             </div>
                             <div class="desc-item">
                                 <span class="item-name">农事编号</span>
                                 {{ dialogData.code }}
                             </div>
                             <div class="desc-item">
-                                <div v-if="dialogData.status === 2">
-                                    <span class="item-name">推荐时间</span>
-                                    {{ dialogData.solarName }}
-                                </div>
-                                <div v-if="dialogData.status === 1">
-                                    <span class="item-name">推荐时间</span>
-                                    {{ dialogData.executeDate }}
-                                </div>
-                                
-                                <div v-if="dialogData.status === 0">
-                                    <span class="item-name">{{ dialogData.reCheck ? "复核时间" : "执行时间" }}</span>
-                                    {{ dialogData.executeDate }}
-                                </div>
-                                <!-- <span class="item-name">{{ dialogData.status === 0 ? (dialogData.reCheck ? "复核农事" : "完成农事") : "推荐时间" }}</span>
-                                {{ dialogData.code }} -->
+                                <span class="item-name">推荐时间</span>
+                                {{ dialogData.executeDate }}
                             </div>
                             <div class="desc-item">
                                 <span class="item-name">农事宗旨</span>
-                                {{ dialogData.purpose }}
+                                {{ dialogData.purpose || dialogData.condition }}
                             </div>
                             <div class="desc-item">
                                 <div class="item-name">药物处方</div>
                                 <div class="item-table">
-                                    <el-table :data="dialogData.pesticideFertilizerList" style="width: 100%" :header-cell-style="{background: '#F5F5F5'}">
+                                    <el-table :data="pesticideFertilizers" style="width: 100%" :header-cell-style="{background: '#F5F5F5'}">
                                         <el-table-column prop="pesticideFertilizerCode" label="功效" width="62">
                                             <template #default="scope">
                                                 {{scope.row.typeName}}
                                             </template>
                                         </el-table-column>
-                                        <el-table-column prop="name" label="名称" />
+                                        <el-table-column prop="defaultName" label="名称" />
                                         <!-- <el-table-column prop="ratio" label="配比" width="50">
                                             <template #default="scope">
                                                 {{scope.row.ratio ? scope.row.ratio : "--"}}
@@ -55,7 +42,7 @@
                                         </el-table-column> -->
                                         <el-table-column prop="ratio" label="方式" width="62">
                                             <template #default="scope">
-                                                {{scope.row.ratio ? "人工" : "人工"}}
+                                                {{scope.row.executeStyle ? (scope.row.executeStyle === 2 ? "无人机" : "人工") : "--"}}
                                             </template>
                                         </el-table-column>
                                     </el-table>
@@ -63,7 +50,7 @@
                             </div>
 
                             <div class="card-link">
-                                <img src="@/assets/img/weather_index/expert-icon.png" />
+                                <img :src="dialogData.expertIcon" />
                                 <div class="expert-name">
                                     {{ dialogData.expertName }}
                                 </div>
@@ -73,9 +60,9 @@
                 </div>
             </div>
 
-            <div class="close-btn">
+            <div class="close-btn" @click.stop="winDialogVisible=false">
                     <!-- <el-icon size="32" color="#fff"><CircleCloseFilled /></el-icon> -->
-                    <el-button type="primary" class="one-btn" @click="toShare"> 立即分享 </el-button>
+                    <el-button type="primary" class="one-btn" @click.stop="toShare"> 立即分享 </el-button>
             </div>
         </div>
         <template #footer>
@@ -110,12 +97,33 @@ const showDialog = (pageParams) => {
         farmId: farmId,
         sampleId: sampleId,
     };
+    settingData()
     winDialogVisible.value = true;
 };
+eventBus.off("detailDialog:showDialog", showDialog);
 eventBus.on("detailDialog:showDialog", (data) => {
     showDialog(data)
 });
 
+// 弹窗关闭
+eventBus.on("detailDialog:toCloseDialog", closeDialog);
+
+
+const pesticideFertilizers = ref([])
+const settingData = () => {
+    pesticideFertilizers.value = flattenDomains(dialogData.value.prescriptionList)
+};
+function flattenDomains(data) {
+  return data.reduce((acc, item) => {
+    return acc.concat(item.pesticideFertilizerList);
+  }, []);
+}
+
+function closeDialog() {
+    winDialogVisible.value = false
+    eventBus.emit("detailDialog:closeDialog")
+}
+
 const toShare = () => {
     wx.miniProgram.navigateTo({
         url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(currentCard.value)}&type=album`,
@@ -127,6 +135,10 @@ const toShare = () => {
 .congratulation-wrap {
     border-radius: 12px;
     background: #f4f5f4;
+    width: 100%;
+}
+.detail-log {
+    width: 100%;
 }
 .close-btn {
     text-align: center;
@@ -158,7 +170,7 @@ const toShare = () => {
         }
     }
     .win-des {
-        height: 205px;
+        height: 180px;
         text-align: center;
     }
     .win-icon {
@@ -167,7 +179,7 @@ const toShare = () => {
     }
 }
 .album-detail-box {
-    padding: 0 24px 16px 12px;
+    padding: 0 10px 16px 10px;
     color: #000;
     position: relative;
     // top: -58px;
@@ -225,6 +237,7 @@ const toShare = () => {
             }
             img {
                 width: 62px;
+                border-radius: 50%;
             }
             .icon {
                 padding-right: 2px;
@@ -253,9 +266,22 @@ const toShare = () => {
             border-radius: 12px;
             background: none;
             box-shadow: none;
+            margin-bottom: 70px;
+            margin-top: 10px;
+            overflow: auto;
+            scrollbar-width: none;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            pointer-events: none;
             .el-dialog__header {
                 padding: 0;
             }
+            .el-dialog__body {
+                pointer-events: all;
+                width: 100%;
+            }
         }
 
         .one-btn {

+ 256 - 0
src/views/home/album_compoents/utils.js

@@ -0,0 +1,256 @@
+/**
+ * 在矩形内绘制矩形
+ * @param {CanvasRenderingContext2D} ctx - 画布的上下文
+ * @param {Object} rect - 矩形范围,包含 { x, y, width, height },以像素为单位
+ * @param {number} startXPercent - 内部矩形起始点横坐标,范围从 0 到 100
+ * @param {number} startYPercent - 内部矩形起始点纵坐标,范围从 0 到 100
+ * @param {number} widthPercent - 内部矩形宽度,范围从 0 到 100
+ * @param {number} heightPercent - 内部矩形高度,范围从 0 到 100
+ * @param {string} color - 填充颜色,可选参数,默认为黑色
+ */
+const drawRectInRect = (ctx, rect, startXPercent, startYPercent, widthPercent, heightPercent, color = 'black') => {
+    // 计算内部矩形的起始坐标
+    const startX = rect.x + (startXPercent / 100) * rect.width;
+    const startY = rect.y + (startYPercent / 100) * rect.height;
+    // 计算内部矩形的宽度和高度
+    const width = (widthPercent / 100) * rect.width;
+    const height = (heightPercent / 100) * rect.height;
+    ctx.fillStyle = color; // 设置填充颜色
+    ctx.fillRect(startX, startY, width, height); // 绘制矩形
+    return { x:startX, y:startY, width, height };
+};
+
+
+
+/**
+ * 在矩形内绘制文本
+ * @param {CanvasRenderingContext2D} ctx - 画布的上下文
+ * @param {Object} rect - 矩形范围,包含 { x, y, width, height },以像素为单位
+ * @param {string} text - 要绘制的文本
+ * @param {number} startXPercent - 文字起始横坐标,范围从 0 到 100
+ * @param {number} startYPercent - 文字起始纵坐标,范围从 0 到 100
+ * @param {number} fontSizePercent - 文字大小,范围从 0 到 100,基于矩形高度
+ */
+const drawTextInRect = (ctx, rect, text, startXPercent, startYPercent, fontSizePercent) => {
+    // 计算文字起始坐标
+    const startX = rect.x + (startXPercent / 100) * rect.width;
+    const startY = rect.y + (startYPercent / 100) * rect.height;
+
+    // 计算文字大小
+    const fontSize = (fontSizePercent / 100) * rect.height;
+    ctx.font = `${fontSize}px sans-serif`; // 设置字体样式
+    ctx.fillText(text, startX, startY);
+};
+
+/**
+ * 在矩形内绘制线
+ * @param {CanvasRenderingContext2D} ctx - 画布的上下文
+ * @param {Object} rect - 矩形范围,包含 { x, y, width, height },以像素为单位
+ * @param {number} startXPercent - 起始点横坐标,范围从 0 到 100
+ * @param {number} startYPercent - 起始点纵坐标,范围从 0 到 100
+ * @param {number} endXPercent - 结束点横坐标,范围从 0 到 100
+ * @param {number} endYPercent - 结束点纵坐标,范围从 0 到 100
+ * @param {string} color - 线条颜色,可选参数,默认为黑色
+ * @param {number} lineWidth - 线条宽度,可选参数,默认为1像素
+ */
+const drawLineInRect = (ctx, rect, startXPercent, startYPercent, endXPercent, endYPercent, color = 'black', lineWidth = 1) => {
+    // 计算起始点坐标
+    const startX = rect.x + (startXPercent / 100) * rect.width;
+    const startY = rect.y + (startYPercent / 100) * rect.height;
+
+    // 计算结束点坐标
+    const endX = rect.x + (endXPercent / 100) * rect.width;
+    const endY = rect.y + (endYPercent / 100) * rect.height;
+
+    // 设置线条样式
+    ctx.strokeStyle = color; // 设置线条颜色
+    ctx.lineWidth = lineWidth; // 设置线条宽度
+
+    // 绘制线
+    ctx.beginPath();
+    ctx.moveTo(startX, startY);
+    ctx.lineTo(endX, endY);
+    ctx.stroke();
+};
+
+/**
+ * 在矩形内绘制点
+ * @param {CanvasRenderingContext2D} ctx - 画布的上下文
+ * @param {Object} rect - 矩形范围,包含 { x, y, width, height },以像素为单位
+ * @param {number} xPercent - 点的横坐标,范围从 0 到 100
+ * @param {number} yPercent - 点的纵坐标,范围从 0 到 100
+ * @param {string} color - 点的颜色,可选参数,默认为黑色
+ * @param {number} radius - 点的半径,可选参数,默认为1像素
+ */
+const drawPointInRect = (ctx, rect, xPercent, yPercent, color = 'black', radius = 1) => {
+    // 计算点的坐标
+    const x = rect.x + (xPercent / 100) * rect.width;
+    const y = rect.y + (yPercent / 100) * rect.height;
+
+    // 设置点的样式
+    ctx.fillStyle = color; // 设置点的颜色
+
+    // 开始绘制路径
+    ctx.beginPath();
+    ctx.arc(x, y, radius, 0, Math.PI * 2); // 绘制一个圆
+    ctx.fill(); // 填充圆
+};
+
+/**
+ * 在矩形内绘制图片
+ * @param {CanvasRenderingContext2D} ctx - 画布的上下文
+ * @param {Object} rect - 矩形范围,包含 { x, y, width, height },以像素为单位
+ * @param {HTMLImageElement} img - 要绘制的图片对象
+ * @param {number} imgXPercent - 图片起始横坐标,范围从 0 到 100
+ * @param {number} imgYPercent - 图片起始纵坐标,范围从 0 到 100
+ * @param {number} imgWidthPercent - 图片宽度百分比,范围从 0 到 100
+ * @param {number} imgHeightPercent - 图片高度百分比,范围从 0 到 100
+ */
+const drawImageInRect = (ctx, rect, img, imgXPercent, imgYPercent, imgWidthPercent, imgHeightPercent) => {
+    // 计算图片的起始坐标
+    const imgX = rect.x + (imgXPercent / 100) * rect.width;
+    const imgY = rect.y + (imgYPercent / 100) * rect.height;
+    // 计算图片的宽度和高度
+    const imgWidth = (imgWidthPercent / 100) * rect.width;
+    const imgHeight = (imgHeightPercent / 100) * rect.height;
+    // 开始绘制图片
+    ctx.drawImage(img, imgX, imgY, imgWidth, imgHeight);
+};
+
+/**
+ * 在矩形内绘制图片,带圆角和白色边框
+ * @param {CanvasRenderingContext2D} ctx - 画布的上下文
+ * @param {Object} rect - 矩形范围,包含 { x, y, width, height },以像素为单位
+ * @param {HTMLImageElement} img - 要绘制的图片对象
+ * @param {number} imgXPercent - 图片起始横坐标,范围从 0 到 100
+ * @param {number} imgYPercent - 图片起始纵坐标,范围从 0 到 100
+ * @param {number} imgWidthPercent - 图片宽度百分比,范围从 0 到 100
+ * @param {number} imgHeightPercent - 图片高度百分比,范围从 0 到 100
+ * @param {number} borderRadius - 圆角半径,以像素为单位
+ * @param {number} borderWidth - 边框宽度,以像素为单位
+ */
+const drawBorderImageInRect = (ctx, rect, img, imgXPercent, imgYPercent, imgWidthPercent, imgHeightPercent, borderRadius, borderWidth) => {
+    // 计算图片的起始坐标
+    const imgX = rect.x + (imgXPercent / 100) * rect.width + borderWidth;
+    const imgY = rect.y + (imgYPercent / 100) * rect.height + borderWidth;
+    // 计算图片的宽度和高度
+    const imgWidth = (imgWidthPercent / 100) * rect.width - 2 * borderWidth;
+    const imgHeight = (imgHeightPercent / 100) * rect.height - 2 * borderWidth;
+
+    // 保存画布状态
+    ctx.save();
+
+    // 开始绘制圆角矩形的边框
+    ctx.beginPath();
+    ctx.moveTo(imgX + borderRadius, imgY);
+    ctx.lineTo(imgX + imgWidth - borderRadius, imgY);
+    ctx.arcTo(imgX + imgWidth, imgY, imgX + imgWidth, imgY + borderRadius, borderRadius);
+    ctx.lineTo(imgX + imgWidth, imgY + imgHeight - borderRadius);
+    ctx.arcTo(imgX + imgWidth, imgY + imgHeight, imgX + imgWidth - borderRadius, imgY + imgHeight, borderRadius);
+    ctx.lineTo(imgX + borderRadius, imgY + imgHeight);
+    ctx.arcTo(imgX, imgY + imgHeight, imgX, imgY + imgHeight - borderRadius, borderRadius);
+    ctx.lineTo(imgX, imgY + borderRadius);
+    ctx.arcTo(imgX, imgY, imgX + borderRadius, imgY, borderRadius);
+    ctx.closePath();
+
+    // 设置边框颜色和宽度
+    ctx.strokeStyle = 'white';
+    ctx.lineWidth = borderWidth;
+    ctx.stroke();
+
+    // 开始绘制圆角矩形的填充(可选,如果需要背景)
+    // ctx.fillStyle = 'transparent'; // 或者设置其他背景颜色
+    // ctx.fill();
+
+    // 开始裁剪
+    ctx.clip();
+
+    // 绘制图片
+    ctx.drawImage(img, imgX, imgY, imgWidth, imgHeight);
+
+    // 恢复画布状态
+    ctx.restore();
+};
+
+/**
+ * 在矩形内绘制横向文本列表
+ * @param {CanvasRenderingContext2D} ctx - 画布的上下文
+ * @param {Object} rect - 矩形范围,包含 { x, y, width, height },以像素为单位
+ * @param {Array<string>} texts - 要绘制的文本数组
+ * @param {number} fontSizePercent - 文字大小,范围从 0 到 100,基于矩形高度
+ * @param {number} startXPercent - 文字起始横坐标,范围从 0 到 100,基于矩形宽度
+ * @param {number} startYPercent - 文字起始纵坐标,范围从 0 到 100,基于矩形高度
+ * @param {number} columnSpacingPercent - 列间距,范围从 0 到 100,基于矩形宽度
+ * @param {string} columnSeparator - 列间隔字符,默认为空字符串
+ * @param {number} columnSeparatorWidthPercent - 列间隔字符的宽度百分比,范围从 0 到 100,基于矩形宽度
+ * @param {number} separatorFontSizePercent - 分隔符字号大小,范围从 0 到 100,基于矩形高度
+ * @param {string} separatorColor - 分隔符颜色,默认为黑色
+ * @param {number} separatorMarginLeftPercent - 分隔符左边的边距,范围从 0 到 100,基于矩形宽度
+ * @param {number} separatorMarginRightPercent - 分隔符右边的边距,范围从 0 到 100,基于矩形宽度
+ */
+const drawHorizontalTextList = (ctx, rect,color='white', texts, fontSizePercent, startXPercent = 0, startYPercent = 50, columnSpacingPercent = 0, columnSeparator = '', columnSeparatorWidthPercent = 0, separatorFontSizePercent = fontSizePercent, separatorColor = 'black', separatorMarginLeftPercent = 0, separatorMarginRightPercent = 0) => {
+    // 计算文字大小
+    const fontSize = (fontSizePercent / 100) * rect.height;
+    ctx.font = `${fontSize}px sans-serif`; // 设置字体样式
+
+    // 计算文字的总宽度
+    let totalTextWidth = 0;
+    texts.forEach(text => {
+        totalTextWidth += ctx.measureText(text).width;
+    });
+
+    // 计算分隔符的字体大小
+    const separatorFontSize = (separatorFontSizePercent / 100) * rect.height;
+
+    // 计算分隔符的总宽度
+    ctx.font = `${separatorFontSize}px sans-serif`; // 设置分隔符字体样式
+    const separatorWidth = (columnSeparatorWidthPercent / 100) * rect.width;
+    const totalSeparatorWidth = (texts.length - 1) * separatorWidth;
+
+    // 计算分隔符的左右边距
+    const separatorMarginLeft = (separatorMarginLeftPercent / 100) * rect.width;
+    const separatorMarginRight = (separatorMarginRightPercent / 100) * rect.width;
+
+    // 计算可用的总间距
+    const totalSpacing = (columnSpacingPercent / 100) * rect.width;
+
+    // 计算所有文字和间隔字符以及间距的总宽度
+    const totalWidth = totalTextWidth + totalSeparatorWidth + totalSpacing + (texts.length - 1) * (separatorMarginLeft + separatorMarginRight);
+
+    // 计算实际的起始坐标,使得文本和间隔字符居中于矩形
+    const startX = rect.x + (startXPercent / 100) * rect.width + (rect.width - totalWidth) / 2;
+    const startY = rect.y + (startYPercent / 100) * rect.height - (fontSize / 2); // 绘制文字,纵坐标居中
+
+    // 绘制文本和间隔字符
+    let currentX = startX;
+    texts.forEach((text, index) => {
+        ctx.font = `${fontSize}px sans-serif`; // 设置文字字体样式
+        ctx.fillStyle = color; // 设置文字颜色
+        ctx.fillText(text, currentX, startY + (fontSize / 2)); // 绘制文字,纵坐标居中
+        currentX += ctx.measureText(text).width;
+
+        // 如果不是最后一列,则绘制间隔字符
+        if (index < texts.length - 1) {
+            // 添加左边距
+            currentX += separatorMarginLeft;
+
+            // 设置分隔符颜色和字体样式
+            ctx.fillStyle = separatorColor;
+            ctx.font = `${separatorFontSize}px sans-serif`;
+
+            // 绘制分隔符
+            ctx.fillText(columnSeparator, currentX, startY + (separatorFontSize / 2));
+
+            // 添加右边距
+            currentX += separatorWidth + separatorMarginRight + columnSpacingPercent / 100 * rect.width;
+        }
+    });
+};
+
+
+
+
+
+
+export { drawTextInRect, drawLineInRect, drawPointInRect ,drawImageInRect,drawBorderImageInRect, drawRectInRect, drawHorizontalTextList};
+

+ 2 - 1
src/views/home/index.vue

@@ -110,9 +110,10 @@ const mapRef = ref();
 onMounted(() => {
     VE_API.farm.fetchFarmDetail({ id:currentFarm.id }).then(({ data }) => {
       homeMap.initMap("POINT("+data.location["lat"] +" " + data.location["lng"]+")", mapRef.value);
+      samplePointLayer = new SamplePointLayer(homeMap.kmap.map, currentFarm, currentRegion);
     });
     // homeMap.initMap(store.getters.userinfo.location, mapRef.value);
-    // samplePointLayer = new SamplePointLayer(homeMap.kmap.map, currentFarm, currentRegion);
+    //
     // regionLayer = new RegionLayer(homeMap.kmap.map, currentFarm, currentRegion);
 
     getYellow();

+ 1 - 1
src/views/home/map/samplePointLayer.js

@@ -127,7 +127,7 @@ class SamplePointLayer {
           if(that.isUpdatePoint){
             fs.set("iconName", "active");
           }
-          eventBus.emit("click:point",{name:fs.get("id"),value:fs.get("highYield")})
+          eventBus.emit("click:point",{farmId:fs.get("farmId"),regionId:fs.get("regionId"), sampleId:fs.get("id"),value:fs.get("highYield")})
         }
         if (layer instanceof VectorLayer && layer.get("name") === "yellow-block") {
           hasFeature = true

+ 153 - 0
src/views/util.js

@@ -0,0 +1,153 @@
+import VectorSource from 'ol/source/Vector.js';
+import WKT from 'ol/format/WKT.js';
+import Feature from 'ol/Feature.js';
+import VectorLayer from 'ol/layer/Vector.js';
+import Draw from "ol/interaction/Draw";
+import Text from "ol/style/Text";
+import Icon from "ol/style/Icon";
+import {Circle, Fill, Stroke, Style} from 'ol/style.js';
+
+export function getRadius(zoom){
+    if(zoom >= 20){
+        return 120;
+    }
+    if(zoom >= 19){
+        return 70;
+    }
+    if(zoom >= 18){
+        return 30;
+    }
+    if(zoom >= 17){
+        return 15;
+    }
+    return 10;
+}
+
+export function getScale(zoom){
+    if(zoom >= 20){
+        return 0.8;
+    }
+    if(zoom >= 19){
+        return 0.4;
+    }
+    if(zoom >= 18){
+        return 0.2;
+    }
+    if(zoom >= 17){
+        return 0.1;
+    }
+    return 0.1;
+}
+
+export function isShow(zoom,id){
+
+    if(zoom > 18){
+        return true;
+    }
+    if(zoom >= 18){
+        return mo(id, 4)
+    }
+    if(zoom >= 17){
+        return mo(id, 5)
+    }
+    return  mo(id, 5)
+}
+export function mo(id,midu){
+    return id % midu == 0
+}
+
+export const colors = ["#9EFE0C","#FEC30C","#FE760C","#FE0B0C","#0DCAFE","#A80BFF","#E4FE0C","#1A0BFF"];
+export const imgs = ["point6.png","point1.png","point2.png","point3.png","point4.png","point5.png","point6.png","point7.png","point8.png"]
+export const pz = ["未知","白糖罂","桂味","井岗红糯","糯米糍","岭丰糯","进奉","冰荔","妃子笑"]
+
+export function getIndex(name){
+    let i = 0;
+    for(let item of pz){
+        if(name === item){
+            return i;
+        }
+        i++;
+    }
+}
+
+/**
+ *
+ * @param data
+ * @param geomField 形状的字段
+ * @param sign 标识
+ * @returns {Feature<{geometry: any}>}
+ */
+export const newPoint = (data, geomField, sign)=>{
+    let point = new WKT().readGeometry(data[geomField])
+    let feature = new Feature({
+        geometry: point
+    });
+    feature.setId(data.id)
+    feature.set("nodeType",sign)
+    for(let key in data){
+        if(key != geomField && data[key]){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+export const newGeomPoint = (data)=>{
+    let point = new WKT().readGeometry(data["geom"])
+    let feature = new Feature({
+        geometry: point
+    });
+    feature.setId(data.id)
+    feature.set("nodeType","tree")
+    for(let key in data){
+        if(key != "geom" && data[key]){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+export const newPolymerFeature = (data)=>{
+    let geom = new WKT().readGeometry(data["wkt"])
+    let feature = new Feature({
+        geometry: geom
+    });
+    feature.set("nodeType","polymer");
+    feature.setId(data.id)
+    for(let key in data){
+        if(key != "wkt"){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+
+export const newAreaFeature = (data)=>{
+    let geom = new WKT().readGeometry(data["geom"])
+    let feature = new Feature({
+        geometry: geom
+    });
+    feature.set("nodeType","area");
+    feature.setId(data.id)
+    for(let key in data){
+        if(key != "geom"){
+            feature.set(key,data[key])
+        }
+    }
+    return feature;
+}
+export const pointToFormat = (pointString)=> {
+    let geom = new WKT().readGeometry(pointString)
+        const longitude = geom.getCoordinates()[0];
+        const latitude = geom.getCoordinates()[1];
+        // 转换为度的格式
+        const formattedLongitude = `${longitude.toFixed(0)}°E`;
+        const formattedLatitude = `${latitude.toFixed(0)}°N`;
+
+        const result = `${formattedLongitude},${formattedLatitude}`;
+        console.log(result); // 输出: 111.011490207°E,21.7739355047°N
+        return result
+
+}
+
+