瀏覽代碼

Merge branch 'master' of http://www.sysuimars.cn:3000/feiniao/feiniao-farm-h5

lxf 2 天之前
父節點
當前提交
eabaf1eb79

二進制
src/assets/img/gallery/camera-icon.png


二進制
src/assets/img/gallery/camera.png


二進制
src/assets/img/gallery/img-icon-act.png


二進制
src/assets/img/gallery/img-icon.png


二進制
src/assets/img/home/example-3.png


二進制
src/assets/img/home/msg_icon.png


二進制
src/assets/watermark/feiniao.png


二進制
src/assets/watermark/fushe.png


二進制
src/assets/watermark/shidu.png


二進制
src/assets/watermark/temp.png


+ 74 - 0
src/components/album_compoents/albumCarousel.vue

@@ -0,0 +1,74 @@
+<template>
+    <album-carousel-item v-if="images" :farmId="farmId" :images="images" :lock="lock"></album-carousel-item>
+</template>
+
+<script setup>
+import { ref, computed, onMounted, onUnmounted, watch } from "vue";
+import "./cacheImg.js"
+import AlbumCarouselItem from "./albumCarouselItem";
+import {dateFormat} from "@/utils/date_util.js"
+import eventBus from "@/api/eventBus.js";
+
+const props =defineProps({
+  sampleId:{
+    type: [Number, String],
+    required: false
+  },
+  farmId:{
+    type: [Number, String],
+    required: true
+  },
+  blueGeoHash:{
+    type: String,
+    required: false
+  },
+  farmWork:{
+    type: Object,
+    required: false
+  },
+  lock:{
+    type: Boolean,
+    default: true
+  }
+})
+const images = ref(null);
+onMounted(() => {
+  getList()
+});
+
+watch(()=>props.farmId,(newValue,oldValue) =>{
+  if(newValue){
+    getList()
+  }
+})
+
+const getList = () =>{
+  if(!props.farmId) return
+  let params = {farmId: props.farmId}
+  if(props.blueGeoHash){
+    params.blueZone = props.blueGeoHash
+  }
+  if(props.sampleId){
+    params.sampleId = props.sampleId
+  }
+  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");
+  }
+  VE_API.image.list(params).then(res => {
+  if(res.code === 0){
+      images.value = res.data
+      eventBus.emit('image:resultNum',res.data.length)
+    }
+  })
+}
+
+</script>
+
+<style lang="scss" scoped>
+@import "src/styles/index";
+</style>

+ 119 - 0
src/components/album_compoents/albumCarousel7d.vue

@@ -0,0 +1,119 @@
+<template>
+  <template v-for="(images) in imagesList" :key="images.id">
+    <album-carousel-item :farmId="farmId" :lock="lock" :images="images"></album-carousel-item>
+    <div style="height: 5px"></div>
+  </template>
+</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";
+import { useRoute } from "vue-router";
+const route = useRoute();
+
+const props =defineProps({
+  sampleId:{
+    type: [Number, String],
+    required: false
+  },
+  lock:{
+    type: Boolean,
+    default: true
+  },
+  farmId:{
+    type: [Number, String],
+    required: true
+  },
+  farmWork:{
+    type: Object,
+    required: false
+  }
+})
+const imagesList = ref([]);
+let params = {farmId: props.farmId}
+
+onMounted(() => {
+  if(props.sampleId){
+    params["sampleId"] = props.sampleId
+  }
+  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) =>{
+  imagesList.value = []
+  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])
+        }
+      }
+    }
+  }).catch(err => {
+    console.log(err)
+  })
+}
+
+// eventBus.off("chart:updateOption",updateData)
+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>

+ 202 - 0
src/components/album_compoents/albumCarouselItem.vue

@@ -0,0 +1,202 @@
+<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
+                        :isShowNum="isShowNum"
+                        :farmId="farmId"
+                        :photo="photo"
+                        :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,
+    },
+    isShowNum: {
+        type: Boolean,
+        default: true,
+    },
+});
+const { images } = toRefs(props);
+let timer = null;
+const currentIndex = ref(0);
+
+onMounted(() => {
+    updateImagePosition();
+    clearAndRestartTimer();
+});
+onUnmounted(() => {
+    clearInterval(timer);
+});
+
+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();
+};
+
+// 上一张图片
+const prev = () => {
+    // 图片总数
+    const totalImages = images.value.length;
+    currentIndex.value = (currentIndex.value - 1 + totalImages) % totalImages;
+    updateImagePosition();
+    clearAndRestartTimer();
+};
+
+const clearAndRestartTimer = () => {
+    if (timer) {
+        clearInterval(timer);
+    }
+    // timer = setInterval(next, 5000);
+};
+</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>

+ 287 - 0
src/components/album_compoents/albumDrawBox.vue

@@ -0,0 +1,287 @@
+<template>
+  <photo-consumer
+      class="carousel-item"
+      :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%;height:25vh;object-fit: cover;" />
+    <canvas  ref="canvasRef" style="position: absolute;"></canvas>
+    <div class="tag-text" v-if="photo.growText && showTagBox" >
+      <span v-html="photo.growText"></span>
+      <button class="close-button" @click="hideTagBox">✖</button>
+    </div>
+    <div class="tag-box right" v-if="isShowNum" :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 {drawTextInRect, drawBorderImageInRect, drawImageInRect, drawRectInRect, drawHorizontalTextList} from "./utils"
+// const resize = "?x-oss-process=image/resize,p_30/format,webp/quality,q_40";
+const resize = "";
+
+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
+  },
+  isShowNum:{
+    type: Number,
+    required: true
+  }
+})
+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) {
+  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.weather7d.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, 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)")
+
+  drawHorizontalTextList(ctx, topRect, '#ffffff90',[data.treeCode, "蓬径:5m", "高度:3m", "高产树",data.pingzhong],
+      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%;
+  max-height: 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;
+}
+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>

+ 80 - 0
src/components/album_compoents/cacheImg.js

@@ -0,0 +1,80 @@
+// 创建一个全局的图片缓存
+const imageCache = new Map();
+
+function loadImage(url, key) {
+    if (!url) {
+        return Promise.reject('图片地址不能为空');
+    }
+    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);
+        };
+    }).catch((error) => {
+        console.error('图片加载失败', 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};
+
+

+ 327 - 0
src/components/album_compoents/detailDailog.vue

@@ -0,0 +1,327 @@
+<template>
+    <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">
+                        <!-- <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" v-if="dialogData?.conditionList && dialogData.conditionList.length">
+                                <span class="item-name">触发条件</span>
+                                {{ 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">
+                                <span class="item-name">推荐时间</span>
+                                {{ dialogData.executeDate }}
+                            </div>
+                            <!-- <div class="desc-item">
+                                <span class="item-name">农事宗旨</span>
+                                {{ dialogData.purpose || dialogData.condition }}
+                            </div> -->
+                            <div class="desc-item">
+                                <div class="item-name">药物处方</div>
+                                <div class="item-table">
+                                    <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="defaultName" 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.executeStyle ? (scope.row.executeStyle === 2 ? "无人机" : "人工") : "--"}}
+                                            </template>
+                                        </el-table-column>
+                                    </el-table>
+                                </div>
+                            </div>
+
+                            <div class="card-link">
+                                <img :src="dialogData.expertIcon || dialogData.expertUserIcon" />
+                                <div class="expert-name">
+                                    {{ dialogData.expertUserName || dialogData.expertName }}
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="close-btn" v-show="dialogData.isBtn" @click.stop="winDialogVisible=false">
+                    <!-- <el-icon size="32" color="#fff"><CircleCloseFilled /></el-icon> -->
+                    <el-button v-show="dialogData.typeStr === 'export'" type="primary" class="one-btn" @click.stop="toPage"> 邀请专家诊断 </el-button>
+                    <el-button v-show="dialogData.typeStr === 'activate'" type="primary" class="one-btn" @click.stop="handleAct"> 立即激活专家农事 </el-button>
+                    <!-- <el-button type="primary" class="one-btn" @click.stop="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 { onActivated, onDeactivated, onMounted, ref } from "vue";
+import eventBus from "@/api/eventBus";
+import wx from "weixin-js-sdk";
+import { useRoute, useRouter } from "vue-router";
+
+const props = defineProps({
+    initReload: {
+        type: Boolean,
+        default: false
+    }
+})
+
+const winDialogVisible = ref(false);
+
+const route = useRoute();
+const router = useRouter();
+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,
+    };
+    settingData()
+    winDialogVisible.value = true;
+};
+
+onMounted(() => {
+    if (props.initReload) {
+        eventBus.on("detailDialog:showDialog", showDialog); 
+    }
+})
+
+onActivated(()=>{
+    eventBus.on("detailDialog:showDialog", showDialog); 
+})
+
+onDeactivated(()=>{
+    eventBus.off("detailDialog:showDialog", showDialog);
+})
+
+//邀请专家诊断
+const toPage = () =>{
+    router.push('/prescription')
+}
+
+//立即激活
+const handleAct = () =>{
+    winDialogVisible.value = false
+    eventBus.emit("uploadUopup:show",{gardenIdVal:dialogData.value.farmId,orderIdVal:dialogData.value.orderId,textVal:dialogData.value.consequenceText});
+}
+
+// 弹窗关闭
+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`,
+    });
+}
+</script>
+
+<style lang="less" scoped>
+.congratulation-wrap {
+    border-radius: 12px;
+    background: #f4f5f4;
+    width: 100%;
+}
+.detail-log {
+    width: 100%;
+}
+.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: 180px;
+        text-align: center;
+    }
+    .win-icon {
+        width: 100%;
+        border-radius: 12px 12px 0 0;
+    }
+}
+.album-detail-box {
+    padding: 0 10px 16px 10px;
+    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;
+                height: 62px;
+                border-radius: 50%;
+                object-fit: cover;
+            }
+            .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;
+            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 {
+            width: 210px;
+            height: 40px;
+            line-height: 40px;
+        }
+    }
+}
+</style>

+ 256 - 0
src/components/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};
+

+ 186 - 0
src/components/popup/uploadPopup.vue

@@ -0,0 +1,186 @@
+<template>
+    <popup class="upload-popup" v-model:show="show" closeable :close-on-click-overlay="false" @closed="handleClosed">
+        <div v-show="isPlan" class="plan-title">
+            请上传农事凭证
+        </div>
+        <div v-show="!isPlan">
+            <div class="title">
+                <img src="@/assets/img/home/msg_icon.png" alt="">
+                <span>如需激活农事,请上传照片</span>
+            </div>
+            <div class="tips">
+                {{text}}
+            </div>
+        </div>
+        <upload :textShow="true" :exampleImg="isPlan">
+            <img class="example" src="@/assets/img/home/example-3.png" alt="">
+        </upload>
+        <div class="footer">
+            <el-button type="primary" class="btn" @click="handleUpload">确认上传</el-button>
+        </div>
+    </popup>
+    <popup class="upload-popup loading" v-model:show="dialogVisible" closeable>
+      正在识别中,请稍后...
+    </popup>
+</template>
+
+<script setup>
+import { Popup } from 'vant';
+import {onActivated, onDeactivated, ref} from 'vue'
+import upload from '@/components/upload'
+import eventBus from "@/api/eventBus";
+import { ElMessage } from "element-plus";
+
+const props = defineProps({
+    executionData: {},
+});
+
+const show = ref(false);
+const gardenId = ref(null)
+const images = ref([])
+const dialogVisible = ref(false)
+
+onActivated(()=>{
+    eventBus.off('upload:changeArr',uploadChange)
+    eventBus.on('upload:changeArr',uploadChange)
+    eventBus.on('uploadUopup:show',handleShow)
+    eventBus.off('close:uploadPopup',closePupop)
+    eventBus.on('close:uploadPopup',closePupop)
+})
+
+function closePupop(){
+    dialogVisible.value = false
+}
+
+function uploadChange(arr){
+    images.value = arr
+}
+
+// 转换为 YYYY-MM-DD 格式
+function formatDate(date) {
+    let year = date.getFullYear();
+    let month = String(date.getMonth() + 1).padStart(2, '0'); // 月份是从0开始的
+    let day = String(date.getDate()).padStart(2, '0');
+    return `${year}-${month}-${day}`;
+}
+
+const handleUpload = () =>{
+    if(images.value.length === 0) return ElMessage.warning('请上传图片')
+    if(isPlan.value){
+        const params = {
+            ...props.executionData,
+            orderStatus: 5,
+            executeEvidence: images.value
+        }
+        VE_API.order.confirm(params).then(({ code }) => {
+            if (code === 0) {
+                show.value = false
+                ElMessage.success('您已上传成功')
+            }
+        })
+    }else{
+        const params = {
+            gardenId:gardenId.value,
+            images:images.value,
+            uploadDate:formatDate(new Date())
+        }
+        VE_API.ali.uploadImg(params).then(res =>{
+            if(res.success){
+                show.value = false
+                ElMessage.success('您已上传成功')
+                dialogVisible.value = true
+                eventBus.emit('confirm:callback')
+            }
+        })
+    }
+}
+
+const orderId = ref(null)
+const text = ref('')
+// 农事规划
+const isPlan = ref(false)
+
+function handleShow({isPlanVal,gardenIdVal,orderIdVal,textVal}){
+    orderId.value = orderIdVal
+    text.value = textVal
+    images.value = []
+    gardenId.value = gardenIdVal
+    show.value = true
+    isPlan.value = isPlanVal ? true : false
+}
+
+const handleClosed = () =>{
+    eventBus.emit('upload:reset')
+}
+
+onDeactivated(() => {
+    eventBus.off('uploadUopup:show',handleShow)
+    show.value = false
+})
+</script>
+
+<style lang="scss" scoped>
+.upload-popup{
+    width: 90%;
+    box-sizing: border-box;
+    padding: 20px 18px;
+    background: linear-gradient(0deg,#FFFFFF 70%,#D1EBFF 100%);
+    border-radius: 10px;
+    &.loading{
+        text-align: center;
+        padding: 40px 0;
+        font-size: 18px;
+    }
+    .example{
+        width: 100%;
+        height: 100%;
+    }
+    ::v-deep{
+        .van-popup__close-icon{
+            color: #000;
+        }
+    }
+    .plan-title {
+        text-align: center;
+        font-size: 26px;
+        font-family: "PangMenZhengDao";
+        padding: 12px 0 20px 0;
+    }
+    .title{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        img{
+            width: 20px;
+            height: 20px;
+            margin-right: 8px;
+        }
+        span{
+            font-size: 18px;
+            font-family: "PangMenZhengDao";
+        }
+    }
+    .tips{
+        margin: 8px 0 16px 0;
+        border: 1px solid rgba(33, 153, 248, 0.52);
+        border-radius: 5px;
+        padding: 9px 5px;
+        font-family: "PangMenZhengDao";
+        font-size: 12px;
+        color: #2199F8;
+        text-align: center;
+        span{
+            color: #FF953D;
+        }
+    }
+    .footer{
+        margin-top: 24px;
+        display: flex;
+        justify-content: center;
+        .btn{
+            width: 80%;
+            padding: 18px;
+        }
+    }
+}
+</style>

+ 161 - 0
src/components/upload.vue

@@ -0,0 +1,161 @@
+<template>
+    <div class="upload-wrap">
+        <div class="tips tips-text" v-if="tipText?.length>0">
+            <span>{{tipText}}</span>
+        </div>
+        <div class="tips" v-if="!textShow">
+            <el-icon class="icon" size="16"><Warning /></el-icon>
+            <span>上传照片可精准预测最佳病虫防治时间</span>
+        </div>
+        <uploader class="uploader" v-model="fileList" multiple :max-count="3" :after-read="afterRead" @delete="deleteImg">
+            <template v-if="exampleImg">
+                <slot v-if="!fileList.length"></slot>
+                <img class="plus" v-else src="@/assets/img/home/plus.png" alt="">
+            </template>
+            <img class="plus" v-else src="@/assets/img/home/plus.png" alt="">
+        </uploader>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref} from "vue";
+import { Uploader } from "vant";
+import eventBus from "@/api/eventBus";
+import { base_img_url2 } from "@/api/config";
+import { getFileExt } from "@/utils/util";
+import { ElMessage } from "element-plus";
+import UploadFile from "@/utils/upliadFile";
+import 'vant/lib/uploader/style';
+import { useStore } from "vuex";
+
+const props = defineProps({
+  tipText: {
+    type: String,
+    default: ''
+  },
+  fullPath:{
+    type: Boolean,
+    default: true
+  },
+  textShow: {
+    type: Boolean,
+    default: true
+  },
+  exampleImg: {
+    type: Boolean,
+    default: false
+  },
+})
+
+
+const store = useStore();
+const miniUserId = store.state.home.miniUserId;
+
+const emit = defineEmits(['handleUpload'])
+
+//上传照片
+const fileList = ref([]);
+const fileArr = ref([])
+const imgArr = ref([])
+const uploadFileObj = new UploadFile();
+
+const afterRead = async (files) => {
+    if (!Array.isArray(files)) {
+      files = [files];
+    }
+    for(let file of files){
+      // 将文件上传至服务器
+      let fileVal = file.file;
+      file.status = "uploading";
+      file.message = "上传中...";
+      let ext = getFileExt(fileVal.name);
+      let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
+      let resFilename = await uploadFileObj.put(key, fileVal)
+      file.status = "done";
+      file.message = "";
+      if(resFilename){
+        fileArr.value.push(props.fullPath ? base_img_url2 + resFilename : resFilename)
+        imgArr.value.push(resFilename)
+        eventBus.emit('upload:change',fileArr.value)
+        eventBus.emit('upload:changeArr',imgArr.value)
+        emit('handleUpload',{imgArr:imgArr.value,fileList:fileArr.value})
+      }else{
+        fileList.value.pop()
+        ElMessage.error('图片上传失败,请稍后再试!')
+      }
+    }
+};
+
+const deleteImg = (file,e) => {
+    fileArr.value.splice(e.index,1)
+    imgArr.value.splice(e.index,1)
+    eventBus.emit('upload:change',fileArr.value)
+    eventBus.emit('upload:changeArr',imgArr.value)
+    emit('handleUpload',{imgArr:imgArr.value,fileList:fileArr.value})
+}
+
+function uploadReset(){
+    fileList.value = []
+    fileArr.value = []
+    imgArr.value = []
+}
+
+onMounted(()=>{
+    eventBus.off('upload:reset',uploadReset)
+    eventBus.on('upload:reset',uploadReset)
+})
+</script>
+
+<style lang="scss" scoped>
+
+.upload-wrap {
+    .tips {
+        display: flex;
+        align-items: center;
+        color: #2199F8;
+        background: #E9F5FF;
+        border-radius: 4px;
+        padding: 2px 10px;
+        box-sizing: border-box;
+        margin-bottom: 10px;
+        .icon {
+            margin-right: 2px;
+        }
+    }
+    .tips-text{
+        background: linear-gradient(240deg,#FFFFFF 22%,rgba(33, 153, 248,0.2) 100%);
+        border-radius: 20px 0 0 20px;
+    }
+    ::v-deep {
+        .van-uploader__input-wrapper{
+            text-align: center;
+        }
+        .el-select__wrapper:hover {
+            box-shadow: 0 0 0 1px #dcdfe6 inset;
+        }
+        .avatar-uploader .el-upload {
+            width: 100%;
+            border: 1px dashed #dddddd;
+            border-radius: 6px;
+            cursor: pointer;
+            position: relative;
+            overflow: hidden;
+        }
+
+        .el-icon.avatar-uploader-icon {
+            font-size: 28px;
+            color: #8c939d;
+            width: 100%;
+            height: 128px;
+            text-align: center;
+            background: #f6f6f6;
+        }
+    }
+    .uploader{
+        .plus{
+            width: 80px;
+            height: 80px;
+        }
+    }
+}
+</style>

+ 6 - 0
src/router/globalRoutes.js

@@ -194,6 +194,12 @@ export default [
         name: "ReviewWork",
         component: () => import("@/views/old_mini/modify_work/reviewWork.vue"),
     },
+    // 分享好友页面
+    {
+        path: "/share_page",
+        name: "SharePage",
+        component: () => import("@/views/old_mini/modify_work/sharePage.vue"),
+    },
     // 报价详情
     {
         path: "/price_detail",

+ 22 - 23
src/utils/date_util.js

@@ -1,32 +1,32 @@
-export function dateFormat(date,fmt) {
+export function dateFormat(date, fmt) {
     let ret;
     const opt = {
-        "Y+": date.getFullYear().toString(),        // 年
-        "m+": (date.getMonth() + 1).toString(),     // 月
-        "d+": date.getDate().toString(),            // 日
-        "H+": date.getHours().toString(),           // 时
-        "M+": date.getMinutes().toString(),         // 分
-        "S+": date.getSeconds().toString()          // 秒
+        "Y+": date.getFullYear().toString(), // 年
+        "m+": (date.getMonth() + 1).toString(), // 月
+        "d+": date.getDate().toString(), // 日
+        "H+": date.getHours().toString(), // 时
+        "M+": date.getMinutes().toString(), // 分
+        "S+": date.getSeconds().toString(), // 秒
         // 有其他格式化字符需求可以继续添加,必须转化成字符串
     };
     for (let k in opt) {
         ret = new RegExp("(" + k + ")").exec(fmt);
         if (ret) {
-            fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
-        };
-    };
+            fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"));
+        }
+    }
     return fmt;
 }
 
 export function formatDateToYYYYMMDD(dateString) {
     // 创建一个 Date 对象
     const date = new Date(dateString);
- 
+
     // 获取年、月、日部分
     const year = date.getFullYear();
-    const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,所以要加1,并且用padStart补0
-    const day = String(date.getDate()).padStart(2, '0'); // 用padStart补0
- 
+    const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从0开始,所以要加1,并且用padStart补0
+    const day = String(date.getDate()).padStart(2, "0"); // 用padStart补0
+
     // 拼接成 YYYY.MM.DD 格式
     return `${year}.${month}.${day}`;
 }
@@ -34,18 +34,17 @@ export function formatDateToYYYYMMDD(dateString) {
 export function formatDate(dateStr) {
     // 创建一个 Date 对象
     const date = new Date(dateStr);
- 
+
     // 提取年、月、日、小时、分钟和秒
     const year = date.getFullYear();
-    const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从0开始,需要加1
-    const day = String(date.getDate()).padStart(2, '0');
-    const hours = String(date.getHours()).padStart(2, '0');
-    const minutes = String(date.getMinutes()).padStart(2, '0');
-    const seconds = String(date.getSeconds()).padStart(2, '0');
- 
+    const month = String(date.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要加1
+    const day = String(date.getDate()).padStart(2, "0");
+    const hours = String(date.getHours()).padStart(2, "0");
+    const minutes = String(date.getMinutes()).padStart(2, "0");
+    const seconds = String(date.getSeconds()).padStart(2, "0");
+
     // 格式化日期字符串
     const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- 
+
     return formattedDate;
 }
-

+ 7 - 1
src/views/old_mini/modify_work/completedWork.vue

@@ -139,7 +139,7 @@
                 </div>
             </div>
             <div class="fixed-btn-wrap">
-                <div class="fixed-btn orange" v-if="curRole == 0">发起需求</div>
+                <div class="fixed-btn orange" v-if="curRole == 0" @click="handleDemand">发起需求</div>
                 <div class="fixed-btn" v-if="curRole == 0" @click="handleOk">{{ status === 0 ? "我已完成" : "立即复核" }}</div>
             </div>
         </div>
@@ -154,7 +154,9 @@ import { onMounted, ref } from "vue";
 import NewFarmMap from "./newFarmMap";
 import { useStore } from "vuex";
 import offerPopup from "@/components/popup/offerPopup.vue";
+import { useRouter } from "vue-router";
 
+const router = useRouter();
 const store = useStore();
 
 // 角色
@@ -175,6 +177,10 @@ const handleOk = () =>{
     }
 }
 
+const handleDemand = () => {
+    router.push("/share_page");
+}
+
 const prescriptioData = ref({
     prescriptionList: [
         {

+ 69 - 35
src/views/old_mini/modify_work/reviewWork.vue

@@ -2,7 +2,7 @@
     <div class="work-wrap">
         <custom-header name="农事成效"></custom-header>
         <div class="work-content recheck-title">
-            <div class="tabs-content-item" v-if="workItem?.farmName">
+            <div class="tabs-content-item">
                 <div class="common-card-title">
                     <img class="icon" src="@/assets/img/home/label-icon.png" alt="" />
                     <span>农事信息</span>
@@ -86,7 +86,6 @@
                     <div class="subject-box cost-box">
                         <div class="subject-item cost-item cost-l">
                             <div class="cost-title">农资投入(元)</div>
-                            <!-- 1125元(25元/亩),农资成本:35元(1.4元/亩) -->
                             <div class="cost-text subject-tag PangMenZhengDao-FONT">
                                 {{
                                     workItem?.cost?.pesticideFertilizerCost
@@ -120,16 +119,16 @@
                     </div>
                     <div class="recheck-box" v-if="workItem?.farmId">
                         <div class="recheck-ablum">
-                            <div class="img-list over-img-box">
-                                <!-- <album-carousel7d
+                            <!-- <div class="img-list over-img-box">
+                                <album-carousel7d
                                     :key="1"
                                     :farmId="workItem.farmId"
                                     :farmWork="{
                                         beforeExecuteDate: workItem.beforeExecuteDate,
                                         executeDate: workItem.executeDate,
                                     }"
-                                ></album-carousel7d> -->
-                            </div>
+                                ></album-carousel7d>
+                            </div> -->
                             <div class="img-list" v-if="!workItem.reviewImage.length && workItem.activeStatus === 0">
                                 <div
                                     class="recheck-text-wrap no-events"
@@ -140,7 +139,7 @@
                                     }"
                                 >
                                     <div class="date" v-show="workItem.reviewDate">{{ workItem.reviewDate }}</div>
-                                    <!-- <upload
+                                    <upload
                                         exampleImg
                                         @handleUpload="handleUpload"
                                         class="upload-wrap"
@@ -183,7 +182,7 @@
                                                 (剩余{{ diffInDays(workItem.reviewDate) }}天)
                                             </div>
                                         </template>
-                                    </upload> -->
+                                    </upload>
                                     <div
                                         class="submit"
                                         v-show="imageArr.length && !diffInDays(workItem.reviewDate) > 0"
@@ -194,12 +193,12 @@
                                 </div>
                             </div>
                             <div class="img-list over-img-box" v-if="workItem.reviewImage.length">
-                                <!-- <album-carousel
+                                <album-carousel
                                     :key="2"
                                     :farmId="workItem.farmId"
                                     :lock="false"
                                     :images="handleConversion(workItem.reviewImage)"
-                                ></album-carousel> -->
+                                ></album-carousel>
                             </div>
                             <div class="img-list" v-if="!workItem.reviewImage2.length && workItem.activeStatus === 0">
                                 <div
@@ -211,7 +210,7 @@
                                     }"
                                 >
                                     <div class="date" v-show="workItem.reviewDate2">{{ workItem.reviewDate2 }}</div>
-                                    <!-- <upload
+                                    <upload
                                         exampleImg
                                         @handleUpload="handleUpload2"
                                         class="upload-wrap"
@@ -254,7 +253,7 @@
                                                 (剩余{{ diffInDays(workItem.reviewDate2) }}天)
                                             </div>
                                         </template>
-                                    </upload> -->
+                                    </upload>
                                     <div
                                         class="submit"
                                         v-show="imageArr2.length && !diffInDays(workItem.reviewDate2) > 0"
@@ -265,14 +264,13 @@
                                 </div>
                             </div>
                             <div class="img-list over-img-box" v-if="workItem.reviewImage2.length">
-                                <!-- <album-carousel
+                                <album-carousel
                                     :key="2"
                                     :farmId="workItem.farmId"
                                     :lock="false"
                                     :images="handleConversion(workItem.reviewImage2)"
-                                ></album-carousel> -->
+                                ></album-carousel>
                             </div>
-                            <!-- <img class="recheck-img" width="100%" src="@/assets/img/gallery/recheck.jpg" alt=""> -->
                         </div>
                     </div>
                 </div>
@@ -309,7 +307,7 @@
             </div>
         </div>
         <!-- 上传图片弹窗 -->
-        <!-- <upload-popup :executionData="workItem"></upload-popup> -->
+        <upload-popup :executionData="workItem"></upload-popup>
     </div>
 </template>
 
@@ -318,16 +316,55 @@ import { Tab, Tabs } from "vant";
 import customHeader from "@/components/customHeader.vue";
 import { onMounted, ref, onDeactivated,onActivated } from "vue";
 import { useRoute, useRouter } from "vue-router";
-// import upload from "@/components/common/upload";
-// import AlbumCarousel7d from "@/views/old_mini/feature_index/pages/album_compoents/albumCarousel7d";
-// import AlbumCarousel from "@/views/old_mini/feature_index/pages/album_compoents/albumCarousel";
+import upload from "@/components/upload";
+import AlbumCarousel7d from "@/components/album_compoents/albumCarousel7d";
+import AlbumCarousel from "@/components/album_compoents/albumCarousel";
 import eventBus from "@/api/eventBus";
 import { ElMessage } from "element-plus";
-// import uploadPopup from "@/components/common/uploadPopup.vue";
+import uploadPopup from "@/components/popup/uploadPopup.vue";
 import { deepClone } from "@/common/commonFun";
 const route = useRoute();
 
-const workItem = ref({});
+const workItem = ref({
+    farmName: "测试农场",
+    farmWorkName: "测试农事",
+    executeDate:'2025-09-01',
+    farmWorkTypeName: "测试农事类型",
+    condition: "测试农事类型",
+    farmWorkType: "叶面施",
+    prescriptionList: [
+        {
+            defaultName: "营养",
+            pesticideFertilizerList: [
+                {
+                    defaultName: "尿素",
+                },
+            ],
+        },
+    ],
+    executeMain: "叶面施",
+    serviceMain: "广州泽秾丰农资有限公司",
+    expertName: "测试专家",
+    expertIcon: "https://birdseye-img.sysuimars.com/birdseye-look-mini/Group%201321316260.png",
+    cost: {
+        pesticideFertilizerCost: 1125,
+        farmWorkServiceCost: 35,
+    },
+    reviewImage: [],
+    reviewImage2: [],
+    reviewDate: "2025-09-01",
+    reviewDate2: "2025-09-01",
+    activeStatus: 0,
+    executeEvidence: [],
+    farmId: 1,
+    libId: 1,
+    uniqueId: 1,
+    isPlan: false,
+    curRole: "",
+    farmId: 766,
+    libId: 1,
+    uniqueId: null,
+});
 const curRole = ref("");
 const farmId = ref(null);
 const libId = ref(null);
@@ -356,7 +393,7 @@ onActivated(() => {
     uniqueId.value = route.query.id;
     isPlan.value = route.query.isPlan ? true : false;
 
-    getDetail(true);
+    // getDetail(true);
 
     eventBus.on("confirm:callback", confirmCallback);
 });
@@ -447,13 +484,6 @@ function toUpload() {
 
 <style lang="scss" scoped>
 .work-wrap {
-    ::v-deep {
-        .header {
-            position: fixed;
-            top: 0;
-            padding-bottom: 1px;
-        }
-    }
     .center-wrap {
         ::v-deep {
             .van-uploader__wrapper {
@@ -462,16 +492,24 @@ function toUpload() {
         }
     }
     .work-content {
-        padding-top: 40px;
+        padding-top: 1px;
         background: #f5f5f5;
         padding-bottom: 12px;
         font-size: 14px;
-        min-height: 100vh;
+        height: calc(100vh - 40px);
         box-sizing: border-box;
+        overflow: auto;
         &.recheck-title {
             padding-bottom: 26px;
             .common-card-title {
                 font-size: 16px;
+                display: flex;
+                align-items: center;
+                .icon {
+                    width: 14px;
+                    height: 8px;
+                    padding-right: 6px;
+                }
             }
         }
         .up-btn-group {
@@ -820,10 +858,6 @@ function toUpload() {
                     height: 40px;
                 }
             }
-            .recheck-img {
-                width: 100%;
-                margin-top: 12px;
-            }
             .sub-title {
                 display: flex;
                 align-items: center;

+ 178 - 0
src/views/old_mini/modify_work/sharePage.vue

@@ -0,0 +1,178 @@
+<template>
+    <div class="share-page">
+        <custom-header name="选择发送到" />
+        <div class="share-content">
+            <div class="search-wrap">
+                <div class="search-input">
+                    <el-input v-model="search" placeholder="搜索昵称">
+                        <template #prefix>
+                            <el-icon><Search /></el-icon>
+                        </template>
+                    </el-input>
+                </div>
+            </div>
+            <div class="recent-chat-wrap">
+                <div class="recent-title">最近聊天</div>
+                <div class="recent-list">
+                    <div class="recent-item" v-for="(item, index) in recentList" :key="index">
+                        <img class="recent-img" src="" alt="" />
+                        <div class="recent-name">{{ item.name }}</div>
+                        <checkbox class="recent-checkbox" v-model="item.checked"></checkbox>
+                    </div>
+                </div>
+            </div>
+            <div class="chat-list">
+                <div class="chat-item" v-for="(item, index) in chatList" :key="index">
+                    <checkbox @change="changeCheck(item)" v-model="item.checked"></checkbox>
+                    <div class="chat-info" :class="{'checked': item.checked}">
+                        <img class="chat-img" src="" alt="" />
+                        <span class="chat-name">{{ item.name }}</span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+    <div class="bottom-btn" v-show="checkedList.length > 0">
+        <div class="btn">取消</div>
+        <div class="btn primary">完成</div>
+    </div>
+</template>
+
+<script setup>
+import customHeader from "@/components/customHeader.vue";
+import { Checkbox } from "vant";
+import { ref } from "vue";
+
+const search = ref("");
+
+const chatList = ref([
+    {
+        name: "张三",
+        checked: false,
+    },
+    {
+        name: "张三",
+        checked: false,
+    },
+]);
+
+const recentList = ref([
+    {
+        name: "农资11",
+        checked: false,
+    },
+    {
+        name: "农资12",
+        checked: false,
+    },
+]);
+
+const checkedList = ref([]);
+const changeCheck = (item) => {
+    console.log(item,chatList.value);
+    checkedList.value = chatList.value.filter((item) => item.checked);
+};
+</script>
+
+<style lang="scss" scoped>
+.share-page {
+    width: 100%;
+    height: 100vh;
+    background-color: #f5f7fb;
+    .share-content {
+        .search-wrap {
+            padding: 20px 20px 0 20px;
+            .search-input {
+                width: 100%;
+                ::v-deep {
+                    .el-input__wrapper {
+                        box-shadow: none;
+                        border-radius: 25px;
+                    }
+                }
+            }
+        }
+        .recent-chat-wrap {
+            padding: 0 20px 20px 20px;
+            .recent-title {
+                font-weight: 500;
+                margin: 12px 0;
+            }
+            .recent-list {
+                display: flex;
+                flex-wrap: wrap;
+                gap: 10px;
+                .recent-item {
+                    position: relative;
+                    .recent-img {
+                        width: 54px;
+                        height: 54px;
+                        border-radius: 2px;
+                        background: red;
+                    }
+                    .recent-name {
+                        text-align: center;
+                        margin-top: 5px;
+                    }
+                    .recent-checkbox{
+                        position: absolute;
+                        right: 3px;
+                        top: 3px;
+                    }
+                }
+            }
+        }
+        .chat-list {
+            background-color: #fff;
+            padding-top: 6px;
+            .chat-item {
+                padding: 10px 20px;
+                display: flex;
+                align-items: center;
+                gap: 12px;
+                .chat-info {
+                    display: flex;
+                    align-items: center;
+                    gap: 10px;
+                    .chat-img {
+                        width: 46px;
+                        height: 46px;
+                        border-radius: 2px;
+                    }
+                    &.checked {
+                        .chat-name {
+                            color: #2199F8;
+                            font-weight: 500;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+.bottom-btn {
+    position: fixed;
+    bottom: 10px;
+    left: 20px;    
+    width: calc(100% - 40px);
+    display: flex;
+
+    .btn {
+        flex: 1;
+        text-align: center;
+        color: #2199F8;
+        padding: 10px;
+        border-radius: 25px;
+        border: 1px solid #2199F8;
+        font-size: 16px;
+        &.primary{
+            background: #2199F8;
+            color: #fff;
+        }
+    }
+    .btn + .btn {
+        margin-left: 15px;
+    }
+}
+</style>

+ 92 - 17
src/views/old_mini/report_detail/index.vue

@@ -22,7 +22,7 @@
                     </div>
                 </div>
             </div>
-            <tabs v-model:active="active" class="tabs" scrollspy>
+            <tabs v-model:active="active" class="tabs" scrollspy sticky offset-top="40" background="#f5f7fb">
                 <tab title="果园总览" class="tab-item">
                     <div class="item-title">果园总览</div>
                     <div class="item-content">
@@ -34,33 +34,69 @@
                             目前XX%的树体暂未萌动新梢,需要进行剪枝农事,提高树体光合效率与通风效率。
                         </div>
                     </div>
+                    <div class="item-content">
+                        <div class="photo-list">
+                            <div class="img-item" v-for="(item, index) in 6" :key="index">
+                                <img
+                                    class="photo-item"
+                                    src="https://birdseye-img-ali-cdn.sysuimars.com/16926861-1e20-4cbd-8bf2-90208db5a2d0/806080da-1a30-4b5b-b64b-b22e722c6cb6/DJI_202509010800_001_806080da-1a30-4b5b-b64b-b22e722c6cb6/DJI_20250901080536_0045_V_code-ws0fsmge97gh.jpeg"
+                                    alt=""
+                                />
+                            </div>
+                        </div>
+                        <div class="list-text" @click="handleSeeMore">点击查看更多</div>
+                    </div>
                 </tab>
                 <tab title="整体园相" class="tab-item">
                     <div class="item-title">整体园相</div>
                     <div class="item-content">
-                        <div class="item-text">果园面积共XX亩,共有XX棵生产树。</div>
-                        <div class="item-text">
-                            本次飞巡拍摄了XX张照片,包括了XX棵树,目前为施用根部有机肥阶段,根据树体冠幅大小,果园预计施用有机肥XXkg。
+                        <div class="item-name">透光率</div>
+                        <div class="item-value">
+                            透光率体现树体自身郁闭程度,当前XX%的树体透光性较差,可能造成整体减产XX%,需立即执行剪枝农事;XX%的树体透光正常,建议继续保持现有管理措施并及时巡园
                         </div>
-                        <div class="item-text">
-                            目前XX%的树体暂未萌动新梢,需要进行剪枝农事,提高树体光合效率与通风效率。
+                    </div>
+                    <div class="item-content">
+                        <div class="item-name">通风率</div>
+                        <div class="item-value">
+                            透光率体现树体自身郁闭程度,当前XX%的树体透光性较差,可能造成整体减产XX%,需立即执行剪枝农事;XX%的树体透光正常,建议继续保持现有管理措施并及时巡园。
+                        </div>
+                        <div class="map-wrap">
+                            <img class="map-img" src="@/assets/img/home/map.png" alt="" />
+                            <div class="map-text">剪枝农事地图</div>
                         </div>
                     </div>
                 </tab>
                 <tab title="营养管理" class="tab-item">
                     <div class="item-title">营养管理</div>
                     <div class="item-content">
-                        <div class="item-text">果园面积共XX亩,共有XX棵生产树。</div>
-                        <div class="item-text">
-                            本次飞巡拍摄了XX张照片,包括了XX棵树,目前为施用根部有机肥阶段,根据树体冠幅大小,果园预计施用有机肥XXkg
+                        <div class="item-name">根肥</div>
+                        <div class="item-value">
+                            透光率体现树体自身郁闭程度,当前XX%的树体透光性较差,可能造成整体减产XX%,需立即执行剪枝农事;XX%的树体透光正常,建议继续保持现有管理措施并及时巡园
                         </div>
-                        <div class="item-text">
-                            目前XX%的树体暂未萌动新梢,需要进行剪枝农事,提高树体光合效率与通风效率。
+                        <div class="map-wrap">
+                            <img class="map-img" src="@/assets/img/home/map.png" alt="" />
+                            <div class="map-text">根肥农事地图</div>
+                        </div>
+                    </div>
+                </tab>
+                <tab title="病虫管理" class="tab-item">
+                    <div class="item-title">病虫管理</div>
+                    <div class="item-content">
+                        <div class="item-value">
+                            透光率体现树体自身郁闭程度,当前XX%的树体透光性较差,可能造成整体减产XX%,需立即执行剪枝农事;XX%的树体透光正常,建议继续保持现有管理措施并及时巡园。
+                        </div>
+                        <div class="map-wrap">
+                            <img class="map-img" src="@/assets/img/home/map.png" alt="" />
+                            <div class="map-text">防虫农事地图</div>
                         </div>
                     </div>
                 </tab>
-                <tab title="病虫管理">内容 3</tab>
-                <tab title="农事记录">内容 3</tab>
+                <tab title="农事记录" class="tab-item">
+                    <div class="item-title">农事记录</div>
+                    <div class="item-content">
+                        农事记录
+                    </div>
+                </tab>
             </tabs>
         </div>
         <!-- 底部 -->
@@ -86,8 +122,10 @@
 import customHeader from "@/components/customHeader.vue";
 import { Icon, Tab, Tabs } from "vant";
 import { ref } from "vue";
-import reportPopup from "@/components/reportPopup.vue"
+import reportPopup from "@/components/reportPopup.vue";
+import { useRouter } from "vue-router";
 
+const router = useRouter();
 const value = ref("");
 
 const options = [
@@ -115,10 +153,14 @@ const options = [
 
 const active = ref(0);
 
-const reportPopupRef = ref(null)
+const reportPopupRef = ref(null);
 function sharePopup() {
-    reportPopupRef.value.handleShow()
+    reportPopupRef.value.handleShow();
 }
+
+const handleSeeMore = () => {
+    router.push("/farm_photo");
+};
 </script>
 
 <style lang="scss" scoped>
@@ -187,8 +229,20 @@ function sharePopup() {
         }
         .tabs {
             margin-top: 10px;
+            ::v-deep {
+                .van-tabs__nav {
+                    height: 70%;
+                    .van-tab--active{
+                        color: #2199F8;
+                        background: rgba(33, 153, 248, 0.2);
+                        border-radius: 25px;
+                    }
+                }
+                .van-tabs__line{
+                    display: none;
+                }
+            }
             .tab-item {
-                margin-top: 10px;
                 border-radius: 14px;
                 padding: 12px;
                 background: #fff;
@@ -217,6 +271,27 @@ function sharePopup() {
                     background: rgba(238, 238, 238, 0.3);
                     padding: 10px;
                     line-height: 21px;
+                    .photo-list {
+                        display: flex;
+                        align-items: center;
+                        width: 100%;
+                        overflow: auto;
+                        padding-bottom: 10px;
+                        .photo-item {
+                            width: 92px;
+                            height: 92px;
+                            border-radius: 8px;
+                            object-fit: cover;
+                        }
+                        .img-item + .img-item {
+                            margin-left: 12px;
+                        }
+                    }
+                    .list-text {
+                        text-align: center;
+                        color: rgba(0, 0, 0, 0.5);
+                        padding-top: 2px;
+                    }
                     .item-name {
                         font-weight: 500;
                     }