董国庆
2026-06-29 19fb2149ae45c471fe6eefe9b9796776f18b8a45
场站-显示tooptip
1个文件已修改
340 ■■■■■ 已修改文件
src/views/businessOperations/operationWorkbench/map.vue 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/businessOperations/operationWorkbench/map.vue
@@ -885,6 +885,310 @@
  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,
@@ -1101,6 +1405,7 @@
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[]>([])
@@ -1165,6 +1470,8 @@
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
@@ -1196,6 +1503,13 @@
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 的聚焦逻辑。
@@ -1648,6 +1962,7 @@
    fenceCreateOutsideCurrentCity.value = false
    resetFenceEditGuideHoverState()
    resetRestrictedEditGuideHoverState()
    resetStationEditGuideHoverState()
    return
  }
@@ -1657,11 +1972,13 @@
    fenceCreateOutsideCurrentCity.value = false
    resetFenceEditGuideHoverState()
    resetRestrictedEditGuideHoverState()
    resetStationEditGuideHoverState()
    return
  }
  syncFenceEditGuideHover(event)
  syncRestrictedEditGuideHover(event)
  syncStationEditGuideHover(event)
  // 鼠标当前位置是否落在当前城市范围内,只在“新建围栏”阶段实时判断。
  // 编辑已有围栏时不再做这层 hover 查询,避免默认进入围栏编辑就出现提示态。
@@ -1678,6 +1995,7 @@
  fenceCreateOutsideCurrentCity.value = false
  resetFenceEditGuideHoverState()
  resetRestrictedEditGuideHoverState()
  resetStationEditGuideHoverState()
}
function handleNativeMouseMove(event: MouseEvent) {
@@ -2972,6 +3290,8 @@
  stationTempPolygon?.setMap?.(null)
  stationMouseTool.value?.close?.()
  stationMouseTool.value = null
  resetStationEditGuideHoverState()
  stationEditGuideDismissed.value = false
  if (stationPolyEditor.value)
    suppressStationEditorEnd.value = true
  stationPolyEditor.value?.setTarget?.(null)
@@ -3067,9 +3387,10 @@
// 打开场站 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?.()
@@ -3122,6 +3443,8 @@
  stationPolyEditor.value.on('addnode', syncStationGeometry)
  stationPolyEditor.value.on('removenode', syncStationGeometry)
  stationPolyEditor.value.open()
  if (showEntryGuide)
    showStationEditGuideOnEntry()
}
// 开始绘制场站区域。
@@ -3153,7 +3476,7 @@
      return
    }
    applyStationDraftGeometry(points, true)
    enableStationDraftEdit()
    enableStationDraftEdit({ showEntryGuide: true })
  })
}
@@ -3165,7 +3488,7 @@
    return
  cleanupStationEditorState()
  applyStationDraftGeometry(polygonPath, true)
  enableStationDraftEdit()
  enableStationDraftEdit({ showEntryGuide: true })
}
// 清理围栏地图编辑器、禁行区编辑器和相关草稿状态。
@@ -5625,6 +5948,7 @@
  if (
    suppressFenceEditGuideDismissOnce.value
    || suppressRestrictedEditGuideDismissOnce.value
    || suppressStationEditGuideDismissOnce.value
  ) {
    return
  }
@@ -5632,6 +5956,8 @@
    dismissFenceEditGuide()
  if (!target?.closest('.restricted-edit-guide'))
    dismissRestrictedEditGuide()
  if (!target?.closest('.station-edit-guide'))
    dismissStationEditGuide()
}
async function handleViewAllList() {
@@ -6863,9 +7189,14 @@
      enforceRestrictedDrawingVertexLimit()
    }, 0)
  }
  if (suppressFenceEditGuideDismissOnce.value)
  if (
    suppressFenceEditGuideDismissOnce.value
    || suppressStationEditGuideDismissOnce.value
  ) {
    return
  }
  dismissFenceEditGuide()
  dismissStationEditGuide()
  if (
    activeTab.value === 'point'
    && isPointFormMode.value
@@ -9068,6 +9399,7 @@
  if (pointSearchTimer)
    clearTimeout(pointSearchTimer)
  clearFenceEditGuideHoverTimer()
  clearStationEditGuideHoverTimer()
  clearMapEntities()
  cleanupPointEditMapState()
  cleanupFenceEditorState()