| | |
| | | applyRestrictedEditGuidePlacement(layout.placement, layout.offset) |
| | | } |
| | | |
| | | function hideStationEditGuide() { |
| | | if (stationEditGuideMarker.value && map.value) |
| | | map.value.remove(stationEditGuideMarker.value) |
| | | stationEditGuideMarker.value = null |
| | | stationEditGuideContentElement = null |
| | | stationEditGuideArrowElement = null |
| | | } |
| | | |
| | | function clearStationEditGuideHoverTimer() { |
| | | if (!stationEditGuideHoverTimer) |
| | | return |
| | | clearTimeout(stationEditGuideHoverTimer) |
| | | stationEditGuideHoverTimer = null |
| | | } |
| | | |
| | | function clearStationEditGuidePendingHover() { |
| | | clearStationEditGuideHoverTimer() |
| | | stationEditGuideHoverVertexIndex.value = -1 |
| | | } |
| | | |
| | | function resetStationEditGuideHoverState(options: { hideMarker?: boolean } = {}) { |
| | | const { hideMarker = true } = options |
| | | clearStationEditGuidePendingHover() |
| | | stationEditGuideVisibleVertexIndex.value = -1 |
| | | stationEditGuideDisplayMode.value = 'idle' |
| | | if (hideMarker) |
| | | hideStationEditGuide() |
| | | } |
| | | |
| | | function dismissStationEditGuide() { |
| | | stationEditGuideDismissed.value = true |
| | | resetStationEditGuideHoverState() |
| | | } |
| | | |
| | | function armStationEditGuide() { |
| | | resetStationEditGuide() |
| | | suppressStationEditGuideDismissOnce.value = true |
| | | window.setTimeout(() => { |
| | | suppressStationEditGuideDismissOnce.value = false |
| | | }, EDIT_GUIDE_DISMISS_SUPPRESS_MS) |
| | | } |
| | | |
| | | function resetStationEditGuide() { |
| | | stationEditGuideDismissed.value = false |
| | | resetStationEditGuideHoverState() |
| | | } |
| | | |
| | | function canShowStationEditGuideByHover() { |
| | | return ( |
| | | !!map.value |
| | | && typeof (window as any).AMap !== 'undefined' |
| | | && isStationFormMode.value |
| | | && stationDraftPoints.value.length >= 3 |
| | | && !stationMouseTool.value |
| | | ) |
| | | } |
| | | |
| | | function getStationEditGuideVertexPixel(index: number) { |
| | | if ( |
| | | !map.value |
| | | || typeof (window as any).AMap === 'undefined' |
| | | || index < 0 |
| | | || index >= stationDraftPoints.value.length |
| | | ) { |
| | | return null |
| | | } |
| | | const [lng, lat] = stationDraftPoints.value[index] |
| | | if (!Number.isFinite(lng) || !Number.isFinite(lat)) |
| | | return null |
| | | const AMap = (window as any).AMap |
| | | return map.value.lngLatToContainer?.(new AMap.LngLat(lng, lat)) || null |
| | | } |
| | | |
| | | function getStationEditGuideEntryVertexIndex() { |
| | | if (!canShowStationEditGuideByHover()) |
| | | return -1 |
| | | const mapSize = map.value?.getSize?.() |
| | | const viewportCenterX = Number(mapSize?.width || 0) / 2 |
| | | let matchedIndex = -1 |
| | | let maxY = Number.NEGATIVE_INFINITY |
| | | let minCenterDistance = Number.POSITIVE_INFINITY |
| | | stationDraftPoints.value.forEach((_, index) => { |
| | | const pixel = getStationEditGuideVertexPixel(index) |
| | | if (!pixel) |
| | | return |
| | | const pixelY = Number(pixel.y) |
| | | const centerDistance = Math.abs(Number(pixel.x) - viewportCenterX) |
| | | if ( |
| | | pixelY > maxY + 2 |
| | | || ( |
| | | Math.abs(pixelY - maxY) <= 2 |
| | | && centerDistance < minCenterDistance |
| | | ) |
| | | ) { |
| | | matchedIndex = index |
| | | maxY = pixelY |
| | | minCenterDistance = centerDistance |
| | | } |
| | | }) |
| | | return matchedIndex |
| | | } |
| | | |
| | | function getStationEditGuideLayout(vertexIndex: number) { |
| | | const pixel = getStationEditGuideVertexPixel(vertexIndex) |
| | | if (!pixel) { |
| | | return { |
| | | placement: 'top' as const, |
| | | offset: new (window as any).AMap.Pixel(-80, -56), |
| | | } |
| | | } |
| | | const pixelY = Number(pixel.y) |
| | | const mapSize = map.value?.getSize?.() |
| | | const mapHeight = Number(mapSize?.height || 0) |
| | | const placement = pixelY < mapHeight * 0.35 ? 'bottom' : 'top' |
| | | return { |
| | | placement, |
| | | offset: |
| | | placement === 'bottom' |
| | | ? new (window as any).AMap.Pixel(-80, 14) |
| | | : new (window as any).AMap.Pixel(-80, -56), |
| | | } |
| | | } |
| | | |
| | | function applyStationEditGuidePlacement( |
| | | placement: 'top' | 'bottom', |
| | | offset: any, |
| | | ) { |
| | | if (!stationEditGuideMarker.value) |
| | | return |
| | | stationEditGuidePlacement.value = placement |
| | | stationEditGuideMarker.value.setOffset?.(offset) |
| | | if (!stationEditGuideArrowElement) |
| | | return |
| | | if (placement === 'bottom') { |
| | | stationEditGuideArrowElement.style.top = '-5px' |
| | | stationEditGuideArrowElement.style.bottom = 'auto' |
| | | } |
| | | else { |
| | | stationEditGuideArrowElement.style.top = 'auto' |
| | | stationEditGuideArrowElement.style.bottom = '-5px' |
| | | } |
| | | } |
| | | |
| | | function showStationEditGuideOnEntry() { |
| | | if (!canShowStationEditGuideByHover()) |
| | | return |
| | | const entryVertexIndex = getStationEditGuideEntryVertexIndex() |
| | | if (entryVertexIndex < 0) |
| | | return |
| | | armStationEditGuide() |
| | | clearStationEditGuidePendingHover() |
| | | stationEditGuideDismissed.value = false |
| | | stationEditGuideVisibleVertexIndex.value = entryVertexIndex |
| | | stationEditGuideDisplayMode.value = 'entry' |
| | | renderStationEditGuide() |
| | | } |
| | | |
| | | function findStationEditGuideHoverVertexIndex(event: any) { |
| | | if (!canShowStationEditGuideByHover() || !event?.lnglat) |
| | | return -1 |
| | | const referencePixels = getEditGuideHoverReferencePixels(event) |
| | | if (!referencePixels.length) |
| | | return -1 |
| | | const AMap = (window as any).AMap |
| | | const stickyVertexIndex = stationEditGuideHoverVertexIndex.value >= 0 |
| | | ? stationEditGuideHoverVertexIndex.value |
| | | : stationEditGuideVisibleVertexIndex.value |
| | | let matchedIndex = -1 |
| | | let minDistance = Number.POSITIVE_INFINITY |
| | | let stickyMatchedIndex = -1 |
| | | let stickyMinDistance = Number.POSITIVE_INFINITY |
| | | stationDraftPoints.value.forEach(([lng, lat], index) => { |
| | | if (!Number.isFinite(lng) || !Number.isFinite(lat)) |
| | | return |
| | | const vertexPixel = map.value?.lngLatToContainer?.( |
| | | new AMap.LngLat(lng, lat), |
| | | ) |
| | | if (!vertexPixel) |
| | | return |
| | | const distance = Math.min( |
| | | ...referencePixels.map(pixel => getEditGuidePixelDistance(pixel, vertexPixel)), |
| | | ) |
| | | if (distance <= EDIT_GUIDE_HOVER_MATCH_DISTANCE_PX && distance < minDistance) { |
| | | matchedIndex = index |
| | | minDistance = distance |
| | | } |
| | | if ( |
| | | index === stickyVertexIndex |
| | | && distance <= EDIT_GUIDE_HOVER_STICKY_DISTANCE_PX |
| | | && distance < stickyMinDistance |
| | | ) { |
| | | stickyMatchedIndex = index |
| | | stickyMinDistance = distance |
| | | } |
| | | }) |
| | | return matchedIndex >= 0 ? matchedIndex : stickyMatchedIndex |
| | | } |
| | | |
| | | function syncStationEditGuideHover(event: any) { |
| | | const hoveredVertexIndex = findStationEditGuideHoverVertexIndex(event) |
| | | if (hoveredVertexIndex < 0) { |
| | | clearStationEditGuidePendingHover() |
| | | if (stationEditGuideDisplayMode.value === 'hover') |
| | | resetStationEditGuideHoverState() |
| | | return |
| | | } |
| | | if (stationEditGuideVisibleVertexIndex.value === hoveredVertexIndex) { |
| | | renderStationEditGuide() |
| | | return |
| | | } |
| | | if (stationEditGuideHoverVertexIndex.value === hoveredVertexIndex) |
| | | return |
| | | clearStationEditGuidePendingHover() |
| | | stationEditGuideHoverVertexIndex.value = hoveredVertexIndex |
| | | stationEditGuideHoverTimer = window.setTimeout(() => { |
| | | stationEditGuideDismissed.value = false |
| | | stationEditGuideVisibleVertexIndex.value = hoveredVertexIndex |
| | | stationEditGuideDisplayMode.value = 'hover' |
| | | stationEditGuideHoverTimer = null |
| | | renderStationEditGuide() |
| | | }, VERTEX_HOVER_DELAY_MS) |
| | | } |
| | | |
| | | function renderStationEditGuide() { |
| | | if ( |
| | | stationEditGuideDismissed.value |
| | | || !canShowStationEditGuideByHover() |
| | | ) { |
| | | stationEditGuideVisibleVertexIndex.value = -1 |
| | | stationEditGuideDisplayMode.value = 'idle' |
| | | hideStationEditGuide() |
| | | return |
| | | } |
| | | const vertexIndex = stationEditGuideVisibleVertexIndex.value |
| | | if (vertexIndex < 0 || vertexIndex >= stationDraftPoints.value.length) { |
| | | stationEditGuideVisibleVertexIndex.value = -1 |
| | | stationEditGuideDisplayMode.value = 'idle' |
| | | hideStationEditGuide() |
| | | return |
| | | } |
| | | const [lng, lat] = stationDraftPoints.value[vertexIndex] |
| | | if (!Number.isFinite(lng) || !Number.isFinite(lat)) { |
| | | stationEditGuideDisplayMode.value = 'idle' |
| | | hideStationEditGuide() |
| | | return |
| | | } |
| | | const AMap = (window as any).AMap |
| | | const layout = getStationEditGuideLayout(vertexIndex) |
| | | if (!stationEditGuideMarker.value) { |
| | | const content = document.createElement('div') |
| | | content.className = 'station-edit-guide' |
| | | content.style.cssText = [ |
| | | 'position: relative', |
| | | 'width: 159px', |
| | | 'padding: 7px 8px', |
| | | 'border-radius: 4px', |
| | | 'background: #171F26', |
| | | 'color: #fff', |
| | | 'text-align: center', |
| | | 'pointer-events: auto', |
| | | 'box-sizing: border-box', |
| | | ].join(';') |
| | | const text = document.createElement('div') |
| | | text.className = 'station-edit-guide__text' |
| | | text.style.cssText = [ |
| | | 'font-size: 13px', |
| | | 'font-weight: 400', |
| | | 'line-height: 18px', |
| | | 'letter-spacing: 0', |
| | | 'white-space: normal', |
| | | 'word-break: break-word', |
| | | ].join(';') |
| | | text.innerHTML = '拖动紫色实心点增加顶点<br />点击白色实心点删除顶点' |
| | | const arrow = document.createElement('div') |
| | | arrow.style.cssText = [ |
| | | 'position: absolute', |
| | | 'left: 50%', |
| | | 'bottom: -5px', |
| | | 'width: 10px', |
| | | 'height: 10px', |
| | | 'background: rgba(15, 23, 42, 0.96)', |
| | | 'transform: translateX(-50%) rotate(45deg)', |
| | | ].join(';') |
| | | content.appendChild(text) |
| | | content.appendChild(arrow) |
| | | content.addEventListener('click', event => event.stopPropagation()) |
| | | stationEditGuideContentElement = content |
| | | stationEditGuideArrowElement = arrow |
| | | stationEditGuideMarker.value = new AMap.Marker({ |
| | | position: [lng, lat], |
| | | content, |
| | | offset: layout.offset, |
| | | zIndex: 1002, |
| | | bubble: false, |
| | | clickable: true, |
| | | }) |
| | | map.value.add(stationEditGuideMarker.value) |
| | | applyStationEditGuidePlacement(layout.placement, layout.offset) |
| | | return |
| | | } |
| | | stationEditGuideMarker.value.setPosition([lng, lat]) |
| | | applyStationEditGuidePlacement(layout.placement, layout.offset) |
| | | } |
| | | |
| | | function getPointItemsWithinPolygon( |
| | | path: LngLatTuple[], |
| | | sourceList: ListItem[] = pointData.value, |
| | |
| | | const pointEditInfoWindow = shallowRef<any>(null) |
| | | const fenceEditGuideMarker = shallowRef<any>(null) |
| | | const restrictedEditGuideMarker = shallowRef<any>(null) |
| | | const stationEditGuideMarker = shallowRef<any>(null) |
| | | // 正式态地图覆盖物集合,重绘前会统一清理。 |
| | | const pointMarkers = shallowRef<any[]>([]) |
| | | const cityBoundaryPolygons = shallowRef<any[]>([]) |
| | |
| | | const suppressFenceEditGuideDismissOnce = ref(false) |
| | | const restrictedEditGuideDismissed = ref(false) |
| | | const suppressRestrictedEditGuideDismissOnce = ref(false) |
| | | const stationEditGuideDismissed = ref(false) |
| | | const suppressStationEditGuideDismissOnce = ref(false) |
| | | // 首次显示编辑提示后,给收尾 click / dblclick 留一个短保护窗, |
| | | // 避免提示刚出现就被新建完成时的尾部事件立刻关闭。 |
| | | const EDIT_GUIDE_DISMISS_SUPPRESS_MS = 400 |
| | |
| | | const restrictedEditGuidePlacement = ref<'top' | 'bottom'>('top') |
| | | let restrictedEditGuideContentElement: HTMLDivElement | null = null |
| | | let restrictedEditGuideArrowElement: HTMLDivElement | null = null |
| | | const stationEditGuideDisplayMode = ref<'idle' | 'entry' | 'hover'>('idle') |
| | | const stationEditGuideHoverVertexIndex = ref(-1) |
| | | const stationEditGuideVisibleVertexIndex = ref(-1) |
| | | let stationEditGuideHoverTimer: ReturnType<typeof setTimeout> | null = null |
| | | const stationEditGuidePlacement = ref<'top' | 'bottom'>('top') |
| | | let stationEditGuideContentElement: HTMLDivElement | null = null |
| | | let stationEditGuideArrowElement: HTMLDivElement | null = null |
| | | const invalidFenceStationIds = ref<string[]>([]) |
| | | // 仅在用户手动切换到围栏 tab 时触发一次总览视角, |
| | | // 避免影响入口深链、详情回显等程序化切 tab 的聚焦逻辑。 |
| | |
| | | fenceCreateOutsideCurrentCity.value = false |
| | | resetFenceEditGuideHoverState() |
| | | resetRestrictedEditGuideHoverState() |
| | | resetStationEditGuideHoverState() |
| | | return |
| | | } |
| | | |
| | |
| | | fenceCreateOutsideCurrentCity.value = false |
| | | resetFenceEditGuideHoverState() |
| | | resetRestrictedEditGuideHoverState() |
| | | resetStationEditGuideHoverState() |
| | | return |
| | | } |
| | | |
| | | syncFenceEditGuideHover(event) |
| | | syncRestrictedEditGuideHover(event) |
| | | syncStationEditGuideHover(event) |
| | | |
| | | // 鼠标当前位置是否落在当前城市范围内,只在“新建围栏”阶段实时判断。 |
| | | // 编辑已有围栏时不再做这层 hover 查询,避免默认进入围栏编辑就出现提示态。 |
| | |
| | | fenceCreateOutsideCurrentCity.value = false |
| | | resetFenceEditGuideHoverState() |
| | | resetRestrictedEditGuideHoverState() |
| | | resetStationEditGuideHoverState() |
| | | } |
| | | |
| | | function handleNativeMouseMove(event: MouseEvent) { |
| | |
| | | stationTempPolygon?.setMap?.(null) |
| | | stationMouseTool.value?.close?.() |
| | | stationMouseTool.value = null |
| | | resetStationEditGuideHoverState() |
| | | stationEditGuideDismissed.value = false |
| | | if (stationPolyEditor.value) |
| | | suppressStationEditorEnd.value = true |
| | | stationPolyEditor.value?.setTarget?.(null) |
| | |
| | | |
| | | // 打开场站 polygon 的可编辑态,并把 AMap 编辑事件同步回表单草稿。 |
| | | // adjust/addnode/removenode 过程中先实时校验,若非法则立即回滚,确保草稿始终保持可保存状态。 |
| | | function enableStationDraftEdit() { |
| | | function enableStationDraftEdit(options: { showEntryGuide?: boolean } = {}) { |
| | | if (!map.value || !stationDraftPolygon.value || stationEditorRollbacking) |
| | | return |
| | | const { showEntryGuide = false } = options |
| | | if (stationPolyEditor.value) |
| | | suppressStationEditorEnd.value = true |
| | | stationPolyEditor.value?.close?.() |
| | |
| | | stationPolyEditor.value.on('addnode', syncStationGeometry) |
| | | stationPolyEditor.value.on('removenode', syncStationGeometry) |
| | | stationPolyEditor.value.open() |
| | | if (showEntryGuide) |
| | | showStationEditGuideOnEntry() |
| | | } |
| | | |
| | | // 开始绘制场站区域。 |
| | |
| | | return |
| | | } |
| | | applyStationDraftGeometry(points, true) |
| | | enableStationDraftEdit() |
| | | enableStationDraftEdit({ showEntryGuide: true }) |
| | | }) |
| | | } |
| | | |
| | |
| | | return |
| | | cleanupStationEditorState() |
| | | applyStationDraftGeometry(polygonPath, true) |
| | | enableStationDraftEdit() |
| | | enableStationDraftEdit({ showEntryGuide: true }) |
| | | } |
| | | |
| | | // 清理围栏地图编辑器、禁行区编辑器和相关草稿状态。 |
| | |
| | | if ( |
| | | suppressFenceEditGuideDismissOnce.value |
| | | || suppressRestrictedEditGuideDismissOnce.value |
| | | || suppressStationEditGuideDismissOnce.value |
| | | ) { |
| | | return |
| | | } |
| | |
| | | dismissFenceEditGuide() |
| | | if (!target?.closest('.restricted-edit-guide')) |
| | | dismissRestrictedEditGuide() |
| | | if (!target?.closest('.station-edit-guide')) |
| | | dismissStationEditGuide() |
| | | } |
| | | |
| | | async function handleViewAllList() { |
| | |
| | | enforceRestrictedDrawingVertexLimit() |
| | | }, 0) |
| | | } |
| | | if (suppressFenceEditGuideDismissOnce.value) |
| | | if ( |
| | | suppressFenceEditGuideDismissOnce.value |
| | | || suppressStationEditGuideDismissOnce.value |
| | | ) { |
| | | return |
| | | } |
| | | dismissFenceEditGuide() |
| | | dismissStationEditGuide() |
| | | if ( |
| | | activeTab.value === 'point' |
| | | && isPointFormMode.value |
| | |
| | | if (pointSearchTimer) |
| | | clearTimeout(pointSearchTimer) |
| | | clearFenceEditGuideHoverTimer() |
| | | clearStationEditGuideHoverTimer() |
| | | clearMapEntities() |
| | | cleanupPointEditMapState() |
| | | cleanupFenceEditorState() |