|
|
@@ -18,8 +18,8 @@
|
|
|
<div class="edit-map-tip" v-if="!viewOnly">操作提示:拖动圆点,即可调整地块边界</div>
|
|
|
<div class="map-container" ref="mapContainer"></div>
|
|
|
<div class="edit-map-footer">
|
|
|
- <div class="footer-back" @click="goBack">
|
|
|
- <img class="back-icon" src="@/assets/img/home/go-back.png" alt="" />
|
|
|
+ <div v-if="viewOnly" class="footer-back" @click="deletePolygon">
|
|
|
+ <img class="back-icon" src="@/assets/img/map/delete.png" alt="" />
|
|
|
</div>
|
|
|
<div class="edit-map-footer-btn" :class="{ 'confirm-btn-box': viewOnly }">
|
|
|
<template v-if="!viewOnly">
|
|
|
@@ -51,6 +51,7 @@ import Style from "ol/style/Style";
|
|
|
import { Fill, Stroke, Circle, Text } from "ol/style.js";
|
|
|
import { Point, Polygon, MultiPolygon } from "ol/geom";
|
|
|
import WKT from "ol/format/WKT.js";
|
|
|
+import { unByKey } from "ol/Observable.js";
|
|
|
import * as proj from "ol/proj";
|
|
|
import { getArea } from "ol/sphere.js";
|
|
|
|
|
|
@@ -90,6 +91,10 @@ const regionsDraftByKey = ref({});
|
|
|
const submitting = ref(false);
|
|
|
// 仅在首次进入页面时根据路由 varietyId 定位一次,避免后续刷新覆盖当前流程进度
|
|
|
const hasAppliedInitialVariety = ref(false);
|
|
|
+const selectedDeleteFeature = ref(null);
|
|
|
+const deleteSelectClickKey = ref(null);
|
|
|
+const selectedDeleteFeatureUid = ref("");
|
|
|
+const selectedDeleteFeatureOriginalStyle = ref(null);
|
|
|
|
|
|
const type = ref(null);
|
|
|
const varietyTabs = ref([]);
|
|
|
@@ -107,7 +112,96 @@ const clearAllRegionDrafts = () => {
|
|
|
regionsDraftByIndex.value = {};
|
|
|
};
|
|
|
|
|
|
+const clearDeleteSelectionState = () => {
|
|
|
+ if (selectedDeleteFeature.value) {
|
|
|
+ selectedDeleteFeature.value.setStyle(selectedDeleteFeatureOriginalStyle.value ?? undefined);
|
|
|
+ selectedDeleteFeature.value = null;
|
|
|
+ }
|
|
|
+ selectedDeleteFeatureUid.value = "";
|
|
|
+ selectedDeleteFeatureOriginalStyle.value = null;
|
|
|
+ if (deleteSelectClickKey.value) {
|
|
|
+ unByKey(deleteSelectClickKey.value);
|
|
|
+ deleteSelectClickKey.value = null;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const getDeleteHighlightStyle = () => {
|
|
|
+ const styleKind = getCanonicalRegionTypeForStyles();
|
|
|
+ if (styleKind === "variety") {
|
|
|
+ return {
|
|
|
+ fillColor: "rgba(24, 170, 139, 0.5)",
|
|
|
+ strokeColor: "#18AA8B",
|
|
|
+ };
|
|
|
+ }
|
|
|
+ if (styleKind === "ABNORMAL") {
|
|
|
+ if (activeVariety.value < 2) {
|
|
|
+ return {
|
|
|
+ fillColor: "rgba(224, 49, 49, 0.5)",
|
|
|
+ strokeColor: "#E03131",
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ fillColor: "rgba(255, 115, 0, 0.5)",
|
|
|
+ strokeColor: "#FF7300",
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (styleKind === "ENVIRONMENT") {
|
|
|
+ return {
|
|
|
+ fillColor: "rgba(255, 199, 102, 0.5)",
|
|
|
+ strokeColor: "#FDCF7F",
|
|
|
+ };
|
|
|
+ }
|
|
|
+ // 其余类型先给默认占位色,后续可按产品视觉再调整
|
|
|
+ return {
|
|
|
+ fillColor: "rgba(229, 229, 229, 0.5)",
|
|
|
+ strokeColor: "#A6A6A6",
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+const highlightDeleteSelectedFeature = (feature) => {
|
|
|
+ if (!feature) return;
|
|
|
+ if (selectedDeleteFeature.value === feature) return;
|
|
|
+ if (selectedDeleteFeature.value && selectedDeleteFeature.value !== feature) {
|
|
|
+ selectedDeleteFeature.value.setStyle(selectedDeleteFeatureOriginalStyle.value ?? undefined);
|
|
|
+ }
|
|
|
+ selectedDeleteFeatureOriginalStyle.value = feature.getStyle ? feature.getStyle() : null;
|
|
|
+ selectedDeleteFeature.value = feature;
|
|
|
+ selectedDeleteFeatureUid.value = String(feature?.ol_uid || feature?.getId?.() || "");
|
|
|
+ const { fillColor, strokeColor } = getDeleteHighlightStyle();
|
|
|
+ // 删除选择态:按当前类型动态高亮(单选)
|
|
|
+ feature.setStyle(
|
|
|
+ new Style({
|
|
|
+ fill: new Fill({ color: fillColor }),
|
|
|
+ stroke: new Stroke({ color: strokeColor, width: 2 }),
|
|
|
+ })
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const enableDeleteSelectionMode = () => {
|
|
|
+ const map = drawRegionMap.kmap?.map;
|
|
|
+ const polygonLayer = drawRegionMap.kmap?.polygonLayer?.layer;
|
|
|
+ if (!map || !polygonLayer || deleteSelectClickKey.value) return;
|
|
|
+ deleteSelectClickKey.value = map.on("singleclick", (evt) => {
|
|
|
+ let hitFeature = null;
|
|
|
+ map.forEachFeatureAtPixel(
|
|
|
+ evt.pixel,
|
|
|
+ (feature) => {
|
|
|
+ hitFeature = feature;
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ {
|
|
|
+ layerFilter: (layer) => layer === polygonLayer,
|
|
|
+ }
|
|
|
+ );
|
|
|
+ if (hitFeature) {
|
|
|
+ highlightDeleteSelectedFeature(hitFeature);
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
const handleRegionTypeClick = (item) => {
|
|
|
+ clearDeleteSelectionState();
|
|
|
const prevMajor = activeRegionType.value;
|
|
|
if (item.code !== prevMajor) {
|
|
|
clearDraftIndexOnly();
|
|
|
@@ -125,6 +219,87 @@ const handleRegionTypeClick = (item) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 删除当前地块
|
|
|
+const deletePolygon = () => {
|
|
|
+ const features = drawRegionMap.kmap?.polygonLayer?.source?.getFeatures?.() || [];
|
|
|
+ let matchedFeature = null;
|
|
|
+ if (features.length === 0) {
|
|
|
+ clearDeleteSelectionState();
|
|
|
+ ElMessage.warning("当前没有可删除的地块");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (features.length > 1) {
|
|
|
+ if (!selectedDeleteFeature.value) {
|
|
|
+ enableDeleteSelectionMode();
|
|
|
+ ElMessage.warning("当前有多个地块,请先选择地块");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ matchedFeature = features.find((f) => {
|
|
|
+ if (f === selectedDeleteFeature.value) return true;
|
|
|
+ const uid = String(f?.ol_uid || f?.getId?.() || "");
|
|
|
+ return uid && uid === selectedDeleteFeatureUid.value;
|
|
|
+ });
|
|
|
+ if (!matchedFeature) {
|
|
|
+ selectedDeleteFeature.value = null;
|
|
|
+ selectedDeleteFeatureUid.value = "";
|
|
|
+ ElMessage.warning("请选择地块后再删除");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ clearDeleteSelectionState();
|
|
|
+ }
|
|
|
+
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ "确认删除当前地块吗?删除后可以重新勾画。",
|
|
|
+ "删除确认",
|
|
|
+ {
|
|
|
+ confirmButtonText: "确认删除",
|
|
|
+ cancelButtonText: "取消",
|
|
|
+ type: "warning",
|
|
|
+ }
|
|
|
+ ).then(async () => {
|
|
|
+ if (selectedDeleteFeature.value && features.length > 1) {
|
|
|
+ drawRegionMap.kmap?.polygonLayer?.source?.removeFeature?.(matchedFeature || selectedDeleteFeature.value);
|
|
|
+ clearDeleteSelectionState();
|
|
|
+ } else {
|
|
|
+ drawRegionMap.deleteCurrentPolygon();
|
|
|
+ }
|
|
|
+
|
|
|
+ const polygonData = drawRegionMap.getAreaGeometry?.();
|
|
|
+ const geometryArr = polygonData?.geometryArr || [];
|
|
|
+ const mergedGeom = mergePolygonWktsForApi(geometryArr);
|
|
|
+
|
|
|
+ try {
|
|
|
+ const ok = await submitCurrentTabGeometryChange(mergedGeom);
|
|
|
+ if (!ok) {
|
|
|
+ ElMessage.error("删除后保存失败");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await fetchRegionInfo();
|
|
|
+ clearAllRegionDrafts();
|
|
|
+ if (activeRegionType.value === "variety") {
|
|
|
+ varietyTabs.value = regionInfo.value?.regionList || [];
|
|
|
+ } else {
|
|
|
+ const group = (regionTypeTabs.value || []).find(
|
|
|
+ (x) => String(x?.code) === String(activeRegionType.value)
|
|
|
+ );
|
|
|
+ varietyTabs.value = group?.children || [];
|
|
|
+ }
|
|
|
+ const nextIndex = Math.min(activeVariety.value, Math.max(varietyTabs.value.length - 1, 0));
|
|
|
+ activeVariety.value = nextIndex;
|
|
|
+ if (varietyTabs.value.length > 0 && varietyTabs.value[nextIndex]) {
|
|
|
+ handleVarietyClick(varietyTabs.value[nextIndex], nextIndex);
|
|
|
+ } else {
|
|
|
+ drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
|
|
|
+ drawRegionMap.fitAllRegions?.();
|
|
|
+ }
|
|
|
+ ElMessage.success("地块已删除");
|
|
|
+ } catch (_) {
|
|
|
+ ElMessage.error("删除后保存失败");
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
/** 取 Polygon / MultiPolygon 的外环坐标(地图投影),用于顶点样式 */
|
|
|
const getOuterRingCoordinates = (geometry) => {
|
|
|
if (!geometry || typeof geometry.getType !== "function") return [];
|
|
|
@@ -238,12 +413,12 @@ const applyRegionStyles = () => {
|
|
|
fillColor = [0, 57, 44, 0.5];
|
|
|
strokeColor = "#18AA8B";
|
|
|
} else if (styleKind === "ABNORMAL") {
|
|
|
- if(activeVariety.value < 2){
|
|
|
+ if (activeVariety.value < 2) {
|
|
|
lineColor = "#E03131";
|
|
|
vertexColor = "#E03131";
|
|
|
fillColor = [100, 0, 0, 0.5];
|
|
|
strokeColor = "#E03131";
|
|
|
- }else{
|
|
|
+ } else {
|
|
|
lineColor = "#FF7300";
|
|
|
vertexColor = "#FF7300";
|
|
|
fillColor = [124, 46, 0, 0.5];
|
|
|
@@ -475,6 +650,7 @@ const syncClosedDrawToDraft = () => {
|
|
|
};
|
|
|
|
|
|
const handleVarietyClick = (tab, index) => {
|
|
|
+ clearDeleteSelectionState();
|
|
|
// 取消上一次延迟渲染,避免快速切换 tab / 切换编辑态时重复追加要素
|
|
|
clearPendingGeomRenderTimer();
|
|
|
|
|
|
@@ -495,10 +671,10 @@ const handleVarietyClick = (tab, index) => {
|
|
|
isValidGeom(draftGeom)
|
|
|
? [String(draftGeom).trim()]
|
|
|
: (() => {
|
|
|
- const fromItems = getGeomArrFromGeomItems(tab);
|
|
|
- if (fromItems.length) return fromItems;
|
|
|
- return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
|
|
|
- })();
|
|
|
+ const fromItems = getGeomArrFromGeomItems(tab);
|
|
|
+ if (fromItems.length) return fromItems;
|
|
|
+ return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
|
|
|
+ })();
|
|
|
const geomArr = flattenGeomWktListForMap(rawGeomArr);
|
|
|
// 保留一份“当前选中地块”的原始字符串形态给其它逻辑使用
|
|
|
regionGeom.value = geomArr.length > 1 ? JSON.stringify(geomArr) : geomArr[0] || "";
|
|
|
@@ -769,6 +945,7 @@ watch(
|
|
|
);
|
|
|
|
|
|
onDeactivated(() => {
|
|
|
+ clearDeleteSelectionState();
|
|
|
activeVariety.value = 0;
|
|
|
activeRegionType.value = "variety";
|
|
|
clearPendingGeomRenderTimer();
|
|
|
@@ -785,8 +962,9 @@ const goBack = () => {
|
|
|
};
|
|
|
|
|
|
const resetPolygon = () => {
|
|
|
+ clearDeleteSelectionState();
|
|
|
ElMessageBox.confirm(
|
|
|
- "确认要重置当前地块吗?重置后将恢复到进入页面时的区域形状。",
|
|
|
+ "确认重置当前地块吗?重置后将恢复为初始状态",
|
|
|
"重置确认",
|
|
|
{
|
|
|
confirmButtonText: "确认重置",
|
|
|
@@ -802,24 +980,36 @@ const resetPolygon = () => {
|
|
|
drawRegionMap.kmap.polygonLayer.source.clear();
|
|
|
}
|
|
|
|
|
|
- // 使用进入页面时路由上带的 polygonData 作为“初始形状”进行回显
|
|
|
- const polygonDataStr = route.query.polygonData;
|
|
|
- if (polygonDataStr) {
|
|
|
- try {
|
|
|
- const parsed = JSON.parse(polygonDataStr);
|
|
|
- if (parsed && Array.isArray(parsed.geometryArr) && parsed.geometryArr.length) {
|
|
|
- const resetGeomArr = flattenGeomWktListForMap(parsed.geometryArr);
|
|
|
- drawRegionMap.setAreaGeometry(
|
|
|
- resetGeomArr.length ? resetGeomArr : parsed.geometryArr,
|
|
|
- false,
|
|
|
- undefined,
|
|
|
- undefined,
|
|
|
- getAbnormalGrowthOverlayMeta()
|
|
|
- );
|
|
|
- }
|
|
|
- } catch (_) {
|
|
|
- // 解析失败则不做处理,仅相当于清空重画
|
|
|
- }
|
|
|
+ // 清空当前 tab 草稿,重置应回到“进入时初始化值”
|
|
|
+ const currentTab = varietyTabs.value?.[activeVariety.value];
|
|
|
+ const currentKey = draftKeyFromParts(activeRegionType.value, currentTab);
|
|
|
+ if (currentKey && regionsDraftByKey.value[currentKey]) {
|
|
|
+ delete regionsDraftByKey.value[currentKey];
|
|
|
+ }
|
|
|
+ if (regionsDraftByIndex.value[activeVariety.value]) {
|
|
|
+ delete regionsDraftByIndex.value[activeVariety.value];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 当前品类有初始化地块则回显;没有则保持空地图
|
|
|
+ const initialGeomArrRaw = (() => {
|
|
|
+ const fromItems = getGeomArrFromGeomItems(currentTab);
|
|
|
+ if (fromItems.length) return fromItems;
|
|
|
+ return isValidGeom(currentTab?.geom) ? [String(currentTab.geom).trim()] : [];
|
|
|
+ })();
|
|
|
+ const initialGeomArr = flattenGeomWktListForMap(initialGeomArrRaw);
|
|
|
+ if (initialGeomArr.length > 0) {
|
|
|
+ drawRegionMap.setAreaGeometry(
|
|
|
+ initialGeomArr,
|
|
|
+ false,
|
|
|
+ undefined,
|
|
|
+ undefined,
|
|
|
+ getAbnormalGrowthOverlayMeta()
|
|
|
+ );
|
|
|
+ regionGeom.value =
|
|
|
+ initialGeomArr.length > 1 ? JSON.stringify(initialGeomArr) : initialGeomArr[0];
|
|
|
+ } else {
|
|
|
+ regionGeom.value = "";
|
|
|
+ drawRegionMap.fitAllRegions?.();
|
|
|
}
|
|
|
ElMessage.success("地块已重置");
|
|
|
})
|
|
|
@@ -845,13 +1035,13 @@ const submitRegions = async () => {
|
|
|
};
|
|
|
const res = await VE_API.basic_farm.ediRegionZone(params);
|
|
|
if (res?.code === 0) {
|
|
|
- if(selectedDrawTypeMeta.value.type === 'ABNORMAL'){
|
|
|
+ if (selectedDrawTypeMeta.value.type === 'ABNORMAL') {
|
|
|
let problemZoneId = null;
|
|
|
- if(params.problemZoneList.length){
|
|
|
+ if (params.problemZoneList.length) {
|
|
|
res.data[0].problemZoneList.forEach(element => {
|
|
|
- if(element.code === selectedDrawTypeMeta.value.type){
|
|
|
+ if (element.code === selectedDrawTypeMeta.value.type) {
|
|
|
element.children.forEach(item => {
|
|
|
- if(item.problemZoneTypeName === selectedDrawTypeMeta.value.category.problemZoneTypeName){
|
|
|
+ if (item.problemZoneTypeName === selectedDrawTypeMeta.value.category.problemZoneTypeName) {
|
|
|
problemZoneId = item.id;
|
|
|
}
|
|
|
});
|
|
|
@@ -875,6 +1065,51 @@ const submitRegions = async () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+const submitCurrentTabGeometryChange = async (geomWkt) => {
|
|
|
+ const tab = varietyTabs.value?.[activeVariety.value];
|
|
|
+ if (!tab) return false;
|
|
|
+ const normalizedGeom = isValidGeom(geomWkt) ? String(geomWkt).trim() : "";
|
|
|
+ const params = {
|
|
|
+ subjectId: route.query.subjectId,
|
|
|
+ regionList: [],
|
|
|
+ problemZoneList: [],
|
|
|
+ };
|
|
|
+
|
|
|
+ if (activeRegionType.value === "variety") {
|
|
|
+ params.regionList = [
|
|
|
+ {
|
|
|
+ regionId: tab.regionId,
|
|
|
+ typeId: tab.typeId,
|
|
|
+ regionName: tab.regionName,
|
|
|
+ geom: normalizedGeom,
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ } else {
|
|
|
+ const group = findProblemZoneGroupByDrawType(activeRegionType.value);
|
|
|
+ if (!group) return false;
|
|
|
+ const remark = tab?.geomItems?.[0]?.remark || "";
|
|
|
+ params.problemZoneList = [
|
|
|
+ {
|
|
|
+ name: group.name,
|
|
|
+ code: group.code,
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ farmSubjectId: route.query.subjectId,
|
|
|
+ problemZoneTypeId: tab.problemZoneTypeId ?? tab.typeId ?? "",
|
|
|
+ problemZoneTypeName: tab.problemZoneTypeName ?? tab.regionName ?? "",
|
|
|
+ parentName: group.name ?? "",
|
|
|
+ parentCode: group.code ?? "",
|
|
|
+ geomItems: normalizedGeom ? [{ geomWkt: normalizedGeom, remark: String(remark) }] : [],
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await VE_API.basic_farm.ediRegionZone(params);
|
|
|
+ return res?.code === 0;
|
|
|
+};
|
|
|
+
|
|
|
/** 以弹窗 confirm 传入的 drawMeta(大类 type + 小类 category + remark)为准写入草稿并提交 */
|
|
|
const confirmArea = async (drawMeta) => {
|
|
|
const meta =
|
|
|
@@ -989,6 +1224,7 @@ const handleConfirmDrawType = async (payload) => {
|
|
|
};
|
|
|
|
|
|
const handleEditRegion = async () => {
|
|
|
+ clearDeleteSelectionState();
|
|
|
// 从查看态切换到可勾画编辑态:移除查看标记(所有大类共用)
|
|
|
const nextQuery = { ...route.query };
|
|
|
delete nextQuery.type;
|
|
|
@@ -1053,10 +1289,10 @@ const handleEditRegion = async () => {
|
|
|
isValidGeom(draftGeom)
|
|
|
? [String(draftGeom).trim()]
|
|
|
: (() => {
|
|
|
- const fromItems = getGeomArrFromGeomItems(tab);
|
|
|
- if (fromItems.length) return fromItems;
|
|
|
- return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
|
|
|
- })();
|
|
|
+ const fromItems = getGeomArrFromGeomItems(tab);
|
|
|
+ if (fromItems.length) return fromItems;
|
|
|
+ return isValidGeom(tab?.geom) ? [String(tab.geom).trim()] : [];
|
|
|
+ })();
|
|
|
const geomArr = flattenGeomWktListForMap(rawGeomArr);
|
|
|
drawRegionMap.kmap?.polygonLayer?.source?.clear?.();
|
|
|
if (geomArr.length > 0) {
|
|
|
@@ -1107,7 +1343,7 @@ const handleTipConfirm = () => {
|
|
|
flex-direction: column;
|
|
|
box-sizing: border-box;
|
|
|
|
|
|
- & > :first-child {
|
|
|
+ &> :first-child {
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
@@ -1171,6 +1407,7 @@ const handleTipConfirm = () => {
|
|
|
box-sizing: border-box;
|
|
|
/* 底部「重置 / 确认」等为 position:fixed,占高约 68px,地图区域需预留避免被盖住 */
|
|
|
padding-bottom: 68px;
|
|
|
+
|
|
|
.edit-map-tip {
|
|
|
position: absolute;
|
|
|
top: 23px;
|
|
|
@@ -1198,14 +1435,14 @@ const handleTipConfirm = () => {
|
|
|
align-items: flex-end;
|
|
|
|
|
|
.footer-back {
|
|
|
- padding: 6px 7px 9px;
|
|
|
+ padding: 4px 5px 7px;
|
|
|
background: #fff;
|
|
|
border-radius: 8px;
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
.back-icon {
|
|
|
- width: 20px;
|
|
|
- height: 18px;
|
|
|
+ width: 23px;
|
|
|
+ height: 23px;
|
|
|
}
|
|
|
}
|
|
|
|