소스 검색

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

lxf 4 일 전
부모
커밋
94b469c5e6

+ 2 - 2
src/api/modules/monitor.js

@@ -115,7 +115,7 @@ module.exports = {
     },
     //农情照片分页查询
     getFarmImagePage: {
-        url: config.base_dev_url + "image/v2/farmImagePage",
-        type: "post",
+        url: config.base_dev_url + "container_crop_archive/farmImagePage",
+        type: "get",
     },
 };

BIN
src/assets/img/map/map_point.png


+ 19 - 6
src/components/pageComponents/ArchivesFarmTimeLine.vue

@@ -833,9 +833,7 @@ const handleStatusDetail = (fw) => {
             router.push({
                 path: "/agricultural_detail",
                 query: {
-                    farmId: props.farmId,
-                    regionId: props.regionId,
-                    date: fw?.createTime?.slice(0, 10),
+                    id: fw?.id,
                     content: fw?.content
                 },
             });
@@ -1034,6 +1032,15 @@ const agriRecordHasPendingAuthInPhenology = (phenology) => {
     );
 };
 
+// 单行生育期下是否有待认证农事(仅农事记录页)
+const reproductiveHasPendingAuthFarmWork = (reproductive) => {
+    if (props.pageType !== "agri_record") return false;
+    return (Array.isArray(reproductive?.farmWorkArrangeList) ? reproductive.farmWorkArrangeList : []).some((fw) => {
+        const s = fw?.flowStatus;
+        return s === 3 || s === "3";
+    });
+};
+
 // 物候期底色/节气规则(不含「农事卡片日期」「待认证」扩展)
 const shouldShowBlueBase = (phenology) => {
     // 优先使用物候期真实日期判断:未来日期不应显示蓝色
@@ -1093,11 +1100,17 @@ const shouldShowBlue = (phenology) => {
     return shouldShowBlueBase(phenology);
 };
 
-// 左侧生育期/物候期文案:农事记录下物候期内有待认证则该段内所有生育期/物候期名称均蓝;否则本行农事≤今天或节气规则
+// 左侧生育期/物候期文案:
+// 1) 本行有农事<=今天:蓝;
+// 2) 本行全部有效日期农事均为未来且本行无待认证:灰(与右侧 future-card 一致);
+// 3) 农事记录下物候期内有待认证:蓝;
+// 4) 其余走节气/日期底色规则。
 const shouldShowBlueLeft = (phenology, reproductive) => {
-    if (agriRecordHasPendingAuthInPhenology(phenology)) return true;
     if (reproductiveHasFarmWorkOnOrBeforeToday(reproductive)) return true;
-    if (reproductiveAllDatedFarmWorksStrictlyFuture(reproductive)) return false;
+    if (reproductiveAllDatedFarmWorksStrictlyFuture(reproductive) && !reproductiveHasPendingAuthFarmWork(reproductive)) {
+        return false;
+    }
+    if (agriRecordHasPendingAuthInPhenology(phenology)) return true;
     return shouldShowBlueBase(phenology);
 };
 

+ 204 - 0
src/components/popup/harvestTimePopup.vue

@@ -0,0 +1,204 @@
+<template>
+    <popup
+        v-model:show="showValue"
+        class="harvest-time-popup"
+        closeable
+    >
+        <div class="popup-content">
+            <div
+                v-for="(item, index) in innerCrops"
+                :key="index"
+                class="crop-row"
+            >
+                <div class="crop-label">
+                    <span class="crop-name">{{ item.name }}</span>
+                    <span>的成熟收获时间</span>
+                </div>
+                <div class="date-inputs">
+                    <div class="date-item">
+                        <el-select
+                            v-model="item.month"
+                            filterable
+                            size="large"
+                            placeholder="请选择月份"
+                            class="month-select"
+                            @change="handleMonthChange(item)"
+                        >
+                            <el-option
+                                v-for="m in 12"
+                                :key="m"
+                                :label="`${m}月`"
+                                :value="m"
+                            />
+                        </el-select>
+                    </div>
+                    <div class="date-item">
+                        <el-select
+                            v-model="item.day"
+                            filterable
+                            size="large"
+                            placeholder="请选择日期"
+                            class="day-select"
+                        >
+                            <el-option
+                                v-for="d in getDayCount(item.month)"
+                                :key="d"
+                                :label="`${d}日`"
+                                :value="d"
+                            />
+                        </el-select>
+                    </div>
+                </div>
+            </div>
+
+            <div class="btn-confirm" @click="handleConfirm">
+                确认
+            </div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { Popup } from "vant";
+import { computed, watch, ref } from "vue";
+import { ElMessage } from "element-plus";
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+    // [{ name: '荔枝', month: '', day: '' }]
+    crops: {
+        type: Array,
+        default: () => [],
+    },
+});
+
+const emit = defineEmits(["update:show", "confirm"]);
+
+const showValue = computed({
+    get: () => props.show,
+    set: (val) => emit("update:show", val),
+});
+
+// 内部可编辑数据,避免直接修改 props
+const innerCrops = ref([]);
+
+const isLeapYear = (year) => {
+    return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
+};
+
+const getDayCount = (month) => {
+    const monthNum = Number(month);
+    if (!monthNum) return 30;
+    if (monthNum === 2) {
+        const currentYear = new Date().getFullYear();
+        return isLeapYear(currentYear) ? 29 : 28;
+    }
+    if ([4, 6, 9, 11].includes(monthNum)) return 30;
+    return 31;
+};
+
+const handleMonthChange = (item) => {
+    const maxDay = getDayCount(item.month);
+    if (Number(item.day) > maxDay) {
+        item.day = "";
+    }
+};
+
+watch(
+    () => props.crops,
+    (val) => {
+        innerCrops.value = (val || []).map((item) => ({
+            name: item.name,
+            month: item.month || "",
+            day: item.day || "",
+        }));
+    },
+    { immediate: true, deep: true }
+);
+
+const handleConfirm = () => {
+    const invalid = innerCrops.value.some(
+        (item) => !item.name || !item.month || !item.day
+    );
+    if (invalid) {
+        ElMessage.warning("请先将所有作物的成熟时间填写完整");
+        return;
+    }
+    emit("confirm", innerCrops.value);
+};
+</script>
+
+<style scoped lang="scss">
+.harvest-time-popup {
+    width: 100%;
+    padding: 24px 18px 20px;
+    border-radius: 12px;
+    background: linear-gradient(0deg, #ffffff 70%, #d1ebff 100%);
+
+    ::v-deep {
+        .van-popup__close-icon {
+            color: #333333;
+        }
+
+        .el-input__wrapper {
+            padding: 4px 8px;
+            box-shadow: none;
+            border-radius: 6px;
+        }
+
+        .el-input__inner {
+            text-align: center;
+        }
+    }
+
+    .popup-content {
+        display: flex;
+        flex-direction: column;
+        align-items: stretch;
+    }
+
+    .crop-row + .crop-row {
+        margin-top: 16px;
+    }
+
+    .crop-label {
+        margin-bottom: 8px;
+        font-size: 14px;
+        color: #333;
+
+        .crop-name {
+            color: #0089f5;
+            margin-right: 4px;
+        }
+    }
+
+    .date-inputs {
+        display: flex;
+        justify-content: space-between;
+        gap: 12px;
+    }
+
+    .date-item {
+        flex: 1;
+        display: flex;
+        align-items: center;
+        background: #ffffff;
+        border-radius: 6px;
+    }
+
+    .btn-confirm {
+        margin-top: 24px;
+        height: 40px;
+        line-height: 40px;
+        text-align: center;
+        border-radius: 24px;
+        background: #2199f8;
+        color: #ffffff;
+        font-size: 16px;
+    }
+}
+</style>
+

+ 6 - 2
src/utils/ol-map/Map.js

@@ -204,6 +204,10 @@ class Map {
 		const styles = [];
 		const type = feature.getGeometry().getType();
 		const coord = feature.getGeometry().getCoordinates();
+
+		// 允许通过 Map.drawStyleColors 全局控制勾画时的颜色(未设置时仍使用原来的蓝色)
+		const lineColor = (Map.drawStyleColors && Map.drawStyleColors.line) || '#2199F8';
+		const vertexColor = (Map.drawStyleColors && Map.drawStyleColors.vertex) || '#2199F8';
 		for (let i = 0; i < coord.length - 1; i++) {
 			if (i % 2) {
 				styles.push(
@@ -212,7 +216,7 @@ class Map {
 						image: new Circle({
 							radius: 4,
 							fill: new Fill({
-								color: '#2199F8'
+								color: vertexColor
 							}),
 							stroke: new Stroke({
 								color: '#fff',
@@ -241,7 +245,7 @@ class Map {
 					new Style({
 						geometry: new LineString([coord[i], coord[i + 1]]),
 						stroke: new Stroke({
-							color: '#2199F8',
+							color: lineColor,
 							width: 2
 						})
 					})

+ 11 - 3
src/views/old_mini/interactionList/index.vue

@@ -38,8 +38,8 @@
                 <!-- 未上传状态内容 -->
                 <div class="uploaded-content" v-show="item.questionStatus === 3 || item.expanded">
                     <div class="content-wrapper">
-                        <span>{{ item.remark }}</span>
-                        <text-ellipsis class="item-desc" rows="0" :content="item.reason" expand-text="展开"
+                        <span>{{ item.reason }}</span>
+                        <text-ellipsis class="item-desc" rows="0" :content="item.remark" expand-text="展开"
                             collapse-text="收起" />
                         <div class="tip-box">如果不确定是否发生,直接上传照片即可</div>
                         <div class="example-wrapper">
@@ -118,7 +118,8 @@
                         <div class="draw-region-btn" v-if="item.rangeWkt && item.questionStatus !== 3"
                             @click="handleViewRegion(item)">
                             查看发生区域</div>
-                    </div>
+
+                        </div>
 
                     <!-- 输入框 -->
                     <div class="input-wrapper" v-if="item.questionStatus === 3 || item.replyText">
@@ -126,6 +127,8 @@
                             clearable />
                     </div>
 
+                    <div class="cancel-text" v-if="item.isConfirmed && item.questionStatus === 2">{{ item.cancelButtonName }}</div>
+
                     <!-- 按钮区域 -->
                     <div class="button-group" v-show="item.questionStatus === 3">
                         <div
@@ -1145,6 +1148,11 @@ const handleSubmitAll = () => {
                 margin: 12px 0;
             }
 
+            .cancel-text{
+                color: #969696;
+                margin-top: 8px;
+            }
+
             .button-group {
                 display: flex;
                 gap: 12px;

+ 10 - 7
src/views/old_mini/interactionList/map/drawRegionMap.js

@@ -30,9 +30,10 @@ class DrawRegionMap {
             style: () => {
                 return new Style({
                     image: new Icon({
-                        src: require("@/assets/img/home/garden-point.png"),
+                        // src: require("@/assets/img/home/garden-point.png"),
+                        src: require("@/assets/img/map/map_point.png"),
                         scale: 0.5,
-                        anchor: [0.5, 0.5],
+                        // anchor: [0.5, 0.5],
                     }),
                 });
             },
@@ -45,14 +46,15 @@ class DrawRegionMap {
                 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 }),
+                        fill: new Fill({ color: "rgba(0, 57, 44, 0.5)" }),
+                        stroke: new Stroke({ color: "#18AA8B", 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)" }),
+                            backgroundFill: new Fill({ color: "rgba(0, 57, 44, 0.85)" }),
                             padding: [2, 6, 2, 6],
                         }),
                     });
@@ -166,13 +168,14 @@ class DrawRegionMap {
             let f = new Feature({ geometry: geometry })
             // 只读模式下,为多边形单独设置样式:仅填充+边框 + 面积文本,不显示可拖动的顶点小圆点
             if (!this.editable) {
+                // 查看模式下单块区域展示:统一品种区样式
                 const styles = [
                     new Style({
                         fill: new Fill({
-                            color: "rgba(0, 0, 0, 0.5)",
+                            color: "rgba(0, 57, 44, 0.5)",
                         }),
                         stroke: new Stroke({
-                            color: "#2199F8",
+                            color: "#18AA8B",
                             width: 2,
                         }),
                     }),

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

@@ -56,9 +56,7 @@ onMounted(() => {
 const imgInfo = ref({});
 const getFarmImagePage = () => {
     VE_API.monitor.getFarmImagePage({
-        farmId: route.query.farmId,
-        regionId: route.query.regionId,
-        uploadDate: route.query.date,
+        id: route.query.id
     }).then((res) => {
         if (res.code === 0) {
             imgInfo.value = res.data;

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

@@ -1,6 +1,17 @@
 <template>
     <div class="edit-map">
         <custom-header @goback="goBack" :isGoBack="true" :name="viewOnly ? '查看区域' : '勾选地块'"></custom-header>
+        <div class="region-type-tabs">
+            <div
+                v-for="item in regionTypeTabs"
+                :key="item.value"
+                class="region-type-tab"
+                :class="{ 'region-type-tab--active': activeRegionType === item.value }"
+                @click="handleRegionTypeClick(item)"
+            >
+                {{ item.label }}
+            </div>
+        </div>
         <div class="variety-tabs" v-if="varietyTabs.length > 0">
             <div v-for="(v, index) in varietyTabs" :key="index" class="variety-tab"
                 :class="{ 'variety-tab--active': activeVariety === index }" @click="handleVarietyClick(v, index)">
@@ -40,10 +51,16 @@ import SaveRegionSuccessPopup from "@/components/popup/saveRegionSuccessPopup.vu
 import { ref, computed, onActivated, onDeactivated, nextTick } from "vue";
 import { useStore } from "vuex";
 import DrawRegionMap from "../../interactionList/map/drawRegionMap.js";
+import { Map as KMapMap } from "@/utils/ol-map/KMap";
 import { useRouter, useRoute } from "vue-router";
 import { convertPointToArray } from "@/utils/index";
 import { ElMessage, ElMessageBox } from "element-plus";
 import TipPopup from "@/components/popup/tipPopup.vue";
+import Style from "ol/style/Style";
+import { Fill, Stroke, Circle, Text } from "ol/style.js";
+import { Point } from "ol/geom";
+import * as proj from "ol/proj";
+import { getArea } from "ol/sphere.js";
 
 const store = useStore();
 const router = useRouter();
@@ -65,6 +82,125 @@ const varietyTabs = ref([]);
 const activeVariety = ref(0);
 const regionGeom = ref(null);
 
+const regionTypeTabs = [
+    { label: "品种区", value: "variety" },
+    { label: "异常问题区", value: "abnormal" },
+    { label: "环境问题区", value: "environment" },
+    { label: "休眠区", value: "sleep" },
+];
+const activeRegionType = ref("variety");
+
+const handleRegionTypeClick = (item) => {
+    activeRegionType.value = item.value;
+    if (drawRegionMap.kmap) {
+        applyRegionStyles();
+    }
+};
+
+const createPolygonStyleFunc = (fillColor, strokeColor) => {
+    return (feature) => {
+        const styles = [];
+        const coord = feature.getGeometry().getCoordinates()[0];
+        for (let i = 0; i < coord[0].length - 1; i++) {
+            if (i % 2) {
+                styles.push(
+                    new Style({
+                        geometry: new Point(coord[0][i]),
+                        image: new Circle({
+                            radius: 4,
+                            fill: new Fill({
+                                color: strokeColor,
+                            }),
+                            stroke: new Stroke({
+                                color: "#fff",
+                                width: 1,
+                            }),
+                        }),
+                    })
+                );
+            } else {
+                styles.push(
+                    new Style({
+                        geometry: new Point(coord[0][i]),
+                        image: new Circle({
+                            radius: 6,
+                            fill: new Fill({
+                                color: "#fff",
+                            }),
+                        }),
+                    })
+                );
+            }
+        }
+        const fillStyle = new Style({
+            fill: new Fill({
+                color: fillColor,
+            }),
+            stroke: new Stroke({
+                color: strokeColor,
+                width: 2,
+            }),
+        });
+        let geom = feature.getGeometry().clone();
+        geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:38572"));
+        let area = getArea(geom);
+        area = (area + area / 2) / 1000;
+        const areaValStyle = new Style({
+            text: new Text({
+                font: "16px sans-serif",
+                text: area.toFixed(2) + "亩",
+                fill: new Fill({ color: "#fff" }),
+            }),
+        });
+        styles.push(fillStyle, areaValStyle);
+        return styles;
+    };
+};
+
+const applyRegionStyles = () => {
+    const kmap = drawRegionMap.kmap;
+    if (!kmap) return;
+
+    let lineColor = "#2199F8";
+    let vertexColor = "#2199F8";
+    let fillColor = [0, 0, 0, 0.5];
+    let strokeColor = "#2199F8";
+
+    if (activeRegionType.value === "variety") {
+        lineColor = "#18AA8B";
+        vertexColor = "#18AA8B";
+        fillColor = [0, 57, 44, 0.5];
+        strokeColor = "#18AA8B";
+    } else if (activeRegionType.value === "abnormal") {
+        lineColor = "#E03131";
+        vertexColor = "#E03131";
+        fillColor = [100, 0, 0, 0.5];
+        strokeColor = "#E03131";
+    }else if (activeRegionType.value === "environment") {
+        lineColor = "#FDCF7F";
+        vertexColor = "#FDCF7F";
+        fillColor = [151, 96, 0, 0.5];
+        strokeColor = "#FDCF7F";
+    }else{
+        lineColor = "#A6A6A6";
+        vertexColor = "#A6A6A6";
+        fillColor = [166, 166, 166, 0.25];
+        strokeColor = "#A6A6A6";
+    }
+    
+
+    // 勾画进行中:通过 Map.drawStyleColors 影响底层 drawStyleFunc 颜色
+    KMapMap.drawStyleColors = {
+        line: lineColor,
+        vertex: vertexColor,
+    };
+
+    kmap.polygonStyle = createPolygonStyleFunc(fillColor, strokeColor);
+    if (kmap.polygonLayer?.layer && typeof kmap.polygonLayer.layer.setStyle === "function") {
+        kmap.polygonLayer.layer.setStyle(kmap.polygonStyle);
+    }
+};
+
 const isValidGeom = (geom) => {
     if (typeof geom !== "string") return false;
     const normalized = geom.trim();
@@ -171,6 +307,7 @@ onActivated(async () => {
     await fetchFarmSubjectDetail();
 
     drawRegionMap.initMap(point.value, mapContainer.value, editable, true, true);
+    applyRegionStyles();
 
     // 首次进入时,fetch 阶段可能先于地图初始化触发了 tab 渲染;
     // 这里在地图可用后再补渲一次,确保“其他品种只读地块”能显示出来
@@ -183,6 +320,7 @@ onActivated(async () => {
     if (viewOnly.value && drawRegionMap.kmap && drawRegionMap.editable) {
         drawRegionMap.destroyMap();
         drawRegionMap.initMap(point.value, mapContainer.value, false, true, false);
+        applyRegionStyles();
         if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
             handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
         }
@@ -192,6 +330,7 @@ onActivated(async () => {
     if (!viewOnly.value && drawRegionMap.kmap && !drawRegionMap.editable) {
         drawRegionMap.destroyMap();
         drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
+        applyRegionStyles();
         if (varietyTabs.value.length > 0 && varietyTabs.value[activeVariety.value]) {
             handleVarietyClick(varietyTabs.value[activeVariety.value], activeVariety.value);
         }
@@ -377,6 +516,7 @@ const handleEditRegion = () => {
         }
 
         drawRegionMap.initMap(point.value, mapContainer.value, true, true, true);
+        applyRegionStyles();
 
         // 切到编辑态后立即补渲“其他品种只读地块”,避免首次点击编辑时不显示
         renderReadonlyVarietyRegions(activeVariety.value);
@@ -409,6 +549,29 @@ const handleTipConfirm = () => {
     height: 100vh;
     overflow: hidden;
 
+    .region-type-tabs {
+        display: flex;
+        align-items: center;
+        background: #f4f4f4;
+        margin: 10px 10px 0;
+        padding: 3px;
+        border-radius: 4px;
+        box-sizing: border-box;
+
+        .region-type-tab {
+            flex: 1;
+            text-align: center;
+            padding: 5px 0;
+            color: #767676;
+        }
+
+        .region-type-tab--active {
+            background: #ffffff;
+            border-radius: 4px;
+            color: #0D0D0D;
+        }
+    }
+
     .variety-tabs {
         display: flex;
         align-items: center;
@@ -422,22 +585,23 @@ const handleTipConfirm = () => {
         .variety-tab {
             padding: 4px 12px;
             border-radius: 2px;
-            color: #767676;
-            background: #fff;
-            border: 0.5px solid rgba(174, 174, 174, 0.8);
+            color: #575757;
+            background: #F4F4F4;
+            border: 1px solid transparent;
             white-space: nowrap;
         }
 
         .variety-tab--active {
-            background: #2199F8;
-            color: #ffffff;
+            background: rgba(33, 153, 248, 0.1);
+            color: #2199F8;
+            border: 1px solid #2199F8;
         }
     }
 
     .edit-map-content {
         width: 100%;
         margin-top: 10px;
-        height: calc(100% - 58px);
+        height: calc(100% - 202px);
         position: relative;
 
         .edit-map-tip {
@@ -459,7 +623,7 @@ const handleTipConfirm = () => {
 
         .edit-map-footer {
             position: absolute;
-            bottom: 110px;
+            bottom: 35px;
             left: 12px;
             width: calc(100% - 24px);
             display: flex;