| | |
| | | const fenceDraftPoints = ref<LngLatTuple[]>([]); |
| | | const fenceRestrictedAreas = ref<FenceRestrictedAreaDraft[]>([]); |
| | | const activeRestrictedAreaId = ref(""); |
| | | const readonlyRestrictedAreaId = ref(""); |
| | | const fenceInteractionMode = ref<FenceInteractionMode>("operation-preview"); |
| | | const activeRestrictedEditorId = ref(""); |
| | | const restrictedDrawSessionId = ref(0); |
| | |
| | | (item) => item.id === selectedId.value |
| | | ); |
| | | if (matchedStation) return matchedStation; |
| | | if (String(stationDetailData.value?.id || "") === String(selectedId.value || "")) |
| | | if ( |
| | | stationDetailData.value |
| | | && selectedId.value |
| | | && String(stationDetailData.value.id || "") === String(selectedId.value) |
| | | ) |
| | | return mapStationApiItemToListItem(stationDetailData.value); |
| | | return ( |
| | | currentList.value[0] || |
| | |
| | | showStopButton: verificationStatus === 5 && operationStatus === 2, |
| | | showRestartButton: |
| | | verificationStatus === 4 || |
| | | operationStatus === 3 || |
| | | operationStatus === 4, |
| | | (verificationStatus === 5 && |
| | | (operationStatus === 3 || operationStatus === 4)), |
| | | showNextStep: |
| | | (verificationStatus === 1 && operationStatus === 1) || |
| | | (verificationStatus === 3 && operationStatus === 1) || |
| | | verificationStatus === 1 || |
| | | verificationStatus === 3 || |
| | | (verificationStatus === 5 && operationStatus === 1), |
| | | }; |
| | | }); |
| | |
| | | : []; |
| | | return { |
| | | operationAreas: areas.filter((item: any) => Number(item.areaType) === 1), |
| | | restrictedAreas: areas.filter((item: any) => Number(item.areaType) === 2), |
| | | restrictedAreas: areas |
| | | .filter((item: any) => Number(item.areaType) === 2) |
| | | .map((item: any) => ({ |
| | | ...item, |
| | | isActive: readonlyRestrictedAreaId.value === String(item.id), |
| | | })), |
| | | }; |
| | | }); |
| | | |
| | |
| | | .filter((item) => item.points.length >= 3); |
| | | } |
| | | |
| | | function getOtherStations() { |
| | | const currentStationId = |
| | | stationEditMode.value && !stationCreateMode.value |
| | | ? String(stationDetailData.value?.id || selectedId.value || "") |
| | | : ""; |
| | | return stationData.value |
| | | .filter((item) => Array.isArray(item.polygonPath) && item.polygonPath.length >= 3) |
| | | .filter((item) => String(item.id || "") !== currentStationId) |
| | | .map((item) => ({ |
| | | id: String(item.id || ""), |
| | | name: item.name || "未命名场站", |
| | | points: clonePolygonPath(item.polygonPath), |
| | | })) |
| | | .filter((item) => item.points.length >= 3); |
| | | } |
| | | |
| | | function getStationGeometryInvalidReason(points: LngLatTuple[]) { |
| | | if (points.length < 3) return "请绘制至少3个点的场站"; |
| | | if (isFencePolygonSelfIntersect(points)) return "场站区域不允许自相交"; |
| | |
| | | isRingInRing(points, area.points) |
| | | ); |
| | | if (!isInsideOperationArea) return "场站需完全处于运营区内"; |
| | | const otherStations = getOtherStations(); |
| | | const geometryUtil = (window as any).AMap?.GeometryUtil; |
| | | if (geometryUtil) { |
| | | const crossStation = otherStations.find((station) => |
| | | geometryUtil.doesRingRingIntersect(points, station.points) |
| | | ); |
| | | if (crossStation) |
| | | return `当前区域与${crossStation.name}相交,请避开已有场站后重新绘制`; |
| | | const insideStation = otherStations.find((station) => |
| | | geometryUtil.isRingInRing(points, station.points) |
| | | ); |
| | | if (insideStation) |
| | | return `当前区域完全落在${insideStation.name}内,请在其他区域绘制`; |
| | | const containStation = otherStations.find((station) => |
| | | geometryUtil.isRingInRing(station.points, points) |
| | | ); |
| | | if (containStation) |
| | | return `当前区域包含${containStation.name},请勿覆盖已有场站`; |
| | | } |
| | | const overlapRestrictedArea = getStationRestrictedAreas().find((area) => |
| | | doesPolygonOverlap(points, area.points) |
| | | ); |
| | |
| | | item.isActive = false; |
| | | }); |
| | | activeRestrictedAreaId.value = ""; |
| | | readonlyRestrictedAreaId.value = ""; |
| | | fenceDraftPoints.value = []; |
| | | invalidFenceStationIds.value = []; |
| | | updateFenceStationWarningStyles(); |
| | |
| | | ? "transparent" |
| | | : "#EF938B", |
| | | fillColor: isActive ? "#F14637" : "#EF938B", |
| | | strokeWeight: isActive ? 2 : 1, |
| | | strokeOpacity: 1, |
| | | fillOpacity: isActive ? 0.18 : isOperationAreaActive ? 1 : 0.18, |
| | | strokeWeight: 0, |
| | | strokeOpacity: 0, |
| | | fillOpacity: isActive ? 0.32 : isOperationAreaActive ? 1 : 0.18, |
| | | strokeStyle: "solid", |
| | | cursor: "pointer", |
| | | zIndex: isActive ? 230 : 210, |
| | |
| | | } |
| | | |
| | | function focusRestrictedArea(id: string) { |
| | | if (!isFenceFormMode.value) { |
| | | const nextId = String(id || ""); |
| | | readonlyRestrictedAreaId.value = |
| | | readonlyRestrictedAreaId.value === nextId ? "" : nextId; |
| | | renderMapEntities(); |
| | | if (!readonlyRestrictedAreaId.value) return; |
| | | const polygon = fencePolygons.value.find((item: any) => { |
| | | const extData = item.getExtData?.() || {}; |
| | | return ( |
| | | Number(extData.areaType) === 2 && |
| | | String(extData.areaId || "") === readonlyRestrictedAreaId.value |
| | | ); |
| | | }); |
| | | if (polygon && map.value) |
| | | map.value.setFitView([polygon], false, [120, 120, 120, 120]); |
| | | return; |
| | | } |
| | | const area = fenceRestrictedAreas.value.find((item) => item.id === id); |
| | | if (!area?.polygon) { |
| | | startRestrictedAreaDraw(id); |
| | |
| | | } catch { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | function resolveStationByPoint(lon: number, lat: number) { |
| | | if (!Number.isFinite(lon) || !Number.isFinite(lat)) return null; |
| | | const targetPoint: LngLatTuple = [lon, lat]; |
| | | return stationData.value.find((item) => { |
| | | if (!Array.isArray(item.polygonPath) || item.polygonPath.length < 3) |
| | | return false; |
| | | return isPointInFencePath(targetPoint, item.polygonPath); |
| | | }) || null; |
| | | } |
| | | |
| | | function getFenceOriginalOperationPoints() { |
| | |
| | | if (tab === "fence" && fenceCreateMode.value) return; |
| | | if (tab === "station" && stationCreateMode.value) return; |
| | | if (!id) { |
| | | readonlyRestrictedAreaId.value = ""; |
| | | cleanupPointEditMapState(); |
| | | pointEditMode.value = false; |
| | | pointCreateMode.value = false; |
| | |
| | | } |
| | | |
| | | if (tab === "point") { |
| | | readonlyRestrictedAreaId.value = ""; |
| | | fenceDetailData.value = null; |
| | | stationDetailData.value = null; |
| | | stationEditMode.value = false; |
| | |
| | | pointCreateMode.value = false; |
| | | pointCreateReady.value = false; |
| | | pointDetailData.value = null; |
| | | readonlyRestrictedAreaId.value = ""; |
| | | stationEditMode.value = false; |
| | | stationCreateMode.value = false; |
| | | stationEditSnapshot.value = ""; |
| | |
| | | return; |
| | | } |
| | | selectedStatuses.value = [...selectedStatuses.value, value]; |
| | | } |
| | | |
| | | function toggleCityDropdown() { |
| | | const nextVisible = !cityDropdownVisible.value; |
| | | statusDropdownVisible.value = false; |
| | | cityDropdownVisible.value = nextVisible; |
| | | } |
| | | |
| | | function toggleStatusDropdown() { |
| | | const nextVisible = !statusDropdownVisible.value; |
| | | cityDropdownVisible.value = false; |
| | | statusDropdownVisible.value = nextVisible; |
| | | } |
| | | |
| | | function handleSelectCity(value: string) { |
| | |
| | | fenceDetailLoading.value = true; |
| | | fenceEditMode.value = false; |
| | | fenceCreateMode.value = false; |
| | | readonlyRestrictedAreaId.value = ""; |
| | | try { |
| | | const res = await getFenceDetail(id); |
| | | if (res.code === 200 && res.data) { |
| | |
| | | let nextV = v; |
| | | let nextO = o; |
| | | |
| | | if (v === 1 && o === 1) nextV = 2; |
| | | else if (v === 3 && o === 1) nextV = 5; |
| | | // verificationStatus 非 5 时,按钮和流转都仅按 verificationStatus 判断; |
| | | // 流转时顺带把 operationStatus 归一到正常链路,避免脏数据继续传递。 |
| | | if (v === 1) { |
| | | nextV = 2; |
| | | nextO = 1; |
| | | } |
| | | else if (v === 3) { |
| | | nextV = 5; |
| | | nextO = 1; |
| | | } |
| | | else if (v === 5 && o === 1) nextO = 2; |
| | | else return; |
| | | |
| | |
| | | let electronicAreaId = |
| | | pointDetailData.value?.electronicAreaId?.toString() || ""; |
| | | let fenceId = (pointDetailData.value as any)?.fenceId?.toString() || ""; |
| | | const containingStation = resolveStationByPoint( |
| | | Number(pointForm.value.pointLon), |
| | | Number(pointForm.value.pointLat) |
| | | ); |
| | | if (operationArea) { |
| | | electronicAreaId = operationArea.id?.toString() || ""; |
| | | fenceId = operationArea.electronicId?.toString() || ""; |
| | |
| | | supportOperationTime, |
| | | electronicAreaId, |
| | | fenceId, |
| | | stationId: containingStation?.id, |
| | | }); |
| | | const res = pointCreateMode.value |
| | | ? await createPoint(basePayload as Api.Point.CreateData) |
| | |
| | | return false; |
| | | }) |
| | | .map((item) => { |
| | | const areaType = Number(item.areaType); |
| | | const points = String(item.fencePoints) |
| | | .split(",") |
| | | .map((pointStr: string) => { |
| | |
| | | const isActive = |
| | | activeTab.value === "fence" && |
| | | selectedId.value === String(item.electronicId); |
| | | const isFenceReadonlyDimmed = |
| | | isFenceFormMode.value && Number(item.areaType) === 1; |
| | | const isReadonlyRestrictedActive = |
| | | areaType === 2 && |
| | | activeTab.value === "fence" && |
| | | !isFenceFormMode.value && |
| | | isActive && |
| | | readonlyRestrictedAreaId.value === String(item.id); |
| | | const isFenceReadonlyDimmed = areaType === 1 && !isActive; |
| | | const isFenceDetailActive = |
| | | Number(item.areaType) === 1 && isActive && !isFenceFormMode.value; |
| | | areaType === 1 && isActive && !isFenceFormMode.value; |
| | | const polygon = new (window as any).AMap.Polygon({ |
| | | path: points, |
| | | fillColor: |
| | | item.areaType === 2 |
| | | ? "#EF938B" |
| | | areaType === 2 |
| | | ? isReadonlyRestrictedActive |
| | | ? "#F14637" |
| | | : "#EF938B" |
| | | : isFenceDetailActive |
| | | ? "#19B144" |
| | | : isFenceReadonlyDimmed |
| | | ? "#171F26" |
| | | : "#171F26", |
| | | strokeColor: |
| | | areaType === 2 |
| | | ? isReadonlyRestrictedActive |
| | | ? "#F14637" |
| | | : "#EF938B" |
| | | : isFenceDetailActive |
| | | ? "#19B144" |
| | | : "#0068FF", |
| | | strokeColor: |
| | | item.areaType === 2 |
| | | ? "#EF938B" |
| | | : isFenceReadonlyDimmed |
| | | ? "#AEB0B2" |
| | | : isFenceDetailActive |
| | | ? "#19B144" |
| | | : "#0068FF", |
| | | strokeWeight: isFenceDetailActive ? 2 : isActive ? 3 : 2, |
| | | : "#AEB0B2", |
| | | strokeWeight: areaType === 2 |
| | | ? 0 |
| | | : isFenceDetailActive |
| | | ? 2 |
| | | : isActive |
| | | ? 3 |
| | | : 2, |
| | | strokeOpacity: areaType === 2 ? 0 : 1, |
| | | fillOpacity: |
| | | item.areaType === 2 |
| | | ? 0.4 |
| | | : isFenceReadonlyDimmed |
| | | ? 0.08 |
| | | areaType === 2 |
| | | ? isReadonlyRestrictedActive |
| | | ? 1 |
| | | : 1 |
| | | : isFenceDetailActive |
| | | ? 0.1 |
| | | : isFenceReadonlyDimmed |
| | | ? 0.08 |
| | | : isActive |
| | | ? 0.24 |
| | | : 0.12, |
| | | ? 0.06 |
| | | : 0.04, |
| | | bubble: true, |
| | | strokeStyle: |
| | | Number(item.areaType) === 1 && |
| | | (isFenceReadonlyDimmed || isFenceDetailActive) |
| | | areaType === 1 && isFenceReadonlyDimmed |
| | | ? "dashed" |
| | | : "solid", |
| | | clickable: |
| | | !isPointMapPickActive.value && |
| | | !isFenceFormMode.value && |
| | | !isStationFormMode.value, |
| | | zIndex: isReadonlyRestrictedActive ? 230 : isActive ? 220 : 205, |
| | | }); |
| | | polygon.on("click", () => { |
| | | if ( |
| | |
| | | polygon.setExtData({ |
| | | id: String(item.electronicId), |
| | | type: "fence", |
| | | areaType: item.areaType, |
| | | areaType, |
| | | areaId: String(item.id || ""), |
| | | }); |
| | | return polygon; |
| | | }) |
| | |
| | | type="button" |
| | | class="city-dropdown-trigger" |
| | | :class="{ active: cityDropdownVisible }" |
| | | @click.stop="cityDropdownVisible = !cityDropdownVisible" |
| | | @click.stop="toggleCityDropdown" |
| | | > |
| | | <span class="city-dropdown-label">{{ currentCityLabel }}</span> |
| | | <img |
| | |
| | | <div |
| | | class="filter-item" |
| | | :class="{ active: selectedStatuses.length > 0 }" |
| | | @click.stop="statusDropdownVisible = !statusDropdownVisible" |
| | | @click.stop="toggleStatusDropdown" |
| | | > |
| | | <span class="filter-text">{{ |
| | | selectedStatuses.length |