forked from drone/command-center-dashboard

shuishen
2025-04-19 600ab7a279a2ffc1a05e722bf18d4bfe0c1da1ea
src/components/CurrentTaskDetails/ControlPanel/ControlComPass/ControlComPass.vue
@@ -1,392 +1,222 @@
<template>
  <div class="instrument-content">
    <div class="left-img" :data-text="`${attitude_pitch}°`">
      <div class="scaleImg">
        <p class="scale" :style="{ top: 45 + ScaleTop + 'px' }"></p>
        <img src="../../../../assets/images/rightmapidentification.png" />
      </div>
    </div>
    <div class="instrument-center">
      <div class="compass-box" :data-text="`${prevRotate?.toFixed(2)}°`">
        <div v-for="(item, index) in str" :key="index" class="scale"
          :style="{ '--rotate': 30 * index - prevRotate + 'deg' }">
          <span class="text">{{ item }}</span>
        </div>
      </div>
      <div class="center-show">
        <img src="../../../../assets/images/mapidentification.png" />
      </div>
      <div class="rotat-btn"></div>
    </div>
    <div class="right-img" :data-text="`${height}m`">
      <div class="ident-arrow">
        <img src="../../../../assets/images/leftmapidentification.png" />
        <div class="arrow-box" :style="{ bottom: realHeight }">
          <div class="arrow"></div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import _ from 'lodash';
export default {
  data () {
    return {
      str: ['W', 30, 33, 'N', 3, 6, 'E', 12, 15, 'S', 21, 24],
      // 俯仰角度数
      attitude_pitch: 0,
      // 真空高度
      height: 0,
      // 旋转方向角度
      prevRotate: 0,
      dockHeight: 0,
    }
  },
  watch: {
    roamPoint: {
      handler (val) {
        // this.prevRotate = val.arrowHeading
        // this.attitude_pitch = val.roll
        // this.height = _.round(val.altitude,1)
      },
    },
  },
  computed: {
    // roamPoint: vuexStateSimplify('pointsWayLine', 'roamPoint'),
    ScaleTop () {
      return (-this.attitude_pitch * 30) / 90
    },
    realHeight () {
         return 0
      // 无人机高度
      const maxHeight = 240
      // 真空高度
      const vacuumHeight = 120
      // 机场高度
      const dockHeightConfig = {
        'e3dea0f5-37f2-4d79-ae58-490af3228069': 14.7,
        '4a574db8-4ad3-48f7-9f16-3edbcd8056e1': 54,
        // 'f47ac10b-58cc-4372-a567-0e02b2c3d479': 81,
      }
      const workspaceId =
        this.$store.state.drone.selectedWorkSpaceId ||
        window.localStorage.getItem('bs_workspace_id')
      const dockHeight = dockHeightConfig?.[workspaceId] || 1
   <div class="instrument-content">
      <div class="left-img">
         <div class="valueBox">{{ props?.options?.pitchAngle || 0 }}°</div>
         <img src="@/assets/images/rightmapidentification.png" alt="" />
         <div class="triangle" :style="pitchAngleStyle"></div>
         <div class="nameBox">俯仰角度</div>
      </div>
      let proportion = 0
      // 处于真空高度和最大高度之间
      if (this.height < maxHeight && this.height >= vacuumHeight) {
        const height = this.height - vacuumHeight
        const proport = height / maxHeight
        proportion = Math.round(proport * 50 + 50)
      }
      // 小于等于真空高度计算
      if (this.height < vacuumHeight && this.height >= dockHeight) {
        const height = this.height - dockHeight
        const copyVacuumHeight = vacuumHeight - dockHeight
        const proport = height / copyVacuumHeight
        proportion = Math.round(proport * 25 + 25)
      }
      // 小于机场高度计算
      if (this.height < dockHeight && this.height >= 0) {
        const proport = this.height / dockHeight
        proportion = Math.round(proport * 25)
      }
      // 大于最大高度计算
      if (this.height >= maxHeight) {
        proportion = 95
      }
      return proportion + '%'
      // const proportion = Math.ceil((this.height / maxHeight) * 100) || 50;
    },
  },
  methods: {
    getAngle (currentLngLat, targetLngLat) {
      const { longitude: lng_a, latitude: lat_a } = currentLngLat
      const { longitude: lng_b, latitude: lat_b } = targetLngLat
      var a = ((90 - lat_b) * Math.PI) / 180
      var b = ((90 - lat_a) * Math.PI) / 180
      var AOC_BOC = ((lng_b - lng_a) * Math.PI) / 180
      var cosc =
        Math.cos(a) * Math.cos(b) +
        Math.sin(a) * Math.sin(b) * Math.cos(AOC_BOC)
      var sinc = Math.sqrt(1 - cosc * cosc)
      var sinA = (Math.sin(a) * Math.sin(AOC_BOC)) / sinc
      var A = (Math.asin(sinA) * 180) / Math.PI
      var res = 0
      if (lng_b > lng_a && lat_b > lat_a) res = A
      else if (lng_b > lng_a && lat_b < lat_a) res = 180 - A
      else if (lng_b < lng_a && lat_b < lat_a) res = 180 - A
      else if (lng_b < lng_a && lat_b > lat_a) res = 360 + A
      else if (lng_b > lng_a && lat_b == lat_a) res = 90
      else if (lng_b < lng_a && lat_b == lat_a) res = 270
      else if (lng_b == lng_a && lat_b > lat_a) res = 0
      else if (lng_b == lng_a && lat_b < lat_a) res = 180
      return res
    },
  },
}
      <div class="instrument-center">
         <div class="compass-box" :style="compassStyle">
            <div v-for="(item, index) in str" :key="index" class="scale" :style="{ '--rotate': 30 * index + 'deg' }">
               <span class="text">{{ item }}</span>
            </div>
         </div>
         <div class="center-show">
            <img src="@/assets/images/mapidentification.png" alt="" />
         </div>
      </div>
      <div class="right-img">
         <div class="valueBox">{{ props.options?.trueAltitude }}m</div>
         <img src="@/assets/images/leftmapidentification.png" alt="" />
         <div class="rightTriangle" :style="trueAltitudeStyle"></div>
         <div class="nameBox">真空高度</div>
      </div>
   </div>
</template>
<script setup>
const str = ['W', 30, 33, 'N', 3, 6, 'E', 12, 15, 'S', 21, 24]
const props = defineProps(['options'])
const pitchAngleStyle = computed(() => {
   const pitchAngle = props?.options?.pitchAngle || 0
   // 将 [-90, 90] 映射到 [0%, 100%]
   const percentage = (((pitchAngle + 90) / 180) * 100).toFixed(2)
   return {
      bottom: `${percentage}%`,
   }
})
const compassStyle = computed(() => {
   return { transform: `rotate(${props?.options?.yawAngle || 0}deg)` }
})
const trueAltitudeStyle = computed(() => {
   const trueAltitude = props?.options?.trueAltitude || 0
   // 将 [0,240] 映射到 [0%, 100%]
   const percentage = ((trueAltitude / 240) * 100).toFixed(2)
   return {
      bottom: `${percentage}%`,
   }
})
</script>
<style lang="scss" scoped>
.instrument-content {
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  position: relative;
  transform: translateY(20px);
   height: 100%;
   width: 100%;
   display: flex;
   gap: 0 30px;
   justify-content: center;
   align-items: center;
   position: relative;
  .left-img,
  .right-img {
    position: relative;
    height: 100%;
    display: flex;
    align-items: center;
    width: 40px;
   .left-img,
   .right-img {
      position: relative;
      width: 9px;
      height: 127px;
    img {
      height: 100px;
    }
  }
      img {
         width: 100%;
         height: 100%;
      }
  .left-img {
    .text {
      position: absolute;
      left: 0px;
      top: 50px;
      width: 60px;
      text-align: center;
    }
      .triangle {
         width: 0px;
         height: 0px;
         margin: auto;
         border: 6px solid transparent;
         border-left-color: #0fff7b;
         position: absolute;
         left: -10px;
    .scaleImg {
      position: relative;
      width: 100%;
      height: 100px;
         transform: translateY(50%);
      }
      img {
        margin-left: 25px;
      }
      .rightTriangle {
         width: 0px;
         height: 0px;
         margin: auto;
         border: 6px solid transparent;
         border-left-color: #0fff7b;
         position: absolute;
         right: -10px;
         transform: translateY(50%) rotate(180deg);
      }
      .scale {
        width: 10px;
        height: 10px;
        background-color: #1fa3f6;
        position: absolute;
        margin-left: 10px;
      .valueBox {
         position: absolute;
         top: -20px;
         left: 50%;
         transform: translateX(-50%);
      }
        &::before {
          content: '';
          position: absolute;
          width: 0;
          height: 0;
          top: 0px;
          left: 10px;
          border-top: solid 5px transparent;
          border-left: solid 5px #1fa3f6;
          border-bottom: solid 5px transparent;
        }
      }
    }
      .nameBox {
         position: absolute;
         bottom: -20px;
         left: 50%;
         width: auto;
         transform: translateX(-50%);
         font-family: Segoe UI, Segoe UI;
         font-weight: 400;
         font-size: 12px;
         white-space: nowrap;
         color: #ffffff;
      }
   }
    &::before {
      content: attr(data-text);
      position: absolute;
      right: 0px;
      top: 50px;
      font-size: 14px;
      font-weight: bolder;
      font-family: none;
    }
   .instrument-center {
      position: relative;
    &::after {
      content: '俯仰角度';
      position: absolute;
      right: 0px;
      text-align: right;
      bottom: 30px;
      font-size: 14px;
      font-weight: bolder;
      font-family: none;
    }
  }
      .compass-box {
         width: 180px;
         height: 180px;
         border-radius: 50%;
         position: relative;
         border: 30px solid rgba($color: #323931, $alpha: 0.5);
         box-shadow: 0 2px 12px 0 #158aff;
         user-select: none;
  .right-img {
    &::before {
      content: attr(data-text);
      position: absolute;
      left: 0px;
      top: 50px;
      font-size: 14px;
      font-weight: bolder;
      font-family: none;
    }
         .scale {
            width: 135%;
            position: absolute;
            top: 50%;
            left: 50%;
            font-weight: bold;
            color: #c1c3c4;
            text-align: left;
            transform: translate(-50%, -50%) rotate(var(--rotate));
    &::after {
      content: '真空高度';
      position: absolute;
      left: 0px;
      bottom: 30px;
      font-size: 14px;
      font-weight: bolder;
      font-family: none;
    }
            &:nth-child(3n - 2) {
               color: #fff;
               font-weight: bolder;
               font-size: 20px;
            }
         }
    .ident-arrow {
      position: relative;
         .scale {
            .text {
               display: inline-block;
               // rotate: -90deg;
               transform: rotate(-90deg);
               -ms-transform: rotate(-90deg);
               -moz-transform: rotate(-90deg);
               -webkit-transform: rotate(-90deg);
               -o-transform: rotate(-90deg);
            }
         }
      }
      .arrow-box {
        position: absolute;
        bottom: 0;
        left: 20px;
      .rotat-btn {
         width: 16px;
         height: 16px;
         background-color: rgba($color: #1fa3f6, $alpha: 1);
         position: absolute;
         top: 10px;
         left: 50%;
         transform: translateX(-50%);
        .arrow {
          position: relative;
          width: 10px;
          height: 10px;
          background-color: #1fa3f6;
         &::before {
            content: '';
            display: block;
            width: 0;
            height: 0;
            border-left: 8px solid transparent;
            border-right: 8px solid transparent;
            border-bottom: 8px solid #1fa3f6;
            position: absolute;
            bottom: 100%;
         }
      }
          &::before {
            content: '';
            position: absolute;
            left: -9px;
            top: 0;
      .center-show {
         width: 30px;
         height: 40px;
         position: absolute;
         left: 50%;
         top: 50%;
         transform: translate(-50%, -50%);
            border: 5px solid transparent {
              right: solid 5px #1fa3f6;
            }
          }
        }
      }
    }
  }
         img {
            width: 100%;
            height: 100%;
            transform: rotate(var(--rotate));
            transition: all 0.5s linear;
         }
      }
  .instrument-center {
    position: relative;
      &::after {
         content: '';
         position: absolute;
         width: 40px;
         top: 50%;
         left: 0;
         z-index: 99;
      }
    .compass-box {
      width: 180px;
      height: 180px;
      border-radius: 50%;
      position: relative;
      border: 30px solid rgba($color: #323931, $alpha: 0.5);
      box-shadow: 0 2px 12px 0 #158aff;
      user-select: none;
      transform: none !important;
      .scale {
        width: 135%;
        position: absolute;
        top: 50%;
        left: 50%;
        font-weight: bold;
        color: #c1c3c4;
        text-align: left;
        transform: translate(-50%, -50%) rotate(var(--rotate));
        &:nth-child(3n - 2) {
          color: #fff;
          font-weight: bolder;
          font-size: 20px;
        }
      }
      .scale {
        .text {
          display: inline-block;
          // rotate: -90deg;
          transform: rotate(-90deg);
          -ms-transform: rotate(-90deg);
          -moz-transform: rotate(-90deg);
          -webkit-transform: rotate(-90deg);
          -o-transform: rotate(-90deg);
        }
      }
      &::before {
        content: '';
        position: absolute;
        top: -45px;
        left: 50%;
        transform: translateX(-50%);
        width: 7px;
        height: 15px;
        background: #00ee8b;
        margin: 0 auto;
        box-shadow: 0 0 4px rgba(0, 0, 0, 0.5), -1px -1px 0 rgba(0, 0, 0, 0.5),
          1px -1px 0 rgba(0, 0, 0, 0.5), -1px 1px 0 rgba(0, 0, 0, 0.5),
          1px 1px 0 rgba(0, 0, 0, 0.5);
      }
      &::after {
        content: attr(data-text);
        position: absolute;
        top: -60px;
        font-size: 16px;
        line-height: 16px;
        font-weight: 600;
        color: #00ee8b;
        left: 50%;
        transform: translateX(-50%);
      }
    }
    .rotat-btn {
      width: 16px;
      height: 16px;
      background-color: rgba($color: #1fa3f6, $alpha: 1);
      position: absolute;
      top: 10px;
      left: 50%;
      transform: translateX(-50%);
      &::before {
        content: '';
        display: block;
        width: 0;
        height: 0;
        border-left: 8px solid transparent;
        border-right: 8px solid transparent;
        border-bottom: 8px solid #1fa3f6;
        position: absolute;
        bottom: 100%;
      }
    }
    .center-show {
      width: 30px;
      height: 40px;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      img {
        width: 100%;
        height: 100%;
        transform: rotate(var(--rotate));
        transition: all 0.5s linear;
      }
    }
    &::after {
      content: '';
      position: absolute;
      width: 40px;
      top: 50%;
      left: 0;
      z-index: 99;
    }
    &::before {
      content: '';
      position: absolute;
      width: 40px;
      top: 50%;
      right: 0;
      z-index: 99;
    }
  }
      &::before {
         content: '';
         position: absolute;
         width: 40px;
         top: 50%;
         right: 0;
         z-index: 99;
      }
   }
}
</style>