Parcourir la source

feat:对接果园详情和病虫害接口

wangsisi il y a 1 semaine
Parent
commit
b517f20dda

+ 8 - 0
src/api/modules/farm.js

@@ -13,6 +13,10 @@ module.exports = {
         url: config.base_dev_url + "v2/farm/createFarm",
         type: "post",
     },
+    updateFarm: {
+        url: config.base_dev_url + "v2/farm/updateFarm",
+        type: "post",
+    },
     userFarmSelectOption: {
         url: config.base_dev_url + "v2/farm/userFarmSelectOption",
         type: "get",
@@ -21,4 +25,8 @@ module.exports = {
         url: config.base_dev_url + "mini_fruits_type_item/list/{parentId}",
         type: "get",
     },
+    getFarmDetail: {
+        url: config.base_dev_url + "v2/farm/getFarmDetail",
+        type: "get",
+    },
 }

+ 4 - 0
src/api/modules/home.js

@@ -22,4 +22,8 @@ module.exports = {
         url: config.base_dev_url + "container_farm_work_scheme/getPhenologyFarmWorkList",
         type: "get",
     },
+    fetchDiseaseDetail: {
+        url: config.base_dev_url + "pest_disease_dictionary/listByIds",
+        type: "post",
+    },
 }

BIN
src/assets/img/monitor/aaa.png


+ 79 - 9
src/components/chatWindow.vue

@@ -85,8 +85,15 @@
 
                         <!-- 对话样式消息 -->
                         <div v-if="msg.messageType === 'dialog'" class="dialog-message">
-                            <div class="dialog-title">{{ msg.content.title }}</div>
-                            <img src="@/assets/img/monitor/image.png" alt="" class="monitor-image">
+                            <template v-if="msg.content.type === 'farm_report'">
+                                <div class="report-title" >{{ msg.content.title }}</div>
+                                <div class="dialog-title">{{ msg.content.content }}</div>
+                                <img src="@/assets/img/monitor/aaa.png" alt="" class="monitor-image">
+                            </template>
+                            <template v-else>
+                                <div class="dialog-title">{{ msg.content.title }}</div>
+                                <img src="@/assets/img/monitor/image.png" alt="" class="monitor-image">
+                            </template>
                         </div>
 
                         <!-- <div class="time">{{ msg.time }}</div> -->
@@ -493,8 +500,8 @@ const functionButtons = ref([
         text: '农场报告',
         handler: () => {
             console.log('点击农场报告,农场ID:', farmVal.value);
-            // 跳转到农场报告页面
-            // router.push(`/farm_report?farmId=${farmVal.value}`);
+            // 发送农场报告对话框消息
+            sendFarmReportDialog();
         }
     },
     {
@@ -581,6 +588,34 @@ const sendDialogMessage = (dialogData) => {
     };
     sendMessage(message);
 }
+
+// 发送农场报告对话框
+const sendFarmReportDialog = () => {
+    const currentFarmName = options.value.find(opt => opt.id === farmVal.value)?.name || '当前农场';
+    
+    const farmReportData = {
+        type: 'farm_report',
+        title: currentFarmName,
+        content: '这是我的果园情况,请查看~',
+        reportDetails: {
+            farmId: farmVal.value,
+            farmName: currentFarmName,
+            reportDate: new Date().toLocaleDateString('zh-CN'),
+            reportType: '综合报告',
+            status: '正常'
+        }
+    };
+    
+    const message = {
+        sender: "sent",
+        messageType: "dialog",
+        senderIcon: senderIcon.value,
+        title: currentFarmName,
+        content: farmReportData,
+        time: getCurrentTime(),
+    };
+    sendMessage(message);
+}
 </script>
 
 <style scoped lang="scss">
@@ -848,17 +883,52 @@ const sendDialogMessage = (dialogData) => {
     background: #fff !important;
     padding: 10px;
     border-radius: 10px;
+    .report-title{
+        font-size: 16px;
+        font-weight: 600;
+        color: #000;
+        margin-bottom: 5px;
+    }
     
     .dialog-title {
         font-size: 12px;
         color: rgba(0, 0, 0, 0.6);
         margin-bottom: 10px;
     }
-    .monitor-image{
-        width: 222px;
-        height: 180px;
-    }
-    
+     .monitor-image{
+         width: 222px;
+         height: 180px;
+        object-fit: cover;
+     }
+     
+     .farm-report-content, .farm-work-content {
+         .report-details, .work-details {
+             background: #f8f9fa;
+             border-radius: 8px;
+             padding: 12px;
+             margin-top: 10px;
+             
+             .detail-item {
+                 display: flex;
+                 margin-bottom: 6px;
+                 font-size: 13px;
+                 
+                 &:last-child {
+                     margin-bottom: 0;
+                 }
+                 
+                 .detail-label {
+                     color: #666;
+                     min-width: 80px;
+                 }
+                 
+                 .detail-value {
+                     color: #333;
+                     flex: 1;
+                 }
+             }
+         }
+     }
 }
 
 /* 我方消息中的对话样式 */

+ 75 - 12
src/components/weatherInfo.vue

@@ -9,8 +9,8 @@
                 <div class="address-select flex-center" v-else>
                     <el-dropdown class="select-garden" trigger="click" popper-class="select-garden-popper">
                         <div class="el-dropdown-link flex-center">
-                            <span :class="farmName === '从化荔博园' ? 'ellipsis-l1' : ''">{{ farmName }}</span>
-                            <div class="default-text" v-show="farmName === '从化荔博园'">默认</div>
+                            <span :class="isDefaultFarm ? 'ellipsis-l1' : ''">{{ farmName }}</span>
+                            <div class="default-text" v-show="isDefaultFarm">默认</div>
                             <el-icon class="el-icon--right"><arrow-down /></el-icon>
                         </div>
                         <template #dropdown>
@@ -21,7 +21,8 @@
                                     :key="item.id"
                                     :class="{ 'selected-active-garden': farmId === item.id }"
                                 >
-                                    {{ item.name }}
+                                    <span>{{ item.name }}</span>
+                                    <span v-if="item.defaultOption" class="dropdown-default-text">默认</span>
                                 </el-dropdown-item>
                             </el-dropdown-menu>
                         </template>
@@ -69,6 +70,12 @@ const router = useRouter();
 const handleCommand = (id, name) => {
     farmName.value = name;
     farmId.value = id;
+    // 更新默认农场标识
+    const selectedFarm = farmList.value.find(farm => farm.id === id);
+    isDefaultFarm.value = selectedFarm ? selectedFarm.defaultOption || false : false;
+    // 保存用户选择的农场到 localStorage
+    localStorage.setItem('selectedFarmId', id);
+    localStorage.setItem('selectedFarmName', name);
     emit('changeGarden',id);
 };
 
@@ -85,18 +92,59 @@ const toggleExpand = () => {
 const farmId = ref(null);
 const farmName = ref("");
 const farmList = ref([]);
+const isDefaultFarm = ref(false); // 添加默认农场标识
+
 // 获取农场列表
 function getFarmList() {
     VE_API.farm.userFarmSelectOption().then(({data}) => {
         farmList.value = data || []
         if (data && data.length > 0) {
-            farmName.value = data[0].name
-            farmId.value = data[0].id
+            // 优先使用 localStorage 中保存的农场选择
+            const savedFarmId = localStorage.getItem('selectedFarmId');
+            const savedFarmName = localStorage.getItem('selectedFarmName');
+            
+            if (savedFarmId && savedFarmName) {
+                // 检查保存的农场是否还在当前列表中
+                const savedFarm = data.find(farm => farm.id == savedFarmId);
+                if (savedFarm) {
+                    farmName.value = savedFarmName;
+                    farmId.value = Number(savedFarmId);
+                    isDefaultFarm.value = savedFarm.defaultOption || false;
+                } else {
+                    // 如果保存的农场不在列表中,按优先级选择
+                    selectDefaultFarm(data);
+                }
+            } else {
+                // 如果没有保存的选择,按优先级选择
+                selectDefaultFarm(data);
+            }
             emit('changeGarden',farmId.value);
         }
     })
 }
 
+// 选择默认农场的逻辑
+function selectDefaultFarm(data) {
+    // 首先查找 defaultOption 为 true 的农场
+    const defaultFarm = data.find(farm => farm.defaultOption === true);
+    
+    if (defaultFarm) {
+        // 如果有默认农场,选择它
+        farmName.value = defaultFarm.name;
+        farmId.value = defaultFarm.id;
+        isDefaultFarm.value = true;
+    } else {
+        // 如果没有默认农场,选择第一个
+        farmName.value = data[0].name;
+        farmId.value = data[0].id;
+        isDefaultFarm.value = data[0].defaultOption || false;
+    }
+    
+    // 保存到 localStorage
+    localStorage.setItem('selectedFarmId', farmId.value);
+    localStorage.setItem('selectedFarmName', farmName.value);
+}
+
 // 暴露getFarmList方法给父组件调用
 defineExpose({
     getFarmList
@@ -143,7 +191,7 @@ const handleAddGarden = () => {
             .address-select {
                 .select-garden {
                     width: fit-content;
-                    max-width: 140px;
+                    max-width: 170px;
                     margin-right: 8px;
                     .el-dropdown-link {
                         font-size: 15px;
@@ -158,8 +206,10 @@ const handleAddGarden = () => {
                         .default-text {
                             font-size: 12px;
                             color: #fff;
-                            margin-left: 5px;
-                            padding: 2px 7px;
+                            padding: 2px 4px;
+                            width: 44px;
+                            text-align: center;
+                            box-sizing: border-box;
                             font-weight: 400;
                             background-color: #2199f8;
                             border-radius: 25px;
@@ -250,13 +300,26 @@ const handleAddGarden = () => {
 .select-garden-popper {
     &.el-dropdown__popper {
         .el-dropdown__list {
-            width: 140px;
+            max-width: 160px;
+        }
+        .el-dropdown-menu__item{
+            background-color: transparent !important;
+            color: #606266 !important;
+        }
+        .selected-active-garden {
+            color: #2199f8 !important;
+            background-color: rgba(33, 153, 248, 0.1) !important;
+            font-weight: 500;
         }
     }
-    .selected-active-garden {
+    .dropdown-default-text {
+        font-size: 11px;
         color: #2199f8;
-        background-color: rgba(33, 153, 248, 0.1) !important;
-        font-weight: 500;
+        margin-left: 8px;
+        padding: 0 5px;
+        background-color: rgba(33, 153, 248, 0.1);
+        border-radius: 10px;
+        font-weight: 400;
     }
 }
 </style>

+ 72 - 0
src/mqtt/MqttClient.js

@@ -0,0 +1,72 @@
+import mqtt from 'mqtt';
+
+class MqttClient {
+  constructor(topics, onMessageCallback) {
+    // 固定参数
+    this.MQTT_MQTTIP = 'wss://ws.emqx.sysuimars.cn/mqtt';
+    this.MQTT_USERNAME = 'admin';
+    this.MQTT_PASSWORD = 'admin';
+    this.topics = topics; // 订阅的主题数组
+    this.onMessageCallback = onMessageCallback; // 接收数据的回调函数
+    this.client = null;
+  }
+
+  connect() {
+    const options = {
+      connectTimeout: 40000,
+      clientId: `clientid-${this.generateClientId()}`, // 生成唯一的客户端 ID
+      username: this.MQTT_USERNAME,
+      password: this.MQTT_PASSWORD,
+      clean: false
+    };
+
+    // 连接 MQTT
+    this.client = mqtt.connect(this.MQTT_MQTTIP, options);
+
+    // 监听连接成功事件
+    this.client.on('connect', () => {
+      console.log('连接成功');
+      this.subscribeToTopics(); // 订阅主题
+    });
+
+    // 监听消息事件
+    this.client.on('message', (topic, message) => {
+      this.onMessageCallback(topic, message.toString()); // 调用回调函数处理消息
+    });
+
+    // 监听重连事件
+    this.client.on('reconnect', (error) => {
+      console.log('正在重连...', error);
+    });
+
+    // 监听错误事件
+    this.client.on('error', (error) => {
+      console.log('连接失败...', error);
+    });
+  }
+
+  // 订阅主题
+  subscribeToTopics() {
+    if (this.topics && this.topics.length > 0) {
+      this.topics.forEach((topic) => {
+        this.client.subscribe(topic, (err) => {
+          if (!err) {
+            console.log(`订阅成功: ${topic}`);
+          } else {
+            console.log(`订阅失败: ${topic}`, err);
+          }
+        });
+      });
+    } else {
+      console.log('未提供订阅主题');
+    }
+  }
+
+  // 生成唯一的客户端 ID
+  generateClientId() {
+    const S4 = () => (((1 + Math.random()) * 0x10000) | 0).toString(32).substring(1);
+    return `${S4()}${S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`;
+  }
+}
+
+export default MqttClient;

+ 6 - 0
src/router/globalRoutes.js

@@ -266,4 +266,10 @@ export default [
         name: "FarmCard",
         component: () => import("@/views/old_mini/plan/farmCard.vue"),
     },
+    // 相册识别
+    {
+        path: "/album_recognize",
+        name: "AlbumRecognize",
+        component: () => import("@/views/old_mini/album_recognize/index.vue"),
+    },
 ];

+ 9 - 3
src/store/modules/home/index.js

@@ -17,9 +17,10 @@ export default {
             tabBarHeight: 50,
             fruitsData: [],
             updateGardenList: false,
-            gardenId: '',
+            gardenId: localStorage.getItem('selectedFarmId') || '',
             temporarilyForm: {}, //添加、编辑农场存放form临时数据
-            polygonData: null
+            polygonData: null,
+            editFarmData: null // 编辑农场时的数据
         }
     },
     getters: {
@@ -41,7 +42,8 @@ export default {
             state.miniUserLocationPoint = locationPoint
         },
         SET_GARDEN_ID(state, id) {
-            state.gardenId = id
+            state.gardenId = id;
+            localStorage.setItem('selectedFarmId', id);
         },
         SET_UPDATE_GARDEN_LIST(state, val) {
             state.updateGardenList = val;
@@ -80,6 +82,10 @@ export default {
         SET_FARM_POLYGON(state, polygonData) {
             state.polygonData = polygonData
         },
+        // 设置编辑农场数据
+        SET_EDIT_FARM_DATA(state, farmData) {
+            state.editFarmData = farmData
+        },
     },
     actions: {
         updateDropdownItem({ commit }, value) {

+ 375 - 0
src/views/old_mini/album_recognize/index.vue

@@ -0,0 +1,375 @@
+<template>
+    <div class="diseases-dictionary-detail">
+        <div class="page-title" @click="goBack">
+            <el-icon class="title-icon" color="rgba(0, 0, 0, 0.8)" size="16"><ArrowLeftBold /></el-icon>
+            识别结果
+        </div>
+        <div class="detail-content">
+            <div class="detail-img">
+                <div class="card-item">
+                    <div class="ing-wrap" v-if="isRecognize">
+                        <div>
+                            <el-icon size="20" class="is-loading">
+                                <Loading />
+                            </el-icon>
+                        </div>
+                        正在识别,请稍后...
+                    </div>
+                    <!-- <img class="card-bg" src="@/assets/img/diseases/1.jpg" /> -->
+                    <img class="card-bg" :src="base_img_url2 + imgKey + resize" />
+                    <div class="card-content" v-if="!isRecognize && !noData">
+                        <div class="title-ques">
+                            <div class="ques-text">病虫名称:{{resList?.name}}</div>
+                        </div>
+                        <div class="dialog-famous">管理方法:</div>
+                        <div class="dialog-answer" >
+                            {{ resList.cure }}
+                          <!-- <template v-if="resData?.phenologyName">
+                            ,物候期为<span style="color: #ffd786;">{{resData?.phenologyName}}</span>
+                          </template>
+                          <template v-if="resData?.pestOrDiseaseName">
+                            ,发现<span style="color: #ffd786;">{{resData?.pestOrDiseaseName}}</span>
+                          </template>
+                          <template v-if="resData?.growthAbnormal">
+                            ,出现<span style="color: #ffd786;">{{resData?.growthAbnormal}}</span>
+                          </template>
+                          <template v-else>
+                            ,暂未发现生长异常。
+                          </template> -->
+                        </div>
+                        <div v-if="resList?.info" class="advice-wrap">
+                            <div class="item-tag">基本信息</div>
+                            <div v-html="resList?.info"></div>
+                        </div>
+                        <!-- <div class="info">基本信息:</div>
+                        <div class="desc">
+                            炭疽病是幼龄树的重要病,严重发病时,病叶率可达30-45%。严重影响幼龄树的生长发育。
+                        </div>
+                        <div class="famous-info">
+                            <img src="@/assets/img/old_mini/link-icon.png" />
+                            <span class="type-name">蛀蒂虫</span>
+                            <span>合作专家:陈晓晓</span>
+                        </div> -->
+                    </div>
+
+                    <div class="card-content no-data" v-if="!isRecognize && noData">
+                        <!-- <img src="@/assets/img/weather_index/good-fill.png" /> -->
+                        长势良好,并未发现病虫害
+                    </div>
+                </div>
+            </div>
+            <div class="btn-wrap" v-if="!isRecognize">
+                <div class="btn primary" @click="goBack">咨询专家</div>
+            </div>
+            <!-- <div class="recognizing-box" v-if="isRecognize">正在识别,请稍等~</div> -->
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onMounted, ref } from "vue";
+import { useRouter, useRoute } from "vue-router";
+import { base_img_url2 } from "@/api/config.js";
+import wx from "weixin-js-sdk";
+import MqttClient from "@/mqtt/MqttClient";
+
+const MQTT_MQTTIP = "wss://ws.emqx.sysuimars.cn/mqtt";
+const MQTT_USERNAME = "admin";
+const MQTT_PASSWORD = "admin";
+
+let resize = '?x-oss-process=image/resize,w_300'
+const route = useRoute();
+const json = JSON.parse(route.query.json || "{}");
+// const json = JSON.parse(`{"imgKey":"birdseye-look-mini/766/1761388777261.png","imgId":769993591052308480}`)
+const imgKey = json.imgKey;
+const farmId = json.farmId;
+
+const isRecognize = ref(true);
+
+const recoginizeResult = ref({});
+const noData = ref(false);
+const resList = ref({});
+onMounted(() => {
+    const mqttClient = new MqttClient(['farm/pest_recognition/task/'+farmId], mqttListener);
+    // const mqttClient = new MqttClient(['phone/image/update/+'], mqttListener);
+    mqttClient.connect();
+});
+
+const mqttListener = (topic, message) => {
+  let res = message.toString();
+  console.log("接收推送信息:", res);
+  let status = JSON.parse(res).status;
+  if (status === 1) {
+    let res =  JSON.parse(message)
+    if(res.result && res.result.length > 0){
+      VE_API.home.fetchDiseaseDetail(res.result).then(({data,code}) => {
+        if(code === 0){
+          resList.value = data[0];
+        }
+      })
+    }else{
+        noData.value = true;
+    }
+    // else if(resData.value?.growthAbnormalId){
+    //     VE_API.growth_anomaly_type.getById({id:resData.value?.growthAbnormalId}).then(({data})=>{
+    //       resData.value.suggest = data.suggest;
+    //     })
+    // }
+    isRecognize.value = false;
+    // recoginizeResult.value.text1 = resData.data.text1
+    // recoginizeResult.value.text2 = resData.data.text2
+  }
+}
+
+const router = useRouter();
+const goBack = () => {
+    // router.go(-1);
+    router.push(`/home`);
+    // router.push(`/feature_home_album?activeImgIndex=13&token=${token}`)
+};
+
+const toDetail = () => {
+    router.push("/diseases_dictionary_detail");
+};
+
+const toRecognize = () => {
+    // router.push("/diseases_recognize")
+    const gardenData = {
+        organId: 25862,
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/recognize_carmera/index?gardenData=${JSON.stringify(gardenData)}`,
+    });
+};
+</script>
+
+<style lang="scss" scoped>
+.diseases-dictionary-detail {
+    position: relative;
+    width: 100%;
+    height: 100vh;
+    overflow: hidden;
+    background: #fff;
+
+    .page-title {
+        height: 44px;
+        line-height: 44px;
+        text-align: center;
+        font-size: 17px;
+        font-weight: bold;
+        // border-bottom: 1px solid #ededed;
+        .title-icon {
+            cursor: pointer;
+            position: absolute;
+            left: 16px;
+            top: 13px;
+        }
+    }
+    .detail-content {
+        height: calc(100% - 44px - 20px);
+        overflow: auto;
+        .detail-img {
+            height: calc(100% - 100px);
+            position: relative;
+            padding: 4px 16px 30px 16px;
+            .card-item {
+                width: 100%;
+                height: 100%;
+                position: relative;
+                .ing-wrap {
+                    color: #fff;
+                    position: absolute;
+                    left: 0;
+                    right: 0;
+                    top: 50%;
+                    transform: translateY(-50%);
+                    margin: 0 auto;
+                    background: rgba(0, 0, 0, 0.6);
+                    border-radius: 12px;
+                    width: 164px;
+                    height: 95px;
+                    display: flex;
+                    flex-direction: column;
+                    align-items: center;
+                    justify-content: center;
+                }
+                .card-bg {
+                    border-radius: 24px 0 36px 4px;
+                    width: 100%;
+                    height: 100%;
+                    object-fit: cover;
+                }
+                .no-data {
+                    color: #fff;
+                    text-align: center;
+                    display: flex;
+                    align-items: center;
+                    justify-content: center;
+                    img {
+                        width: 17px;
+                        margin-right: 4px;
+                    }
+                }
+                .card-content {
+                    position: absolute;
+                    bottom: 0;
+                    left: 0;
+                    padding: 12px 12px 26px 12px;
+                    border-radius: 24px 0 36px 4px;
+                    color: #ffffff;
+                    background: rgba(0, 0, 0, 0.6);
+                    backdrop-filter: blur(4px);
+                    max-height: calc(100% - 38px);
+                    overflow: auto;
+                    width: 100%;
+                    box-sizing: border-box;
+                    &.no-data {
+                        padding: 16px 0 20px 0;
+                    }
+                    .ques-text {
+                        color: #ffd786;
+                        font-size: 18px;
+                        position: relative;
+                        padding-left: 12px;
+                    }
+                    .ques-text:after {
+                        content: "";
+                        position: absolute;
+                        width: 4px;
+                        height: 15px;
+                        top: 5px;
+                        left: 0;
+                        border-radius: 2px;
+                        background: #ffd186;
+                    }
+                    .dialog-famous {
+                        padding: 8px 0 4px 0;
+                        color: #ffd786;
+                        font-size: 16px;
+                    }
+                    .dialog-answer {
+                        line-height: 24px;
+                    }
+                    .advice-wrap {
+                        padding-top: 12px;
+                    }
+                    .item-tag {
+                        width: fit-content;
+                        padding: 4px 10px;
+                        border-radius: 20px;
+                        color: #FFD786;
+                        background: rgba(255, 215, 134, 0.2);
+                        margin-bottom: 6px;
+                    }
+                    .info {
+                        padding: 10px 10px 6px 0;
+                        font-size: 15px;
+                    }
+                    .desc {
+                        font-size: 14px;
+                        line-height: 24px;
+                    }
+                    .famous-info {
+                        display: flex;
+                        align-items: center;
+                        color: #ffd786;
+                        font-size: 16px;
+                        padding: 8px 0 0px 0;
+                        .type-name {
+                            padding: 0 4px 0 8px;
+                        }
+                        img {
+                            width: 16px;
+                        }
+                    }
+                }
+            }
+            .recognize-img {
+                width: 100%;
+            }
+        }
+        .btn-wrap {
+            width: 100%;
+            height: 56px;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            margin: 0 auto;
+            padding: 0 16px;
+            box-sizing: border-box;
+            .btn {
+                height: 40px;
+                line-height: 40px;
+                font-size: 16px;
+                text-align: center;
+                color: #000;
+                border-radius: 4px;
+                &.primary {
+                    flex: 1;
+                    color: #fff;
+                    background: #2199f8;
+                }
+                &.share {
+                    background: #fff;
+                    width: 20%;
+                    min-width: 80px;
+                    text-align: center;
+                    border: 1px solid #8E8E8E;
+                }
+            }
+            .btn + .btn {
+                margin-left: 15px;
+            }
+        }
+        .recognizing-box {
+            padding-top: 30px;
+            text-align: center;
+            font-size: 16px;
+        }
+        .box-title {
+            font-size: 16px;
+            font-weight: bold;
+            color: #000;
+            padding: 16px 2px 16px 0;
+        }
+        .padding-t {
+            padding-top: 26px;
+        }
+        .expert-box {
+            .expert-item {
+                display: flex;
+                justify-content: space-between;
+            }
+            .expert-l {
+                display: inline-flex;
+                align-items: center;
+                .expert-name {
+                    padding-left: 10px;
+                }
+                .expert-tag {
+                    background: #f7ecc7;
+                    padding: 2px 6px;
+                    color: #ae7d22;
+                    width: fit-content;
+                    border-radius: 4px;
+                    font-size: 12px;
+                    display: flex;
+                    align-items: center;
+                    margin-left: 10px;
+                }
+                img {
+                    width: 40px;
+                    height: 40px;
+                    border-radius: 50%;
+                }
+            }
+            .line {
+                margin: 16px;
+                width: calc(100% - 32px);
+                height: 1px;
+                background: #f1f1f1;
+            }
+        }
+    }
+}
+</style>

+ 135 - 15
src/views/old_mini/create_farm/index.vue

@@ -74,7 +74,6 @@
                                             <el-select
                                                 v-model="ruleForm.typeId"
                                                 placeholder="品种"
-                                                @change="changeTypeId"
                                                 class="period-select select-item"
                                             >
                                                 <el-option
@@ -126,8 +125,15 @@
                                             placeholder="请输入您的农场名称"
                                             v-model="ruleForm.name"
                                             autocomplete="off"
+                                            @input="handleFarmNameInput"
                                         />
                                     </el-form-item>
+                                    <el-form-item label="默认农场" prop="default-farm" class="defaultFarm">
+                                        <radio-group class="radio-group" v-model="ruleForm.defaultFarm" direction="horizontal">
+                                            <radio class="radio-item" :name="true">是</radio>
+                                            <radio class="radio-item" :name="false">否</radio>
+                                        </radio-group>
+                                    </el-form-item>
                                 </el-form>
                             </div>
                             <div class="create-btn">
@@ -153,6 +159,7 @@ import { onMounted, ref, reactive, watch, onActivated } from "vue";
 import { useStore } from "vuex";
 import { convertPointToArray } from "@/utils/index";
 import { ElMessage } from "element-plus";
+import { RadioGroup, Radio } from "vant";
 import { transformFromGCJToWGS, transformFromWGSToGCJ } from "@/utils/WSCoordinate.js";
 
 const route = useRoute();
@@ -168,6 +175,9 @@ const isFromEditMap = ref(false);
 // 标记是否已创建默认地块
 const hasDefaultPolygon = ref(false);
 
+// 标记用户是否手动修改过农场名称
+const isFarmNameManuallyModified = ref(false);
+
 /**
  * 根据中心点生成指定边长的正方形地块WKT
  * @param {Array} center - 中心点坐标 [lng, lat]
@@ -270,7 +280,14 @@ onMounted(() => {
     ruleForm.mu = '';
     ruleForm.typeId = '';
 
-    getSpecieList();
+    // 如果是编辑模式,等待品类列表加载完成后再回填数据
+    if (route.query.type === 'edit' && store.state.home.editFarmData) {
+        getSpecieList().then(() => {
+            populateEditData();
+        });
+    } else {
+        getSpecieList();
+    }
 });
 
 const polygonArr = ref(null);
@@ -295,10 +312,17 @@ onActivated(() => {
         polygonArr.value = null;
         ruleForm.mu = '';
         ruleForm.typeId = '';
+        ruleForm.defaultFarm = false;
         
         return; // 直接返回,不执行下面的逻辑
     }
     
+    // 如果是编辑模式,回填数据
+    if (route.query.type === 'edit' && store.state.home.editFarmData) {
+        populateEditData();
+        return;
+    }
+    
     indexMap.clearLayer();
     // 绘制勾画范围(从edit_map返回的情况)
     const polygonData = store.state.home.polygonData;
@@ -331,6 +355,7 @@ onActivated(() => {
             ruleForm.mu = '';
             isFromEditMap.value = false;
             hasDefaultPolygon.value = false;
+            ruleForm.defaultFarm = false;
         }
     } else if (centerPoint.value && polygonArr.value) {
         // 没有新的编辑数据,保持当前地块
@@ -399,6 +424,7 @@ const ruleForm = reactive({
     name: "",
     fzr: "",
     tel: "",
+    defaultFarm:0, // 0:否 1:是
 });
 // 自定义验证规则:验证面积必须是大于0的数字
 const validateMianji = (rule, value, callback) => {
@@ -434,6 +460,7 @@ const rules = reactive({
             trigger: "blur" 
         }
     ],
+    defaultFarm: [{ required: true, message: "请选择是否为默认农场", trigger: "blur" }],
 });
 
 const submitForm = (formEl) => {
@@ -445,22 +472,36 @@ const submitForm = (formEl) => {
                 wkt: centerPoint.value,
                 speciesId: ruleForm.speciesItem?.id,
                 containerId: ruleForm.speciesItem?.defaultContainerId,
-                geom: polygonArr.value,
+                // 编辑时geom不是数组,新增时是数组
+                geom: route.query.type === 'edit' 
+                    ? (polygonArr.value && polygonArr.value.length > 0 ? polygonArr.value[0] : null)
+                    : polygonArr.value,
             };
-            VE_API.farm.saveFarm(params).then((res) => {
+            
+            // 如果是编辑模式,添加农场ID
+            if (route.query.type === 'edit' && route.query.farmId) {
+                params.farmId = route.query.farmId;
+            }
+            
+            const apiCall = route.query.type === 'edit' 
+                ? VE_API.farm.updateFarm(params)
+                : VE_API.farm.saveFarm(params);
+                
+            apiCall.then((res) => {
                 if(res.code === 0) {
-                    ElMessage.success("创建成功");
+                    ElMessage.success(route.query.type === 'edit' ? "修改成功" : "创建成功");
                     // 重置表单和地块数据
                     ruleFormRef.value.resetFields();
                     store.commit("home/SET_FARM_POLYGON", null);
+                    store.commit("home/SET_EDIT_FARM_DATA", null); // 清除编辑数据
                     polygonArr.value = null;
                     isFromEditMap.value = false;
                     
                     // 根据来源页面决定跳转目标
                     const fromPage = route.query.from;
                     if (fromPage === 'monitor') {
-                        // 如果是从monitor页面来的,跳转回monitor页面并刷新农场列表
-                        router.replace(`/${fromPage}`);
+                        // 如果是从monitor页面来的,跳转回monitor页面并刷新农场列表和基本信息弹窗
+                        router.replace(`/${fromPage}?refreshFarmInfo=true`);
                     } else {
                         // 默认跳转到首页
                         router.replace("/home?reload=true&showSuccess=true");
@@ -546,7 +587,7 @@ function getLocationName(location) {
         const add = result.formatted_addresses?.recommend ? result.formatted_addresses.recommend : result.address + "";
         ruleForm.address = add;
         pointAddress.value = result.address;
-        farmCity.value = result.address_component?.city || '';
+        farmCity.value = result.address_component?.city + result.address_component?.district || '';
     });
 }
 
@@ -564,18 +605,20 @@ watch(
 const specieList = ref([]);
 
 function getSpecieList() {
-    VE_API.farm.fetchSpecieList().then(({ data }) => {
+    return VE_API.farm.fetchSpecieList().then(({ data }) => {
         specieList.value = data;
+        return data;
     });
 }
 
 function changeSpecie(v) {
     getFruitsTypeItemList(v.id);
-}
-
-function changeTypeId(v) {
-    const fruitsName = fruitsList.value.find(item => item.id === v).name;
-    ruleForm.name = farmCity.value + fruitsName + "农场";
+    // 清空品种选择
+    ruleForm.typeId = '';
+    // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
+    if (route.query.type !== 'edit' && !isFarmNameManuallyModified.value) {
+        ruleForm.name = farmCity.value + v.name + "农场";
+    }
 }
 
 const fruitsList = ref([]);
@@ -628,6 +671,72 @@ function handleMianjiKeypress(event) {
     }
 }
 
+// 处理农场名称输入
+function handleFarmNameInput() {
+    // 标记用户已手动修改过农场名称
+    isFarmNameManuallyModified.value = true;
+}
+
+// 回填编辑数据
+function populateEditData() {
+    const editData = store.state.home.editFarmData;
+    if (!editData) {
+        return;
+    }
+    
+    // 回填基本信息
+    ruleForm.name = editData.name || '';
+    ruleForm.fzr = editData.fzr || '';
+    ruleForm.tel = editData.tel || '';
+    ruleForm.defaultFarm = editData.defaultOption || false;
+    ruleForm.mu = editData.mianji || '';
+    ruleForm.address = editData.address || '';
+    
+    // 设置地图中心点
+    if (editData.pointWkt) {
+        centerPoint.value = editData.pointWkt;
+        const arr = convertPointToArray(editData.pointWkt);
+        indexMap.setMapPosition(arr);
+    }
+    
+    // 处理地块数据
+    if (editData.geomWkt) {
+        polygonArr.value = [editData.geomWkt];
+        indexMap.setAreaGeometry([editData.geomWkt]);
+        hasDefaultPolygon.value = true;
+        isFromEditMap.value = true; // 编辑模式下锁定面积输入
+    }
+    
+    // 处理作物数据
+    if (editData.speciesId && editData.speciesName) {
+        // 设置品类 - 需要匹配模板中的数据结构
+        ruleForm.speciesItem = {
+            value: editData.speciesId,
+            id: editData.speciesId,
+            name: editData.speciesName,
+            defaultContainerId: editData.containerId
+        };
+        
+        // 获取品种列表
+        getFruitsTypeItemList(editData.speciesId);
+        
+        // 设置品种
+        if (editData.typeId) {
+            ruleForm.typeId = editData.typeId;
+        }
+    }
+    
+    // 设置地址信息
+    if (editData.district) {
+        try {
+            const districtInfo = JSON.parse(editData.district);
+            pointAddress.value = districtInfo.province + districtInfo.city + districtInfo.district;
+        } catch (e) {
+            console.warn('解析地址信息失败:', e);
+        }
+    }
+}
+
 // 处理面积输入
 let mianjiInputTimer = null;
 function handleMianjiInput(value) {
@@ -707,7 +816,12 @@ function handleMianjiInput(value) {
     overflow: hidden;
     .map-container {
         width: 100%;
-        height: calc(100% - 240px);
+        height: calc(100% - 320px);
+    }
+    .radio-group {
+        .radio-item {
+            margin-right: 30px;
+        }
     }
     .farm-content {
         position: absolute;
@@ -891,6 +1005,12 @@ function handleMianjiInput(value) {
                             height: 1px;
                             background: rgba(0, 0, 0, 0.08);
                         }
+                        &.default-farm {
+                            margin-bottom: 0;
+                            &::after {
+                                background: none;
+                            }
+                        }
                     }
                     .el-input__wrapper {
                         box-shadow: none;

+ 91 - 18
src/views/old_mini/home/components/farmInfoPopup.vue

@@ -5,12 +5,30 @@
             <div class="popup-content">
                 <div class="map-box">
                     <div class="map" ref="mapContainer"></div>
-                    <div class="map-text" @click="handleEditMap">点击编辑地块</div>
+                    <!-- <div class="map-text" @click="handleEditMap">点击编辑地块</div> -->
                 </div>
                 <cell-group inset class="cell-group">
                     <field v-model="farmInfo.name" readonly label="农场名称" />
-                    <field v-model="farmInfo.area" readonly label="农场面积" />
-                    <field v-model="farmInfo.plant" readonly label="种植作物" />
+                    <field readonly label="农场面积">
+                        <template #input>
+                            <span>{{ farmInfo.mianji }}亩</span>
+                        </template>
+                    </field>
+                    <field readonly label="种植作物">
+                        <template #input>
+                            <span>{{ farmInfo.speciesName }}-{{ farmInfo.typeName }}</span>
+                        </template>
+                    </field>
+                    <field v-model="farmInfo.fzr" readonly label="联系人" />
+                    <field v-model="farmInfo.tel" readonly label="联系电话" />
+                    <field readonly label="默认农场">
+                        <template #input>
+                            <radio-group class="radio-group" v-model="farmInfo.defaultOption" direction="horizontal">
+                                <radio class="radio-item" :name="true">是</radio>
+                                <radio class="radio-item" :name="false">否</radio>
+                            </radio-group>
+                        </template>
+                    </field>
                     <field class="address-field" v-model="farmInfo.address" readonly label="农场位置" />
                 </cell-group>
             </div>
@@ -23,54 +41,101 @@
 </template>
 
 <script setup>
-import { Popup, Field, CellGroup } from "vant";
+import { Popup, Field, CellGroup, RadioGroup, Radio } from "vant";
 import { ref, nextTick } from "vue";
 import { useRouter } from "vue-router";
 import IndexMap from "../map/index.js";
 import { useStore } from "vuex";
 
+const props = defineProps({
+    farmId: {
+        type: [Number, String],
+        default: null,
+    },
+});
+
 const store = useStore();
 const router = useRouter();
 const show = ref(false);
 const mapContainer = ref(null);
 const indexMap = new IndexMap();
-const farmInfo = ref({
-    name: "从化荔博园",
-    area: "1000亩",
-    plant: "荔枝",
-    species: "桂味",
-    address: "广东省广州市从化区",
-});
 
+// 农场信息
+const farmInfo = ref();
 const handleShow = () => {
-    show.value = true;
-    nextTick(() => {
-        const point = store.state.home.miniUserLocationPoint
-        indexMap.initMap(point, mapContainer.value);
+    VE_API.farm.getFarmDetail({ farmId: props.farmId }).then((res) => {
+        if (res.code === 0) {
+            farmInfo.value = res.data;
+            show.value = true;
+            nextTick(() => {
+                const point = farmInfo.value.pointWkt;
+                const geometryArr = farmInfo.value.geomWkt;
+
+                // 如果地图已经初始化,则更新中心点和地块;否则初始化地图
+                if (indexMap.kmap) {
+                    indexMap.updateCenter(point);
+                    // 如果有地块数据,则添加地块
+                    if (geometryArr && geometryArr.length > 0) {
+                        indexMap.setAreaGeometry([geometryArr]);
+                    }
+                } else {
+                    indexMap.initMap(point, mapContainer.value);
+                    // 初始化地图后,如果有地块数据,则添加地块
+                    if (geometryArr && geometryArr.length > 0) {
+                        indexMap.setAreaGeometry([geometryArr]);
+                    }
+                }
+            });
+        }
     });
 };
 
 const handleEditMap = () => {
+    // 在跳转前,将当前农场的地块数据存储到store中
+    if (farmInfo.value.geomWkt) {
+        const polygonData = {
+            geometryArr: [farmInfo.value.geomWkt],
+            isConfirmed: false,
+        };
+        store.commit("home/SET_FARM_POLYGON", polygonData);
+    }
+
     router.push(
-        `/edit_map?mapCenter=${store.state.home.miniUserLocationPoint}&pointName=${farmInfo.value.address}&pointAddress=${farmInfo.value.address}&type=edit`
+        `/edit_map?mapCenter=${farmInfo.value.pointWkt}&pointName=${farmInfo.value.address}&pointAddress=${JSON.parse(
+            farmInfo.value.district
+        )}&type=edit`
     );
 };
 
 const handleEdit = () => {
-    router.push("/create_farm?type=edit");
+    // 在跳转前,将当前农场的地块数据存储到store中
+    if (farmInfo.value.geomWkt) {
+        const polygonData = {
+            geometryArr: [farmInfo.value.geomWkt],
+            mianji: farmInfo.value.mianji,
+            isConfirmed: true, // 编辑模式下标记为已确认
+        };
+        store.commit("home/SET_FARM_POLYGON", polygonData);
+    }
+    
+    // 将农场数据存储到store中,供编辑页面使用
+    store.commit("home/SET_EDIT_FARM_DATA", farmInfo.value);
+    
+    router.push(`/create_farm?type=edit&farmId=${props.farmId}&from=monitor`);
 };
 
 const handleCancel = () => {
     show.value = false;
 };
 
-defineExpose({handleShow});
+defineExpose({ handleShow });
 </script>
 
 <style lang="scss" scoped>
 .farm-info-popup {
     width: 100%;
     border-radius: 8px;
+    overflow: hidden;
     .popup-content-box {
         background: url("@/assets/img/home/popup-mask.png") no-repeat center left / 100% 100%;
         padding: 20px;
@@ -125,6 +190,14 @@ defineExpose({handleShow});
                     transform: scaleY(0.5);
                 }
             }
+            .radio-group {
+                display: flex;
+                align-items: center;
+                pointer-events: none;
+                .radio-item {
+                    margin-right: 30px;
+                }
+            }
         }
     }
     .popup-footer {

+ 16 - 16
src/views/old_mini/home/index.vue

@@ -91,22 +91,22 @@ const monitorCards = ref({
 
 // 卡片点击事件
 const handleCardClick = (card) => {
-    showFarmPopup.value = true;
-    // if(card.route === "/pest"){
-    //     const dropdownGardenItem = ref({
-    //         organId:766,
-    //         periodId:1,
-    //         wktVal:'wktVal',
-    //         address:'address',
-    //         district:'district',
-    //         name:'荔博园',
-    //     });
-    //     wx.miniProgram.navigateTo({
-    //         url: `/pages/subPages/carmera/index?gardenData=${JSON.stringify(dropdownGardenItem.value)}`,
-    //     });
-    // }else{
-    //     router.push(card.route);
-    // }
+    // showFarmPopup.value = true;
+    if(card.route === "/pest"){
+        const dropdownGardenItem = ref({
+            organId:766,
+            periodId:1,
+            wktVal:'wktVal',
+            address:'address',
+            district:'district',
+            name:'荔博园',
+        });
+        wx.miniProgram.navigateTo({
+            url: `/pages/subPages/recognize_carmera/index?gardenData=${JSON.stringify(dropdownGardenItem.value)}`,
+        });
+    }else{
+        router.push(card.route);
+    }
 };
 
 const handleBtn = () => {

+ 57 - 0
src/views/old_mini/home/map/index.js

@@ -5,6 +5,7 @@ import { Point } from 'ol/geom';
 import Feature from "ol/Feature";
 import Style from "ol/style/Style";
 import Icon from "ol/style/Icon";
+import LngLat from "@/utils/ol-map/LngLat";
 /**
  * @description 地图层对象
  */
@@ -46,6 +47,62 @@ class IndexMap {
           console.log('evt.coordinate', evt.coordinate);
         });
   }
+
+  /**
+   * 更新地图中心点
+   * @param {string} location WKT格式的位置字符串
+   */
+  updateCenter(location) {
+    if (!this.kmap) {
+      console.warn('地图未初始化,无法更新中心点');
+      return;
+    }
+    const that = this;
+    
+    try {
+      let coordinate = util.wktCastGeom(location).getFirstCoordinate();
+      // 清除之前的点标记
+      that.clickPointLayer.source.clear()
+      // 添加新的点标记
+      let point = new Feature(new Point(coordinate));
+      that.clickPointLayer.addFeature(point);
+      // 更新地图中心点
+      that.kmap.getView().animate({
+        center: coordinate,
+        zoom: 16,
+        duration: 0,
+      });
+    } catch (error) {
+      console.error('更新地图中心点失败:', error);
+    }
+  }
+
+  setAreaGeometry(geometryArr) {
+    this.clearLayer()
+    let that = this
+    geometryArr.map(item => {
+      that.kmap.setLayerWkt(item)
+    })
+    this.fitView()
+  }
+
+  /**
+   * 清除地块图层
+   */
+  clearLayer() {
+    if (this.kmap && this.kmap.polygonLayer) {
+      this.kmap.polygonLayer.source.clear()
+    }
+  }
+
+  /**
+   * 调整地图视图以适应地块范围
+   */
+  fitView(){
+    let extent = this.kmap.polygonLayer.source.getExtent()
+    // 地图自适应到区域可视范围
+    this.kmap.getView().fit(extent, { duration: 50, padding: [100, 100, 100, 100] });
+  }
 }
 
 export default IndexMap;

+ 20 - 4
src/views/old_mini/monitor/index.vue

@@ -50,10 +50,10 @@
             <div class="broadcast-header">
                 <div class="header-left">
                     <span class="broadcast-title">实时播报</span>
-                    <div class="broadcast-action" :class="{ speaking: isSpeaking }" @click="handleBroadcast">
+                    <!-- <div class="broadcast-action" :class="{ speaking: isSpeaking }" @click="handleBroadcast">
                         <img class="speaker-icon" src="@/assets/img/monitor/speaker.png" alt="播报" />
                         <span class="broadcast-text">{{ isSpeaking ? '停止播报' : '点击播报' }}</span>
-                    </div>
+                    </div> -->
                 </div>
             </div>
 
@@ -87,7 +87,7 @@
         </div>
     </div>
     <!-- 农场信息 -->
-    <farm-info-popup ref="farmInfoRef"></farm-info-popup>
+    <farm-info-popup ref="farmInfoRef" :farmId="gardenId"></farm-info-popup>
 </template>
 
 <script setup>
@@ -251,6 +251,20 @@ const handleBroadcast = () => {
 // 组件激活时检查是否需要刷新农场列表
 onActivated(() => {
     weatherInfoRef.value.getFarmList();
+    
+    // 检查是否从编辑农场页面返回,需要刷新基本信息弹窗
+    if (route.query.refreshFarmInfo === 'true') {
+        // 清除URL参数,避免刷新页面时再次触发
+        router.replace({ 
+            path: '/monitor', 
+            query: {} 
+        });
+        
+        // 刷新农场信息弹窗数据
+        if (farmInfoRef.value) {
+            farmInfoRef.value.handleShow();
+        }
+    }
 });
 
 // 组件卸载时停止语音播放
@@ -266,9 +280,11 @@ const weatherExpanded = (isExpandedValue) => {
     isExpanded.value = isExpandedValue;
 };
 
-const gardenId = ref(null);
+const gardenId = ref(store.state.home.gardenId);
 const changeGarden = (id) => {
     gardenId.value = id;
+    // 更新 store 中的状态
+    store.commit('home/SET_GARDEN_ID', id);
     // 重置分页状态
     currentPage.value = 1;
     finished.value = false;