forked from drone/command-center-dashboard

罗广辉
2025-03-29 f1e4e5843ff9ae37ccecd0ea391911f67de0e50d
feat: 机巢点击交互
2 files modified
4 files added
243 ■■■■■ changed files
src/assets/images/home/mapPopUpBox/itembg.png patch | view | raw | blame | history
src/assets/images/home/mapPopUpBox/mapPopUpBg.png patch | view | raw | blame | history
src/assets/images/home/mapPopUpBox/titleImg.png patch | view | raw | blame | history
src/views/Home/Home.vue 3 ●●●●● patch | view | raw | blame | history
src/views/Home/MapPopUpBox.vue 166 ●●●●● patch | view | raw | blame | history
src/views/Home/useAggregation.js 74 ●●●● patch | view | raw | blame | history
src/assets/images/home/mapPopUpBox/itembg.png
src/assets/images/home/mapPopUpBox/mapPopUpBg.png
src/assets/images/home/mapPopUpBox/titleImg.png
src/views/Home/Home.vue
@@ -10,6 +10,9 @@
import HomeLeft from '@/views/Home/components/HomeLeft/HomeLeft.vue';
import SearchBox from '@/views/Home/SearchBox.vue';
import { useAggregation } from '@/views/Home/useAggregation';
import MapPopUpBox from '@/views/Home/MapPopUpBox.vue';
import { render } from 'vue';
useAggregation()
</script>
src/views/Home/MapPopUpBox.vue
New file
@@ -0,0 +1,166 @@
<script setup>
import { Close } from '@element-plus/icons-vue';
const list = [
  { name: '执行中', value: '1', color: '#FFA768' },
  { name: '在线', value: '20', color: '#8EFFAC' },
  { name: '离线', value: '14', color: '#FFFFFF' },
  { name: '异常', value: '25', color: '#FF6262' },
];
const props = defineProps(['data', 'removeLabel']);
</script>
<template>
  <div class="mapPopUpBox">
    <div class="header">
      <div class="headerLeft">
        <img class="header-image" src="@/assets/images/home/MapPopUpBox/titleImg.png" alt="" />
        <div class="header-title">安义县机场</div>
      </div>
      <el-icon class="header-close" @click="props.removeLabel">
        <Close />
      </el-icon>
    </div>
    <div class="info">
      <div class="info-item">
        机巢数:
        <div class="num">10</div>
      </div>
      <div class="info-item">
        任务数:
        <div class="num">32</div>
      </div>
    </div>
    <div class="status-title">机巢状态</div>
    <div class="status-list">
      <div v-for="item in list" :key="item.name" class="status-item">
        <div class="status-value" :style="{ color: item.color }">{{ item.value }}</div>
        <div class="status-name">{{ item.name }}</div>
      </div>
    </div>
  </div>
</template>
<style scoped lang="scss">
.mapPopUpBox {
  width: 418px;
  height: 240px;
  background: url('@/assets/images/home/MapPopUpBox/mapPopUpBg.png') no-repeat center / 100% 100%;
  padding-left: 98px;
  .header {
    padding: 0 12px;
    width: 320px;
    height: 51px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    .headerLeft {
      display: flex;
    }
    img {
      width: 26px;
      height: 26px;
    }
    &-title {
      font-family: YouSheBiaoTiHei, YouSheBiaoTiHei, serif;
      font-weight: 400;
      font-size: 26px;
      line-height: 30px;
      letter-spacing: 2px;
      text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.25);
      background: linear-gradient(180deg, #ffffff 41%, #35d0ff 86%);
      -webkit-background-clip: text;
      -webkit-text-fill-color: transparent;
      margin-left: 4px;
    }
    &-close {
      width: 25px;
      height: 100%;
      line-height: 100%;
      display: flex;
      align-items: center;
      color: #9cd5ff;
      pointer-events: all;
      cursor: pointer;
    }
  }
  .info {
    height: 34px;
    font-family: Source Han Sans CN, Source Han Sans CN, serif;
    font-weight: 400;
    font-size: 16px;
    color: #ffffff;
    line-height: 19px;
    display: flex;
    align-items: center;
    padding-left: 15px;
    gap: 0 20px;
    margin: 18px 0;
    .info-item {
      display: flex;
      align-items: center;
      .num {
        font-family: YouSheBiaoTiHei, YouSheBiaoTiHei, serif;
        font-weight: 400;
        font-size: 26px;
        color: #ffffff;
        line-height: 30px;
      }
    }
  }
  .status-title {
    font-family: YouSheBiaoTiHei, YouSheBiaoTiHei, serif;
    font-weight: 400;
    font-size: 20px;
    color: #ffffff;
    line-height: 23px;
    text-shadow: 0px 0px 12px rgba(68, 105, 255, 0.67);
    padding-left: 15px;
    margin-bottom: 14px;
  }
  .status-list {
    display: flex;
    justify-content: space-between;
    align-items: center;
    .status-item {
      width: 66px;
      height: 54px;
      background: url('@/assets/images/home/MapPopUpBox/itembg.png') no-repeat center / 100% 100%;
      text-align: center;
      .status-value {
        font-family: YouSheBiaoTiHei, YouSheBiaoTiHei, serif;
        font-weight: 400;
        font-size: 26px;
        color: #ffa768;
        line-height: 17px;
        text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
        position: relative;
        top: -3px;
      }
      .status-name {
        font-family: Source Han Sans CN, Source Han Sans CN, serif;
        font-weight: 400;
        font-size: 14px;
        color: #ffffff;
        line-height: 16px;
        position: relative;
        top: -3px;
      }
    }
  }
}
</style>
src/views/Home/useAggregation.js
@@ -4,19 +4,24 @@
import jiangxishi from '@/assets/geojson/jiangxishi.json';
import jiangxi from '@/assets/geojson/jiangxi.json';
import zg from '@/assets/geojson/zg.json';
import MapPopUpBox from '@/views/Home/MapPopUpBox.vue';
import { render } from 'vue';
/**
 * 机巢聚合功能
 */
export const useAggregation = () => {
  let viewer = null;
  const scalingJudgment = [
    { name: '县', value: [0, 48651], gJson: null },
    { name: '市', value: [48651, 314863], gJson: jiangxishi },
    { name: '省', value: [314863, 1169651], gJson: jiangxi },
    { name: '国', value: [1169651, 37962800], gJson: zg },
  ];
  let viewer = null;
  const active = ref('');
  let handler = null;
  let labelDiv = null;
  let positionC3 = null;
  const init = () => {
    determineScaling();
    viewer.camera.moveEnd.addEventListener(() => {
@@ -24,18 +29,15 @@
    });
  };
  const removeFun = () => {
    viewer.dataSources.removeAll(true);
    viewer.entities.removeAll();
  };
  // 确定缩放比例
  const determineScaling = () => {
    let height = viewer.camera.positionCartographic.height;
    // 根据高度展示对应的 gJson
    for (let item of scalingJudgment) {
      if (height > item.value[0] && height <= item.value[1]) {
        if (active.value !== item.name) {
          removeFun();
          removeEntities();
          removeLabel();
          active.value = item.name;
          item.gJson ? aggregation(item) : splashed();
        }
@@ -83,6 +85,7 @@
      if (!feature.position) return;
      const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1]);
      viewer.entities.add({
        id: feature.id,
        position: position,
        label: {
          // 随机整数
@@ -99,6 +102,12 @@
          image: new Cesium.ConstantProperty(data2),
          width: 24,
          height: 24,
        },
        properties: {
          id: feature.id,
          customData: {
            name: feature.name,
          },
        },
      });
    });
@@ -118,15 +127,60 @@
    });
  };
  const getLabelDom = () => {
    const vNode = h(MapPopUpBox, { data: '参数',removeLabel });
    const tooltipContainer = document.createElement('div');
    tooltipContainer.style.position = 'absolute';
    tooltipContainer.style.transform = 'translateY(-50%)';
    tooltipContainer.style.pointerEvents = 'none';
    document.querySelector('.page-index').append(tooltipContainer);
    render(vNode, tooltipContainer);
    return tooltipContainer;
  };
  const labelBox = () => {
    if (!labelDiv) {
      labelDiv = getLabelDom();
    }
    const screenPosition = viewer.scene.cartesianToCanvasCoordinates(positionC3);
    if (screenPosition) {
      labelDiv.style.left = `${screenPosition.x}px`;
      labelDiv.style.top = `${screenPosition.y}px`;
    }
  };
  const clickFun = click => {
    const pickedObject = viewer.scene.pick(click.position);
    if (Cesium.defined(pickedObject) && pickedObject.id) {
      const entity = pickedObject.id;
      positionC3 = entity.position._value;
      viewer.scene.postRender.addEventListener(labelBox);
    }
  };
  const removeEntities = () => {
    viewer.dataSources.removeAll(true);
    viewer.entities.removeAll();
  };
  const removeLabel = () => {
    viewer.scene.postRender.removeEventListener(labelBox);
    if (labelDiv && labelDiv.parentNode) {
      labelDiv.parentNode.removeChild(labelDiv);
      labelDiv = null;
    }
  };
  onUnmounted(() => {
    console.log('xiezai');
    removeFun();
    removeEntities();
    removeLabel();
    handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
  });
  onMounted(() => {
    console.log('触发');
    nextTick(() => {
      viewer = window.$viewer;
      handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
      handler.setInputAction(clickFun, Cesium.ScreenSpaceEventType.LEFT_CLICK);
      init();
    });
  });