|
|
@@ -1,118 +1,558 @@
|
|
|
<template>
|
|
|
<div class="edit-map">
|
|
|
<custom-header :name="viewOnly ? '查看区域' : '勾画区域'"></custom-header>
|
|
|
+ <div class="region-type-tabs">
|
|
|
+ <div v-for="item in regionTypeTabs" :key="item.code" class="region-type-tab"
|
|
|
+ :class="{ 'region-type-tab--active': activeRegionType === item.code }">
|
|
|
+ {{ item.name }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="variety-tabs" v-if="varietyTabs.length > 0 && activeRegionType !== 'DORMANCY'">
|
|
|
+ <div v-for="(v, index) in varietyTabs" :key="index" class="variety-tab"
|
|
|
+ :class="{ 'variety-tab--active': activeVariety === index }">
|
|
|
+ {{ v.regionName || v.problemZoneTypeName }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<div class="edit-map-content">
|
|
|
<div class="edit-map-tip" v-if="!viewOnly">操作提示:拖动圆点,即可调整地块边界</div>
|
|
|
<div class="map-container" ref="mapContainer"></div>
|
|
|
- <div class="edit-map-footer">
|
|
|
+ <div class="edit-map-footer" :style="{ 'bottom': activeRegionType !== 'DORMANCY' ? '85px' : '59px' }">
|
|
|
<div class="footer-back" @click="goBack">
|
|
|
<img class="back-icon" src="@/assets/img/home/go-back.png" alt="" />
|
|
|
</div>
|
|
|
<div class="edit-map-footer-btn" v-if="!viewOnly">
|
|
|
<div class="btn-delete" @click="deletePolygon">删除地块</div>
|
|
|
- <div class="btn-cancel" @click="goBack">取消</div>
|
|
|
- <div class="btn-confirm" @click="confirm">确认</div>
|
|
|
+ <!-- <div class="btn-cancel" @click="goBack">取消</div> -->
|
|
|
+ <div class="btn-confirm" @click="confirm">确认区域</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <popup v-model:show="showAbnormalTypePopup" round closeable class="abnormal-popup"
|
|
|
+ :close-on-click-overlay="true">
|
|
|
+ <div class="abnormal-popup-content">
|
|
|
+ <div class="abnormal-popup-title">请选择异常类型</div>
|
|
|
+ <el-select v-model="selectedAbnormalType" class="abnormal-type-select" placeholder="请选择类型" size="large">
|
|
|
+ <el-option v-for="item in abnormalTypeOptions" :key="item.value" :label="item.name"
|
|
|
+ :value="item.value" />
|
|
|
+ </el-select>
|
|
|
+ <div class="abnormal-popup-confirm" @click="handleConfirmUpload">确认上传</div>
|
|
|
+ </div>
|
|
|
+ </popup>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import customHeader from "@/components/customHeader.vue";
|
|
|
import { ref, computed, onMounted, onActivated, onDeactivated } from "vue";
|
|
|
+import { Popup } from "vant";
|
|
|
import DrawRegionMap from "./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 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 router = useRouter();
|
|
|
const route = useRoute();
|
|
|
const mapContainer = ref(null);
|
|
|
const drawRegionMap = new DrawRegionMap();
|
|
|
+const DEFAULT_MAP_CENTER = "POINT (113.6142086995688 23.585836479509055)";
|
|
|
|
|
|
const type = ref(null);
|
|
|
const viewOnly = computed(() => route.query.viewOnly === "1" || route.query.viewOnly === "true");
|
|
|
+const showDataMode = computed(() => route.query.showData === "1" || route.query.showData === "true");
|
|
|
+const showAbnormalTypePopup = ref(false);
|
|
|
+const selectedAbnormalType = ref("");
|
|
|
+const abnormalTypeOptions = [{
|
|
|
+ name: "病害",
|
|
|
+ value: "DISEASE"
|
|
|
+}, {
|
|
|
+ name: "虫害",
|
|
|
+ value: "PEST"
|
|
|
+}, {
|
|
|
+ name: "不确定",
|
|
|
+ value: "UNCERTAIN"
|
|
|
+}];
|
|
|
|
|
|
onMounted(() => {
|
|
|
type.value = route.query.type;
|
|
|
- const point = route.query.mapCenter || "POINT (113.6142086995688 23.585836479509055)";
|
|
|
+ const point = route.query.mapCenter || DEFAULT_MAP_CENTER;
|
|
|
const editable = !viewOnly.value;
|
|
|
const showPoint = !viewOnly.value;
|
|
|
drawRegionMap.initMap(point, mapContainer.value, editable, true, showPoint);
|
|
|
+ applyRegionStyles();
|
|
|
+});
|
|
|
+
|
|
|
+onActivated(async () => {
|
|
|
+ const point = route.query.mapCenter || DEFAULT_MAP_CENTER;
|
|
|
+ await fetchRegionInfo();
|
|
|
+
|
|
|
+ syncMapEditableState(point);
|
|
|
+ const polygonData = route.query.polygonData;
|
|
|
+ const rawRangeWkt = route.query.rangeWkt;
|
|
|
+ const rangeWkt = rawRangeWkt ? decodeURIComponent(rawRangeWkt) : null;
|
|
|
+ const shouldRenderFromApi = showDataMode.value && !rangeWkt && !polygonData;
|
|
|
+
|
|
|
+ applyRegionStyles();
|
|
|
+ if (shouldRenderFromApi) {
|
|
|
+ renderRegionsFromApi();
|
|
|
+ }
|
|
|
+ if (rangeWkt) {
|
|
|
+ renderRegionsFromRouteRangeWkt(rangeWkt);
|
|
|
+ }
|
|
|
+ restoreEditablePolygonFromRoute(polygonData);
|
|
|
+ updateMapCenterForEditMode(point);
|
|
|
|
|
|
- // 地图初始化之后(比如 initPreviewMap 里)
|
|
|
- // const regions = [
|
|
|
- // {
|
|
|
- // geometry:
|
|
|
- // "MULTIPOLYGON(((113.61674040430906 23.586573370597367,113.61586610436014 23.585922976493354,113.61710291900188 23.58486741952544,113.61770000158238 23.585651090473736,113.61674040430906 23.586573370597367)))",
|
|
|
- // status: "unresolved", // 未解决(蓝色)
|
|
|
- // },
|
|
|
- // {
|
|
|
- // geometry:
|
|
|
- // "MULTIPOLYGON(((113.61516640298626 23.588441931082958,113.61445736699218 23.58799411906573,113.61572616841707 23.586954554834552,113.61642987338976 23.588180707433526,113.61516640298626 23.588441931082958)))",
|
|
|
- // status: "resolved", // 已解决(灰色)
|
|
|
- // },
|
|
|
- // ];
|
|
|
-
|
|
|
- // drawRegionMap.setStatusRegions(regions);
|
|
|
+ applyRegionStyles();
|
|
|
});
|
|
|
|
|
|
-onActivated(() => {
|
|
|
- const point = route.query.mapCenter || "POINT (113.6142086995688 23.585836479509055)";
|
|
|
+const regionTypeTabs = ref([]);
|
|
|
+const activeRegionType = ref("variety");
|
|
|
+const regionInfo = ref([]);
|
|
|
+const formatDateToYmdDash = (raw) => {
|
|
|
+ const sourceDate = raw ? new Date(raw) : new Date();
|
|
|
+ const resolvedDate = Number.isNaN(sourceDate.getTime()) ? new Date() : sourceDate;
|
|
|
+ const y = resolvedDate.getFullYear();
|
|
|
+ const m = String(resolvedDate.getMonth() + 1).padStart(2, "0");
|
|
|
+ const d = String(resolvedDate.getDate()).padStart(2, "0");
|
|
|
+ return `${y}-${m}-${d}`;
|
|
|
+};
|
|
|
+
|
|
|
+const isValidGeomWkt = (geom) => {
|
|
|
+ const text = String(geom || "").trim();
|
|
|
+ return text.length > 10 && (text.includes("POLYGON") || text.includes("MULTIPOLYGON"));
|
|
|
+};
|
|
|
+
|
|
|
+const isTreatmentOrControlledStatus = (status) => {
|
|
|
+ const normalizedStatus = String(status || "").trim();
|
|
|
+ return normalizedStatus === "2" || normalizedStatus === "3";
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 将不同入口(接口回显/路由透传)的几何数据统一映射为地图组件可消费的数据结构,
|
|
|
+ * 避免散落在各处拼装导致字段不一致。
|
|
|
+ */
|
|
|
+const buildRegionBasePayload = (
|
|
|
+ geometry,
|
|
|
+ regionStyleKind,
|
|
|
+ reproductiveName,
|
|
|
+ updatedTime,
|
|
|
+ handleStatus,
|
|
|
+ lockStyleType = "",
|
|
|
+ label = ""
|
|
|
+) => {
|
|
|
+ if (!isValidGeomWkt(geometry)) return null;
|
|
|
+ const hasTreatmentStatus = isTreatmentOrControlledStatus(handleStatus);
|
|
|
+ const shouldUseLockedDiseaseStyle = regionStyleKind === "ABNORMAL" && hasTreatmentStatus && !!lockStyleType;
|
|
|
+ return {
|
|
|
+ geometry: String(geometry).trim(),
|
|
|
+ status: "unresolved",
|
|
|
+ regionStyleKind,
|
|
|
+ reproductiveName,
|
|
|
+ updatedTime,
|
|
|
+ displayMode: shouldUseLockedDiseaseStyle ? "lockedDisease" : "",
|
|
|
+ handleStatus: hasTreatmentStatus ? String(handleStatus).trim() : "",
|
|
|
+ lockStyleType: shouldUseLockedDiseaseStyle ? lockStyleType : "",
|
|
|
+ label,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+const buildAbnormalLabel = (name, handleStatus) => {
|
|
|
+ const status = String(handleStatus || "").trim();
|
|
|
+ const isTreatingOrControlled = isTreatmentOrControlledStatus(status);
|
|
|
+ const prefix = name.includes("虫害")
|
|
|
+ ? "虫害"
|
|
|
+ : name.includes("病害")
|
|
|
+ ? "病害"
|
|
|
+ : name.includes("过快")
|
|
|
+ ? "长势过快"
|
|
|
+ : name.includes("过慢")
|
|
|
+ ? "长势过慢"
|
|
|
+ : "";
|
|
|
+ if (!prefix) return "";
|
|
|
+ if (isTreatingOrControlled) {
|
|
|
+ return status === "3" ? `${prefix}已控制` : `${prefix}治疗中`;
|
|
|
+ }
|
|
|
+ return `新增${prefix}`;
|
|
|
+};
|
|
|
+
|
|
|
+const buildShowDataRegionsFromApi = () => {
|
|
|
+ const currentTab = (varietyTabs.value || [])[activeVariety.value] || {};
|
|
|
+ if (!currentTab) return [];
|
|
|
+ const styleKind = getCanonicalRegionTypeForStyles();
|
|
|
+ const reproductiveName = String(currentTab.problemZoneTypeName || currentTab.regionName || "");
|
|
|
+ const isAbnormal = styleKind === "ABNORMAL";
|
|
|
+ const geomItems = Array.isArray(currentTab.geomItems) ? currentTab.geomItems : [];
|
|
|
+ const regions = [];
|
|
|
+
|
|
|
+ geomItems.forEach((item) => {
|
|
|
+ const geometry = String(item?.geomWkt || "").trim();
|
|
|
+ if (!isValidGeomWkt(geometry)) return;
|
|
|
+ const handleStatus = String(item?.handleStatus ?? "").trim();
|
|
|
+ const hasTreatmentStatus = isTreatmentOrControlledStatus(handleStatus);
|
|
|
+ const isGrowthAbnormal = reproductiveName.includes("过慢") || reproductiveName.includes("过快");
|
|
|
+ const region = buildRegionBasePayload(
|
|
|
+ geometry,
|
|
|
+ styleKind,
|
|
|
+ reproductiveName,
|
|
|
+ formatDateToYmdDash(item?.createTime || currentTab?.createTime),
|
|
|
+ handleStatus,
|
|
|
+ isAbnormal && hasTreatmentStatus
|
|
|
+ ? (handleStatus === "3" ? "controlled" : (isGrowthAbnormal ? "growthTreating" : "diseaseTreating"))
|
|
|
+ : "",
|
|
|
+ isAbnormal ? buildAbnormalLabel(reproductiveName, handleStatus) : ""
|
|
|
+ );
|
|
|
+ if (region) regions.push(region);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (regions.length > 0) return regions;
|
|
|
+ const fallbackGeom = String(currentTab.geom || "").trim();
|
|
|
+ if (!isValidGeomWkt(fallbackGeom)) return [];
|
|
|
+ const fallbackRegion = buildRegionBasePayload(
|
|
|
+ fallbackGeom,
|
|
|
+ styleKind,
|
|
|
+ reproductiveName,
|
|
|
+ formatDateToYmdDash(currentTab?.createTime),
|
|
|
+ "",
|
|
|
+ "",
|
|
|
+ isAbnormal ? buildAbnormalLabel(reproductiveName, "") : ""
|
|
|
+ );
|
|
|
+ return fallbackRegion ? [fallbackRegion] : [];
|
|
|
+};
|
|
|
|
|
|
- // 从编辑态进入仅查看时,需重新初始化为不可编辑
|
|
|
- if (viewOnly.value && drawRegionMap.kmap && drawRegionMap.editable) {
|
|
|
- drawRegionMap.destroyMap();
|
|
|
- drawRegionMap.initMap(point, mapContainer.value, false, true, false);
|
|
|
+const fitAllRegionsWhenViewOnly = () => {
|
|
|
+ if (viewOnly.value && drawRegionMap.fitAllRegions) {
|
|
|
+ drawRegionMap.fitAllRegions();
|
|
|
}
|
|
|
+};
|
|
|
+
|
|
|
+const renderRegionsFromApi = () => {
|
|
|
+ const apiRegions = buildShowDataRegionsFromApi();
|
|
|
+ if (!apiRegions.length) return;
|
|
|
+ drawRegionMap.setStatusRegions(apiRegions);
|
|
|
+ fitAllRegionsWhenViewOnly();
|
|
|
+};
|
|
|
|
|
|
- // 从仅查看进入勾画(编辑)时,需重新初始化为可编辑
|
|
|
- if (!viewOnly.value && drawRegionMap.kmap && !drawRegionMap.editable) {
|
|
|
- drawRegionMap.destroyMap();
|
|
|
- drawRegionMap.initMap(point, mapContainer.value, true, true, true);
|
|
|
+/**
|
|
|
+ * 路由可能传 JSON(geometryArr)或单条 WKT 字符串,这里统一转换为 regions。
|
|
|
+ * 该函数只负责“解析+映射”,不关心地图渲染,便于后续复用和测试。
|
|
|
+ */
|
|
|
+const buildRegionsFromRouteRangeWkt = (rangeWkt) => {
|
|
|
+ const regionStyleKind = getCanonicalRegionTypeForStyles();
|
|
|
+ const currentVarietyTab = (varietyTabs.value || [])[activeVariety.value] || {};
|
|
|
+ // 异常区名称有时不会从路由透传,补充当前 tab 兜底,避免标签回退为“病害治疗中”
|
|
|
+ const reproductiveName = String(
|
|
|
+ route.query.reproductiveName ||
|
|
|
+ route.query.problemZoneTypeName ||
|
|
|
+ currentVarietyTab.problemZoneTypeName ||
|
|
|
+ currentVarietyTab.regionName ||
|
|
|
+ ""
|
|
|
+ );
|
|
|
+ const isAbnormal = regionStyleKind === "ABNORMAL";
|
|
|
+ const routeHandleStatus = String(route.query.handleStatus || "").trim();
|
|
|
+ const hasTreatmentStatus = isTreatmentOrControlledStatus(routeHandleStatus);
|
|
|
+ const isGrowthAbnormal =
|
|
|
+ isAbnormal && (reproductiveName.includes("长势") || reproductiveName.includes("过慢") || reproductiveName.includes("过快"));
|
|
|
+ const abnormalLabel =
|
|
|
+ !isAbnormal
|
|
|
+ ? ""
|
|
|
+ : reproductiveName.includes("病害")
|
|
|
+ ? "新增病害"
|
|
|
+ : reproductiveName.includes("虫害")
|
|
|
+ ? "新增虫害"
|
|
|
+ : reproductiveName.includes("过慢")
|
|
|
+ ? "新增长势过慢"
|
|
|
+ : reproductiveName.includes("过快")
|
|
|
+ ? "新增长势过快"
|
|
|
+ : "";
|
|
|
+ const lockStyleType = !isAbnormal || !hasTreatmentStatus
|
|
|
+ ? ""
|
|
|
+ : (isGrowthAbnormal ? "growthTreating" : "diseaseTreating");
|
|
|
+
|
|
|
+ const toRegionPayload = (geometryText) => buildRegionBasePayload(
|
|
|
+ geometryText,
|
|
|
+ regionStyleKind,
|
|
|
+ reproductiveName,
|
|
|
+ route.query.updatedTime,
|
|
|
+ routeHandleStatus,
|
|
|
+ lockStyleType,
|
|
|
+ abnormalLabel
|
|
|
+ );
|
|
|
+
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(rangeWkt);
|
|
|
+ if (parsed && Array.isArray(parsed.geometryArr)) {
|
|
|
+ return parsed.geometryArr
|
|
|
+ .map((item) => toRegionPayload(String(item || "").trim()))
|
|
|
+ .filter(Boolean);
|
|
|
+ }
|
|
|
+ } catch (_) {
|
|
|
+ // 非 JSON 透传时,按单条 WKT 处理
|
|
|
}
|
|
|
|
|
|
- // 先绘制地块
|
|
|
- const polygonData = route.query.polygonData;
|
|
|
- const rawRangeWkt = route.query.rangeWkt;
|
|
|
- const rangeWkt = rawRangeWkt ? decodeURIComponent(rawRangeWkt) : null;
|
|
|
+ if (typeof rangeWkt === "string" && rangeWkt.trim().length > 10) {
|
|
|
+ const region = toRegionPayload(rangeWkt.trim());
|
|
|
+ return region ? [region] : [];
|
|
|
+ }
|
|
|
+ return [];
|
|
|
+};
|
|
|
|
|
|
- if (rangeWkt) {
|
|
|
- let regions = [];
|
|
|
- try {
|
|
|
- const parsed = JSON.parse(rangeWkt);
|
|
|
- if (parsed && Array.isArray(parsed.geometryArr)) {
|
|
|
- regions = parsed.geometryArr.map((item) => ({
|
|
|
- geometry: item,
|
|
|
- status: "unresolved",
|
|
|
- reproductiveName: route.query.reproductiveName,
|
|
|
- updatedTime: route.query.updatedTime,
|
|
|
- }));
|
|
|
- } else if (typeof rangeWkt === "string" && rangeWkt.trim().length > 10) {
|
|
|
- regions = [{ geometry: rangeWkt.trim(), status: "unresolved", reproductiveName: route.query.reproductiveName, updatedTime: route.query.updatedTime }];
|
|
|
+const renderRegionsFromRouteRangeWkt = (rangeWkt) => {
|
|
|
+ const regions = buildRegionsFromRouteRangeWkt(rangeWkt);
|
|
|
+ if (!regions.length) return;
|
|
|
+ drawRegionMap.setStatusRegions(regions);
|
|
|
+ fitAllRegionsWhenViewOnly();
|
|
|
+};
|
|
|
+
|
|
|
+const restoreEditablePolygonFromRoute = (polygonData) => {
|
|
|
+ if (viewOnly.value || !polygonData) return;
|
|
|
+ drawRegionMap.setAreaGeometry(
|
|
|
+ JSON.parse(polygonData)?.geometryArr,
|
|
|
+ false,
|
|
|
+ undefined,
|
|
|
+ undefined,
|
|
|
+ getAbnormalGrowthOverlayMeta()
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const updateMapCenterForEditMode = (point) => {
|
|
|
+ // 查看模式通常会通过 fitAllRegions 自适应,不主动重置中心,避免打断用户缩放视角
|
|
|
+ if (viewOnly.value) return;
|
|
|
+ drawRegionMap.setMapPosition(convertPointToArray(point));
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 页面从 keep-alive 恢复时,如果查看态和编辑态发生切换,需要重建地图实例。
|
|
|
+ * 仅在状态不一致时重建,避免每次激活都销毁重建带来的闪烁和性能开销。
|
|
|
+ */
|
|
|
+const syncMapEditableState = (point) => {
|
|
|
+ if (!drawRegionMap.kmap) return;
|
|
|
+ const shouldEditable = !viewOnly.value;
|
|
|
+ if (drawRegionMap.editable === shouldEditable) return;
|
|
|
+ drawRegionMap.destroyMap();
|
|
|
+ drawRegionMap.initMap(point, mapContainer.value, shouldEditable, true, shouldEditable);
|
|
|
+};
|
|
|
+
|
|
|
+async function fetchRegionInfo() {
|
|
|
+ const subjectId = route.query.subjectId || localStorage.getItem('selectedFarmId');
|
|
|
+ const { data } = await VE_API.basic_farm.fetchRegionInfo({ subjectId });
|
|
|
+ if (data && data.length > 0) {
|
|
|
+ regionInfo.value = data[0] || [];
|
|
|
+ regionTypeTabs.value = regionInfo.value.problemZoneList || [];
|
|
|
+ regionTypeTabs.value.unshift({ name: "品种区", code: "variety" });
|
|
|
+ varietyTabs.value = regionInfo.value.regionList || [];
|
|
|
+ activeVariety.value = 0;
|
|
|
+
|
|
|
+ if (route.query.firstAct) {
|
|
|
+ activeRegionType.value = route.query.firstAct;
|
|
|
+ if (route.query.firstAct === "variety") {
|
|
|
+ varietyTabs.value = regionInfo.value.regionList || [];
|
|
|
+ const index = Number(route.query.secondAct);
|
|
|
+ activeVariety.value = Number.isInteger(index) && index >= 0 ? index : 0;
|
|
|
+ return;
|
|
|
}
|
|
|
- } catch (_) {
|
|
|
- if (typeof rangeWkt === "string" && rangeWkt.trim().length > 10) {
|
|
|
- regions = [{ geometry: rangeWkt.trim(), status: "unresolved", reproductiveName: route.query.reproductiveName, updatedTime: route.query.updatedTime }];
|
|
|
+ const matchedGroup = (regionInfo.value.problemZoneList || []).find(
|
|
|
+ (item) => String(item?.code) === String(route.query.firstAct)
|
|
|
+ );
|
|
|
+ if (matchedGroup) {
|
|
|
+ varietyTabs.value = matchedGroup.children || [];
|
|
|
+ const index = Number(route.query.secondAct);
|
|
|
+ activeVariety.value = Number.isInteger(index) && index >= 0 ? index : 0;
|
|
|
}
|
|
|
}
|
|
|
- if (regions.length) {
|
|
|
- drawRegionMap.setStatusRegions(regions);
|
|
|
- if (viewOnly.value && drawRegionMap.fitAllRegions) {
|
|
|
- drawRegionMap.fitAllRegions();
|
|
|
- }
|
|
|
+ if (activeVariety.value >= varietyTabs.value.length) {
|
|
|
+ activeVariety.value = 0;
|
|
|
}
|
|
|
}
|
|
|
- if (!viewOnly.value && polygonData) {
|
|
|
- drawRegionMap.setAreaGeometry(JSON.parse(polygonData)?.geometryArr);
|
|
|
+}
|
|
|
+
|
|
|
+const varietyTabs = ref([]);
|
|
|
+const activeVariety = ref(0);
|
|
|
+
|
|
|
+/** 样式用的大类:与接口 tab.code 解耦(避免 ABNORMAL / 数字 code 等导致勾画色落到默认灰) */
|
|
|
+const getCanonicalRegionTypeForStyles = () => {
|
|
|
+ const raw = activeRegionType.value;
|
|
|
+ if (raw === "variety") return "variety";
|
|
|
+ const tabs = regionTypeTabs.value || [];
|
|
|
+ const item = tabs.find((t) => String(t?.code) === String(raw));
|
|
|
+ if (item) {
|
|
|
+ const kind = item.code;
|
|
|
+ if (kind) return kind;
|
|
|
}
|
|
|
+ return "SLEEP";
|
|
|
+};
|
|
|
|
|
|
- // 查看模式下已通过 fitAllRegions 适配;编辑模式再设置地图中心
|
|
|
- if (!viewOnly.value) {
|
|
|
- drawRegionMap.setMapPosition(convertPointToArray(point));
|
|
|
+const ABNORMAL_BADGE_BG_DISEASE_PEST = "#E32A28";
|
|
|
+const ABNORMAL_BADGE_BG_GROWTH = "#F76F00";
|
|
|
+
|
|
|
+/** 异常区小类(长势/病害/虫害等)闭合地块后在多边形内展示标签与发现日期 */
|
|
|
+const getAbnormalGrowthOverlayMeta = () => {
|
|
|
+ if (getCanonicalRegionTypeForStyles() !== "ABNORMAL") return null;
|
|
|
+ const tab = varietyTabs.value?.[activeVariety.value];
|
|
|
+ if (!tab) return null;
|
|
|
+ const name = (tab.problemZoneTypeName || tab.regionName || "").toString();
|
|
|
+ let badgeText = "";
|
|
|
+ let badgeBackground = ABNORMAL_BADGE_BG_GROWTH;
|
|
|
+ if (name.includes("病害")) {
|
|
|
+ badgeText = "新增病害";
|
|
|
+ badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
|
|
|
+ } else if (name.includes("虫害")) {
|
|
|
+ badgeText = "新增虫害";
|
|
|
+ badgeBackground = ABNORMAL_BADGE_BG_DISEASE_PEST;
|
|
|
+ } else if (name.includes("过慢")) {
|
|
|
+ badgeText = "新增长势过慢";
|
|
|
+ } else if (name.includes("过快")) {
|
|
|
+ badgeText = "新增长势过快";
|
|
|
+ } else {
|
|
|
+ return null;
|
|
|
}
|
|
|
-});
|
|
|
+ const now = new Date();
|
|
|
+ const y = now.getFullYear();
|
|
|
+ const m = String(now.getMonth() + 1).padStart(2, "0");
|
|
|
+ const d = String(now.getDate()).padStart(2, "0");
|
|
|
+ return { badgeText, discoveryDate: `${y}.${m}.${d}`, badgeBackground };
|
|
|
+};
|
|
|
+
|
|
|
+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 growth = getAbnormalGrowthOverlayMeta();
|
|
|
+ if (growth) {
|
|
|
+ styles.push(
|
|
|
+ new Style({
|
|
|
+ text: new Text({
|
|
|
+ text: growth.badgeText,
|
|
|
+ font: "bold 13px sans-serif",
|
|
|
+ fill: new Fill({ color: "#ffffff" }),
|
|
|
+ backgroundFill: new Fill({ color: growth.badgeBackground || ABNORMAL_BADGE_BG_GROWTH }),
|
|
|
+ padding: [4, 10, 4, 10],
|
|
|
+ offsetY: -40,
|
|
|
+ }),
|
|
|
+ }),
|
|
|
+ new Style({
|
|
|
+ text: new Text({
|
|
|
+ text: `发现时间:${growth.discoveryDate}`,
|
|
|
+ font: "12px sans-serif",
|
|
|
+ fill: new Fill({ color: "#ffffff" }),
|
|
|
+ offsetY: -16,
|
|
|
+ }),
|
|
|
+ })
|
|
|
+ );
|
|
|
+ }
|
|
|
+ const areaValStyle = new Style({
|
|
|
+ text: new Text({
|
|
|
+ font: "16px sans-serif",
|
|
|
+ text: area.toFixed(2) + "亩",
|
|
|
+ fill: new Fill({ color: "#fff" }),
|
|
|
+ offsetY: growth ? 14 : 0,
|
|
|
+ }),
|
|
|
+ });
|
|
|
+ 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";
|
|
|
+
|
|
|
+ const styleKind = getCanonicalRegionTypeForStyles();
|
|
|
+ if (styleKind === "variety") {
|
|
|
+ lineColor = "#18AA8B";
|
|
|
+ vertexColor = "#18AA8B";
|
|
|
+ fillColor = [0, 57, 44, 0.5];
|
|
|
+ strokeColor = "#18AA8B";
|
|
|
+ } else if (styleKind === "ABNORMAL") {
|
|
|
+ if (activeVariety.value < 2) {
|
|
|
+ lineColor = "#E03131";
|
|
|
+ vertexColor = "#E03131";
|
|
|
+ fillColor = [100, 0, 0, 0.5];
|
|
|
+ strokeColor = "#E03131";
|
|
|
+ } else {
|
|
|
+ lineColor = "#FF7300";
|
|
|
+ vertexColor = "#FF7300";
|
|
|
+ fillColor = [124, 46, 0, 0.5];
|
|
|
+ strokeColor = "#FF7300";
|
|
|
+ }
|
|
|
+ } else if (styleKind === "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";
|
|
|
+ }
|
|
|
+
|
|
|
+ KMapMap.drawStyleColors = {
|
|
|
+ line: lineColor,
|
|
|
+ vertex: vertexColor,
|
|
|
+ fill: fillColor,
|
|
|
+ stroke: strokeColor,
|
|
|
+ };
|
|
|
+
|
|
|
+ kmap.polygonStyle = createPolygonStyleFunc(fillColor, strokeColor);
|
|
|
+ if (kmap.polygonLayer?.layer && typeof kmap.polygonLayer.layer.setStyle === "function") {
|
|
|
+ kmap.polygonLayer.layer.setStyle(kmap.polygonStyle);
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
onDeactivated(() => {
|
|
|
drawRegionMap.clearLayer()
|
|
|
@@ -132,19 +572,97 @@ const deletePolygon = () => {
|
|
|
type: 'warning',
|
|
|
}
|
|
|
).then(() => {
|
|
|
- drawRegionMap.deleteCurrentPolygon();
|
|
|
+ drawRegionMap.abortOngoingDrawSketch();
|
|
|
+ if (drawRegionMap.kmap && drawRegionMap.kmap.polygonLayer && drawRegionMap.kmap.polygonLayer.source) {
|
|
|
+ drawRegionMap.kmap.polygonLayer.source.clear();
|
|
|
+ }
|
|
|
ElMessage.success("地块已删除");
|
|
|
- sessionStorage.removeItem("drawRegionPolygonData");
|
|
|
}).catch(() => {
|
|
|
// 用户取消删除,不做任何操作
|
|
|
});
|
|
|
};
|
|
|
|
|
|
-const confirm = () => {
|
|
|
+const saveAndBack = () => {
|
|
|
const polygonData = drawRegionMap.getAreaGeometry();
|
|
|
+ const subjectId = route.query.subjectId || localStorage.getItem("selectedFarmId") || "";
|
|
|
+ const geometryArr = Array.isArray(polygonData?.geometryArr)
|
|
|
+ ? polygonData.geometryArr
|
|
|
+ .map((g) => (g == null ? "" : String(g).trim()))
|
|
|
+ .filter((g) => g.length > 10)
|
|
|
+ : [];
|
|
|
+ const geom = geometryArr[0] || "";
|
|
|
+
|
|
|
+ const currentGroup = (regionTypeTabs.value || []).find(
|
|
|
+ (item) => String(item?.code) === String(activeRegionType.value)
|
|
|
+ );
|
|
|
+ const currentTab = (varietyTabs.value || [])[activeVariety.value] || {};
|
|
|
+
|
|
|
+ const regionList =
|
|
|
+ activeRegionType.value === "variety" && geom
|
|
|
+ ? [
|
|
|
+ {
|
|
|
+ regionId: currentTab.regionId ?? currentTab.id ?? "",
|
|
|
+ typeId: currentTab.typeId ?? "",
|
|
|
+ regionName: currentTab.regionName ?? currentTab.problemZoneTypeName ?? "",
|
|
|
+ geom,
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ : [];
|
|
|
+
|
|
|
+ const problemZoneList =
|
|
|
+ activeRegionType.value !== "variety" && geom
|
|
|
+ ? [
|
|
|
+ {
|
|
|
+ name: currentGroup?.name ?? "",
|
|
|
+ code: currentGroup?.code ?? activeRegionType.value,
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ farmSubjectId: subjectId,
|
|
|
+ handleStatus: 1,
|
|
|
+ createTime: new Date().toISOString(),
|
|
|
+ problemZoneTypeId: currentTab.problemZoneTypeId ?? currentTab.typeId ?? "",
|
|
|
+ problemZoneTypeName: currentTab.problemZoneTypeName ?? currentTab.regionName ?? "",
|
|
|
+ parentName: currentGroup?.name ?? "",
|
|
|
+ parentCode: currentGroup?.code ?? activeRegionType.value,
|
|
|
+ geomItems: [{ geomWkt: geom, remark: "" }],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ]
|
|
|
+ : [];
|
|
|
+
|
|
|
+ const params = {
|
|
|
+ subjectId,
|
|
|
+ regionList,
|
|
|
+ problemZoneList,
|
|
|
+ };
|
|
|
+
|
|
|
sessionStorage.setItem("drawRegionPolygonData", JSON.stringify(polygonData));
|
|
|
+ sessionStorage.setItem("drawRegionSubmitParams", JSON.stringify(params));
|
|
|
+ if (selectedAbnormalType.value) {
|
|
|
+ sessionStorage.setItem("drawRegionAbnormalType", selectedAbnormalType.value);
|
|
|
+ } else {
|
|
|
+ sessionStorage.removeItem("drawRegionAbnormalType");
|
|
|
+ }
|
|
|
router.back();
|
|
|
};
|
|
|
+
|
|
|
+const handleConfirmUpload = () => {
|
|
|
+ if (!selectedAbnormalType.value) {
|
|
|
+ ElMessage.warning("请选择异常类型");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ showAbnormalTypePopup.value = false;
|
|
|
+ saveAndBack();
|
|
|
+};
|
|
|
+
|
|
|
+const confirm = () => {
|
|
|
+ if (getCanonicalRegionTypeForStyles() === "ABNORMAL") {
|
|
|
+ showAbnormalTypePopup.value = true;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ saveAndBack();
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
@@ -153,9 +671,59 @@ const confirm = () => {
|
|
|
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;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 10px 12px 0;
|
|
|
+ overflow-x: auto;
|
|
|
+ overflow-y: hidden;
|
|
|
+ flex-wrap: nowrap;
|
|
|
+ -webkit-overflow-scrolling: touch;
|
|
|
+
|
|
|
+ .variety-tab {
|
|
|
+ padding: 4px 12px;
|
|
|
+ border-radius: 2px;
|
|
|
+ color: #575757;
|
|
|
+ background: #F4F4F4;
|
|
|
+ border: 1px solid transparent;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+
|
|
|
+ .variety-tab--active {
|
|
|
+ background: rgba(33, 153, 248, 0.1);
|
|
|
+ color: #2199F8;
|
|
|
+ border: 1px solid #2199F8;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
.edit-map-content {
|
|
|
width: 100%;
|
|
|
- height: 100%;
|
|
|
+ height: calc(100% - 96px);
|
|
|
+ margin-top: 10px;
|
|
|
position: relative;
|
|
|
|
|
|
.edit-map-tip {
|
|
|
@@ -177,7 +745,7 @@ const confirm = () => {
|
|
|
|
|
|
.edit-map-footer {
|
|
|
position: absolute;
|
|
|
- bottom: 80px;
|
|
|
+ bottom: 85px;
|
|
|
left: 12px;
|
|
|
width: calc(100% - 24px);
|
|
|
display: flex;
|
|
|
@@ -188,7 +756,7 @@ const confirm = () => {
|
|
|
padding: 6px 7px 9px;
|
|
|
background: #fff;
|
|
|
border-radius: 8px;
|
|
|
- margin-bottom: 12px;
|
|
|
+ margin-bottom: 30px;
|
|
|
|
|
|
.back-icon {
|
|
|
width: 20px;
|
|
|
@@ -228,4 +796,43 @@ const confirm = () => {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.abnormal-popup {
|
|
|
+ width: 100%;
|
|
|
+
|
|
|
+ ::v-deep {
|
|
|
+ .van-popup__close-icon {
|
|
|
+ color: #000;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .abnormal-popup-content {
|
|
|
+ background: linear-gradient(180deg, #d9ecff 0%, #ffffff 60%);
|
|
|
+ border-radius: 18px;
|
|
|
+ padding: 24px 16px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ position: relative;
|
|
|
+
|
|
|
+ .abnormal-popup-title {
|
|
|
+ font-size: 16px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .abnormal-type-select {
|
|
|
+ width: 100%;
|
|
|
+ }
|
|
|
+
|
|
|
+ .abnormal-popup-confirm {
|
|
|
+ margin-top: 24px;
|
|
|
+ border-radius: 25px;
|
|
|
+ padding: 8px;
|
|
|
+ background: #2199F8;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 16px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|