4
0

2 Коммиты ddb1199fce ... 9b99c71b9c

Автор SHA1 Сообщение Дата
  wangsisi 9b99c71b9c Merge branch 'farmer' of http://www.sysuimars.cn:3000/feiniao/feiniao-farm-h5 into farmer 1 день назад
  wangsisi 2fe12bfb03 feat:修改bug 1 день назад

+ 28 - 17
src/views/old_mini/create_farm/index.vue

@@ -41,7 +41,7 @@
                                     <el-form-item label="种植作物" prop="speciesItem">
                                         <div class="select-wrap specie-wrap">
                                             <el-select v-model="ruleForm.speciesItem" class="select-item specie-select"
-                                                multiple collapse-tags placeholder="品类(可多选)" @change="changeSpecie">
+                                                multiple :max-collapse-tags="3" collapse-tags placeholder="种植作物(可多选)" @change="changeSpecie">
                                                 <el-option v-for="(item, index) in specieList" :key="index"
                                                     :label="item.name" :value="{ value: item.id, ...item }" />
                                             </el-select>
@@ -460,15 +460,15 @@ const rules = reactive({
         { required: true, message: "请输入农场面积", trigger: "blur" },
         { validator: validateMianji, trigger: ["blur", "change"] },
     ],
-    speciesItem: [{ required: true, message: "请选择品类", trigger: "blur" }],
+    speciesItem: [{ required: true, message: "请选择种植作物", trigger: ["blur", "change"] }],
     name: [{ required: true, message: "请输入您的农场名称", trigger: ["blur", "change"] }],
     fzr: [{ required: true, message: "请输入联系人姓名", trigger: ["blur", "change"] }],
     tel: [
-        { required: true, message: "请输入联系人电话", trigger: ["blur"] },
+        { required: true, message: "请输入联系人电话", trigger: ["blur", "change"] },
         {
             pattern: /^1[3-9]\d{9}$/,
             message: "请输入正确的手机号码",
-            trigger: ["blur"],
+            trigger: ["blur", "change"],
         },
     ],
     defaultFarm: [{ required: true, message: "请选择是否为默认农场", trigger: "blur" }],
@@ -687,21 +687,23 @@ const specieList = ref([]);
 function getSpecieList() {
     return VE_API.farm.fetchSpecieList({ point: centerPoint.value }).then(({ data }) => {
         const list = Array.isArray(data) ? data : [];
-        // 只保留名称包含“荔枝”的品类
-        const litchiList = list.filter((item) => item?.name && item.name.includes("荔枝"));
+        // 只保留名称包含“水稻”或“荔枝”的品类
+        const litchiList = list.filter(
+            (item) => item?.name && (item.name.includes("水稻") || item.name.includes("荔枝"))
+        );
         specieList.value = litchiList;
         // 返回空列表时,重置已选品类,避免保留上一次默认项
         if (litchiList.length === 0) {
             ruleForm.speciesItem = [];
             return litchiList;
         }
-        //列表有值时,默认选中第一项
-        if (litchiList.length > 0) {
-            const first = { value: litchiList[0].id, ...litchiList[0] };
-            ruleForm.speciesItem = [first];
-            // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
-            changeSpecie(first);
-        }
+        // //列表有值时,默认选中第一项
+        // if (litchiList.length > 0) {
+        //     const first = { value: litchiList[0].id, ...litchiList[0] };
+        //     ruleForm.speciesItem = [first];
+        //     // 同步触发品类变更逻辑(加载品种、自动生成农场名等)
+        //     changeSpecie(first);
+        // }
         return litchiList;
     });
 }
@@ -716,6 +718,15 @@ function getBaseTypeList() {
 
 async function changeSpecie(v) {
     const current = Array.isArray(v) ? v[v.length - 1] : v;
+    // 选择后立即更新该字段的校验状态,避免错误提示残留
+    nextTick(() => {
+        if (!ruleFormRef.value) return;
+        if (Array.isArray(ruleForm.speciesItem) && ruleForm.speciesItem.length > 0) {
+            ruleFormRef.value.clearValidate(["speciesItem"]);
+            return;
+        }
+        ruleFormRef.value.validateField("speciesItem");
+    });
     // 只有在创建模式下且用户没有手动修改过农场名称时,才自动设置农场名称
     if (route.query.type !== "edit" && !isFarmNameManuallyModified.value && farmCity.value && current) {
         ruleForm.name = farmCity.value + current.name + "农场";
@@ -1181,10 +1192,10 @@ function handleMianjiInput(value) {
                         }
                     }
 
-                    .select-item {
-                        min-width: 80px;
-                        margin-left: 10px;
-                    }
+                    // .select-item {
+                    //     min-width: 80px;
+                    //     margin-left: 10px;
+                    // }
                 }
 
                 ::v-deep {

+ 1 - 1
src/views/old_mini/home/subPages/prescriptionPage.vue

@@ -263,7 +263,7 @@ const basicForm = ref({
 const basicFarmFormData = ref({});
 const getBasicFarmFormData = () => {
     loadingPage.value = true;
-    VE_API.basic_farm.fetchBasicFarmFormData().then(({ data }) => {
+    VE_API.basic_farm.fetchBasicFarmFormData({subjectId: route.query?.subjectId}).then(({ data }) => {
         basicFarmFormData.value = data;
         
         // 根据返回的数据进行默认赋值

+ 72 - 28
src/views/old_mini/interactionList/index.vue

@@ -129,9 +129,10 @@
                     <!-- 按钮区域 -->
                     <div class="button-group" v-show="item.questionStatus === 3">
                         <div class="btn-not-reached" @click="handleConfirm(item, false)">{{ item.cancelButtonName }}</div>
-                        <div class="btn-default" :class="{ 'btn-confirm': item.imagePaths.length > 0 }"
+                        <div class="btn-default"
+                            :class="{ 'btn-confirm': item.imagePaths.length > 0, 'btn-loading': isItemSubmitting(item) }"
                             @click="handleConfirm(item, true)">
-                            确认提交
+                            {{ isItemSubmitting(item) ? '提交中...' : '确认提交' }}
                         </div>
                     </div>
 
@@ -209,7 +210,9 @@
                 <div class="region-map-text">点击勾画新发生区域</div>
             </div>
         </template>
-        <div class="confirm-btn" @click="handleConfirmUpload">确认上传</div>
+        <div class="confirm-btn" :class="{ 'confirm-btn-loading': confirmUploadLoading }" @click="handleConfirmUpload">
+            {{ confirmUploadLoading ? '上传中...' : '确认上传' }}
+        </div>
     </popup>
 
     <!-- 查看更多弹窗 -->
@@ -270,6 +273,15 @@ const format = () => {
 
 const drawRegionMap = new DrawRegionMap();
 const mapContainer = ref(null);
+const itemSubmittingMap = ref({});
+const confirmUploadLoading = ref(false);
+
+const getItemSubmitKey = (item) => String(item?.id ?? "");
+const isItemSubmitting = (item) => {
+    const key = getItemSubmitKey(item);
+    if (!key) return false;
+    return !!itemSubmittingMap.value[key];
+};
 
 const renderRegionFromSession = () => {
     if (!drawRegionMap.kmap) return;
@@ -514,7 +526,7 @@ const loadData = async () => {
             const interactionTypeNameList = interactionTypeNameStr
                 ? interactionTypeNameStr.split(':').map(s => s.trim()).filter(Boolean)
                 : [];
-            const interactionThemeText = `${interactionTypeNameList[0]}:${item.reproductiveName}`;
+            const interactionThemeText = `${interactionTypeNameList[0]}${interactionTypeNameList[1] ? ':' : ''}${item.reproductiveName || ''}`;
             return {
                 ...item,
                 interactionTypeNameList,
@@ -541,6 +553,8 @@ const removeUploadedImage = (item, imgIndex) => {
 
 // 确认上传 / 暂未到达进程
 const handleConfirm = async (item, isConfirm) => {
+    if (isItemSubmitting(item)) return;
+
     if (isConfirm) {
         const list = item.questionList || [];
         // const needFill = list.length || 1;
@@ -566,22 +580,35 @@ const handleConfirm = async (item, isConfirm) => {
         answerValues: item.answerValues,
         regionId: ''
     }
-    await VE_API.home.uploadAnswerData(parmas);
-    const { code, msg } = await VE_API.home.uploadAnswer(parmas);
-    if (code === 0) {
-        ElMessage.success("上传成功");
-        // 清空上传数据
-        uploadData.value = [];
-        // 刷新列表
-        await refreshList();
-        sessionStorage.removeItem("drawRegionPolygonData");
-        sessionStorage.removeItem("drawRegionInteractionId");
-    } else {
-        ElMessage.error(msg || '上传失败');
+    const key = getItemSubmitKey(item);
+    if (key) {
+        itemSubmittingMap.value[key] = true;
+    }
+    try {
+        await VE_API.home.uploadAnswerData(parmas);
+        const { code, msg } = await VE_API.home.uploadAnswer(parmas);
+        if (code === 0) {
+            ElMessage.success("上传成功");
+            // 清空上传数据
+            uploadData.value = [];
+            // 刷新列表
+            await refreshList();
+            sessionStorage.removeItem("drawRegionPolygonData");
+            sessionStorage.removeItem("drawRegionInteractionId");
+        } else {
+            ElMessage.error(msg || '上传失败');
+        }
+    } catch (error) {
+        ElMessage.error("上传失败,请稍后重试");
+    } finally {
+        if (key) {
+            itemSubmittingMap.value[key] = false;
+        }
     }
 };
 
 const handleConfirmUpload = async () => {
+    if (confirmUploadLoading.value) return;
     // 校验是否有上传图片
     if (!uploadData.value || uploadData.value.length === 0) {
         ElMessage.warning("请先上传照片");
@@ -605,18 +632,25 @@ const handleConfirmUpload = async () => {
         replyText: item.replyText,
         answerValues: answerVals,
     }
-    const { code, msg } = await VE_API.home.uploadAnswerData(parmas);
-    if (code === 0) {
-        ElMessage.success("确认成功");
-        // 清空上传数据及勾画关联
-        uploadData.value = [];
-        sessionStorage.removeItem("drawRegionPolygonData");
-        sessionStorage.removeItem("drawRegionInteractionId");
-        showUploadProgressPopup.value = false;
-        // 刷新列表
-        await refreshList();
-    } else {
-        ElMessage.error(msg || '确认失败');
+    confirmUploadLoading.value = true;
+    try {
+        const { code, msg } = await VE_API.home.uploadAnswerData(parmas);
+        if (code === 0) {
+            ElMessage.success("确认成功");
+            // 清空上传数据及勾画关联
+            uploadData.value = [];
+            sessionStorage.removeItem("drawRegionPolygonData");
+            sessionStorage.removeItem("drawRegionInteractionId");
+            showUploadProgressPopup.value = false;
+            // 刷新列表
+            await refreshList();
+        } else {
+            ElMessage.error(msg || '确认失败');
+        }
+    } catch (error) {
+        ElMessage.error("确认失败,请稍后重试");
+    } finally {
+        confirmUploadLoading.value = false;
     }
 };
 
@@ -1079,6 +1113,11 @@ const handleSubmitAll = () => {
                     border: 1px solid #F1F1F1;
                 }
 
+                .btn-loading {
+                    opacity: 0.7;
+                    pointer-events: none;
+                }
+
                 .btn-confirm {
                     background: #2199f8;
                     color: #ffffff;
@@ -1276,5 +1315,10 @@ const handleSubmitAll = () => {
         font-size: 16px;
         margin-top: 16px;
     }
+
+    .confirm-btn-loading {
+        opacity: 0.7;
+        pointer-events: none;
+    }
 }
 </style>

+ 21 - 2
src/views/old_mini/interactionList/map/drawRegionMap.js

@@ -39,8 +39,25 @@ class DrawRegionMap {
         });
 
         // 只读状态区域图层(展示多 polygon,用于“已解决 / 未解决”等状态)
-        this.staticRegionLayer = new KMap.VectorLayer("staticRegionLayer", 9998, {
+        // 层级需低于默认可编辑 polygonLayer(1000),避免标签遮挡当前编辑地块
+        this.staticRegionLayer = new KMap.VectorLayer("staticRegionLayer", 900, {
             style: (f) => {
+                const displayMode = f.get("displayMode");
+                // 品种勾画页:已勾画的其他品种仅做只读灰色展示,并显示品种名
+                if (displayMode === "readonlyVariety") {
+                    return new Style({
+                        fill: new Fill({ color: "rgba(120, 120, 120, 0.35)" }),
+                        stroke: new Stroke({ color: "#8E8E8E", width: 1.5 }),
+                        text: new Text({
+                            text: f.get("label") || "",
+                            font: "12px sans-serif",
+                            fill: new Fill({ color: "#ffffff" }),
+                            backgroundFill: new Fill({ color: "rgba(90, 90, 90, 0.85)" }),
+                            padding: [2, 6, 2, 6],
+                        }),
+                    });
+                }
+
                 const status = f.get("status"); // 'resolved' | 'unresolved'
                 const isResolved = status === "resolved";
                 // 已解决:深灰填充,浅白描边;未解决:浅蓝填充,亮蓝描边
@@ -285,7 +302,7 @@ class DrawRegionMap {
 
     /**
      * 设置只读状态区域图层(多个 polygon,不可编辑)
-     * @param {Array<{ geometry: string, status?: 'resolved' | 'unresolved' }>} regions
+     * @param {Array<{ geometry: string, status?: 'resolved' | 'unresolved', label?: string, displayMode?: string }>} regions
      *
      * 使用示例:
      * drawRegionMap.setStatusRegions([
@@ -313,6 +330,8 @@ class DrawRegionMap {
                 const feature = new Feature({ geometry });
                 feature.set("status", region.status || "unresolved");
                 feature.set("updatedTime", region.updatedTime);
+                feature.set("label", region.label || "");
+                feature.set("displayMode", region.displayMode || "");
                 this.staticRegionLayer.addFeature(feature);
             } catch (e) {
                 // 单个区域解析失败时忽略

+ 1 - 0
src/views/old_mini/monitor/index.vue

@@ -226,6 +226,7 @@ onMounted(() => {
 
 const changeGarden = ({ id }) => {
     gardenId.value = id;
+    activeVariety.value = 0;
     // 更新 store 中的状态
     store.commit("home/SET_GARDEN_ID", id);
     getVarietyTabs();

+ 1 - 1
src/views/old_mini/monitor/subPages/agriculturalDetail.vue

@@ -7,7 +7,7 @@
             <div class="card-wrap">
                 <div class="card-box sampling-card">
                     <div class="sampling-title">采样时间: {{ imgInfo.samplingTime }}</div>
-                    <div class="sampling-desc">本次飞巡采样{{ imgInfo.regions }},拍摄了 {{ imgInfo.total }} 棵树</div>
+                    <div class="sampling-desc">本次采样{{ imgInfo.regions }},拍摄了 {{ imgInfo.total }} 棵树</div>
                 </div>
             </div>
 

+ 77 - 7
src/views/old_mini/monitor/subPages/darwArea.vue

@@ -57,23 +57,76 @@ const isGuidedFlow = ref(false);
 // 每个品种(tab)对应的地块数据草稿:key 为 tab index,value 为 { geomArr, geom }
 const regionsDraftByIndex = ref({});
 const submitting = ref(false);
+// 仅在首次进入页面时根据路由 varietyId 定位一次,避免后续刷新覆盖当前流程进度
+const hasAppliedInitialVariety = ref(false);
 
 const type = ref(null);
 const varietyTabs = ref([]);
 const activeVariety = ref(0);
 const regionGeom = ref(null);
 
+const isValidGeom = (geom) => {
+    if (typeof geom !== "string") return false;
+    const normalized = geom.trim();
+    if (!normalized) return false;
+    if (["[]", "{}", "null", "undefined", '""'].includes(normalized)) return false;
+    return true;
+};
+
+const resolveInitialVarietyIndex = (regionList) => {
+    const queryVarietyId = route.query?.varietyId;
+    if (!queryVarietyId || !Array.isArray(regionList) || regionList.length === 0) return 0;
+    const queryId = String(queryVarietyId).trim();
+    const matchedIndex = regionList.findIndex((item) => {
+        const candidateIds = [item?.typeId, item?.varietyId, item?.id]
+            .filter((v) => v !== undefined && v !== null)
+            .map((v) => String(v).trim());
+        return candidateIds.includes(queryId);
+    });
+    return matchedIndex >= 0 ? matchedIndex : 0;
+};
+
+const getReadonlyVarietyRegions = (activeIndex) => {
+    if (!Array.isArray(varietyTabs.value) || varietyTabs.value.length === 0) return [];
+    const readonlyRegions = [];
+    for (let i = 0; i < varietyTabs.value.length; i++) {
+        if (i === activeIndex) continue;
+        const tab = varietyTabs.value[i];
+        if (!tab) continue;
+        const draftGeom = regionsDraftByIndex.value[i]?.geom;
+        const geometry = draftGeom || tab.geom;
+        if (!isValidGeom(geometry)) continue;
+        readonlyRegions.push({
+            geometry,
+            label: tab.regionName || "",
+            displayMode: "readonlyVariety",
+        });
+    }
+    return readonlyRegions;
+};
+
+const renderReadonlyVarietyRegions = (activeIndex) => {
+    if (drawRegionMap && typeof drawRegionMap.setStatusRegions === "function") {
+        drawRegionMap.setStatusRegions(getReadonlyVarietyRegions(activeIndex));
+    }
+};
+
 const handleVarietyClick = (tab, index) => {
     activeVariety.value = index;
-    regionGeom.value = tab.geom;
+    const currentGeom = regionsDraftByIndex.value[index]?.geom || tab.geom;
+    regionGeom.value = currentGeom;
+    // 地图尚未初始化时,仅更新状态,不做图层绘制,避免首次进入重复叠加
+    if (!drawRegionMap.kmap) return;
 
-    // 每次切换/重绘地块前先清空,避免多边形叠加残留
+    // 每次切换/重绘地块前先清空“当前可编辑图层”
     if (drawRegionMap.kmap?.polygonLayer?.source) {
         drawRegionMap.kmap.polygonLayer.source.clear();
     }
-    if (tab.geom?.length) {
+    // 渲染当前品种之前的已勾画地块(灰色只读 + 品种名)
+    renderReadonlyVarietyRegions(index);
+    if (isValidGeom(currentGeom)) {
         setTimeout(() => {
-            drawRegionMap.setAreaGeometry([tab.geom], true);
+            drawRegionMap.setAreaGeometry([currentGeom], true);
         }, 50);
     }
 };
@@ -93,8 +146,9 @@ async function fetchFarmSubjectDetail() {
 
     
     if (data?.regionList?.length) {
-        if(route.query?.varietyId) {
-            activeVariety.value = data?.regionList.findIndex(item => item.typeId == route.query?.varietyId);
+        if (!hasAppliedInitialVariety.value && route.query?.varietyId) {
+            activeVariety.value = resolveInitialVarietyIndex(data?.regionList);
+            hasAppliedInitialVariety.value = true;
         }
 
         point.value = data.farmLocation;
@@ -105,6 +159,7 @@ async function fetchFarmSubjectDetail() {
 
 onActivated(async () => {
     activeVariety.value = 0;
+    hasAppliedInitialVariety.value = false;
     // keep-alive 场景下再次进入前先销毁旧地图实例,避免重复 init 导致图层状态错位
     if (drawRegionMap.kmap) {
         drawRegionMap.abortOngoingDrawSketch?.();
@@ -117,17 +172,29 @@ onActivated(async () => {
 
     drawRegionMap.initMap(point.value, mapContainer.value, editable, true, true);
 
+    // 首次进入时,fetch 阶段可能先于地图初始化触发了 tab 渲染;
+    // 这里在地图可用后再补渲一次,确保“其他品种只读地块”能显示出来
+    if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
+        handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
+    }
+
 
     // 从编辑态进入仅查看时,需重新初始化为不可编辑
     if (viewOnly.value && drawRegionMap.kmap && drawRegionMap.editable) {
         drawRegionMap.destroyMap();
         drawRegionMap.initMap(point.value, mapContainer.value, false, true, false);
+        if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
+            handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
+        }
     }
 
     // 从仅查看进入勾画(编辑)时,需重新初始化为可编辑
     if (!viewOnly.value && drawRegionMap.kmap && !drawRegionMap.editable) {
         drawRegionMap.destroyMap();
         drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
+        if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
+            handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
+        }
     }
 
     // 查看模式下已通过 fitAllRegions 适配;编辑模式再设置地图中心
@@ -308,7 +375,10 @@ const handleEditRegion = () => {
 
         drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
 
-        if (varietyTabs.value.length && regionGeom.value.length) {
+        // 切到编辑态后立即补渲“其他品种只读地块”,避免首次点击编辑时不显示
+        renderReadonlyVarietyRegions(activeVariety.value);
+
+        if (varietyTabs.value.length && regionGeom.value?.length) {
             drawRegionMap.setAreaGeometry([regionGeom.value],true);
         }
         // 切到编辑态后,统一自适应到所有区域,避免画面偏移

+ 39 - 13
src/views/old_mini/monitor/subPages/farmInfo.vue

@@ -20,7 +20,7 @@
                         <span class="info-label">联系人:</span>
                         <span class="info-value">{{ farmInfo.contactName }}</span>
                     </div>
-                    <div class="info-row">
+                    <div class="info-row center-row">
                         <span class="info-label">联系电话:</span>
                         <span class="info-value">{{ farmInfo.contactPhone }}</span>
                     </div>
@@ -30,6 +30,12 @@
                             <span v-for="crop in farmInfo.regionList" :key="crop.regionId" class="crop-tag">
                                 {{ crop.regionName }}
                             </span>
+                            <div class="add-variety-btn" @click="handleAddVariety">
+                                <el-icon size="12">
+                                    <Plus />
+                                </el-icon>
+                                <span>新增品种</span>
+                            </div>
                         </div>
                     </div>
                     <div class="info-row">
@@ -222,6 +228,10 @@ const handleEditMap = () => {
         ElMessage.warning("暂无种植作物,无法编辑地块");
     }
 };
+
+const handleAddVariety = () => {
+    router.push(`/interaction?addVariety=true&subjectId=${route.query.subjectId}`);
+};
 </script>
 
 <style lang="scss" scoped>
@@ -302,6 +312,9 @@ const handleEditMap = () => {
                     display: flex;
                     align-items: flex-start;
                     margin-bottom: 6px;
+                    &.center-row {
+                        align-items: center;
+                    }
 
                     .info-label {
                         min-width: 80px;
@@ -310,16 +323,26 @@ const handleEditMap = () => {
 
                     .crop-tags {
                         display: flex;
+                        align-items: center;
                         flex-wrap: wrap;
                         gap: 6px;
-                    }
-
-                    .crop-tag {
-                        padding: 2px 8px;
-                        background: #E8F3FF;
-                        color: #2199f8;
-                        border-radius: 2px;
-                        font-size: 12px;
+                        .crop-tag {
+                            padding: 2px 8px;
+                            background: #E8F3FF;
+                            color: #2199f8;
+                            border-radius: 2px;
+                            font-size: 12px;
+                        }
+                        .add-variety-btn {
+                            display: flex;
+                            align-items: center;
+                            gap: 3px;
+                            padding: 3px 6px;
+                            border: 1px solid #DCDCDC;
+                            border-radius: 3px;
+                            background: #fff;
+                            font-size: 12px;
+                        }
                     }
 
                     .problem-tags {
@@ -344,15 +367,18 @@ const handleEditMap = () => {
                         padding: 6px;
                         border-radius: 4px;
                         border: 0.5px solid rgba(33, 153, 248, 0.2);
-                        display: flex;
-                        flex-wrap: wrap;
-                        justify-content: space-between;
+                        display: grid;
+                        grid-template-columns: repeat(2, minmax(0, 1fr));
+                        column-gap: 12px;
+                        row-gap: 4px;
 
                         .device-item {
                             display: flex;
                             align-items: center;
+                            justify-content: space-between;
                             padding: 4px 0;
-                            gap: 15px;
+                            min-width: 0;
+                            gap: 8px;
                             font-size: 13px;
                             color: #1D2129;