罗广辉
2025-06-19 f27ca082eb0a839449dd50c49007b58e5ed6946f
src/pages/page-web/projects/components/route-edit/index.vue
@@ -1,12 +1,26 @@
<template>
  <div class="route-edit-box">
    <div class="btn-group">
      <a-button type="text" @click="backPage">
        <template #icon>
          <ArrowLeftOutlined />
      <a-popover
        :visible="modfiyPopupShow"
        overlayClassName="popup-container"
        placement="leftBottom"
        :getPopupContainer="(triggerNode: any) => triggerNode.parentNode">
        <template #content>
          <div class="popup-text">返回将退出航线编辑器,您要保存吗?如果不保存,您更改的内容将会丢失。</div>
          <div class="btn-tool">
            <a-button size="small" class="btn-cancel" @click="modfiyPopupShow = false">取消</a-button>
            <a-button size="small" class="btn-cancel" @click="handleModfiySave(false)">不保存</a-button>
            <a-button size="small" type="primary" @click="handleModfiySave(true)">保存</a-button>
          </div>
        </template>
        返回
      </a-button>
        <a-button type="text" @click="backPage">
          <template #icon>
            <ArrowLeftOutlined />
          </template>
          返回
        </a-button>
      </a-popover>
      <div class="save-button" @click="saveWaylineFile">
        <SaveOutlined />
      </div>
@@ -34,22 +48,39 @@
        :key="index"
        :class="{ 'active-point': index == selectPointIndex }"
        @click="pointSelect(item, index)">
        <div class="graph">
          <div class="left"></div>
          <div class="right">{{ index + 1 }}</div>
        </div>
        <div class="graph-right">
          <div v-for="(event, index) in item.eventList" :key="index" class="s-event-icon">
            <a-tooltip placement="top">
              <template #title>
                {{ event.name }}
              </template>
              <img :src="event.icon" alt="icon" />
            </a-tooltip>
        <a-tooltip placement="top">
          <template #title>
            <span>{{ item.isUseGlobalHeight ? '跟随全局' : '不跟随全局' }}</span>
          </template>
          <div class="graph">
            <div class="left" :style="{ borderTopColor: item.isUseGlobalHeight ? '#61d396' : '#409eff' }"></div>
            <div class="right">{{ index + 1 }}</div>
          </div>
        </div>
        </a-tooltip>
        <contextMenu :menu="[{ title: '删除航点', value: { index, item } }]">
          <div class="graph-right">
            <div
              v-for="(event, eIndex) in item.eventList"
              :key="eIndex"
              :class="[
                's-event-icon',
                selectedPointAndAction.selectedPoint === index &&
                  selectedPointAndAction.selectedAction === event?.key &&
                  'active-event',
              ]"
              @click="selectionEvent(event, index)">
              <a-tooltip placement="top">
                <template #title>
                  {{ event?.name }}
                </template>
                <img :src="event?.icon" alt="icon" />
              </a-tooltip>
            </div>
          </div>
        </contextMenu>
      </li>
    </ul>
    <startPointTips v-if="xmlTemplate.missionConfig?.takeOffRefPoint['#text'] === ''" />
  </div>
</template>
@@ -57,15 +88,14 @@
import _ from 'lodash'
import * as Cesium from 'cesium'
import setting from './components/setting.vue'
import useKmzTsa, { kmlStr } from '/@/utils/cesium/use-kmz-tsa'
import { ref, reactive, defineEmits, defineProps, watch, onMounted } from 'vue'
import useKmzTsa, { kmlStr, template as xmlTemplate } from '/@/utils/cesium/use-kmz-tsa'
import { ref, defineEmits, defineProps, watch, onMounted } from 'vue'
import { ArrowLeftOutlined, CaretDownOutlined, SaveOutlined } from '@ant-design/icons-vue'
import { cesiumOperation } from '/@/hooks/use-cesium-tsa'
import { useMyStore } from '/@/store'
import ImageTrailMaterial from '/@/utils/cesium/ImageTrailMaterial'
import useMapDraw, { kmlEntities as globalEntities } from '/@/utils/cesium/use-map-draw'
import { getKmlParams } from '/@/utils/cesium/kmz'
import { getHaeHeight } from '/@/utils/cesium/mapUtils'
import useMapDraw, { kmlEntities as globalEntities, selectPointIndex as pointIdx } from '/@/utils/cesium/use-map-draw'
import contextMenu from './components/content-menu.vue'
import startPointTips from './components/setting-point-tip.vue'
const store = useMyStore()
const { appContext }: any = getCurrentInstance()
const global = appContext.config.globalProperties
@@ -88,6 +118,7 @@
interface tragetPoint {
  position: Cesium.Cartesian3
  isUseGlobalHeight: boolean
  eventList: eventParmas[]
}
@@ -119,6 +150,7 @@
const loadCompleted = ref<boolean>(false)
// 编辑时初始化
const initDrawRoute = () => {
  const options = {
    camera: global.$viewer.scene.camera,
@@ -145,6 +177,18 @@
    })
    waylineDetails.value = details
    tragetPointArr.value = waylinePointsEvent.value
    watch(
      () => waylinePointsEvent.value,
      (val) => {
        tragetPointArr.value = val
      },
      {
        deep: true,
      },
    )
    store.commit('SET_WAYLINE_INFO', {
      isShow: true,
    })
    kmlEntities.value = entities.value
    if (!mouseRightClickEvent) {
      // 添加右键事件
@@ -152,8 +196,31 @@
    }
  })
}
// const createPointOrPolyline = () => {}
// 新建航线初始化
const initCreateRoute = () => {
  mapDraw = useMapDraw('', [], '', global)
  const { drawWayline, waylineDetails: details, waylinePointsEvent, kmlEntities: entities, clearWaylineData } = mapDraw
  clearWaylineData()
  drawWayline()
  waylineDetails.value = details
  tragetPointArr.value = waylinePointsEvent.value
  watch(
    () => waylinePointsEvent.value,
    (val) => {
      tragetPointArr.value = val
    },
    {
      deep: true,
    },
  )
  kmlEntities.value = entities.value
  // const xml = kmzUtils.generateXML(xmlTemplate.value)
  // mapDraw.drawWayline([], xml)
  if (!mouseRightClickEvent) {
    // 添加右键事件
    addMapPointEvent()
  }
}
// 创建广告牌
const createBillboard = (title: string | number, color: string) => {
@@ -182,15 +249,18 @@
const selectPointIndex = ref<string | number | any>(null)
const pointSelect = (value: tragetPoint, index: number) => {
  removeCesiumChildDom(popupDom)
  if (selectPointIndex.value === index) {
    global.$viewer.entities.remove(prevPointEntity)
    global.$viewer.entities.remove(nextPointEntity)
    kmlEntities.value.forEach((entity: Cesium.Entity | any, i: number) => {
      entity.billboard.image = createBillboard(i + 1, '#61d396')
    })
    selectPointIndex.value = null
    return
  }
  // if (selectPointIndex.value === index) {
  //   global.$viewer.entities.remove(prevPointEntity)
  //   global.$viewer.entities.remove(nextPointEntity)
  //   kmlEntities.value.forEach((entity: Cesium.Entity | any, i: number) => {
  //     entity.billboard.image = createBillboard(i + 1, '#61d396')
  //     entity.label.show = false
  //   })
  //   selectPointIndex.value = null
  //   pointIdx.value = null
  //   store.commit('SET_WAYLINE_INFO', {})
  //   return
  // }
  if (prevPointEntity) {
    global.$viewer.entities.remove(prevPointEntity)
  }
@@ -222,11 +292,31 @@
  kmlEntities.value.forEach((entity: Cesium.Entity | any, i: number) => {
    if (i === index) {
      entity.billboard.image = createBillboard(index + 1, '#f3be4f')
      const dashLineEntity: Cesium.Entity | any = getEntityById(`dashLine${i}`)
      dashLineEntity.polyline.material = new Cesium.PolylineDashMaterialProperty({
        color: Cesium.Color.fromBytes(235, 192, 99),
      })
      entity.label.show = true
    } else {
      entity.billboard.image = createBillboard(i + 1, '#61d396')
      const dashLineEntity: Cesium.Entity | any = getEntityById(`dashLine${i}`)
      dashLineEntity.polyline.material = new Cesium.PolylineDashMaterialProperty({
        color: Cesium.Color.WHITE,
      })
      entity.label.show = false
    }
  })
  // 默认选中第一个事件
  // if (selectPointIndex.value !== index) {
  //   store.commit('SET_WAYLINE_INFO', {
  //     selectedPoint: index,
  //     // 默认选中第一个
  //     selectedAction: value.eventList[0],
  //   })
  //   selectionEvent(value.eventList[0], index)
  // }
  selectPointIndex.value = index
  pointIdx.value = index
}
// 添加右键事件,弹出窗口
@@ -239,6 +329,7 @@
    removeCesiumChildDom(popupDom)
    const { position } = click
    const { x, y } = position
    const createTakeOffPointEvent = [{ class: 'start-point', title: '设置参考起飞点' }]
    const pointEvents = [
      {
        class: 'add-prev-point',
@@ -246,11 +337,16 @@
      },
      {
        class: 'add-next-point',
        title: `在${Number(selectPointIndex.value) + 1}号航点前插入`,
        title: `在${Number(selectPointIndex.value) + 1}号航点后插入`,
      },
    ]
    const defaultEvents = [{ class: 'finally-point', title: '在最后航点新增航点' }]
    const events = selectPointIndex.value !== null ? [...defaultEvents, ...pointEvents] : defaultEvents
    let events = []
    if (xmlTemplate.value.missionConfig?.takeOffRefPoint['#text'] === '') {
      events = createTakeOffPointEvent
    } else {
      events = selectPointIndex.value !== null ? [...defaultEvents, ...pointEvents] : defaultEvents
    }
    popupDom = createPointPopupDom(events)
    global.$viewer.container.appendChild(popupDom)
    popupDom.style.transform = `translate3d(${x}px, ${y}px, 0)`
@@ -311,45 +407,87 @@
    const longitude = Cesium.Math.toDegrees(c2Postion.longitude)
    const latitude = Cesium.Math.toDegrees(c2Postion.latitude)
    // 获取全局高度
    const globalHeight: any = getKmlParams(kmlStr.value, true, {
      name: 'globalHeight',
      findRegx: '([\\s\\S]*?)',
    })
    const height = Number(globalHeight[1])
    const globalHeight: string = xmlTemplate.value.Folder.globalHeight['#text']
    const height = Number(globalHeight)
    // 右键点击的位置
    const createPointPosition = Cesium.Cartesian3.fromDegrees(longitude, latitude, height)
    const entity: Cesium.Entity = global.$viewer.entities.add({
      position: createPointPosition,
    })
    // 加入kml文件中
    // 起飞点海拔
    const takeOffRefPointAGLHeight = xmlTemplate.value.missionConfig.takeOffRefPointAGLHeight
    const posterHeight = Number(takeOffRefPointAGLHeight['#text'])
    // 当前点位个数
    const points = xmlTemplate.value.Folder.Placemark
    // 加入到解析的JSON对象中
    const setting = {
      Point: {
        coordinates: `${longitude},${latitude}`,
        coordinates: { '#text': `${longitude},${latitude}` },
      },
      index: tragetPointArr.value.length,
      height,
      ellipsoidHeight: height,
      index: { '#text': points.length },
      ellipsoidHeight: { '#text': height - posterHeight },
      height: { '#text': height },
      useGlobalHeadingParam: { '#text': 1 },
      useGlobalHeight: { '#text': 1 },
      useGlobalSpeed: { '#text': 1 },
      useGlobalTurnParam: { '#text': 1 },
      useStraightLine: { '#text': 1 },
      waypointHeadingParam: {
        waypointHeadingAngle: { '#text': 0 },
        waypointHeadingMode: { '#text': 'followWayline' },
        waypointHeadingPathMode: { '#text': 'followBadArc' },
        waypointHeadingPoiIndex: { '#text': 0 },
        waypointPoiPoint: { '#text': '0.000000,0.000000,0.000000' },
      },
      waypointSpeed: { '#text': 10 },
      waypointTurnParam: {
        waypointTurnDampingDist: { '#text': 0.2 },
        waypointTurnMode: { '#text': 'toPointAndStopWithDiscontinuityCurvature' },
      },
      isRisky: { '#text': 0 },
    }
    // 添加参考点
    if (className === 'start-point') {
      // 默认高度为6
      const height = 6
      const takeOffPoint = [latitude, longitude, height]
      xmlTemplate.value.missionConfig.takeOffRefPoint['#text'] = takeOffPoint.join(',')
    }
    // 添加航点
    if (className === 'finally-point') {
      removeAllDataSource()
      removeAllPoint()
      kmzUtils.writePoint(setting)
      globalEntities.value.push(entity)
      mapDraw.drawWayline(globalEntities.value, kmlStr.value)
      xmlTemplate.value.Folder.Placemark.push(setting)
      tragetPointArr.value.push({
        position: createPointPosition,
        eventList: [],
        isUseGlobalHeight: Boolean(setting.useGlobalHeight['#text']),
      })
    }
    if (className === 'add-prev-point') {
      // console.log(1)
      globalEntities.value.splice(selectPointIndex.value, 0, entity)
      xmlTemplate.value.Folder.Placemark.splice(selectPointIndex.value, 0, setting)
      tragetPointArr.value.splice(selectPointIndex.value, 0, {
        position: createPointPosition,
        eventList: [],
        isUseGlobalHeight: Boolean(setting.useGlobalHeight['#text']),
      })
    }
    if (className === 'add-next-point') {
      // const index = selectPointIndex.value
      // globalEntities.value.splice(index, 0, entity)
      // console.log()
      const index = _.cloneDeep(selectPointIndex.value + 1)
      globalEntities.value.splice(index, 0, entity)
      xmlTemplate.value.Folder.Placemark.splice(index, 0, setting)
      tragetPointArr.value.splice(index, 0, {
        position: createPointPosition,
        eventList: [],
        isUseGlobalHeight: Boolean(setting.useGlobalHeight['#text']),
      })
    }
    // clearCesiumMap()
    // createMapMarker(undefined, [...kmlEntities.value, entity])
    // 更新点位序号
    xmlTemplate.value.Folder.Placemark.forEach((placemark: { index: { [x: string]: number } }, index: number) => {
      placemark.index['#text'] = index
    })
    const xmlStr = kmzUtils.generateXML(xmlTemplate.value)
    mapDraw.drawWayline(globalEntities.value, xmlStr)
  })
}
@@ -361,8 +499,25 @@
  }
}
const modfiyPopupShow = ref<boolean>(false)
const backPage = () => {
  emits('backFn')
  if (isModify.value) {
    modfiyPopupShow.value = true
  } else {
    mapDraw.clearWaylineData()
    kmlStr.value = ''
    emits('backFn')
  }
}
const handleModfiySave = (isSave: boolean) => {
  if (isSave) {
    saveWaylineFile()
    modfiyPopupShow.value = false
  } else {
    mapDraw.clearWaylineData()
    kmlStr.value = ''
    emits('backFn')
  }
}
// 清空画布
const clearCesiumMap = () => {
@@ -379,12 +534,49 @@
// 保存文件
const saveWaylineFile = () => {
  kmzUtils.save()
  mapDraw.clearWaylineData()
  kmlStr.value = ''
  emits('backFn')
}
// 判断当前文件是否被修改
const isModify = ref<boolean>(false)
watch(
  () => kmlStr.value,
  (val) => {
    isModify.value = true
  },
  {
    deep: true,
  },
)
// 选中事件
const selectedPointAndAction = reactive<{
  selectedPoint: number | null
  selectedAction: any | null
}>({
  selectedPoint: null,
  selectedAction: null,
})
const selectionEvent = (action: any, index: number) => {
  const params = {
    selectedPoint: index,
    selectedAction: action,
  }
  selectedPointAndAction.selectedPoint = index
  selectedPointAndAction.selectedAction = action.key
  store.commit('SET_WAYLINE_INFO', params)
}
onMounted(() => {
  // 清空画布
  clearCesiumMap()
  initDrawRoute()
  if (filePath.value) {
    initDrawRoute()
  } else {
    initCreateRoute()
  }
})
onUnmounted(() => {
@@ -394,8 +586,6 @@
  clearCesiumMap()
  store.commit('SET_WAYLINE_INFO', {
    isShow: false,
    wayline: {},
    position: null,
  })
})
</script>
@@ -410,17 +600,48 @@
    align-items: center;
    justify-content: flex-start;
    :deep() {
      .popup-container {
        .ant-popover-content {
          .ant-popover-arrow,
          .ant-popover-inner {
            border-color: #282828;
            background-color: #282828;
            box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
            .popup-text {
              color: #fff;
              width: 400px;
            }
            .btn-tool {
              margin-top: 10px;
              display: flex;
              justify-content: flex-end;
              .ant-btn {
                border: 0;
              }
              .btn-cancel {
                background-color: #3c3c3c;
                margin-right: 5px;
              }
            }
          }
        }
      }
    }
    .ant-btn {
      color: #fff;
    }
    .save-button {
      font-size: 20px;
      margin: 0 10px 0 auto;
      cursor: pointer;
      &:hover {
        color: #409eff;
      }
    }
    .setting-btn {
      background-color: #3c3c3c;
      margin-right: 10px;
@@ -472,6 +693,7 @@
    padding: 0;
    height: calc(100vh - 180px);
    overflow: auto;
    li {
      cursor: pointer;
      padding: 10px 0;
@@ -497,22 +719,33 @@
      }
      .graph-right {
        width: calc(100% - 40px);
        width: 100%;
        height: 30px;
        border-bottom: 1px solid #4f4f4f;
        display: flex;
        align-items: center;
        .s-event-icon {
          width: 25px;
          height: 25px;
          width: 20px;
          height: 20px;
          display: flex;
          justify-content: center;
          align-items: center;
          border-radius: 2px;
          margin-right: 3px;
          overflow: hidden;
          &:hover {
            background-color: #409eff;
          }
          img {
            width: 70%;
          }
        }
        .active-event {
          background-color: #409eff;
        }
      }
@@ -522,8 +755,10 @@
    }
  }
}
.active-point {
  background-color: #3c3c3c;
  .graph {
    .left {
      border-top-color: #f3bf4e !important;