forked from drone/command-center-dashboard

罗广辉
2025-04-17 25ff7cbac4d8db9692ab63c7d24a2ededa0c8921
Merge remote-tracking branch 'origin/master'
9 files modified
1118 ■■■■ changed files
src/assets/images/home/useUavHome/eventAggregationImg.png patch | view | raw | blame | history
src/hooks/useMapAggregation/useMapAggregation.js 993 ●●●● patch | view | raw | blame | history
src/store/modules/task.js 2 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/SearchBox.vue 62 ●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/AddTask.vue 1 ●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue 2 ●●● patch | view | raw | blame | history
src/views/TaskManage/TaskTop/TaskEvent.vue 1 ●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskTop/TaskIndustry.vue 48 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskAlgorithmBusiness.vue 9 ●●●●● patch | view | raw | blame | history
src/assets/images/home/useUavHome/eventAggregationImg.png

src/hooks/useMapAggregation/useMapAggregation.js
@@ -6,7 +6,9 @@
import DevicePopUpBox from '@/hooks/components/DevicePopUpBox.vue'
import EventPopUpBox from '@/hooks/components/EventPopUpBox.vue'
// 图标
import offlineImg from '@/assets/images/home/useEventOperate/offline.png'
import onlineImg from '@/assets/images/home/useEventOperate/eventSingle.png'
import { render } from 'vue'
import { useStore } from 'vuex'
import { getCenterPoint } from '@/utils/cesium/mapUtil'
@@ -18,548 +20,577 @@
 * 机巢聚合功能
 */
let arrColor = ["rgb(15,176,255)", "rgb(18,76,154)", "#40C4E4", "#42B2BE", "rgb(51,176,204)", "#8CB7E5", "rgb(0,244,188)", "#139FF0"]
let arrColor = [
    'rgb(15,176,255)',
    'rgb(18,76,154)',
    '#40C4E4',
    '#42B2BE',
    'rgb(51,176,204)',
    '#8CB7E5',
    'rgb(0,244,188)',
    '#139FF0',
]
let index = 0
function getColor () {
  return arrColor[++index % arrColor.length]
function getColor() {
    return arrColor[++index % arrColor.length]
}
export const useMapAggregation = type => {
  const { flyTo } = cesiumOperation()
export const useMapAggregation = (type, status) => {
    const { flyTo } = cesiumOperation()
  const singleImg = type === 'device' ? uavImg : eventSingle
  const mergeImg = type === 'device' ? aggregationImg : eventAggregationImg
  const MapPopUpBox = type === 'device' ? DevicePopUpBox : EventPopUpBox
  const styleTransform = type === 'device' ? 'translateY(-50%)' : 'translate(-50%,-110%)'
    const singleImg = type === 'device' ? onlineImg : eventSingle
  const offlinesingleImg = type === 'device' ? offlineImg : eventSingle
    const mergeImg = type === 'device' ? aggregationImg : eventAggregationImg
    const MapPopUpBox = type === 'device' ? DevicePopUpBox : EventPopUpBox
    const styleTransform = type === 'device' ? 'translateY(-50%)' : 'translate(-50%,-110%)'
  let scalingJudgment = [
    { name: '县', splashedList: [], gJson: null, show: false, outline: {}, value: [0, 48651], height: 31753 },
    { name: '市', splashedList: [], gJson: null, show: false, outline: {}, value: [48651, 314863], height: 257731 },
    {
      name: '省',
      splashedList: [],
      gJson: null,
      show: false,
      outline: {},
      value: [314863, 3796280000],
      height: 1987280,
    },
  ]
  let viewer = null
  let active = null
  let handler = null
  let currentEntity = null
    let scalingJudgment = [
        { name: '县', splashedList: [], gJson: null, show: false, outline: {}, value: [0, 48651], height: 31753 },
        { name: '市', splashedList: [], gJson: null, show: false, outline: {}, value: [48651, 314863], height: 257731 },
        {
            name: '省',
            splashedList: [],
            gJson: null,
            show: false,
            outline: {},
            value: [314863, 3796280000],
            height: 1987280,
        },
    ]
    let viewer = null
    let active = null
    let handler = null
    let currentEntity = null
  const store = useStore()
  const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode)
  const selectedAreaCode = computed(() => store.state.user.selectedAreaCode)
    const store = useStore()
    const userAreaCode = computed(() => store.state.user.userInfo.detail.areaCode)
    const selectedAreaCode = computed(() => store.state.user.selectedAreaCode)
    const eventTimeType = computed(() => store.state.home.eventTimeType)
    const eventTimeRang = computed(() => store.state.home.eventTimeRang)
  const eventTimeType = computed(() => store.state.home.eventTimeType)
  const eventTimeRang = computed(() => store.state.home.eventTimeRang)
    const singleUavHome = computed(() => store.state.home.singleUavHome)
  const singleUavHome = computed(() => store.state.home.singleUavHome)
    let needFly = true
  let needFly = true
    const combinedValues = computed(() => ({
        selectedAreaCode: selectedAreaCode.value,
        eventTimeType: eventTimeType.value,
        eventTimeRang: eventTimeRang.value,
        singleUavHome: singleUavHome.value,
    }))
  const combinedValues = computed(() => ({
    selectedAreaCode: selectedAreaCode.value,
    eventTimeType: eventTimeType.value,
    eventTimeRang: eventTimeRang.value,
    singleUavHome: singleUavHome.value,
  }))
    let saveParams = { area_code: '', date_enum: 'CURRENT_WEEK' }
  let saveParams = { area_code: '', date_enum: 'CURRENT_WEEK' }
    // 确定缩放比例
    const determineScaling = () => {
        // console.log('确定缩放比例');
        if (!viewer) return
        let height = viewer.camera.positionCartographic.height
        // 根据高度展示对应的 gJson
        for (let [index, item] of scalingJudgment.entries()) {
            if (!item.show) return
            if (height > item.value[0] && height <= item.value[1]) {
                if (active === item.name) return
                active = item.name
                removeEntities()
                removeLabel()
                renderOutline(item)
                if (!item.gJson && !item.splashedList?.length) return
                item.gJson ? aggregation(item) : splashed(item)
                break
            }
        }
    }
    // 转换为目录结构, 返回数组,
    function convertToHierarchy(code) {
        const codeStr = code.toString()
        const provinceCode = codeStr.slice(0, 2) + '0000'
        const cityCode = codeStr.slice(0, 4) + '00'
        if (codeStr.slice(2, 4) === '00' && codeStr.slice(4, 6) === '00') {
            return [provinceCode]
        } else if (codeStr.slice(4, 6) === '00') {
            return [provinceCode, cityCode]
        }
        return [provinceCode, cityCode, code]
    }
  // 确定缩放比例
  const determineScaling = () => {
    // console.log('确定缩放比例');
    if (!viewer) return
    let height = viewer.camera.positionCartographic.height
    // 根据高度展示对应的 gJson
    for (let [index, item] of scalingJudgment.entries()) {
      if (!item.show) return
      if (height > item.value[0] && height <= item.value[1]) {
        if (active === item.name) return
        active = item.name
        removeEntities()
        removeLabel()
        renderOutline(item)
        if (!item.gJson && !item.splashedList?.length) return
        item.gJson ? aggregation(item) : splashed(item)
        break
      }
    }
  }
    // 获取设备聚合
    function getDeviceCount(areaCode) {
        return getDeviceRegionCount({ areaCode }).then(res => {
            return res?.data?.data || []
        })
    }
    // 获取设备散点
    function getDeviceList(areaCode) {
        return getDeviceRegion({ areaCode }).then(res => {
            return (res?.data?.data || []).map(i => ({ ...i, type }))
  // 转换为目录结构, 返回数组,
  function convertToHierarchy (code) {
    const codeStr = code.toString()
    const provinceCode = codeStr.slice(0, 2) + '0000'
    const cityCode = codeStr.slice(0, 4) + '00'
    if (codeStr.slice(2, 4) === '00' && codeStr.slice(4, 6) === '00') {
      return [provinceCode]
    } else if (codeStr.slice(4, 6) === '00') {
      return [provinceCode, cityCode]
    }
    return [provinceCode, cityCode, code]
  }
        })
    }
  // 获取设备聚合
  function getDeviceCount (areaCode) {
    return getDeviceRegionCount({ areaCode }).then(res => {
      return (res?.data?.data || [])
    })
  }
  // 获取设备散点
  function getDeviceList (areaCode) {
    return getDeviceRegion({ areaCode }).then(res => {
      return (res?.data?.data || []).map(i => ({ ...i, type }))
    })
  }
    // 事件散点
    let eventList = []
    function processChildren(childrens) {
        // console.log(childrens, '事件点')
        return (childrens || []).map(item => {
            const arr = processChildren(item.childrens)
            if (item.data) {
                eventList = eventList.concat(item.data)
            }
            const returnObj = {
                total_device_count: item.number,
                region_code: item.id,
                region_name: item.name,
            }
            if (arr.length !== 0) {
                returnObj.childrens = arr
            }
            return returnObj
        })
    }
  // 事件散点
  let eventList = []
  function processChildren (childrens) {
    // console.log(childrens, '事件点')
    return (childrens || []).map(item => {
      const arr = processChildren(item.childrens)
      if (item.data) {
        eventList = eventList.concat(item.data)
      }
      const returnObj = {
        total_device_count: item.number,
        region_code: item.id,
        region_name: item.name,
      }
      if (arr.length !== 0) {
        returnObj.childrens = arr
      }
      return returnObj
    })
  }
    // 获取事件聚合
    function getMapEventCount(params) {
        return getMapEvents(params).then(res => {
            const resData = res?.data?.data
            if (resData?.data) {
                eventList = resData?.data
                return []
            }
            return processChildren(resData?.childrens)
        })
    }
  // 获取事件聚合
  function getMapEventCount (params) {
    return getMapEvents(params).then(res => {
      const resData = res?.data?.data
      if (resData?.data) {
        eventList = resData?.data
        return []
      }
      return processChildren(resData?.childrens)
    })
  }
    const findFun = (featItem, numItem) => Number(featItem.region_code.slice(0, 6)) === numItem.properties.adcode
    const { VITE_APP_BASE, VITE_APP_ENV } = import.meta.env
    const defaultDir = `${VITE_APP_BASE}${VITE_APP_ENV === 'development' ? 'public/' : ''}geoJson/100000/`
  const findFun = (featItem, numItem) => Number(featItem.region_code.slice(0, 6)) === numItem.properties.adcode
  const { VITE_APP_BASE, VITE_APP_ENV } = import.meta.env
  const defaultDir = `${VITE_APP_BASE}${VITE_APP_ENV === 'development' ? 'public/' : ''}geoJson/100000/`
    const getFiler = async url => {
        const res = await fetch(url)
        return await res.json()
    }
    const getOutLine = async (jsonPathPre, hierarchy) => {
        const parentGJson = await getFiler(`${defaultDir}${jsonPathPre}/index.json`)
        let features = parentGJson.features.find(item => item.properties.adcode === Number(hierarchy[hierarchy.length - 1]))
        return { type: 'FeatureCollection', features: [features] }
    }
  const getFiler = async url => {
    const res = await fetch(url)
    return await res.json()
  }
  const getOutLine = async (jsonPathPre, hierarchy) => {
    const parentGJson = await getFiler(`${defaultDir}${jsonPathPre}/index.json`)
    let features = parentGJson.features.find(
      item => item.properties.adcode === Number(hierarchy[hierarchy.length - 1])
    )
    return { type: 'FeatureCollection', features: [features] }
  }
    const injectData = (gJson, dataList) => {
        return {
            ...gJson,
            features: gJson.features.map(item => {
                const findData = dataList.find(item1 => findFun(item1, item))
                return { ...item, data: { ...findData, type: type + 'Aggregation' } }
            }),
        }
    }
  const injectData = (gJson, dataList) => {
    return {
      ...gJson,
      features: gJson.features.map(item => {
        const findData = dataList.find(item1 => findFun(item1, item))
        return { ...item, data: { ...findData, type: type + 'Aggregation' } }
      }),
    }
  }
    const initMapData = async areaCode => {
        eventList = []
        if (!areaCode) return
        saveParams.area_code = areaCode
        const list = type === 'device' ? await getDeviceCount(areaCode) : await getMapEventCount(saveParams)
        const splashedList =
            type === 'device'
                ? await getDeviceList(areaCode)
                : eventList.map(i => ({
                        eventId: i.id,
                        latitude: Number(i.latitude),
                        longitude: Number(i.longitude),
                        type: 'event',
                  }))
        const hierarchy = convertToHierarchy(areaCode.slice(0, 6))
        const jsonPath = hierarchy.join('/')
        const jsonPathPre = hierarchy.slice(0, hierarchy.length - 1).join('/')
        scalingJudgment = scalingJudgment.map(item => ({ ...item, show: true }))
        scalingJudgment[0].gJson = null
        scalingJudgment[0].splashedList = splashedList
        active = null
        // 省
        if (hierarchy.length === 1) {
            const gJson1 = await getFiler(`${defaultDir}${jsonPath}/indexDistrict.json`)
            const gJson2 = await getFiler(`${defaultDir}${jsonPath}/index.json`)
            scalingJudgment[1].gJson = {
                ...gJson1,
                features: gJson1.features.map(item => {
                    const findData = list.flatMap(item => item.childrens || []).find(item1 => findFun(item1, item))
                    return { ...item, data: { ...findData, type: type + 'Aggregation' } }
                }),
            }
            scalingJudgment[2].gJson = injectData(gJson2, list)
        }
        // 市
        if (hierarchy.length === 2) {
            scalingJudgment[2].gJson = null
            scalingJudgment[2].show = false
            const gJson1 = await getFiler(`${defaultDir}${jsonPath}/index.json`)
            scalingJudgment[1].gJson = injectData(gJson1, list)
        }
        // 区县
        if (hierarchy.length === 3) {
            scalingJudgment[1].gJson = null
            scalingJudgment[1].show = false
            scalingJudgment[2].gJson = null
            scalingJudgment[2].show = false
        }
        // 轮廓
        const outlineGJson = await getOutLine(jsonPathPre, hierarchy)
        scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson))
        const [longitude, latitude] = outlineGJson.features[0].properties.centroid
        const height = scalingJudgment[(hierarchy.length - 3) * -1].height
        setCenterPosition({ longitude, latitude, height })
        flyTo({ longitude, latitude }, 0, height)
    }
  const initMapData = async areaCode => {
    eventList = []
    if (!areaCode) return
    saveParams.area_code = areaCode
    const list = type === 'device' ? await getDeviceCount(areaCode) : await getMapEventCount(saveParams)
    const splashedList = type === 'device'
      ? await getDeviceList(areaCode)
      : eventList.map(i => ({ eventId: i.id, latitude: Number(i.latitude), longitude: Number(i.longitude), type: 'event' }))
    const hierarchy = convertToHierarchy(areaCode.slice(0, 6))
    const jsonPath = hierarchy.join('/')
    const jsonPathPre = hierarchy.slice(0, hierarchy.length - 1).join('/')
    scalingJudgment = scalingJudgment.map(item => ({ ...item, show: true }))
    scalingJudgment[0].gJson = null
    scalingJudgment[0].splashedList = splashedList
    active = null
    // 省
    if (hierarchy.length === 1) {
      const gJson1 = await getFiler(`${defaultDir}${jsonPath}/indexDistrict.json`)
      const gJson2 = await getFiler(`${defaultDir}${jsonPath}/index.json`)
      scalingJudgment[1].gJson = {
        ...gJson1,
        features: gJson1.features.map(item => {
          const findData = list.flatMap(item => item.childrens || []).find(item1 => findFun(item1, item))
          return { ...item, data: { ...findData, type: type + 'Aggregation' } }
        }),
      }
      scalingJudgment[2].gJson = injectData(gJson2, list)
    }
    // 市
    if (hierarchy.length === 2) {
      scalingJudgment[2].gJson = null
      scalingJudgment[2].show = false
      const gJson1 = await getFiler(`${defaultDir}${jsonPath}/index.json`)
      scalingJudgment[1].gJson = injectData(gJson1, list)
    }
    // 区县
    if (hierarchy.length === 3) {
      scalingJudgment[1].gJson = null
      scalingJudgment[1].show = false
      scalingJudgment[2].gJson = null
      scalingJudgment[2].show = false
    }
    // 轮廓
    const outlineGJson = await getOutLine(jsonPathPre, hierarchy)
    scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson))
    const [longitude, latitude] = outlineGJson.features[0].properties.centroid
    const height = scalingJudgment[(hierarchy.length - 3) * (-1)].height
    setCenterPosition({ longitude, latitude, height })
    flyTo({ longitude, latitude }, 0, height)
  }
    const userAreaPosition = computed(() => store.state.home.userAreaPosition)
  const userAreaPosition = computed(() => store.state.home.userAreaPosition)
    const setCenterPosition = position => {
        store.commit('setCurrentAreaPosition', position)
        if (!userAreaPosition.value.longitude) {
            store.commit('setUserAreaPosition', position)
        }
    }
  const setCenterPosition = (position) => {
    store.commit('setCurrentAreaPosition', position)
    if (!userAreaPosition.value.longitude) {
      store.commit('setUserAreaPosition', position)
    }
  }
    watch(
        combinedValues,
        async (newValue, oldValue) => {
            if (newValue.singleUavHome?.device_sn) {
                clearMapEntity()
                return
            }
  watch(combinedValues, async (newValue, oldValue) => {
    if (newValue.singleUavHome?.device_sn) {
      clearMapEntity()
      return
    }
            if (newValue.eventTimeType) {
                saveParams = { area_code: newValue.selectedAreaCode, date_enum: store.state.home.eventTimeParams }
            }
    if (newValue.eventTimeType) {
      saveParams = { area_code: newValue.selectedAreaCode, date_enum: store.state.home.eventTimeParams }
    }
            if (newValue.eventTimeRang) {
                saveParams = {
                    area_code: newValue.selectedAreaCode,
                    start_date: newValue.eventTimeRang[0],
                    end_date: newValue.eventTimeRang[1],
                }
            }
    if (newValue.eventTimeRang) {
      saveParams = { area_code: newValue.selectedAreaCode, start_date: newValue.eventTimeRang[0], end_date: newValue.eventTimeRang[1] }
    }
            needFly = true
            if (!viewer) return
            handlerInit()
    needFly = true
    if (!viewer) return
    handlerInit()
            viewer.scene.postRender.removeEventListener(determineScaling)
    viewer.scene.postRender.removeEventListener(determineScaling)
            initMapData(newValue.selectedAreaCode).then(() => {
                viewer.scene.postRender.addEventListener(determineScaling)
            })
        },
        { deep: true }
    )
    initMapData(newValue.selectedAreaCode).then(() => {
      viewer.scene.postRender.addEventListener(determineScaling)
    })
  },
    { deep: true }
  )
    //散点机巢
    function splashed(row) {
        row.splashedList.forEach((item, index) => {
  //散点机巢
  function splashed (row) {
    row.splashedList.forEach((item, index) => {
      viewer.entities.add({
        id: `aggregation-splashed-${index}`,
        position: Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude),
        label: {
          text: item.nickname,
          font: '12pt Source Han Sans CN',
          fillColor: Cesium.Color.WHITE,
          outlineColor: Cesium.Color.BLACK,
          outlineWidth: 2,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          pixelOffset: new Cesium.Cartesian2(0, -9),
        },
        billboard: {
          image: new Cesium.ConstantProperty(singleImg),
          width: 24,
          height: 24,
        },
        properties: {
          customData: {
            data: item,
          },
        },
      })
    })
  }
            viewer.entities.add({
                id: `aggregation-splashed-${index}`,
                position: Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude),
                label: {
                    text: item.nickname,
                    font: '12pt Source Han Sans CN',
                    fillColor: Cesium.Color.WHITE,
                    outlineColor: Cesium.Color.BLACK,
                    outlineWidth: 2,
                    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    pixelOffset: new Cesium.Cartesian2(0, -9),
                },
                billboard: {
          // singleImg
                    image: new Cesium.ConstantProperty(()=>{
            return item.status ==="OFFLINE" ? offlinesingleImg:singleImg
          }),
                    width: 24,
                    height: 24,
                },
                properties: {
                    customData: {
                        data: item,
                    },
                },
            })
        })
    }
  // 渲染轮廓
  const renderOutline = item => {
    item.outline &&
      Cesium.GeoJsonDataSource.load(item.outline).then(dataSource => {
        const entities = dataSource.entities.values
        entities.forEach((entity, index) => {
          // 创建独立折线作为轮廓
          const positions = entity.polygon.hierarchy.getValue().positions
          const randomInt = Math.floor(Math.random() * 5) + 1
    // 渲染轮廓
    const renderOutline = item => {
        item.outline &&
            Cesium.GeoJsonDataSource.load(item.outline).then(dataSource => {
                const entities = dataSource.entities.values
                entities.forEach((entity, index) => {
                    // 创建独立折线作为轮廓
                    const positions = entity.polygon.hierarchy.getValue().positions
                    const randomInt = Math.floor(Math.random() * 5) + 1
          let polygon = {}
                    let polygon = {}
          if (item.name === '县') {
            let material = new PolyGradientMaterial({
              color: Cesium.Color.fromCssColorString(arrColor[randomInt]),
              opacity: 0.7,
              alphaPower: 1.3
            })
                    if (item.name === '县') {
                        let material = new PolyGradientMaterial({
                            color: Cesium.Color.fromCssColorString(arrColor[randomInt]),
                            opacity: 0.7,
                            alphaPower: 1.3,
                        })
            entity.polygon.extrudedHeight = (entity.properties.childrenNum._value || 1) * 500
                        entity.polygon.extrudedHeight = (entity.properties.childrenNum._value || 1) * 500
            entity.polygon.material = material
                        entity.polygon.material = material
            polygon = entity.polygon
            entity.polygon.outline = false // 显示边框
          }
                        polygon = entity.polygon
                        entity.polygon.outline = false // 显示边框
                    }
          viewer.entities.add({
            id: `aggregation-outline-${index}`,
            polyline: {
              positions: positions,
              width: 5, // 直接设置宽度
              material: new Cesium.PolylineGlowMaterialProperty({
                glowPower: 0.5,
                color: Cesium.Color.AQUA,
              }),
            },
                    viewer.entities.add({
                        id: `aggregation-outline-${index}`,
                        polyline: {
                            positions: positions,
                            width: 5, // 直接设置宽度
                            material: new Cesium.PolylineGlowMaterialProperty({
                                glowPower: 0.5,
                                color: Cesium.Color.AQUA,
                            }),
                        },
            polygon
          })
        })
      })
  }
                        polygon,
                    })
                })
            })
    }
  // 聚合机巢
  const aggregation = (item) => {
    if (!item.gJson) return
    const featuresList = item.gJson.features.map(item1 => {
      // const {lng,lat} = getCenterPoint(item1.geometry.coordinates[0][0])
      return {
        name: item1.properties.name,
        position: item1.properties.centroid,
        data: item1.data,
        id: item1.region_code,
      }
    })
    // 遍历特征并添加实体
    featuresList.forEach((feature, index) => {
      if (!feature.position) return
      const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1], 0)
      viewer.entities.add({
        position: position,
        id: `aggregation-name-${index}`,
        label: {
          text: feature.name,
          font: '14pt Source Han Sans CN',
          fillColor: Cesium.Color.WHITE,
          outlineColor: Cesium.Color.BLACK,
          outlineWidth: 2,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
          pixelOffset: new Cesium.Cartesian2(0, -9),
        },
      })
      viewer.entities.add({
        id: `aggregation-count-${index}`,
        position: position,
        label: {
          text: (feature.data.total_device_count || 0).toString(),
          font: '12pt Source Han Sans CN',
          fillColor: Cesium.Color.BLACK,
          outlineColor: Cesium.Color.BLACK,
          style: Cesium.LabelStyle.FILL_AND_OUTLINE,
          eyeOffset: new Cesium.Cartesian3(0, 0, -10), // 让label "浮" 在广告牌前面
        },
        billboard: {
          image: new Cesium.ConstantProperty(mergeImg),
          width: 35,
          height: 35,
        },
        properties: {
          id: feature.id,
          customData: {
            data: feature.data,
          },
        },
      })
    })
    // 聚合机巢
    const aggregation = item => {
        if (!item.gJson) return
        const featuresList = item.gJson.features.map(item1 => {
            // const {lng,lat} = getCenterPoint(item1.geometry.coordinates[0][0])
            return {
                name: item1.properties.name,
                position: item1.properties.centroid,
                data: item1.data,
                id: item1.region_code,
            }
        })
        // 遍历特征并添加实体
        featuresList.forEach((feature, index) => {
            if (!feature.position) return
            const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1], 0)
            viewer.entities.add({
                position: position,
                id: `aggregation-name-${index}`,
                label: {
                    text: feature.name,
                    font: '14pt Source Han Sans CN',
                    fillColor: Cesium.Color.WHITE,
                    outlineColor: Cesium.Color.BLACK,
                    outlineWidth: 2,
                    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                    verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                    pixelOffset: new Cesium.Cartesian2(0, -9),
                },
            })
            viewer.entities.add({
                id: `aggregation-count-${index}`,
                position: position,
                label: {
                    text: (feature.data.total_device_count || 0).toString(),
                    font: '12pt Source Han Sans CN',
                    fillColor: Cesium.Color.BLACK,
                    outlineColor: Cesium.Color.BLACK,
                    style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                    eyeOffset: new Cesium.Cartesian3(0, 0, -10), // 让label "浮" 在广告牌前面
                },
                billboard: {
                    image: new Cesium.ConstantProperty(mergeImg),
                    width: 35,
                    height: 35,
                },
                properties: {
                    id: feature.id,
                    customData: {
                        data: feature.data,
                    },
                },
            })
        })
    // 加载边界
    Cesium.GeoJsonDataSource.load(item.gJson).then(dataSource => {
      viewer.dataSources.add(dataSource)
      item.BJDataSource = dataSource // 保存数据源以便后续删除
      // 获取数据源中的实体
      const entities = dataSource.entities.values
        // 加载边界
        Cesium.GeoJsonDataSource.load(item.gJson).then(dataSource => {
            viewer.dataSources.add(dataSource)
            item.BJDataSource = dataSource // 保存数据源以便后续删除
            // 获取数据源中的实体
            const entities = dataSource.entities.values
      entities.forEach(entity => {
        let material = new PolyGradientMaterial({
          color: Cesium.Color.fromCssColorString(getColor()),
          opacity: 0.7,
          alphaPower: 1.3
        })
            entities.forEach(entity => {
                let material = new PolyGradientMaterial({
                    color: Cesium.Color.fromCssColorString(getColor()),
                    opacity: 0.7,
                    alphaPower: 1.3,
                })
        const randomInt = Math.floor(Math.random() * 8) + 1
                const randomInt = Math.floor(Math.random() * 8) + 1
        entity.polygon.extrudedHeight = (entity.properties.childrenNum._value || randomInt) * 500
                entity.polygon.extrudedHeight = (entity.properties.childrenNum._value || randomInt) * 500
        entity.polygon.material = material
        entity.polygon.outline = false // 显示边框
      })
                entity.polygon.material = material
                entity.polygon.outline = false // 显示边框
            })
      needFly && viewer.flyTo(dataSource, {
        offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-60), 0),
        duration: 0.5,
      })
      needFly = false
    })
  }
            needFly &&
                viewer.flyTo(dataSource, {
                    offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-60), 0),
                    duration: 0.5,
                })
            needFly = false
        })
    }
  // 获取弹框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
  }
    // 获取弹框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'
    }
  }
    // 弹框位置刷新
    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'
        }
    }
  /**
   * 根据条件获取项
   * @param {Array} arr 数据源
   * @param {Function} condition 条件
   * @returns
   */
  const findTypeItem = (arr, condition) => {
    return arr.find(item => condition(item))
  }
    /**
     * 根据条件获取项
     * @param {Array} arr 数据源
     * @param {Function} condition 条件
     * @returns
     */
    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
    // 左键单机事件
    const singleMachineEvent = async click => {
        let clickTargets = viewer.scene.drillPick(click.position).map(item => item.id)
        if (!clickTargets.length) return
    console.log(clickTargets, 11111)
        // console.log(clickTargets, 11111)
    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
        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)
    }
  }
        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)
    }
  }
    const removeDom = () => {
        const dom = document.querySelector('#mapPopUpBox')
        if (dom && dom.parentNode) {
            dom.parentNode.removeChild(dom)
        }
    }
  // 移除 点 和 gjson 实体
  const removeEntities = () => {
    // dataSources移除
    scalingJudgment.forEach(item => {
      item.BJDataSource && viewer.dataSources.remove(item.BJDataSource)
      item.BJDataSource = null
    })
    // entities移除
    const entitiesIDs = viewer.entities.values.map(i => i.id)
    entitiesIDs.forEach(item => {
      item.includes('aggregation-') && viewer.entities.removeById(item)
    })
  }
  // 移除弹框标签
  const removeLabel = () => {
    viewer?.scene.postRender.removeEventListener(labelBoxRender)
    removeDom()
  }
    // 移除 点 和 gjson 实体
    const removeEntities = () => {
        // dataSources移除
        scalingJudgment.forEach(item => {
            item.BJDataSource && viewer.dataSources.remove(item.BJDataSource)
            item.BJDataSource = null
        })
        // entities移除
        const entitiesIDs = viewer.entities.values.map(i => i.id)
        entitiesIDs.forEach(item => {
            item.includes('aggregation-') && viewer.entities.removeById(item)
        })
    }
    // 移除弹框标签
    const removeLabel = () => {
        viewer?.scene.postRender.removeEventListener(labelBoxRender)
        removeDom()
    }
  // 移除所有监听事件,变量置空
  const removeAll = () => {
    clearMapEntity()
    viewer = null
  }
    // 移除所有监听事件,变量置空
    const removeAll = () => {
        clearMapEntity()
        viewer = null
    }
  const clearMapEntity = () => {
    if (!viewer) return
    removeEntities()
    removeLabel()
    // 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 clearMapEntity = () => {
        if (!viewer) return
        removeEntities()
        removeLabel()
        // 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 = () => {
    viewer = window.$viewer
    viewer.scene.postRender.removeEventListener(determineScaling)
    initMapData(selectedAreaCode.value || userAreaCode.value).then(() => {
      viewer.scene.postRender.addEventListener(determineScaling)
    })
    const init = () => {
        viewer = window.$viewer
        viewer.scene.postRender.removeEventListener(determineScaling)
        initMapData(selectedAreaCode.value || userAreaCode.value).then(() => {
            viewer.scene.postRender.addEventListener(determineScaling)
        })
    handlerInit()
  }
        handlerInit()
    }
  const handlerInit = () => {
    if (handler) return
    const handlerInit = () => {
        if (handler) return
    handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
    handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK)
  }
        handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
        handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK)
    }
  onBeforeUnmount(() => { })
  // onMounted(() => {
  //   nextTick(() => {
  //     viewer = window.$viewer;
  //     handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  //     handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  //   });
  // });
    onBeforeUnmount(() => {})
    // onMounted(() => {
    //   nextTick(() => {
    //     viewer = window.$viewer;
    //     handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
    //     handler.setInputAction(singleMachineEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    //   });
    // });
  return { init, removeAll }
    return { init, removeAll }
}
src/store/modules/task.js
@@ -8,8 +8,6 @@
  actions: {},
  mutations: {
    setTaskSearchParams: (state, data) => {
      console.log(data)
            // setStore({ name: 'taskSearchParams', content: data })
            state.taskSearchParams = data;
        },
  },
src/views/TaskManage/SearchBox.vue
@@ -40,7 +40,7 @@
          </div>
        </el-form-item>
        <el-form-item label="任务算法:" v-if="isExpand">
          <TaskAlgorithmBusiness :setWidth="200" :showAlgorithm="true" @algorithmChange="algorithmChange"/>
          <TaskAlgorithmBusiness :setWidth="186" :showAlgorithm="true" @algorithmChange="algorithmChange"/>
        </el-form-item>
        <el-form-item label="所属部门:" v-if="isExpand">
          <el-select class="ztzf-select" v-model="searchForm.create_dept" placeholder="请选择部门" clearable>
@@ -53,7 +53,7 @@
          </el-select>
        </el-form-item>
        <el-form-item label="任务类型:" v-if="isExpand">
          <TaskAlgorithmBusiness :setWidth="200" :showBusiness="true" @businessChange="businessChange"/>
          <TaskAlgorithmBusiness :setWidth="186" :showBusiness="true" @businessChange="businessChange"/>
        </el-form-item>
        <div class="more" v-if="isExpand" @click="toggleExpand">收起</div>
        <div class="more" v-else @click="toggleExpand">更多</div>
@@ -84,7 +84,7 @@
  ai_types: [], // 算法类型
  area_code: '', // 区域code
  create_dept: '', // 创建部门
  date_enum: '', // 日期枚举,可用值:TODAY,CURRENT_WEEK,CURRENT_MONTH,CURRENT_YEAR
  date_enum: 'TODAY', // 日期枚举,可用值:TODAY,CURRENT_WEEK,CURRENT_MONTH,CURRENT_YEAR
  device_sn: '', // 设备编号
  end_date: null, // 结束时间
  industry_type: '', // 行业key
@@ -102,30 +102,6 @@
];
const emit = defineEmits(['search','addTask']);
const handleSearch = () => {
  // 提交至store
  let params = {
    ...searchForm,
    start_date: dateRange.value.length ? `${dateRange.value[0]} 00:00:00` : null,
    end_date: dateRange.value.length ? `${dateRange.value[1]} 23:59:59` : null
  }
  store.commit('setTaskSearchParams', params);
  emit('search', params);
};
const handleReset = () => {
  dateRange.value = null;
  Object.keys(searchForm).forEach(key => {
    searchForm[key] = '';
  });
  handleSearch();
};
// 新增任务
const addTask = () => {
  emit('addTask');
};
const algorithmChange = (val) => {
  searchForm.ai_types = val;
@@ -160,6 +136,7 @@
};
const deptChange = (value) => {
  // 处理机巢数据
  searchForm.device_sn = '';
  machineData.value = '';
  machineData.value = deptTreeData.value.find(item => item.area_code === value)?.devices || [];
  // 所属部门重新请求值
@@ -193,13 +170,42 @@
  // model.value = dateRanges[item];
  // emit('change', dateRanges[item],timeListEnum[index]);
  searchForm.date_enum = timeListEnum[index];
  handleSearch();
};
// 搜索
const handleSearch = () => {
  if (!dateRange.value) { dateRange.value = []; }
  // 提交至store
  let params = {
    ...searchForm,
    start_date: dateRange.value.length ? `${dateRange?.value[0]} 00:00:00` : null,
    end_date: dateRange.value.length ? `${dateRange?.value[1]} 23:59:59` : null
  }
  store.commit('setTaskSearchParams', params);
  emit('search', params);
};
// 重置
const handleReset = () => {
  dateRange.value = [];
  Object.keys(searchForm).forEach(key => {
    searchForm[key] = '';
  });
  searchForm.ai_types = [];
  searchForm.date_enum = 'TODAY';
  checked.value = 'today';
  handleSearch();
};
// 新增任务
const addTask = () => {
  emit('addTask');
};
onMounted(() => {
  // requestDictionary();
  getDeptsByAreaCode();
  requestDockInfo();
  // 查询一次
  handleSearch();
});
</script>
src/views/TaskManage/TaskIntermediateContent/AddTask.vue
@@ -8,6 +8,7 @@
        :width="pxToRem(1500)"
        :close-on-click-modal="false"
        :destroy-on-close="true"
        @close="cancel"
    >
        <!-- <el-divider content-position="left">新建任务</el-divider> -->
        <div class="task-contain">
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue
@@ -105,7 +105,7 @@
        if (row.status === 2 || row.status === 1){
            rowData.value = row? row : {};
            isShowCurrentTaskDetails.value = true;
        }else{
        } else{
            ElMessage.warning('todo 跳历史任务详情')
        }
    }else{
src/views/TaskManage/TaskTop/TaskEvent.vue
@@ -117,7 +117,6 @@
// 添加监听
watch(() => store.state.task.taskSearchParams, (newVal) => {
  console.log(newVal, '1111');
  if (newVal) {
    getJobEventBar(newVal);
  }
src/views/TaskManage/TaskTop/TaskIndustry.vue
@@ -1,7 +1,7 @@
<!-- 任务算法统计 -->
 <template>
  <div class="task-industry">
    <div class="title">任务算法统计</div>
    <div class="title">任务算法类型</div>
    <div class="chart" ref="chartRef"></div>
  </div>
 </template>
@@ -41,13 +41,14 @@
      roseType: 'radius',
      radius: ['20%', '70%'],
      center: ['50%', '45%'],
      data: [
        { name: '国土类', value: 0, itemStyle: { color: '#3D7FFF' } },
        { name: '城管类', value: 0, itemStyle: { color: '#8277E9' } },
        { name: '消防类', value: 0, itemStyle: { color: '#FFB77E' } },
        { name: '林业类', value: 0, itemStyle: { color: '#44D7B6' } },
        { name: '公安类', value: 0, itemStyle: { color: '#62F4FF' } }
      ],
      data: [],
      // [
      //   { name: '国土类', value: 0, itemStyle: { color: '#3D7FFF' } },
      //   { name: '城管类', value: 0, itemStyle: { color: '#8277E9' } },
      //   { name: '消防类', value: 0, itemStyle: { color: '#FFB77E' } },
      //   { name: '林业类', value: 0, itemStyle: { color: '#44D7B6' } },
      //   { name: '公安类', value: 0, itemStyle: { color: '#62F4FF' } }
      // ],
      label: {
        show: true,
        position: 'outside',
@@ -71,36 +72,19 @@
const getIndustryJobNumPieChart = (value) => {
  industryJobNumPieChart(value).then(res => {
    if (res.data.code !== 0) return;
    option.series[0].data.forEach(item => {
      const matchData = res.data.data.find(d => d.name === item.name);
      if (matchData) {
        item.value = matchData.value;
      }
    });
    option.series[0].data = res.data.data;
    // forEach(item => {
    //   const matchData = res.data.data.find(d => d.name === item.name);
    //   if (matchData) {
    //     item.value = matchData.value;
    //   }
    // });
    chart.value.setOption(option);
  });
};
// 获取行业统计数据
// const getIndustryJobNumPieChart = () => {
//   industryJobNumPieChart().then(res => {
//     if (res.data.code !== 0) return;
//     const data = res.data.data.map(item => ({
//       name: item.name,
//       value: item.value,
//       itemStyle: {
//         color: getRandomColor()
//       }
//     }));
//     option.series[0].data = data;
//     chart.value.setOption(option);
//   });
// };
// 添加监听
watch(() => store.state.task.taskSearchParams, (newVal) => {
  console.log(newVal, '2222');
  if (newVal) {
    getIndustryJobNumPieChart(newVal);
  }
src/views/TaskManage/components/TaskAlgorithmBusiness.vue
@@ -1,12 +1,12 @@
<!-- 关联算法和综合业务 -->
<template>
  <div class="task-algorithm" v-if="showAlgorithm">
    <el-select class="ztzf-select" :style="{ width:setWidth +'px' }" v-model="ai_types" multiple collapse-tags collapse-tags-tooltip placeholder="请选择算法" clearable @change="handleAlgorithmChange">
    <el-select class="ztzf-select" :style="{ width: pxToRem(setWidth) }" v-model="ai_types" multiple collapse-tags collapse-tags-tooltip placeholder="请选择算法" clearable @change="handleAlgorithmChange">
      <el-option v-for="item in taskAlgorithm" :key="item.id" :label="item.dictValue" :value="item.dictKey" />
    </el-select>
  </div>
  <div class="task-business" v-if="showBusiness">
    <el-select class="ztzf-select" :style="{ width:setWidth +'px' }" v-model="industry_type" placeholder="请选择类型" clearable @change="handleBusinessChange">
    <el-select class="ztzf-select" :style="{ width: pxToRem(setWidth) }" v-model="industry_type" placeholder="请选择类型" clearable @change="handleBusinessChange">
      <el-option v-for="item in taskBusiness" :key="item.id" :label="item.dictValue" :value="item.dictKey" />
    </el-select>
  </div>
@@ -14,7 +14,8 @@
<script setup>
import { getMultipleDictionary } from '@/api/system/dictbiz'
import { getMultipleDictionary } from '@/api/system/dictbiz';
import { pxToRem } from '@/utils/rem'
// 接收父组件传参
const props = defineProps({
@@ -27,7 +28,7 @@
    default: false
  },
  setWidth: {
    type: String,
    type: Number,
    default: '100%'
  }
});