forked from drone/command-center-dashboard

shuishen
2025-04-17 b775b3077324cb9dd7dfdedbed8baaf7d40bf9f1
feat:事件点、聚合点---点击事件抽离
3 files modified
363 ■■■■■ changed files
src/hooks/components/useMapHandlerClick.js 89 ●●●● patch | view | raw | blame | history
src/hooks/useMapAggregation/useMapAggregation.js 139 ●●●● patch | view | raw | blame | history
src/hooks/useSingleDroneMap/useSingleDroneMap.js 135 ●●●● patch | view | raw | blame | history
src/hooks/components/useMapHandlerClick.js
@@ -2,33 +2,72 @@
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2025-04-15 22:41:40
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2025-04-16 19:02:39
 * @LastEditTime: 2025-04-17 19:43:36
 * @FilePath: \command-center-dashboard\src\hooks\components\useMapHandlerClick.js
 * @Description: 
 * 
 * Copyright (c) 2025 by shuishen, All Rights Reserved. 
 */
import * as Cesium from 'cesium'
import { render } from 'vue'
import { useStore } from 'vuex'
import DevicePopUpBox from '@/hooks/components/DevicePopUpBox.vue'
import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue'
/**
 * 
 * @param {Object} options - 配置选项
 */
export function useMapHandlerClick (viewer, options = {
  type: 'single-drone-event',
  customDom: EventPopUpBox
}) {
  const { type } = options
export function useMapHandlerClick (options = {}) {
  const {
    popupType = 'event-popup',
    eventType = 'single-drone-event',
    styleTransform = 'translate(-50%,-110%)',
    getViewer
  } = options
  const currentClickEntity = ref(null)
  const store = useStore()
  const popupData = {
    'event-popup': EventPopUpBox,
    'device-popup': DevicePopUpBox
  }
  const MapPopUpBox = popupData[popupType]
  let viewer = null
  let handler = null
  let currentClickEntity = null
  // 查找特定类型的实体
  const findEntityByType = (entities, type) => {
    return entities.find(entity =>
      entity?.properties?.customData?._value?.data?.type === type
    )
    let types = []
    Array.isArray(type) ? types = type : types = [type]
    return types.reduce((pre, curType) => {
      let entity = entities.find(entity => entity?.properties?.customData?._value?.data?.type === curType)
      return entity ? (pre.push({
        type: curType,
        entity
      }), pre) : pre
    }, [])
  }
  const publicEvent = (entity) => {
    viewer.scene.postRender.addEventListener(labelBoxRender)
  }
  const typeEvent = {
    'deviceAggregation': publicEvent,
    'single-drone-event': publicEvent,
    'event': publicEvent,
    'device': (entity) => {
      const device = entity.properties.customData._value.data
      store.commit('setSingleUavHome', device)
    }
  }
  // 左键单机事件
@@ -36,17 +75,21 @@
    let clickedEntities = viewer?.scene.drillPick(click.position).map(item => item.id)
    if (!clickedEntities.length) return
    currentClickEntity.value = findEntityByType(clickedEntities, type)
    let curClick = findEntityByType(clickedEntities, eventType)
    removeLabel()
    if (currentClickEntity.value) {
      viewer?.scene.postRender.addEventListener(labelBoxRender)
    if (curClick.length > 0 && typeEvent[curClick[0].type]) {
      currentClickEntity = curClick[0].entity
      typeEvent[curClick[0].type](currentClickEntity)
    }
  }
  // 事件初始化
  const handlerInit = () => {
    !viewer && (viewer = getViewer())
    if (handler) return
    handler = new Cesium.ScreenSpaceEventHandler(viewer?.scene.canvas)
@@ -62,7 +105,7 @@
  // 获取弹框box
  const getLabelDom = data => {
    const vNode = h(customDom, { data, removeLabel })
    const vNode = h(MapPopUpBox, { data, removeLabel })
    const tooltipContainer = document.createElement('div')
    tooltipContainer.id = 'mapPopUpBox'
    tooltipContainer.style.position = 'absolute'
@@ -75,12 +118,12 @@
  // 弹框位置刷新
  const labelBoxRender = () => {
    if (!currentEntity) return
    if (!currentClickEntity) return
    let dom = document.querySelector('#mapPopUpBox')
    if (!dom) {
      dom = getLabelDom(currentEntity.properties.customData._value.data)
      dom = getLabelDom(currentClickEntity.properties.customData._value.data)
    }
    const screenPosition = viewer?.scene.cartesianToCanvasCoordinates(currentEntity?.position?._value)
    const screenPosition = viewer?.scene.cartesianToCanvasCoordinates(currentClickEntity?.position?._value)
    if (screenPosition) {
      dom.style.left = `${screenPosition.x}px`
      dom.style.top = `${screenPosition.y}px`
@@ -101,15 +144,19 @@
    removeDom()
  }
  // 自动清理
  onUnmounted(() => {
  const removeAll = () => {
    removeHandler()
    removeLabel()
  }
  // 自动清理
  onUnmounted(() => {
    removeAll()
  })
  return {
    currentClickEntity,
    handlerInit,
    removeHandler
    removeAll,
    removeLabel
  }
}
src/hooks/useMapAggregation/useMapAggregation.js
@@ -3,30 +3,19 @@
import eventAggregationImg from '@/assets/images/home/useUavHome/eventAggregationImg.png'
import uavImg from '@/assets/images/home/useUavHome/uavImg.png'
// 事件图标
import eventSingle from '@/assets/images/home/useEventOperate/eventSingle.png'
import { getEventImage } from '@/utils/stateToImageMap/event'
import { getDroneStatusImage } from '@/utils/stateToImageMap/drone'
import eventPending from '@/assets/images/home/useEventOperate/eventPending.png' // 待处理 0
import eventWaitAudit from '@/assets/images/home/useEventOperate/eventWaitAudit.png' // 待审核 2
import eventProcessing from '@/assets/images/home/useEventOperate/eventProcessing.png' // 处理中 3
import eventCompleted from '@/assets/images/home/useEventOperate/eventCompleted.png' // 已完成 4
import eventClosed from '@/assets/images/home/useEventOperate/eventClosed.png' // 已完结 5
import DevicePopUpBox from '@/hooks/components/DevicePopUpBox.vue'
import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue'
// 机巢图标
import endingImg from '@/assets/images/aiNowFly/ending.png'
import endingHighImg from '@/assets/images/aiNowFly/ending-high.png'
import { render } from 'vue'
import { useStore } from 'vuex'
import { getCenterPoint } from '@/utils/cesium/mapUtil'
import cesiumOperation from '@/utils/cesium-tsa'
import { getDeviceRegion, getDeviceRegionCount, getEventDetails, getMapEvents } from '@/api/home/aggregation'
import { PolyGradientMaterial } from '@/utils/cesium/Material'
import { start } from 'nprogress'
// hook
import { useMapHandlerClick } from '@/hooks/components/useMapHandlerClick'
/**
 * 机巢聚合功能
 */
@@ -51,8 +40,21 @@
  const { flyTo } = cesiumOperation()
  const mergeImg = type === 'device' ? aggregationImg : eventAggregationImg
  const MapPopUpBox = type === 'device' ? DevicePopUpBox : EventPopUpBox
  const styleTransform = type === 'device' ? 'translateY(-50%)' : 'translate(-50%,-110%)'
  const eventType = type === 'device' ? ['deviceAggregation', 'device'] : 'event'
  const {
    handlerInit: mapHandlerInit,
    removeAll: removeMapHandlerAll,
    removeLabel: removeMapLabel
  } = useMapHandlerClick({
    popupType: `${type}-popup`,
    eventType: eventType,
    styleTransform,
    getViewer () {
      return viewer
    }
  })
  let scalingJudgment = [
    { name: '县', splashedList: [], gJson: null, show: false, outline: {}, value: [0, 48651], height: 31753 },
@@ -69,8 +71,6 @@
  ]
  let viewer = null
  let active = null
  let handler = null
  let currentEntity = null
  const store = useStore()
  const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode)
@@ -104,7 +104,7 @@
        if (active === item.name) return
        active = item.name
        removeEntities()
        removeLabel()
        removeMapLabel()
        renderOutline(item)
        if (!item.gJson && !item.splashedList?.length) return
        item.gJson ? aggregation(item) : splashed(item)
@@ -288,7 +288,7 @@
      needFly = true
      if (!viewer) return
      handlerInit()
      mapHandlerInit()
      viewer.scene.postRender.removeEventListener(determineScaling)
@@ -302,15 +302,7 @@
  //散点机巢
  function splashed (row) {
    row.splashedList.forEach((item, index) => {
      const eventImage = {
        0: eventPending,
        2: eventWaitAudit,
        3: eventProcessing,
        4: eventCompleted,
        5: eventClosed
      }
      const image = type === 'device' ? (item.status === "OFFLINE" ? endingHighImg : endingImg) : eventImage[item.status] || eventSingle
      const image = type === 'device' ? getDroneStatusImage(item.status) : getEventImage(item.status)
      viewer.entities.add({
        id: `aggregation-splashed-${index}`,
@@ -470,33 +462,6 @@
    })
  }
  // 获取弹框box
  const getLabelDom = data => {
    const vNode = h(MapPopUpBox, { data, removeLabel })
    const tooltipContainer = document.createElement('div')
    tooltipContainer.id = 'mapPopUpBox'
    tooltipContainer.style.position = 'absolute'
    tooltipContainer.style.transform = styleTransform
    tooltipContainer.style.pointerEvents = 'none'
    document.querySelector('.page-index').append(tooltipContainer)
    render(vNode, tooltipContainer)
    return tooltipContainer
  }
  // 弹框位置刷新
  const labelBoxRender = () => {
    if (!currentEntity) return
    let dom = document.querySelector('#mapPopUpBox')
    if (!dom) {
      dom = getLabelDom(currentEntity.properties.customData._value.data)
    }
    const screenPosition = viewer.scene.cartesianToCanvasCoordinates(currentEntity?.position?._value)
    if (screenPosition) {
      dom.style.left = `${screenPosition.x}px`
      dom.style.top = `${screenPosition.y}px`
      dom.style.display = 'block'
    }
  }
  /**
   * 根据条件获取项
@@ -506,41 +471,6 @@
   */
  const findTypeItem = (arr, condition) => {
    return arr.find(item => condition(item))
  }
  // 左键单机事件
  const singleMachineEvent = async click => {
    let clickTargets = viewer.scene.drillPick(click.position).map(item => item.id)
    if (!clickTargets.length) return
    let deviceAggregationFind = findTypeItem(
      clickTargets,
      item => item?.properties?.customData?._value?.data?.type === 'deviceAggregation'
    )
    let deviceFind = findTypeItem(clickTargets, item => item?.properties?.customData?._value?.data?.type === 'device')
    // "event"
    let eventFind = findTypeItem(clickTargets, item => item?.properties?.customData?._value?.data?.type === 'event')
    // let eventFind = findTypeItem(clickTargets, (item) => item?.properties?.customData?._value?.data?.type === 'eventAggregation')
    currentEntity = deviceAggregationFind || deviceFind || eventFind
    if (!currentEntity) return
    if (!currentEntity?.position?._value) return
    // 一定要移除
    removeLabel()
    if (deviceAggregationFind || eventFind) {
      viewer.scene.postRender.addEventListener(labelBoxRender)
    }
    if (deviceFind) {
      const device = deviceFind.properties.customData._value.data
      store.commit('setSingleUavHome', device)
    }
  }
  const removeDom = () => {
    const dom = document.querySelector('#mapPopUpBox')
    if (dom && dom.parentNode) {
      dom.parentNode.removeChild(dom)
    }
  }
  // 移除 点 和 gjson 实体
@@ -556,11 +486,7 @@
      item.includes('aggregation-') && viewer.entities.removeById(item)
    })
  }
  // 移除弹框标签
  const removeLabel = () => {
    viewer?.scene.postRender.removeEventListener(labelBoxRender)
    removeDom()
  }
  // 移除所有监听事件,变量置空
  const removeAll = () => {
@@ -571,14 +497,12 @@
  const clearMapEntity = () => {
    if (!viewer) return
    removeEntities()
    removeLabel()
    removeMapHandlerAll()
    // viewer.camera.moveEnd.removeEventListener(determineScaling);
    viewer.scene.postRender.removeEventListener(determineScaling)
    handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
    handler?.destroy()
    active = null
    handler = null
    currentEntity = null
  }
  const init = () => {
@@ -588,14 +512,7 @@
      viewer.scene.postRender.addEventListener(determineScaling)
    })
    handlerInit()
  }
  const handlerInit = () => {
    if (handler) return
    handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
    handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK)
    mapHandlerInit()
  }
  onBeforeUnmount(() => { })
src/hooks/useSingleDroneMap/useSingleDroneMap.js
@@ -2,25 +2,18 @@
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2025-04-15 22:41:40
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2025-04-17 15:05:32
 * @LastEditTime: 2025-04-17 20:24:19
 * @FilePath: \command-center-dashboard\src\hooks\useSingleDroneMap\useSingleDroneMap.js
 * @Description: 
 * 
 * Copyright (c) 2025 by shuishen, All Rights Reserved. 
 */
import * as Cesium from 'cesium'
import { render } from 'vue'
import endingImg from '@/assets/images/aiNowFly/ending.png'
import endingHighImg from '@/assets/images/aiNowFly/ending-high.png'
import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue'
import eventSingle from '@/assets/images/home/useEventOperate/eventSingle.png'
import { getEventImage } from '@/utils/stateToImageMap/event'
import { getDroneFlagImage } from '@/utils/stateToImageMap/drone'
import eventPending from '@/assets/images/home/useEventOperate/eventPending.png' // 待处理 0
import eventWaitAudit from '@/assets/images/home/useEventOperate/eventWaitAudit.png' // 待审核 2
import eventProcessing from '@/assets/images/home/useEventOperate/eventProcessing.png' // 处理中 3
import eventCompleted from '@/assets/images/home/useEventOperate/eventCompleted.png' // 已完成 4
import eventClosed from '@/assets/images/home/useEventOperate/eventClosed.png' // 已完结 5
import { useMapHandlerClick } from '@/hooks/components/useMapHandlerClick'
/**
 * 
@@ -31,20 +24,18 @@
  eventApi: null,
  eventApiParams: {}
}) {
  const styleTransform = 'translate(-50%,-110%)'
  const { eventPositions, eventApi, eventApiParams } = options
  const eventImage = {
    0: eventPending,
    2: eventWaitAudit,
    3: eventProcessing,
    4: eventCompleted,
    5: eventClosed
  }
  let viewer = null
  let handler = null
  let currentEntity = null
  const {
    handlerInit: mapHandlerInit,
    removeAll: removeMapHandlerAll
  } = useMapHandlerClick({
    getViewer () {
      return viewer
    }
  })
  // 初始化机场位置
  const initDroneEntity = (dronePosition) => {
@@ -52,7 +43,7 @@
    if (!lng || !lat) return
    const markerImg = status ? endingHighImg : endingImg
    const markerImg = getDroneFlagImage(status)
    const position = Cesium.Cartesian3.fromDegrees(+lng, +lat, 0)
    const droneEntity = viewer?.entities.add({
@@ -93,7 +84,7 @@
    eventData.length && eventData.forEach((item, index) => {
      const { longitude, latitude, status, id } = item
      const curImg = eventImage[status] || eventSingle
      const curImg = getEventImage(status)
      const position = Cesium.Cartesian3.fromDegrees(+longitude, +latitude, 0)
@@ -109,7 +100,7 @@
          customData: {
            data: {
              ...item,
              eventId: item.id,
              eventId: id,
              type: 'single-drone-event'
            }
          }
@@ -155,104 +146,20 @@
    })
  }
  // 查找特定类型的实体
  const findEntityByType = (entities, type) => {
    return entities.find(entity =>
      entity?.properties?.customData?._value?.data?.type === type
    )
  }
  // 左键单机事件
  const singleMachineEvent = async click => {
    currentEntity = null
    let clickedEntities = viewer?.scene.drillPick(click.position).map(item => item.id)
    if (!clickedEntities?.length) return
    currentEntity = findEntityByType(clickedEntities, 'single-drone-event')
    removeLabel()
    if (currentEntity) {
      viewer?.scene.postRender.addEventListener(labelBoxRender)
    }
  }
  // 事件初始化
  const handlerInit = () => {
    if (handler) return
    handler = new Cesium.ScreenSpaceEventHandler(viewer?.scene.canvas)
    handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK)
  }
  // 事件移除
  const removeHandler = () => {
    handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
    handler?.destroy()
    handler = null
  }
  // 获取弹框box
  const getLabelDom = data => {
    const vNode = h(EventPopUpBox, { data, removeLabel })
    const tooltipContainer = document.createElement('div')
    tooltipContainer.id = 'mapPopUpBox'
    tooltipContainer.style.position = 'absolute'
    tooltipContainer.style.transform = styleTransform
    tooltipContainer.style.pointerEvents = 'none'
    document.querySelector('.page-index').append(tooltipContainer)
    render(vNode, tooltipContainer)
    return tooltipContainer
  }
  // 弹框位置刷新
  const labelBoxRender = () => {
    if (!currentEntity) return
    let dom = document.querySelector('#mapPopUpBox')
    if (!dom) {
      dom = getLabelDom(currentEntity.properties.customData._value.data)
    }
    const screenPosition = viewer?.scene.cartesianToCanvasCoordinates(currentEntity?.position?._value)
    if (screenPosition) {
      dom.style.left = `${screenPosition.x}px`
      dom.style.top = `${screenPosition.y}px`
      dom.style.display = 'block'
    }
  }
  const removeDom = () => {
    const dom = document.querySelector('#mapPopUpBox')
    if (dom && dom.parentNode) {
      dom.parentNode.removeChild(dom)
    }
  }
  // 移除弹框标签
  const removeLabel = () => {
    viewer?.scene.postRender.removeEventListener(labelBoxRender)
    removeDom()
  }
  const init = () => {
    viewer = window.$viewer
    handlerInit()
    mapHandlerInit()
  }
  const removeAll = () => {
    removeLayer()
    removeHandler()
    removeLabel()
    removeMapHandlerAll()
  }
  onMounted(() => {
    nextTick(() => {
      init()
    })
  onMounted(async () => {
    await nextTick()
    init()
  })
  // 自动清理