| | |
| | | const fenceDraftPolygon = shallowRef<any>(null); |
| | | const fenceDraftRestrictedPolygons = shallowRef<any[]>([]); |
| | | const fenceDraftPoints = ref<LngLatTuple[]>([]); |
| | | const restrictedDrawingVertexCount = ref(0); |
| | | const fenceRestrictedAreas = ref<FenceRestrictedAreaDraft[]>([]); |
| | | const activeRestrictedAreaId = ref(""); |
| | | const readonlyRestrictedAreaId = ref(""); |
| | |
| | | const restrictedDrawSessionId = ref(0); |
| | | const restrictedAreaEditorClosed = ref(false); |
| | | const suppressRestrictedEditorEnd = ref(false); |
| | | let restrictedEditorRollbacking = false; |
| | | const restrictedAreaModalVisible = ref(false); |
| | | const fencePanelRef = ref<any>(null); |
| | | const restrictedAreaModalRef = ref<any>(null); |
| | |
| | | const isFenceEditWorkflow = computed( |
| | | () => fenceEditMode.value && !fenceCreateMode.value |
| | | ); |
| | | const isFenceGeometryLocked = computed( |
| | | () => |
| | | isFenceEditWorkflow.value && |
| | | Number(fenceForm.value.areaStatus || fenceDetailData.value?.areaStatus || 1) === 1 |
| | | ); |
| | | const isStationFormMode = computed( |
| | | () => |
| | | activeTab.value === "station" && (stationEditMode.value || stationCreateMode.value) |
| | |
| | | if (!Number.isFinite(lon) || !Number.isFinite(lat)) return null; |
| | | return [lon, lat]; |
| | | }); |
| | | const restrictedAreaVertexLimit = 16; |
| | | const currentRestrictedVertexCount = computed(() => { |
| | | if (fenceInteractionMode.value === "restricted-draw" && fenceMouseTool.value) |
| | | return restrictedDrawingVertexCount.value; |
| | | const activeRestrictedArea = fenceRestrictedAreas.value.find( |
| | | (item) => item.id === activeRestrictedAreaId.value |
| | | ); |
| | | return activeRestrictedArea?.points.length || 0; |
| | | }); |
| | | const showRestrictedVertexCounter = computed(() => { |
| | | if (!isFenceFormMode.value || activeTab.value !== "fence") return false; |
| | | if (isFenceGeometryLocked.value) return false; |
| | | if (!activeRestrictedAreaId.value) return false; |
| | | return ["restricted-draw", "restricted-edit"].includes( |
| | | fenceInteractionMode.value |
| | | ); |
| | | }); |
| | | const fenceMapTipText = computed(() => { |
| | | if (!isFenceFormMode.value) return ""; |
| | | const activeRestrictedArea = fenceRestrictedAreas.value.find( |
| | |
| | | if (activeRestrictedArea) { |
| | | if (activeRestrictedArea.points.length < 3) |
| | | return "请在地图上【蓝色运营区域】绘制多边形区域,双击闭合"; |
| | | if (isFenceGeometryLocked.value) return "【红色禁行区】已选中"; |
| | | return "【红色禁行区】可调整区域点位,请保持在蓝色运营区内"; |
| | | } |
| | | if (fenceDraftPoints.value.length < 3) |
| | |
| | | activeRestrictedAreaId.value = ""; |
| | | readonlyRestrictedAreaId.value = ""; |
| | | fenceDraftPoints.value = []; |
| | | restrictedDrawingVertexCount.value = 0; |
| | | invalidFenceStationIds.value = []; |
| | | updateFenceStationWarningStyles(); |
| | | } |
| | | |
| | | function getRestrictedDrawingVertexCount() { |
| | | const path = fenceMouseTool.value?.polygonTemp?.obj?.getPath?.(); |
| | | if (!Array.isArray(path) || !path.length) return 0; |
| | | return path.length > 1 ? path.length - 1 : path.length; |
| | | } |
| | | |
| | | function refreshRestrictedDrawingVertexCount() { |
| | | restrictedDrawingVertexCount.value = getRestrictedDrawingVertexCount(); |
| | | } |
| | | |
| | | function applyRestrictedPolygonPath(polygon: any, points: LngLatTuple[]) { |
| | | polygon?.setPath?.( |
| | | points.map(([lng, lat]) => new (window as any).AMap.LngLat(lng, lat)) |
| | | ); |
| | | } |
| | | |
| | | function enforceRestrictedDrawingVertexLimit() { |
| | | const tempPolygon = fenceMouseTool.value?.polygonTemp?.obj; |
| | | const path = tempPolygon?.getPath?.(); |
| | | if (!Array.isArray(path) || !path.length) return false; |
| | | const vertexCount = getRestrictedDrawingVertexCount(); |
| | | if (vertexCount <= restrictedAreaVertexLimit) return false; |
| | | const retainedLength = Math.min(path.length, restrictedAreaVertexLimit + 1); |
| | | tempPolygon.setPath?.(path.slice(0, retainedLength)); |
| | | restrictedDrawingVertexCount.value = restrictedAreaVertexLimit; |
| | | window.$message?.warning( |
| | | `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位` |
| | | ); |
| | | return true; |
| | | } |
| | | |
| | | function updateFenceStationWarningStyles() { |
| | |
| | | function setFenceDraftPolygonStyle(editable: boolean) { |
| | | if (!fenceDraftPolygon.value) return; |
| | | fenceDraftPolygon.value.setOptions({ |
| | | fillColor: "#0068FF", |
| | | strokeColor: "#0068FF", |
| | | fillColor: "#F19216", |
| | | strokeColor: "#F19216", |
| | | strokeWeight: 2, |
| | | strokeOpacity: 1, |
| | | fillOpacity: 0.1, |
| | | strokeStyle: editable ? "solid" : "dashed", |
| | | strokeStyle: "solid", |
| | | cursor: "pointer", |
| | | zIndex: 220, |
| | | }); |
| | |
| | | if (fenceDraftPolygon.value) map.value.remove(fenceDraftPolygon.value); |
| | | fenceDraftPolygon.value = new (window as any).AMap.Polygon({ |
| | | path: points, |
| | | fillColor: "#0068FF", |
| | | strokeColor: "#0068FF", |
| | | fillColor: "#F19216", |
| | | strokeColor: "#F19216", |
| | | strokeWeight: 2, |
| | | strokeOpacity: 1, |
| | | fillOpacity: 0.1, |
| | | strokeStyle: "dashed", |
| | | strokeStyle: "solid", |
| | | bubble: true, |
| | | zIndex: 220, |
| | | }); |
| | |
| | | fenceDraftPolygon.value, |
| | | { |
| | | controlPoint: { |
| | | strokeColor: "#0068FF", |
| | | strokeColor: "#F19216", |
| | | strokeOpacity: 1, |
| | | strokeWeight: 2, |
| | | }, |
| | | midControlPoint: { fillColor: "#0068FF" }, |
| | | editOptions: { fillColor: "#0068FF" }, |
| | | midControlPoint: { fillColor: "#F19216" }, |
| | | editOptions: { fillColor: "#F19216" }, |
| | | } |
| | | ); |
| | | fencePolyEditor.value.open(); |
| | |
| | | cleanupFenceEditorState(); |
| | | fenceMouseTool.value = new (window as any).AMap.MouseTool(map.value); |
| | | fenceMouseTool.value.polygon({ |
| | | strokeColor: "#0068FF", |
| | | strokeColor: "#F19216", |
| | | strokeOpacity: 1, |
| | | strokeWeight: 2, |
| | | fillColor: "#0068FF", |
| | | fillColor: "#F19216", |
| | | fillOpacity: 0.1, |
| | | strokeStyle: "solid", |
| | | cursor: "pointer", |
| | |
| | | cleanupFenceEditorState(); |
| | | fenceDraftPoints.value = points; |
| | | createFenceDraftPolygon(points); |
| | | enableFenceDraftEdit(); |
| | | if (!isFenceGeometryLocked.value) enableFenceDraftEdit(); |
| | | fenceRestrictedAreas.value = ( |
| | | Array.isArray(detail?.electronicFenceAreas) |
| | | ? detail.electronicFenceAreas |
| | |
| | | } |
| | | |
| | | function updateRestrictedPolygonStyles() { |
| | | const isOperationAreaActive = |
| | | isFenceFormMode.value && |
| | | !activeRestrictedAreaId.value && |
| | | fenceDraftPoints.value.length >= 3; |
| | | fenceRestrictedAreas.value.forEach((item) => { |
| | | const polygon = item.polygon; |
| | | if (!polygon?.setOptions) return; |
| | | const isActive = item.id === activeRestrictedAreaId.value; |
| | | const isEditing = |
| | | !isFenceGeometryLocked.value && |
| | | item.id === activeRestrictedEditorId.value; |
| | | item.isActive = isActive; |
| | | try { |
| | | polygon.setOptions({ |
| | | strokeColor: isActive |
| | | strokeColor: isEditing |
| | | ? "#F14637" |
| | | : isOperationAreaActive |
| | | ? "transparent" |
| | | : "#EF938B", |
| | | : "transparent", |
| | | fillColor: isActive ? "#F14637" : "#EF938B", |
| | | strokeWeight: 0, |
| | | strokeOpacity: 0, |
| | | fillOpacity: isActive ? 0.32 : isOperationAreaActive ? 1 : 0.18, |
| | | strokeWeight: isEditing ? 1 : 0, |
| | | strokeOpacity: isEditing ? 1 : 0, |
| | | fillOpacity: isEditing |
| | | ? 0 |
| | | : isActive |
| | | ? 1 |
| | | : 0.18, |
| | | strokeStyle: "solid", |
| | | cursor: "pointer", |
| | | zIndex: isActive ? 230 : 210, |
| | | zIndex: isEditing ? 230 : isActive ? 220 : 210, |
| | | }); |
| | | } catch { |
| | | item.polygon = null; |
| | |
| | | updateRestrictedPolygonStyles(); |
| | | } |
| | | |
| | | function syncLockedFenceReadonlySelection(restrictedAreaId = "") { |
| | | if (!map.value) return; |
| | | fenceInteractionMode.value = "operation-preview"; |
| | | activeRestrictedAreaId.value = restrictedAreaId; |
| | | activeRestrictedEditorId.value = ""; |
| | | restrictedAreaEditorClosed.value = false; |
| | | suppressRestrictedEditorEnd.value = true; |
| | | hideFenceEditGuide(); |
| | | fencePolyEditor.value?.close?.(); |
| | | fencePolyEditor.value = null; |
| | | setFenceDraftPolygonStyle(false); |
| | | renderFenceRestrictedPolygons(); |
| | | } |
| | | |
| | | function focusOperationArea(fitView = false) { |
| | | if ( |
| | | !isFenceFormMode.value || |
| | |
| | | fenceDraftPoints.value.length < 3 |
| | | ) |
| | | return; |
| | | if (isFenceGeometryLocked.value) { |
| | | syncLockedFenceReadonlySelection(); |
| | | if (fitView && map.value) |
| | | map.value.setFitView( |
| | | [fenceDraftPolygon.value], |
| | | false, |
| | | [120, 120, 120, 120] |
| | | ); |
| | | return; |
| | | } |
| | | applyFenceInteractionMode("operation-edit"); |
| | | enableFenceDraftEdit(); |
| | | if (fitView && map.value) |
| | |
| | | return; |
| | | } |
| | | const area = fenceRestrictedAreas.value.find((item) => item.id === id); |
| | | const isCurrentRestrictedAreaActive = activeRestrictedAreaId.value === id; |
| | | if (isFenceGeometryLocked.value) { |
| | | if (isCurrentRestrictedAreaActive) { |
| | | syncLockedFenceReadonlySelection(); |
| | | return; |
| | | } |
| | | syncLockedFenceReadonlySelection(id); |
| | | const selectedArea = fenceRestrictedAreas.value.find((item) => item.id === id); |
| | | if (selectedArea?.polygon && map.value) |
| | | map.value.setFitView([selectedArea.polygon], false, [120, 120, 120, 120]); |
| | | return; |
| | | } |
| | | if (!area?.polygon) { |
| | | startRestrictedAreaDraw(id); |
| | | return; |
| | | } |
| | | if (isCurrentRestrictedAreaActive && !fenceMouseTool.value) { |
| | | focusOperationArea(); |
| | | return; |
| | | } |
| | | if ( |
| | |
| | | if (area?.polygon && map.value) { |
| | | map.value.setFitView([area.polygon], false, [120, 120, 120, 120]); |
| | | if (!isFenceFormMode.value) return; |
| | | let lastValidPoints = clonePolygonPath(area.points); |
| | | fencePolyEditor.value?.close?.(); |
| | | fencePolyEditor.value = new (window as any).AMap.PolyEditor( |
| | | map.value, |
| | |
| | | const current = fenceRestrictedAreas.value.find((item) => item.id === id); |
| | | const polygon = current?.polygon; |
| | | if (!current || !polygon?.getPath) return; |
| | | current.points = polygon |
| | | const nextPoints = polygon |
| | | .getPath() |
| | | .map((point: any) => [point.lng, point.lat] as LngLatTuple); |
| | | if ( |
| | | nextPoints.length > restrictedAreaVertexLimit && |
| | | lastValidPoints.length >= 3 && |
| | | !restrictedEditorRollbacking |
| | | ) { |
| | | restrictedEditorRollbacking = true; |
| | | window.$message?.warning( |
| | | `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位` |
| | | ); |
| | | applyRestrictedPolygonPath(polygon, lastValidPoints); |
| | | restrictedDrawingVertexCount.value = lastValidPoints.length; |
| | | setTimeout(() => { |
| | | restrictedEditorRollbacking = false; |
| | | }, 0); |
| | | return; |
| | | } |
| | | current.points = nextPoints; |
| | | current.area = calcFenceArea(current.points); |
| | | restrictedDrawingVertexCount.value = current.points.length; |
| | | if (current.points.length >= 3) { |
| | | lastValidPoints = clonePolygonPath(current.points); |
| | | } |
| | | }; |
| | | const handleRestrictedAreaEditEnd = () => { |
| | | if (restrictedEditorRollbacking) return; |
| | | if (suppressRestrictedEditorEnd.value) { |
| | | suppressRestrictedEditorEnd.value = false; |
| | | return; |
| | |
| | | }; |
| | | fencePolyEditor.value.on("end", handleRestrictedAreaEditEnd); |
| | | fencePolyEditor.value.on("adjust", syncPoints); |
| | | fencePolyEditor.value.on("addnode", syncPoints); |
| | | fencePolyEditor.value.on("removenode", syncPoints); |
| | | fencePolyEditor.value.open(); |
| | | } |
| | | } |
| | |
| | | window.$message?.warning("请绘制至少3个点的禁行区"); |
| | | return false; |
| | | } |
| | | if (points.length > 16) { |
| | | window.$message?.warning("禁行区最多支持设置 16 个点位"); |
| | | if (points.length > restrictedAreaVertexLimit) { |
| | | window.$message?.warning( |
| | | `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位` |
| | | ); |
| | | return false; |
| | | } |
| | | if (isFencePolygonSelfIntersect(points)) { |
| | |
| | | restrictedDrawSessionId.value += 1; |
| | | const currentSessionId = restrictedDrawSessionId.value; |
| | | applyFenceInteractionMode("restricted-draw", areaId); |
| | | restrictedDrawingVertexCount.value = 0; |
| | | const previousMouseTool = fenceMouseTool.value; |
| | | fenceMouseTool.value = null; |
| | | previousMouseTool?.close?.(); |
| | |
| | | const points = event.obj |
| | | .getPath() |
| | | .map((point: any) => [point.lng, point.lat] as LngLatTuple); |
| | | restrictedDrawingVertexCount.value = points.length; |
| | | map.value.remove(event.obj); |
| | | if (points.length < 3) { |
| | | window.$message?.warning("请绘制至少3个点的禁行区"); |
| | | setTimeout(() => startRestrictedAreaDraw(areaId), 0); |
| | | return; |
| | | } |
| | | if (points.length > 16) { |
| | | window.$message?.warning("禁行区最多支持设置 16 个点位"); |
| | | if (points.length > restrictedAreaVertexLimit) { |
| | | window.$message?.warning( |
| | | `禁行区最多支持设置 ${restrictedAreaVertexLimit} 个点位` |
| | | ); |
| | | setTimeout(() => startRestrictedAreaDraw(areaId), 0); |
| | | return; |
| | | } |
| | |
| | | } catch { |
| | | return; |
| | | } |
| | | if (!restrictedAreaForm.value.operationStrategy.length) { |
| | | window.$message?.warning("请至少添加一个禁行区策略组"); |
| | | return; |
| | | } |
| | | if (!validateRestrictedAreaStrategies()) return; |
| | | // 测试运营工作台暂时不需要禁行区运营策略,先注释相关校验逻辑,后续确认后再统一删除。 |
| | | // if (!restrictedAreaForm.value.operationStrategy.length) { |
| | | // window.$message?.warning("请至少添加一个禁行区策略组"); |
| | | // return; |
| | | // } |
| | | // if (!validateRestrictedAreaStrategies()) return; |
| | | current.areaName = restrictedAreaForm.value.areaName.trim(); |
| | | current.areaDescription = restrictedAreaForm.value.areaDescription.trim(); |
| | | current.areaStatus = restrictedAreaForm.value.areaStatus; |
| | | current.operationStrategy = cloneRestrictedAreaStrategies( |
| | | restrictedAreaForm.value.operationStrategy |
| | | ); |
| | | // 测试运营工作台暂时不需要禁行区运营策略,先注释相关回写逻辑,后续确认后再统一删除。 |
| | | // current.operationStrategy = cloneRestrictedAreaStrategies( |
| | | // restrictedAreaForm.value.operationStrategy |
| | | // ); |
| | | restrictedAreaModalVisible.value = false; |
| | | resetRestrictedAreaForm(); |
| | | } |
| | |
| | | if (value) showCreateMenu.value = false; |
| | | }); |
| | | |
| | | watch(isFenceGeometryLocked, (locked) => { |
| | | if (!locked || !isFenceFormMode.value) return; |
| | | const selectedRestrictedAreaId = activeRestrictedAreaId.value; |
| | | syncLockedFenceReadonlySelection(selectedRestrictedAreaId); |
| | | if (!selectedRestrictedAreaId) return; |
| | | const currentArea = fenceRestrictedAreas.value.find( |
| | | (item) => item.id === selectedRestrictedAreaId |
| | | ); |
| | | if (currentArea?.polygon && map.value) |
| | | map.value.setFitView([currentArea.polygon], false, [120, 120, 120, 120]); |
| | | }); |
| | | |
| | | async function handleMapEntityClick(type: WorkbenchTab, id: string) { |
| | | if ( |
| | | (activeTab.value !== type || selectedId.value !== id) && |
| | |
| | | function handleMapCanvasClick(event: any) { |
| | | const lnglat = event?.lnglat; |
| | | if (!lnglat) return; |
| | | if ( |
| | | activeTab.value === "fence" && |
| | | isFenceFormMode.value && |
| | | fenceMouseTool.value && |
| | | fenceInteractionMode.value === "restricted-draw" |
| | | ) { |
| | | setTimeout(() => { |
| | | refreshRestrictedDrawingVertexCount(); |
| | | enforceRestrictedDrawingVertexLimit(); |
| | | }, 0); |
| | | } |
| | | if (suppressFenceEditGuideDismissOnce.value) return; |
| | | dismissFenceEditGuide(); |
| | | if ( |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <div |
| | | v-if="showRestrictedVertexCounter" |
| | | class="restricted-vertex-counter" |
| | | > |
| | | <span class="restricted-vertex-counter__label">顶点数量</span> |
| | | <span class="restricted-vertex-counter__value" |
| | | >{{ currentRestrictedVertexCount }}/{{ |
| | | restrictedAreaVertexLimit |
| | | }}</span |
| | | > |
| | | </div> |
| | | |
| | | <div class="tool-menu-wrapper coverage"> |
| | | <div |
| | | class="operate-item" |