Selaa lähdekoodia

fix: 修复地图loadFeature问题

刘秀芳 6 päivää sitten
vanhempi
commit
bc07123835

+ 3 - 0
src/components/subTitle.vue

@@ -26,6 +26,9 @@ onMounted(()=>{
 
 <style lang="scss" scoped>
 .sub-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
     .sub-t {
         display: inline-flex;
         align-items: center;

+ 406 - 0
src/views/home/album_compoents/albumCarousel.vue

@@ -0,0 +1,406 @@
+<template>
+    <el-dialog
+        v-model="dialogVisible"
+        width="60%"
+        align-center
+        class="picture-preview-wrap v-dialog"
+        :show-close="false"
+        append-to-body
+        @close="closeDialog"
+    >
+        <div class="picture-file">
+            <div class="left-img">
+                <!-- v-loading="isLoadingImg"  element-loading-background="rgba(0, 0, 0, 0.3)" -->
+                <album-carousel-item
+                    lbum-carousel-item
+                    v-if="images"
+                    :key="nameRef"
+                    :name="nameRef"
+                    :farmId="farmId"
+                    :images="images"
+                    :lock="lock"
+                ></album-carousel-item>
+            </div>
+            <div class="file-wrap">
+                <div class="file-title">
+                    <img src="@/assets/images/common/chart-yellow.png" alt="" />
+                    果树档案
+                    <span class="tag" v-if="showTag">小农户</span>
+                </div>
+                <div class="overview-file">
+                    <div class="box-title">总体档案</div>
+                    <div class="base-data">
+                        <div class="base-item" v-for="item in photoBaseData" :key="item.label">
+                            <span class="label">{{ item.label }}</span>
+                            <div class="value">{{ item.value }}</div>
+                        </div>
+                    </div>
+                    <div class="list">
+                        <div class="list-item" v-for="item in photoList" :key="item.key">
+                            <div class="list-name">
+                                <img src="@/assets/images/common/title-icon.png" alt="" />
+                                {{ item.key }}
+                            </div>
+                            {{ item.statement }}
+                        </div>
+                    </div>
+                </div>
+
+                <div class="overview-file">
+                    <div class="box-title">产量详情</div>
+                    <div class="box-wrap">
+                        <div
+                            class="box-item"
+                            v-for="(item, index) in outputBox"
+                            :key="index"
+                            @click="toggleAcitve(item.name)"
+                            :class="{ active: activeOuput === item.name }"
+                        >
+                            <div class="item-name">{{ item.name }}</div>
+                            <div class="item-val">{{ item.value }}</div>
+                        </div>
+                    </div>
+                </div>
+
+                <div class="overview-file">
+                    <div class="box-title">质量详情</div>
+                    <div class="box-wrap">
+                      <div
+                            class="box-item"
+                            v-for="(item, index) in qualityBox"
+                            :key="index"
+                            @click="toggleAcitve(item.name)"
+                            :class="{ active: activeOuput === item.name }"
+                        >
+                            <div class="item-name">{{ item.name }}</div>
+                            <div class="item-val">{{ item.value }}</div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted } from "vue";
+import "./cacheImg.js";
+import AlbumCarouselItem from "./albumCarouselItem";
+import { dateFormat } from "@/utils/date_util.js";
+import eventBus from "@/api/eventBus";
+
+const lock = ref(false);
+const farmId = ref(766);
+const nameRef = ref("");
+
+const isLoadingImg = ref(true)
+const images = ref(null);
+const dialogVisible = ref(false);
+// 获取当前日期
+const currentDate = new Date();
+// 获取当前日期的前一个月
+const startDate = new Date(currentDate);
+startDate.setMonth(currentDate.getMonth() - 1);
+
+// 格式化日期
+const formattedStartDate = dateFormat(startDate, "YY-mm-dd");
+const formattedEndDate = dateFormat(currentDate, "YY-mm-dd");
+
+eventBus.on("change:watermark",function(name){
+  nameRef.value = name
+})
+
+const outputBox = ref([
+    { id: 1, name: "产量估计", value: "" },
+    { id: 2, name: "高质果率", value: "" },
+    { id: 3, name: "雌花比例", value: "" },
+]);
+
+const qualityBox = ref([
+    { id: 5, name: "通风率", value: "" },
+    { id: 6, name: "透光率", value: "" },
+    { id: 7, name: "病虫比例", value: "" },
+]);
+const showTag = ref(false)
+eventBus.on("click:point",function({farmId,sampleId, data}){
+  sampleId = data.id;
+  getSampleFiles(data.geoHashSample);
+  photoBaseData.value[0].value = data.pz;
+    isLoadingImg.value = true
+  let startDate = new Date(currentDate);
+  startDate.setMonth(currentDate.getMonth() - 2);
+
+// 格式化日期
+  let params = {sampleId,farmId,startDate:dateFormat(startDate, "YY-mm-dd")}
+  VE_API.miniimage.list(params).then(res => {
+      if(res.code === 0){
+        dialogVisible.value = true
+      images.value = res.data
+      isLoadingImg.value = false
+    }
+  })
+  showTag.value = data.nonghu == 1 ? true : false
+//   photoBaseData.value[0].value = data.pz;
+//   photoBaseData.value[1].value = data?.sgbmj ? data?.sgbmj + "平方米" : "--";
+//   photoBaseData.value[2].value = data?.zzts ? data.zzts + "" : "--";
+//   photoBaseData.value[3].value = data?.sl ? data?.sl + "年" : "--";
+//   outputBox.value[0].value = data?.cl ? (data.cl + "斤") : "--";
+//   outputBox.value[1].value = data?.spgl? (data.spgl + "%") : "--";
+//   qualityBox.value[0].value = data?.tfl? (data.tfl + "%") : "--";
+//   qualityBox.value[1].value = data?.tgl? (data.tgl + "%") : "--";
+//   qualityBox.value[2].value = data?.dxtj? (data.dxtj + "%") : "--";
+})
+
+eventBus.off("albumCarousel", toggleActiveImg);
+eventBus.on("albumCarousel", toggleActiveImg);
+
+const currentIndex = ref(0);
+function toggleActiveImg(index) {
+    currentIndex.value = index;
+}
+
+const getSampleFiles = (geoHash) => {
+    // photoList.value = [];
+    VE_API.mini_farm.getSampleFiles({ geoHashSample: geoHash }).then(({data}) => {
+        photoBaseData.value[0].value = data.meta_info.type_id;
+        let pj = "--"
+        if(data.meta_info.crown){
+          pj = Math.sqrt(data.meta_info.crown * 1.2).toFixed(1);
+        }
+        photoBaseData.value[1].value = data.meta_info.crown + "平方米(蓬径"+pj+"米)";
+        photoBaseData.value[2].value = data.meta_info.branch_num ;
+
+    photoBaseData.value[3].value = data.meta_info.age + "年"
+    photoList.value[0].key = data.meta_info.dp_alert_info.key
+    photoList.value[0].statement = data.meta_info.dp_alert_info.statement
+    photoList.value[1].key = data.meta_info.grow_alert_info.key
+    photoList.value[1].statement = data.meta_info.grow_alert_info.statement
+    photoList.value[2].key = data.meta_info.nutrition_info.key
+    photoList.value[2].statement = data.meta_info.nutrition_info.statement
+    photoList.value[3].key = data.meta_info.prescription_info.key
+    photoList.value[3].statement = data.meta_info.prescription_info.statement
+      // if(data.production_info.production){
+      //   data.production_info.production = (data.production_info.production * 1.3).toFixed(1)
+      // }
+    outputBox.value[0].value = data.production_info.production + "斤"
+    outputBox.value[1].value = data.production_info.quality.toFixed(0) + "%"
+    outputBox.value[2].value = data.production_info.cihua_ratio.toFixed(0) + "%"
+    qualityBox.value[0].value = data.ecology_info.ventilation + "%"
+    qualityBox.value[1].value = data.ecology_info.transmittance + "%"
+    qualityBox.value[2].value = data.ecology_info.dp_situation+"%"
+    });
+};
+
+const photoBaseData = ref([
+    {
+        label: "品种",
+        value: "--",
+    },
+    {
+        label: "冠幅表面积",
+        value: "--",
+    },
+    {
+        label: "总枝条",
+        value: "--",
+    },
+    {
+        label: "树龄",
+        value: "--",
+    },
+]);
+
+const photoList = ref([
+    {key: "病虫", statement: "--"},
+    {key: "异常", statement: "--"},
+    {key: "营养", statement: "--"},
+    {key: "农事", statement: "--"},
+]);
+
+const activeOuput = ref(null);
+
+// 产量详情
+function toggleAcitve(name) {
+    activeOuput.value = name;
+    if (name.indexOf("开花") > -1) {
+        eventBus.emit("change:watermark", "开花目标框")
+    } else if (name.indexOf("花穗") > -1) {
+        eventBus.emit("change:watermark", "花穗目标框")
+    } else if (name.indexOf("雄花") > -1) {
+        eventBus.emit("change:watermark", "雄花目标框")
+    } else if (name.indexOf("枝条数") > -1) {
+        eventBus.emit("change:watermark", "")
+    } else {
+        eventBus.emit("change:watermark", "")
+    }
+}
+
+function closeDialog() {
+    activeOuput.value = null
+    eventBus.emit("change:watermark", "")
+    eventBus.emit("resetImgIndex")
+}
+
+// 质量详情
+function toggleQualityAcitve() {
+
+}
+</script>
+
+<style lang="scss" scoped>
+@import "src/styles/index";
+.picture-file {
+    display: flex;
+    .left-img {
+        min-width: 780px;
+        min-height: 300px;
+    }
+    .file-wrap {
+        background: url("@/assets/images/home/file-bg.png") no-repeat top center / 100% 100%;
+        margin-left: 12px;
+        padding: 12px;
+        .file-title {
+            font-size: 20px;
+            color: #ffd489;
+            display: flex;
+            align-items: center;
+
+            .tag {
+                border: 1px solid #FFD489;
+                border-radius: 4px;
+                font-size: 12px;
+                display: inline-block;
+                width: 44px;
+                height: 20px;
+                text-align: center;
+                line-height: 18px;
+                margin-left: 3px;
+                padding: 1px 4px;
+            }
+        }
+        .overview-file {
+            padding-top: 20px;
+            .box-title {
+                font-size: 16px;
+                padding-left: 13px;
+                margin-bottom: 16px;
+                position: relative;
+                display: flex;
+                justify-content: space-between;
+                color: #fff;
+                &::before {
+                    content: "";
+                    position: absolute;
+                    left: 0;
+                    top: 3px;
+                    width: 3px;
+                    height: 16px;
+                    background: #fff;
+                    border-radius: 11px;
+                }
+            }
+            .title {
+                color: #f3c11d;
+                font-size: 16px;
+                font-family: "PangMenZhengDao";
+                margin-bottom: 20px;
+                .big {
+                    width: 13px;
+                    height: 13px;
+                    margin: -10px 0 0 4px;
+                }
+                .small {
+                    width: 7px;
+                    height: 7px;
+                    margin-left: -3px;
+                }
+            }
+            .base-data {
+                background: rgba(207, 207, 207, 0.1);
+                border-radius: 4px;
+                padding: 6px 0;
+                display: flex;
+                .base-item {
+                    flex: 1;
+                    text-align: center;
+                    .label {
+                        font-size: 12px;
+                        color: #666666;
+                    }
+                    .value {
+                        padding-top: 2px;
+                        font-size: 16px;
+                        color: #ffffff;
+                    }
+                }
+                .base-item + .base-item {
+                    border-left: 1px solid rgba(102, 102, 102, 0.42);
+                }
+            }
+            .list {
+                margin-top: 15px;
+                width: max-content;
+                font-size: 14px;
+                .list-item {
+                    color: #bbbbbb;
+                    display: flex;
+                    margin-bottom: 8px;
+                    .list-name {
+                        color: #f3c11d;
+                        margin-right: 6px;
+                        img {
+                            width: 17px;
+                            height: 13px;
+                        }
+                    }
+                }
+            }
+        }
+        .overview-file + .overview-file {
+            margin-top: 8px;
+        }
+        .box-wrap {
+            display: flex;
+            .box-item {
+                min-width: 140px;
+                box-sizing: border-box;
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+                align-items: center;
+                padding: 6px;
+                background: rgba(207, 207, 207, 0.1);
+                border-radius: 4px;
+                border: 1px solid rgba(207, 207, 207, 0.1);
+                cursor: pointer;
+                .item-name {
+                    font-size: 12px;
+                    color: #666666;
+                    width: max-content;
+                }
+                .item-val {
+                    font-size: 18px;
+                    color: #fff;
+                    width: max-content;
+                    padding-top: 3px;
+                }
+                &.active {
+                    background: rgba(255, 212, 137, 0.16);
+                    border: 1px solid #ffd489;
+                    .item-name {
+                        color: #bbbbbb;
+                    }
+                }
+            }
+            .box-item + .box-item {
+                margin-left: 8px;
+            }
+        }
+    }
+}
+</style>
+<style lang="scss">
+.picture-preview-wrap {
+    background: none;
+}
+</style>

+ 107 - 0
src/views/home/album_compoents/albumCarousel7d.vue

@@ -0,0 +1,107 @@
+<template>
+  <template v-for="(images,index) in imagesList" :key="index">
+    <album-carousel-item :farmId="farmId" :images="images"></album-carousel-item>
+    <div style="height: 5px"></div>
+  </template>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted } from "vue";
+import AlbumDrawBoxItem from "./albumCarouselItem";
+import "./cacheImg.js"
+import AlbumCarouselItem from "./albumCarouselItem";
+import {dateFormat} from "@/utils/date_util.js"
+import eventBus from "@/api/eventBus";
+
+const props =defineProps({
+  // sampleId:{
+  //   type: [Number, String],
+  //   required: false
+  // },
+  farmId:{
+    type: [Number, String],
+    required: true
+  },
+  farmWork:{
+    type: Object,
+    required: false
+  }
+})
+const imagesList = ref([]);
+let params = {farmId: props.farmId}
+
+onMounted(() => {
+  if(props.farmWork?.executeDate){
+    let execcuteDate = new Date(props.farmWork.executeDate)
+    let beforeExecuteDate = new Date(props.farmWork.beforeExecuteDate)
+    const pastDate = new Date(beforeExecuteDate);
+    const futureDate = new Date(execcuteDate);
+    params.startDate = dateFormat(pastDate, "YY-mm-dd");
+    params.endDate = dateFormat(futureDate, "YY-mm-dd");
+  }
+  // getImageList(params)
+});
+
+const updateData = () =>{
+  params.farmId = props.farmId
+  getImageList(params)
+}
+
+onUnmounted(()=>{
+  eventBus.off("chart:updateOption",updateData)
+})
+
+const getImageList = (params) =>{
+  VE_API.image.list(params).then(res => {
+    if(res.code === 0){
+      let result = splitByWeek(res.data, params.startDate, params.endDate);
+      if(result && result.length > 0){
+        result = result.reverse()
+      }
+      for(let i=0;result != null && i<result.length;i++){
+        if (result[i] && result[i].length > 0){
+          imagesList.value.push(result[i])
+        }
+      }
+    }
+  })
+}
+
+eventBus.on("chart:updateOption",updateData)
+
+function splitByWeek(items, startDate, endDate) {
+  // 将开始时间和结束时间转换为时间戳
+  const start = new Date(startDate).getTime();
+  const end = new Date(endDate).getTime();
+
+  // 创建一个用于存储按周分组的结果
+  const weeklyGroups = [];
+
+  // 遍历每一项
+  items.forEach(item => {
+    const uploadDate = new Date(item.uploadDate).getTime();
+
+    // 确保上传日期在开始日期和结束日期之间
+    if (uploadDate >= start && uploadDate <= end) {
+      // 计算上传日期属于第几周
+      const weekIndex = Math.floor((uploadDate - start) / (7 * 24 * 60 * 60 * 1000));
+
+      // 如果该周的数组不存在,则创建一个
+      if (!weeklyGroups[weekIndex]) {
+        weeklyGroups[weekIndex] = [];
+      }
+
+      // 将当前项添加到对应的周数组中
+      weeklyGroups[weekIndex].push(item);
+    }
+  });
+
+  return weeklyGroups;
+}
+
+
+</script>
+
+<style lang="scss" scoped>
+@import "src/styles/index";
+</style>

+ 213 - 0
src/views/home/album_compoents/albumCarouselItem.vue

@@ -0,0 +1,213 @@
+<template>
+  <div class="carousel-container">
+    <!-- 图片列表 -->
+    <div class="carousel-wrapper"  :style="carouselStyle">
+      <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" :name="name" :current="currentIndex" :index="index" :length="images.length"
+          ></album-draw-box>
+        </template>
+      </photo-provider>
+    </div>
+    <div class="blur-bg" v-if="lock && currentIndex !== 0">
+      <div class="blur-content">
+        <div class="blur-img">
+          <img src="@/assets/img/gallery/camera-icon.png" />
+        </div>
+      </div>
+    </div>
+
+    <!-- 左右箭头 -->
+    <div @click.stop="prev" v-if="currentIndex !== 0" class="arrow left-arrow">
+      <el-icon color="#F0D09C"><ArrowLeftBold /></el-icon>
+    </div>
+    <div
+      @click.stop="next"
+      v-if="images && currentIndex !== images.length - 1"
+      class="arrow right-arrow"
+    >
+      <el-icon color="#F0D09C"><ArrowRightBold /></el-icon>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import eventBus from "@/api/eventBus";
+import { toRefs, ref, computed, onMounted, onUnmounted } from "vue";
+import AlbumDrawBox from "./albumDrawBox";
+import "./cacheImg.js"
+const props =defineProps({
+  images:{
+    type: Array,
+    required: true
+  },
+  farmId:{
+    type: [Number,String],
+    required: true
+  },
+  lock:{
+    type: Boolean,
+    default: true
+  },
+  name:{
+    type: String,
+    default: ""
+  }
+})
+const {images} = toRefs(props);
+let timer = null;
+const currentIndex = ref(0);
+
+onMounted(() => {
+  updateImagePosition();
+  clearAndRestartTimer();
+  eventBus.on("resetImgIndex", resetImgIndex)
+  eventBus.on("resetImgIndex", resetImgIndex)
+});
+onUnmounted(() => {
+  clearInterval(timer);
+  eventBus.off("resetImgIndex", resetImgIndex)
+});
+
+const updateImagePosition = () => {
+  carouselStyle.value.transform = `translateX(-${currentIndex.value * 100}%)`;
+};
+
+const clickPhotoShow = () => {
+  if (timer) {
+    clearInterval(timer)
+  };
+}
+
+// 图片显隐切换回调
+const handleVisibleChange = ({visible}) => {
+  if (visible.value) {
+    if (timer) {
+      clearInterval(timer)
+    }
+  } else {
+    clearAndRestartTimer();
+  }
+}
+
+// 计算轮播图样式
+const carouselStyle = computed(() => {
+  return {
+    transform: `translateX(-${currentIndex.value * 100}%)`,
+  };
+});
+
+// 下一张图片
+const next = () => {
+  // 图片总数
+  const totalImages = images.value.length;
+  currentIndex.value = (currentIndex.value + 1) % totalImages;
+  updateImagePosition();
+  clearAndRestartTimer();
+  eventBus.emit("albumCarousel", currentIndex.value)
+};
+
+// 上一张图片
+const prev = () => {
+  // 图片总数
+  const totalImages = images.value.length;
+  currentIndex.value = (currentIndex.value - 1 + totalImages) % totalImages;
+  updateImagePosition();
+  clearAndRestartTimer();
+  eventBus.emit("albumCarousel", currentIndex.value)
+};
+
+const clearAndRestartTimer = () => {
+  if (timer) {
+    clearInterval(timer);
+  }
+  // timer = setInterval(next, 5000);
+};
+
+function resetImgIndex() {
+  setTimeout(() => {
+    currentIndex.value = 0
+  }, 500)
+}
+
+</script>
+
+<style lang="scss" scoped>
+@import "src/styles/index";
+.carousel-container {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+  margin: 0 auto;
+  .carousel-wrapper {
+    display: flex;
+    transition: transform 0.5s ease;
+    width: 100%;
+  }
+  .blur-bg {
+    position: absolute;
+    top: 0;
+    width: 100%;
+    height: 100%;
+    backdrop-filter: blur(1.4px);
+    .blur-content {
+      border-radius: 8px;
+      background: rgba(0, 0, 0, 0.5);
+      width: 100%;
+      height: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      font-size: 12px;
+      color: #fff;
+      .blur-img {
+        img {
+          width: 54px;
+          position: relative;
+          left: 4px;
+          top: 4px;
+        }
+      }
+      .blur-text {
+        padding: 8px 0;
+        text-align: center;
+        line-height: 1.5;
+      }
+      .blur-btn {
+        padding: 0 40px;
+        box-shadow: 0 -2px 2px #86C9FF;
+        height: 28px;
+        line-height: 28px;
+        border-radius: 50px;
+        background: rgba(33, 153, 248, 0.7);
+        // background: linear-gradient(#86C9FF, rgba(255, 255, 255, 0));
+      }
+    }
+  }
+
+  .arrow {
+    position: absolute;
+    top: 50%;
+    transform: translateY(-50%);
+    background: rgba(0, 0, 0, 0.5);
+    width: rpx(72);
+    height: rpx(72);
+    border-radius: 50%;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    pointer-events: all;
+  }
+
+  .left-arrow {
+    left: rpx(32);
+  }
+
+  .right-arrow {
+    right: rpx(32);
+  }
+}
+</style>

+ 307 - 0
src/views/home/album_compoents/albumDrawBox.vue

@@ -0,0 +1,307 @@
+<template>
+  <photo-consumer
+      class="carousel-item"
+      :src="watermark || base_img_url2 + (photo.resFilename && !name ? photo.resFilename : photo.filename) + resize"
+  >
+    <img v-if="Math.abs(current - index) < 3" crossorigin="anonymous" @load="drawWatermark($event, name)" loading="lazy" :src="watermark || (base_img_url2 + (photo.resFilename && !name ? photo.resFilename : photo.filename) + resize)" style="width: 100%;" />
+    <canvas  ref="canvasRef" style="position: absolute;"></canvas>
+    <!-- <div class="tag-text" v-if="showTagBox" >
+      <span v-html="photo.growText"></span>
+      <button class="close-button" @click="hideTagBox">✖</button>
+    </div> -->
+    <div class="tag-box right" :class="{'leftTop': 'leftTop'}">{{ index+1 }}/{{ length }}</div>
+<!--    <div class="center-mark">mark</div>-->
+  </photo-consumer>
+
+</template>
+
+<script setup>
+import { ref, onMounted, onBeforeUnmount, defineProps } from "vue";
+import { base_img_url2 } from "@/api/config";
+import {imageCache,loadImage} from "./cacheImg.js"
+import {dateFormat} from "@/utils/date_util.js"
+import {pointToFormat} from "@/utils/util.js"
+import {drawTextInRect, drawBorderImageInRect, drawImageInRect, drawRectInRect, drawHorizontalTextList, drawTargetRectangles} from "./utils"
+const resize = "?x-oss-process=image/resize,p_50/format,webp/quality,q_50";
+const canvasRef = ref(null);
+const watermark = ref(null)
+const baseMapBig = ref(false)
+
+const props = defineProps({
+  farmId:{
+    type: [Number,String],
+    required: true
+  },
+  photo:{
+    type: Object,
+    required: true
+  },
+  index:{
+    type: Number,
+    required: true
+  },
+  length:{
+    type: Number,
+    required: true
+  },
+  current:{
+    type: Number,
+    required: true
+  },
+  name:{
+    type: String,
+    default: ""
+  }
+})
+let img = null;
+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,name) {
+  console.log(name)
+  if(!name){
+    drawWatermark1(event)
+  }else{
+    drawTargetRectangles2(event.target,props.photo.watermarks.find(item=>item.name === name))
+  }
+}
+
+function drawTargetRectangles2(img, obj) {
+  const canvas = canvasRef.value;
+  let scale = 3
+  canvas.width = img.width * scale;
+  canvas.height = img.height * scale;
+  ctx = canvas.getContext('2d');
+  ctx.scale(scale, scale)
+  ctx.drawImage(img, 0, 0, img.width, img.height);
+  drawTargetRectangles(ctx, obj, 4000,3000,img.width, img.height)
+  watermark.value = canvas.toDataURL();
+}
+
+async function drawWatermark1(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.image.findSuitabilityByPoint(param).then((res)=>{
+      if(res.code === 0){
+        weather = res.data
+        drawWatermark2(img,weather)
+      }else{
+        drawWatermark2(img,null)
+      }
+    })
+  }
+}
+
+
+function drawWatermark2(img,weather) {
+  const canvas = canvasRef.value;
+  let scale = 3
+  canvas.width = img.width * scale;
+  canvas.height = img.height * scale;
+  ctx = canvas.getContext('2d');
+  ctx.scale(scale, scale)
+  ctx.drawImage(img, 0, 0, img.width, img.height);
+  drawBottom(img.width, img.height, weather)
+  watermark.value = canvas.toDataURL();
+}
+
+
+// console.log(data)
+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
+  }
+  // 设置文本样式
+  ctx.font = "8px Arial";
+  ctx.textAlign = "left"; // 设置为左对齐
+  let imgRect = { x: 0, y: 0, width: imgWidth, height: imgHeight}
+  // 绘制头部黑色半透明遮罩
+  let topRect = drawRectInRect(ctx,imgRect, 0, 0, 30, 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)")
+
+  // drawHorizontalTextList(ctx, topRect, '#ffffff90',[data.treeCode, "蓬径:5m", "高度:3m", "高产树",data.pingzhong],
+  drawHorizontalTextList(ctx, topRect, '#ffffff90',[data.treeCode],
+      40, 0,
+      65 , 0,"|",0,
+      30,"#ffffff50",1,2)
+  ctx.fillStyle = "white"; // 文本颜色为白色
+  // 绘制温度
+  let startXPercent = 1;
+  drawImageInRect(ctx, bottomRect, data.tempImg, startXPercent, 5+10, 5, 30)
+  drawTextInRect(ctx, bottomRect,`${data.temp}`,startXPercent + 4, 28+10, 20)
+  // 绘制湿度
+  drawImageInRect(ctx, bottomRect, data.shiduImg, startXPercent+26, 7 + 10, 4, 30)
+  drawTextInRect(ctx, bottomRect,`${data.shidu}`,startXPercent + 31, 28 + 10, 20)
+  // 绘制辐射
+  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)
+
+  if(data.baseMap){
+    drawBorderImageInRect(ctx, imgRect, data.baseMap, 2/3*100, 2/3*100,
+        1/3*100, 1/3*100, 5, 5)
+  }
+}
+
+
+const showTagBox = ref(true); // 控制 tag-box 的显示状态
+const hideTagBox = (event) => {
+  event.stopPropagation();
+  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>
+
+<style lang="scss" scoped>
+.canvas-container {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  justify-content: center;
+}
+.carousel-item {
+  min-width: 100%;
+  flex-shrink: 0;
+  width: 100%;
+  pointer-events: auto;
+  position: relative;
+  .tag-box {
+    position: absolute;
+    bottom: 30%;
+    left: 50%;
+    transform: translate(-50%, 50%); // 确保在高二分之一的位置水平居中
+    height: 18px;
+    padding: 0 6px;
+    background: rgba(108, 108, 108, 0.67);
+    border-radius: 10px;
+    display: flex;
+    align-items: center;
+    color: #FFFFFF;
+    font-size: 12px;
+
+    &.right {
+      left: auto;
+      right: 10px;
+    }
+    &.leftTop {
+      height: 25px;
+      line-height: 26px;
+      padding: 0 8px;
+      border-radius: 16px;
+      background: rgba(0, 0, 0, 0.6);
+      bottom: auto;
+      top: 6px;
+    }
+  }
+  .tag-text {
+    position: absolute;
+    bottom: 31%;
+    left: 50%;
+    width: 80%;
+    transform: translate(-50%, 50%); // 确保在高二分之一的位置水平居中
+    height: 24px;
+    padding: 10px 0px 10px 0px;
+    background: rgba(0, 0, 0, 0.67);
+    border-radius: 6px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+    color: #FFFFFF;
+    font-size: 12px;
+  }
+
+  .center-mark {
+    position: absolute;
+    bottom: 10px;
+    left: 50%;
+    transform: translateX(-50%);
+    color: #36402c;
+    font-size: rpx(24);
+    font-weight: bold;
+    padding: rpx(14) rpx(30);
+    background: linear-gradient(
+            90deg,
+            rgba(255, 255, 255, 0) 0%,
+            rgba(255, 255, 255, 0.6) 24%,
+            rgba(255, 255, 255, 0.6) 76%,
+            rgba(255, 255, 255, 0) 100%
+    );
+  }
+}
+.carousel-item img {
+  width: 100%;
+  display: block;
+  border-radius: 12px;
+}
+canvas {
+  position: absolute;
+}
+.close-button {
+  background: transparent;
+  border: none;
+  color: #FFFFFF;
+  cursor: pointer;
+  font-size: 10px; // 可以根据需求调整大小
+  position: absolute;
+  top: -1px;
+  right: -9px; // 调整为合适的间距
+  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>

+ 75 - 0
src/views/home/album_compoents/cacheImg.js

@@ -0,0 +1,75 @@
+// 创建一个全局的图片缓存
+const imageCache = new Map();
+
+function loadImage(url, key) {
+    return new Promise((resolve, reject) => {
+        // 检查缓存中是否存在该key的图片
+        if (imageCache.has(key)) {
+            resolve(imageCache.get(key));
+            return;
+        }
+
+        // 如果缓存中没有,则创建一个新的图片对象
+        const img = new Image();
+        img.src = url;
+        img.crossOrigin = 'anonymous';
+
+        img.onload = () => {
+            // 图片加载完成后,将其存入缓存
+            imageCache.set(key, img);
+            resolve(img);
+        };
+
+        img.onerror = (error) => {
+            reject(error);
+        };
+    });
+}
+
+// 使用示例
+loadImage(require('@/assets/watermark/feiniao.png'), 'feiniao')
+    .then((img) => {
+        // 在这里使用加载完成的图片
+        // console.log('图片加载成功', img);
+    })
+    .catch((error) => {
+        console.error('图片加载失败', error);
+    });
+loadImage(require('@/assets/watermark/fushe.png'), 'fushe')
+    .then((img) => {
+        // 在这里使用加载完成的图片
+        // console.log('图片加载成功', img);
+    })
+    .catch((error) => {
+        console.error('图片加载失败', error);
+    });
+loadImage(require('@/assets/watermark/shidu.png'), 'shidu')
+    .then((img) => {
+        // 在这里使用加载完成的图片
+        // console.log('图片加载成功', img);
+    })
+    .catch((error) => {
+        console.error('图片加载失败', error);
+    });
+loadImage(require('@/assets/watermark/temp.png'), 'temp')
+    .then((img) => {
+        // 在这里使用加载完成的图片
+        // console.log('图片加载成功', img);
+    })
+    .catch((error) => {
+        console.error('图片加载失败', error);
+    });
+
+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};
+
+

+ 580 - 0
src/views/home/album_compoents/compareDialog.vue

@@ -0,0 +1,580 @@
+<template>
+    <el-dialog
+        v-model="dialogVisible"
+        width="80%"
+        align-center
+        class="picture-preview-wrap v-dialog"
+        :show-close="false"
+        append-to-body
+    >
+        <div class="picture-file">
+            <div class="compare-l">
+                <div class="left-img">
+                    <album-carousel-item
+                        lbum-carousel-item
+                        v-if="images"
+                        :key="nameRef+farmId+sampleId"
+                        :name="nameRef"
+                        :farmId="farmId"
+                        :images="images"
+                        :lock="lock"
+                    ></album-carousel-item>
+                </div>
+                <div class="file-wrap">
+                    <div class="file-title">
+                        <img src="@/assets/images/common/chart-yellow.png" alt="" />
+                        果树档案
+                        <span v-show="showTag" class="tag">小农户</span>
+                    </div>
+
+                    <div class="box-wrap">
+                        <div class="overview-file">
+                            <div class="box-title">总体档案</div>
+                            <div class="base-data">
+                                <div class="base-item" v-for="item in photoBaseData" :key="item.label">
+                                    <span class="label">{{ item.label }}</span>
+                                    <div class="value">{{ item.value }}</div>
+                                </div>
+                            </div>
+                            <div class="list">
+                                <div class="list-item" v-for="item in photoList" :key="item.key">
+                                    <div class="list-name">
+                                        <img src="@/assets/images/common/title-icon.png" alt="" />
+                                        {{ item.key }}
+                                    </div>
+                                    {{ item.statement }}
+                                </div>
+                            </div>
+                        </div>
+
+                        <div class="box-r">
+                            <div class="overview-file">
+                                <div class="box-title">产量详情</div>
+                                <div class="box-wrap">
+                                    <div
+                                        class="box-item"
+                                        v-for="(item, index) in outputBox"
+                                        :key="index"
+                                        @click="toggleAcitve(item.name)"
+                                        :class="{ active: activeOuput === item.name }"
+                                    >
+                                        <div class="item-name">{{ item.name }}</div>
+                                        <div class="item-val">{{ item.value }}</div>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="overview-file">
+                                <div class="box-title">质量详情</div>
+                                <div class="box-wrap">
+                                    <div
+                                        class="box-item"
+                                        v-for="(item, index) in qualityBox"
+                                        :key="index"
+                                        @click="toggleQualityAcitve(item.name)"
+                                        :class="{ active: activeOuput === item.name }"
+                                    >
+                                        <div class="item-name">{{ item.name }}</div>
+                                        <div class="item-val">{{ item.value }}</div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <div class="compare-r">
+                <div class="left-img">
+                    <album-carousel-item
+                        lbum-carousel-item
+                        v-if="images2"
+                        :key="nameRef + '2' + farmId2 + sampleId2"
+                        :name="nameRef"
+                        :farmId="farmId2"
+                        :images="images2"
+                        :lock="lock"
+                    ></album-carousel-item>
+                </div>
+                <div class="file-wrap">
+                    <div class="file-title">
+                        <img src="@/assets/images/common/chart-yellow.png" alt="" />
+                        果树档案
+                        <span v-show="showTag2" class="tag">小农户</span>
+                    </div>
+                    <div class="box-wrap">
+                        <div class="overview-file">
+                            <div class="box-title">总体档案</div>
+                            <div class="base-data">
+                                <div class="base-item" v-for="item in photoBaseData2" :key="item.label">
+                                    <span class="label">{{ item.label }}</span>
+                                    <div class="value">{{ item.value }}</div>
+                                </div>
+                            </div>
+                            <div class="list">
+                                <div class="list-item" v-for="item in photoList2" :key="item.key">
+                                    <div class="list-name">
+                                        <img src="@/assets/images/common/title-icon.png" alt="" />
+                                        {{ item.key }}
+                                    </div>
+                                    {{ item.statement }}
+                                </div>
+                            </div>
+                        </div>
+                        <div class="box-r">
+                            <div class="overview-file">
+                                <div class="box-title">产量详情</div>
+                                <div class="box-wrap">
+                                    <div
+                                        class="box-item"
+                                        v-for="(item, index) in outputBox2"
+                                        :key="index"
+                                        @click="toggleAcitve2(item.name)"
+                                        :class="{ active: activeOuput2 === item.name }"
+                                    >
+                                        <div class="item-name">{{ item.name }}</div>
+                                        <div class="item-val">{{ item.value }}</div>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="overview-file">
+                                <div class="box-title">质量详情</div>
+                                <div class="box-wrap">
+                                    <div
+                                        class="box-item"
+                                        v-for="(item, index) in qualityBox2"
+                                        :key="index"
+                                        @click="toggleQualityAcitve2(item.name)"
+                                        :class="{ active: activeOuput2 === item.name }"
+                                    >
+                                        <div class="item-name">{{ item.name }}</div>
+                                        <div class="item-val">{{ item.value }}</div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </el-dialog>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted } from "vue";
+import "./cacheImg.js";
+import AlbumCarouselItem from "./albumCarouselItem";
+import { dateFormat } from "@/utils/date_util.js";
+import eventBus from "@/api/eventBus";
+
+const lock = ref(false);
+const farmId = ref(766);
+const farmId2 = ref(766);
+const sampleId = ref(null);
+const sampleId2 = ref(null);
+const nameRef = ref("");
+
+const isLoadingImg = ref(true);
+const images = ref(null);
+const images2 = ref(null);
+const dialogVisible = ref(false);
+// 获取当前日期
+const currentDate = new Date();
+// 获取当前日期的前一个月
+const startDate = new Date(currentDate);
+startDate.setMonth(currentDate.getMonth() - 1);
+
+// 格式化日期
+const formattedStartDate = dateFormat(startDate, "YY-mm-dd");
+const formattedEndDate = dateFormat(currentDate, "YY-mm-dd");
+
+eventBus.on("change:watermark", function (name) {
+    nameRef.value = name;
+});
+
+const outputBox = ref([
+    { id: 1, name: "产量估计", value: "" },
+    { id: 2, name: "高质果率", value: "" },
+    { id: 3, name: "雌花比例", value: "" },
+]);
+
+const qualityBox = ref([
+    { id: 5, name: "通风率", value: "" },
+    { id: 6, name: "透光率", value: "" },
+    { id: 7, name: "病虫比例", value: "" },
+]);
+const outputBox2 = ref([
+    { id: 1, name: "产量估计", value: "" },
+    { id: 2, name: "高质果率", value: "" },
+    { id: 3, name: "雌花比例", value: "" },
+]);
+
+const qualityBox2 = ref([
+    { id: 5, name: "通风率", value: "" },
+    { id: 6, name: "透光率", value: "" },
+    { id: 7, name: "病虫比例", value: "" },
+]);
+
+const showTag = ref(false)
+const showTag2 = ref(false)
+eventBus.on("showCompareDialog", function (compareArr) {
+    console.log("compareArr", compareArr);
+    images.value = []
+    images2.value = []
+    compareArr.map((item, index) => {
+        dialogVisible.value = true;
+        if (index === 0) {
+            farmId.value = item.farmId
+            sampleId.value = item.sampleId
+            VE_API.miniimage.list({ farmId: item.farmId, sampleId: item.sampleId }).then((res) => {
+                if (res.code === 0) {
+                    images.value = res.data;
+                }
+            });
+            const dataItem = item.data;
+            showTag.value = dataItem.nonghu == 1 ? true : false;
+
+            VE_API.mini_farm.getSampleFiles({ geoHashSample: dataItem.geoHashSample }).then(({data}) => {
+                photoBaseData.value[0].value = dataItem.pz;
+                photoBaseData.value[1].value = data.meta_info.crown + "平方米";
+                photoBaseData.value[2].value = data.meta_info.branch_num ;
+
+            photoBaseData.value[3].value = data.meta_info.age + "年"
+            photoList.value[0].key = data.meta_info.dp_alert_info.key
+            photoList.value[0].statement = data.meta_info.dp_alert_info.statement
+            photoList.value[1].key = data.meta_info.grow_alert_info.key
+            photoList.value[1].statement = data.meta_info.grow_alert_info.statement
+            photoList.value[2].key = data.meta_info.nutrition_info.key
+            photoList.value[2].statement = data.meta_info.nutrition_info.statement
+            photoList.value[3].key = data.meta_info.prescription_info.key
+            photoList.value[3].statement = data.meta_info.prescription_info.statement
+            outputBox.value[0].value = data.production_info.production + "斤"
+            outputBox.value[1].value = data.production_info.quality.toFixed(0) + "%"
+            outputBox.value[2].value = data.production_info.cihua_ratio.toFixed(0) + "%"
+            qualityBox.value[0].value = data.ecology_info.ventilation + "%"
+            qualityBox.value[1].value = data.ecology_info.transmittance + "%"
+            qualityBox.value[2].value = data.ecology_info.dp_situation+"%"
+            });
+            // qualityBox.value[2].value = data.dxtj ? data.dxtj + "%" : "--";
+        } else {
+            farmId2.value = item.farmId
+            sampleId2.value = item.sampleId
+            VE_API.miniimage.list({ farmId: item.farmId, sampleId: item.sampleId }).then((res) => {
+                if (res.code === 0) {
+                    images2.value = res.data;
+                }
+            });
+            const dataItem = item.data;
+            showTag2.value = dataItem.nonghu == 1 ? true : false;
+            
+            VE_API.mini_farm.getSampleFiles({ geoHashSample: dataItem.geoHashSample }).then(({data}) => {
+                photoBaseData2.value[0].value = dataItem.pz;
+                photoBaseData2.value[1].value = data.meta_info.crown + "平方米";
+                photoBaseData2.value[2].value = data.meta_info.branch_num ;
+
+                photoBaseData2.value[3].value = data.meta_info.age + "年"
+                photoList2.value[0].key = data.meta_info.dp_alert_info.key
+                photoList2.value[0].statement = data.meta_info.dp_alert_info.statement
+                photoList2.value[1].key = data.meta_info.grow_alert_info.key
+                photoList2.value[1].statement = data.meta_info.grow_alert_info.statement
+                photoList2.value[2].key = data.meta_info.nutrition_info.key
+                photoList2.value[2].statement = data.meta_info.nutrition_info.statement
+                photoList2.value[3].key = data.meta_info.prescription_info.key
+                photoList2.value[3].statement = data.meta_info.prescription_info.statement
+                outputBox2.value[0].value = data.production_info.production + "斤"
+                outputBox2.value[1].value = data.production_info.quality.toFixed(0) + "%"
+                outputBox2.value[2].value = data.production_info.cihua_ratio.toFixed(0) + "%"
+                qualityBox2.value[0].value = data.ecology_info.ventilation + "%"
+                qualityBox2.value[1].value = data.ecology_info.transmittance + "%"
+                qualityBox2.value[2].value = data.ecology_info.dp_situation+"%"
+            });
+        }
+    });
+    //     isLoadingImg.value = true
+    //   let params = {sampleId,farmId}
+    //       dialogVisible.value = true
+    //   VE_API.miniimage.list(params).then(res => {
+    //     if(res.code === 0){
+    //       images.value = res.data
+    //       isLoadingImg.value = false
+    //     }
+    //   })
+    //   photoBaseData.value[0].value = data.pz;
+    //   photoBaseData.value[1].value = data.sgbmj + "米";
+    //   photoBaseData.value[2].value = data.cl + "斤";
+    //   photoBaseData.value[3].value = data.spgl + "%";
+    //   outputBox.value[0].value = data.hsl ? (data.hsl + "%") : "--";
+    //   outputBox.value[1].value = data.zzts? data.zzts : "--";
+    //   outputBox.value[2].value = data.khl? (data.khl + "%") : "--";
+    //   outputBox.value[3].value = data.xhl? (data.xhl + "%") : "--";
+    //   qualityBox.value[0].value = data.tfl? (data.tfl + "%") : "--";
+    //   qualityBox.value[1].value = data.tgl? (data.tgl + "%") : "--";
+    //   qualityBox.value[2].value = data.dxtj? (data.dxtj + "%") : "--";
+});
+
+eventBus.off("albumCarousel", toggleActiveImg);
+eventBus.on("albumCarousel", toggleActiveImg);
+
+const currentIndex = ref(0);
+function toggleActiveImg(index) {
+    currentIndex.value = index;
+}
+
+const photoBaseData = ref([
+    {
+        label: "品种",
+        value: "--",
+    },
+    {
+        label: "冠幅表面积",
+        value: "--",
+    },
+    {
+        label: "总枝条",
+        value: "--",
+    },
+    {
+        label: "树龄",
+        value: "--",
+    },
+]);
+
+const photoBaseData2 = ref([
+    {
+        label: "品种",
+        value: "--",
+    },
+    {
+        label: "冠幅表面积",
+        value: "--",
+    },
+    {
+        label: "总枝条",
+        value: "--",
+    },
+    {
+        label: "树龄",
+        value: "--",
+    },
+]);
+
+const photoList = ref([
+    {key: "病虫", statement: "--"},
+    {key: "异常", statement: "--"},
+    {key: "营养", statement: "--"},
+    {key: "农事", statement: "--"},
+]);
+const photoList2 = ref([
+    {key: "病虫", statement: "--"},
+    {key: "异常", statement: "--"},
+    {key: "营养", statement: "--"},
+    {key: "农事", statement: "--"},
+]);
+
+
+const activeOuput = ref(1);
+const activeOuput2 = ref(1);
+
+// 产量详情
+function toggleAcitve(name) {
+    activeOuput.value = name;
+    toggleNamePic(name)
+}
+
+function toggleNamePic(name) {
+    if (name.indexOf("开花") > -1) {
+        eventBus.emit("change:watermark", "开花目标框");
+    } else if (name.indexOf("花穗") > -1) {
+        eventBus.emit("change:watermark", "花穗目标框");
+    } else if (name.indexOf("雄花") > -1) {
+        eventBus.emit("change:watermark", "雄花目标框");
+    } else if (name.indexOf("枝条数") > -1) {
+        eventBus.emit("change:watermark", "");
+    } else {
+        eventBus.emit("change:watermark", "");
+    }
+}
+
+// 质量详情
+function toggleQualityAcitve() {}
+function toggleQualityAcitve2() {}
+
+function toggleAcitve2 (name){
+    activeOuput.value = name;
+    toggleNamePic(name)
+}
+
+</script>
+
+<style lang="scss" scoped>
+@import "src/styles/index";
+.picture-file {
+    display: flex;
+    .left-img {
+        // min-width: 500px;
+        width: 782px;
+        height: 580px;
+    }
+    .compare-l {
+        padding-right: 60px;
+    }
+    .file-wrap {
+        margin-top: 16px;
+        background: url("@/assets/images/home/file-bg-w.png") no-repeat top center / 100% 100%;
+        padding: 12px;
+        .file-title {
+            font-size: 20px;
+            color: #ffd489;
+            .tag {
+                border: 1px solid #FFD489;
+                border-radius: 4px;
+                font-size: 12px;
+                display: inline-block;
+                width: 44px;
+                height: 20px;
+                text-align: center;
+                line-height: 18px;
+                margin-left: 3px;
+                padding: 1px 4px;
+            }
+        }
+        .box-wrap {
+            display: flex;
+            .box-r {
+                padding-left: 40px;
+            }
+        }
+        .overview-file {
+            padding-top: 20px;
+            .box-title {
+                font-size: 16px;
+                padding-left: 13px;
+                margin-bottom: 16px;
+                position: relative;
+                display: flex;
+                justify-content: space-between;
+                color: #fff;
+                &::before {
+                    content: "";
+                    position: absolute;
+                    left: 0;
+                    top: 3px;
+                    width: 3px;
+                    height: 16px;
+                    background: #fff;
+                    border-radius: 11px;
+                }
+            }
+            .title {
+                color: #f3c11d;
+                font-size: 16px;
+                font-family: "PangMenZhengDao";
+                margin-bottom: 20px;
+                .big {
+                    width: 13px;
+                    height: 13px;
+                    margin: -10px 0 0 4px;
+                }
+                .small {
+                    width: 7px;
+                    height: 7px;
+                    margin-left: -3px;
+                }
+            }
+            .base-data {
+                background: rgba(207, 207, 207, 0.1);
+                border-radius: 4px;
+                padding: 6px 0;
+                display: flex;
+                .base-item {
+                    flex: 1;
+                    text-align: center;
+                    padding: 0 4px;
+                    min-width: 50px;
+                    .label {
+                        font-size: 12px;
+                        color: #666666;
+                        width: max-content;
+                        display: block;
+                        margin: 0 auto;
+                        padding-bottom: 2px;
+                    }
+                    .value {
+                        padding-top: 2px;
+                        font-size: 16px;
+                        color: #ffffff;
+                        width: max-content;
+                        margin: 0 auto;
+                        display: block;
+                    }
+                }
+                .base-item + .base-item {
+                    border-left: 1px solid rgba(102, 102, 102, 0.42);
+                }
+            }
+            .list {
+                margin-top: 15px;
+                width: max-content;
+                font-size: 14px;
+                .list-item {
+                    color: #bbbbbb;
+                    display: flex;
+                    margin-bottom: 8px;
+                    .list-name {
+                        color: #f3c11d;
+                        margin-right: 6px;
+                        img {
+                            width: 17px;
+                            height: 13px;
+                        }
+                    }
+                }
+            }
+        }
+        .overview-file + .overview-file {
+            margin-top: 8px;
+        }
+        .box-wrap {
+            display: flex;
+            .box-item {
+                flex: 1;
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+                align-items: center;
+                padding: 6px 16px;
+                background: rgba(207, 207, 207, 0.1);
+                border-radius: 4px;
+                border: 1px solid rgba(207, 207, 207, 0.1);
+                cursor: pointer;
+                .item-name {
+                    font-size: 12px;
+                    color: #666666;
+                    width: max-content;
+                }
+                .item-val {
+                    font-size: 18px;
+                    color: #fff;
+                    width: max-content;
+                    padding-top: 3px;
+                }
+                &.active {
+                    background: rgba(255, 212, 137, 0.16);
+                    border: 1px solid #ffd489;
+                    .item-name {
+                        color: #bbbbbb;
+                    }
+                }
+            }
+            .box-item + .box-item {
+                margin-left: 8px;
+            }
+        }
+    }
+}
+</style>
+<style lang="scss">
+.picture-preview-wrap {
+    background: none;
+}
+</style>

+ 268 - 0
src/views/home/album_compoents/detailDailog.vue

@@ -0,0 +1,268 @@
+<template>
+    <el-dialog v-model="winDialogVisible" lock-scroll modal-class="album-detail-modal" :showClose="false" width="90%" align-center>
+        <div>
+            <div class="congratulation-wrap">
+                <div class="congratulation-box">
+                    <div class="win-des">
+                        <!-- <img src="@/assets/img/weather_index/box-top.png" class="win-icon" /> -->
+                    </div>
+                    <div class="album-detail-box">
+                        <div class="detail-title">{{ dialogData.farmWorkName }}</div>
+                        <div class="detail-desc-box">
+                            <div class="desc-item">
+                                <span class="item-name">触发条件</span>
+                                {{ dialogData.condition }}
+                            </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 }} -->
+                            </div>
+                            <div class="desc-item">
+                                <span class="item-name">农事宗旨</span>
+                                {{ dialogData.purpose }}
+                            </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-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="ratio" label="配比" width="50">
+                                            <template #default="scope">
+                                                {{scope.row.ratio ? scope.row.ratio : "--"}}
+                                            </template>
+                                        </el-table-column> -->
+                                        <el-table-column prop="ratio" label="方式" width="62">
+                                            <template #default="scope">
+                                                {{scope.row.ratio ? "人工" : "人工"}}
+                                            </template>
+                                        </el-table-column>
+                                    </el-table>
+                                </div>
+                            </div>
+
+                            <div class="card-link">
+                                <img src="@/assets/img/weather_index/expert-icon.png" />
+                                <div class="expert-name">
+                                    {{ dialogData.expertName }}
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="close-btn">
+                    <!-- <el-icon size="32" color="#fff"><CircleCloseFilled /></el-icon> -->
+                    <el-button type="primary" class="one-btn" @click="toShare"> 立即分享 </el-button>
+            </div>
+        </div>
+        <template #footer>
+            <div class="dialog-footer">
+                <div class="close-btn">
+                    <!-- <el-icon size="32" color="#fff"><CircleCloseFilled /></el-icon> -->
+                    <!-- <el-button type="primary" class="one-btn" @click="toShare"> 立即分享 </el-button> -->
+                </div>
+            </div>
+        </template>
+    </el-dialog>
+</template>
+<script setup>
+import { ref } from "vue";
+import eventBus from "@/api/eventBus";
+import wx from "weixin-js-sdk";
+import { useRoute, useRouter } from "vue-router";
+
+const winDialogVisible = ref(false);
+
+const route = useRoute();
+const sampleId = route.query.sampleId
+const farmId = route.query.farmId;
+
+const dialogData = ref({});
+const currentCard = ref({});
+const showDialog = (pageParams) => {
+    dialogData.value = pageParams.card;
+    currentCard.value = {
+        activeIndex: pageParams.activeIndex,
+        farmWorkName: dialogData.value.farmWorkName,
+        farmId: farmId,
+        sampleId: sampleId,
+    };
+    winDialogVisible.value = true;
+};
+eventBus.on("detailDialog:showDialog", (data) => {
+    showDialog(data)
+});
+
+const toShare = () => {
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(currentCard.value)}&type=album`,
+    });
+}
+</script>
+
+<style lang="less" scoped>
+.congratulation-wrap {
+    border-radius: 12px;
+    background: #f4f5f4;
+}
+.close-btn {
+    text-align: center;
+    margin-top: 20px;
+}
+.congratulation-box {
+    border-radius: 12px;
+    background: url("@/assets/img/weather_index/box-top.png") no-repeat top center /contain;
+    .el-message-box__message {
+        padding: 12px 0 24px 0;
+    }
+    .win-title {
+        color: #1d1e1f;
+        font-family: "PangMenZhengDao", sans-serif; /* 使用自定义字体 */
+        text-align: center;
+        font-size: 24px;
+        line-height: 32px;
+    }
+    .win-detail {
+        text-align: center;
+        color: #252525;
+        padding-top: 6px;
+        font-size: 16px;
+        span {
+            font-size: 22px;
+            color: #2199f8;
+            padding: 0 6px;
+            font-weight: bold;
+        }
+    }
+    .win-des {
+        height: 205px;
+        text-align: center;
+    }
+    .win-icon {
+        width: 100%;
+        border-radius: 12px 12px 0 0;
+    }
+}
+.album-detail-box {
+    padding: 0 24px 16px 12px;
+    color: #000;
+    position: relative;
+    // top: -58px;
+    .detail-title {
+        font-size: 24px;
+        font-weight: bold;
+        padding-bottom: 8px;
+        letter-spacing: 1.6px;
+    }
+    .detail-desc-box {
+        position: relative;
+        .desc-item {
+            font-size: 14px;
+            .item-name {
+                color: #999999;
+                margin-right: 12px;
+            }
+            .item-table {
+                margin-top: 8px;
+                border: 1px solid rgba(255, 255, 255, 0.6);
+                border-radius: 4px;
+                ::v-deep {
+                    .el-table th.el-table__cell {
+                        padding: 6px 0;
+                    }
+                    .el-table--default .el-table__cell {
+                        padding: 6px 0;
+                    }
+                    .el-table--default .cell {
+                        padding: 0px 6px;
+                    }
+                }
+            }
+        }
+        .desc-item + .desc-item {
+            padding-top: 8px;
+        }
+        .card-link {
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            justify-content: center;
+            color: #2199f8;
+            font-size: 12px;
+            position: absolute;
+            right: 6px;
+            top: 0px;
+            .expert-name {
+                background: #d3e8ff;
+                border-radius: 4px;
+                padding: 1px 12px;
+                margin-top: 4px;
+                display: flex;
+                align-items: center;
+            }
+            img {
+                width: 62px;
+            }
+            .icon {
+                padding-right: 2px;
+            }
+        }
+    }
+}
+.dialog-footer {
+    position: relative;
+    .close-btn {
+        position: absolute;
+        bottom: -58px;
+        left: 0;
+        right: 0;
+        margin: 0 auto;
+        text-align: center;
+    }
+}
+</style>
+
+<style lang="less">
+.album-detail-modal {
+    .el-overlay-dialog {
+        .el-dialog {
+            padding: 0;
+            border-radius: 12px;
+            background: none;
+            box-shadow: none;
+            .el-dialog__header {
+                padding: 0;
+            }
+        }
+
+        .one-btn {
+            width: 210px;
+            height: 40px;
+            line-height: 40px;
+        }
+    }
+}
+</style>

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

@@ -0,0 +1,284 @@
+/**
+ * 在矩形内绘制矩形
+ * @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;
+        }
+    });
+};
+
+
+function drawTargetRectangles(ctx, obj, originalWidth, originalHeight, imgWidth, imgHeight) {
+    // 计算缩放比例
+    const scaleX = imgWidth / originalWidth;
+    const scaleY = imgHeight / originalHeight; // 假设实际宽度和高度缩放比例相同,如果需要不同,请传入actualHeight并计算scaleY
+    let targetRectangles = obj.cor;
+    let color = obj.color;
+    // 遍历每个目标框
+    targetRectangles.forEach(rect => {
+        // 解构出坐标
+        const [x1, y1, x2, y2] = rect;
+
+        // 计算缩放后的新坐标
+        const newX1 = x1 * scaleX;
+        const newY1 = y1 * scaleY;
+        const newX2 = x2 * scaleX;
+        const newY2 = y2 * scaleY;
+
+        // 绘制矩形框
+        ctx.strokeStyle = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; // 使用你提供的颜色
+        ctx.lineWidth = 2; // 线条宽度可以根据需要调整
+        ctx.strokeRect(newX1, newY1, newX2 - newX1, newY2 - newY1);
+    });
+}
+
+
+
+
+
+
+
+
+
+export { drawTextInRect, drawLineInRect, drawPointInRect ,drawImageInRect,drawBorderImageInRect, drawRectInRect, drawHorizontalTextList, drawTargetRectangles};
+

+ 79 - 5
src/views/home/components/gardenIntroduce.vue

@@ -35,7 +35,18 @@
         </div>
 
         <div class="sub-box">
-            <sub-title name="果园简介"></sub-title>
+            <sub-title name="果园简介">
+                <template v-slot:title-right>
+                    <div class="title-right" @click="editDesc" v-show="!isEdit">
+                        <img class="sub-edit" src="@/assets/images/common/edit-icon.png" alt="">
+                        编辑
+                    </div>
+                    <div class="title-right-btn" v-show="isEdit">
+                        <div class="btn cancel-btn" @click="isEdit=false">取消</div>
+                        <div class="btn edit-btn" @click="saveDesc">保存</div>
+                    </div>
+                </template>
+            </sub-title>
             <div class="sub-content">
                 <div class="content-text" v-show="isEdit">
                     <el-input
@@ -74,8 +85,11 @@
 
         <div class="sub-box">
             <sub-title name="美景视频">
-                <template>
-                    更多
+                <template v-slot:title-right>
+                    <div class="title-right">
+                        更多
+                        <el-icon class="sub-icon"><ArrowRight /></el-icon>
+                    </div>
                 </template>
             </sub-title>
             <div class="video-wrap" @click="showVideo">
@@ -85,8 +99,11 @@
 
         <div class="sub-box">
             <sub-title name="农事记录">
-                <template>
-                    更多
+                <template v-slot:title-right>
+                    <div class="title-right">
+                        更多
+                        <el-icon class="sub-icon"><ArrowRight /></el-icon>
+                    </div>
                 </template>
             </sub-title>
             <div class="video-wrap">
@@ -113,8 +130,18 @@ import subTitle from "@/components/subTitle.vue"
 import { Popup } from 'vant';
 import eventBus from "@/api/eventBus";
 
+// 简介
 const isEdit = ref(false)
 const gardenDes = ref("柏桥村位于广东茂名高州根子镇,是千年荔枝之乡的核心产区,盛产果肉晶莹、鲜甜多汁的白糖罂荔枝。村内6800亩荔枝林与古荔园中48棵百年以上古树交相辉映,诉说着种植传奇。依托冷链物流和电商直播,新鲜荔枝一键直达全国。")
+function editDesc() {
+    isEdit.value = true
+}
+
+function saveDesc() {
+    // 保存
+    isEdit.value = false
+}
+
 const showPopup = ref(false)
 
 onMounted(()=>{
@@ -203,6 +230,52 @@ function showVideo() {
         text-align: justify;
         font-size: 12px;
         color: #999999;
+
+        ::v-deep {
+            .el-textarea__inner {
+                background: rgba(255, 212, 137, 0.08);
+                box-shadow: 0 0 0 1px #FFD489 inset;
+                color: #FFD489;
+                font-size: 12px;
+            }
+        }
+    }
+    .title-right {
+        background: rgba(255, 212, 137, 0.1);
+        border-radius: 20px;
+        font-size: 12px;
+        color: #FFD489;
+        padding: 2px 8px;
+        display: flex;
+        align-items: center;
+        .sub-edit {
+            width: 12px;
+            padding-right: 4px;
+        }
+        .sub-icon {
+            padding-left: 2px;
+        }
+    }
+    .title-right-btn {
+        display: flex;
+        align-items: center;
+        .btn {
+            cursor: pointer;
+            padding: 2px 8px;
+            font-size: 12px;
+            border-radius: 4px;
+            border: 1px solid #FFD489;
+        }
+        .cancel-btn {
+            color: #FFD489;
+        }
+        .edit-btn {
+            background: #FFD489;
+            color: #000000;
+        }
+        .btn + .btn {
+            margin-left: 8px;
+        }
     }
     .honour-name {
         display: flex;
@@ -211,6 +284,7 @@ function showVideo() {
         color: #FFD489;
         font-size: 16px;
         font-family: 'PangMenZhengDao';
+        padding-bottom: 12px;
         .honour-l {
             padding-right: 6px;
         }

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

@@ -100,7 +100,7 @@ import { useStore } from "vuex";
 import RegionLayer from "./map/regionLayer";
 import BlueRegionLayer from "./map/blueRegionLayer";
 import eventBus from "@/api/eventBus";
-// import AlbumCarousel from "./album_compoents/albumCarousel.vue";
+import AlbumCarousel from "./album_compoents/albumCarousel.vue";
 // import compareDialog from "./album_compoents/compareDialog.vue";
 // import album from "./album/index.vue";
 import PdfDialog from "../../components/PdfDialog";

+ 10 - 3
src/views/home/map/samplePointLayer.js

@@ -37,7 +37,7 @@ class SamplePointLayer {
     this.isUpdatePoint = null
     this.isUpdateArea = null
 
-    this.treeClusterLayer = new KMap.VectorLayer("tree-cluster", 999, {
+    this.treeClusterLayer = new KMap.VectorLayer("tree-cluster-home", 999, {
       minZoom: 15,
       source: this.clusterSource,
       style: (f) => this.getStyle(f)
@@ -55,7 +55,7 @@ class SamplePointLayer {
         });
       },
     })
-    map.addLayer(this.treeClusterLayer.layer)
+    // map.addLayer(this.treeClusterLayer.layer)
     // map.addLayer(this.yellowBlockLayer.layer);
 
     let point = new Feature(new Point([113.61396985128522, 23.5859386716038]));
@@ -171,6 +171,13 @@ class SamplePointLayer {
         features: features,
       });
       that.clusterSource.setSource(source)
+
+      const layers = that.mapRef.map.getLayers().getArray();
+      const exists = layers.includes(that.treeClusterLayer.layer);
+
+      if (!exists) {
+        that.mapRef.addLayer(that.treeClusterLayer.layer);
+      }
       setTimeout(() => {
         that.mapRef.fit(that.clusterSource.source.getExtent(), { padding: [100, 100, 100, 100] })
       }, 100)
@@ -284,7 +291,7 @@ class SamplePointLayer {
     kmap.on("singleclick", (evt) => {
       let hasFeature = false
       kmap.forEachFeatureAtPixel(evt.pixel, function (feature, layer) {
-        if (layer instanceof VectorLayer && layer.get("name") === "tree-cluster") {
+        if (layer instanceof VectorLayer && layer.get("name") === "tree-cluster-home") {
           hasFeature = true
           if (that.curPoint) {
             that.curPoint.set("iconName", "defalut");