forked from drone/command-center-dashboard

罗广辉
2025-04-14 0157d1306b53570ede7157597cfb11624d89ca88
Merge remote-tracking branch 'origin/master'
11 files modified
411 ■■■■■ changed files
src/api/home/index.js 2 ●●● patch | view | raw | blame | history
src/api/home/machineNest.js 4 ●●●● patch | view | raw | blame | history
src/styles/element-ui.scss 56 ●●●●● patch | view | raw | blame | history
src/views/Home/HomeLeft/MachineNestList.vue 22 ●●●● patch | view | raw | blame | history
src/views/Home/HomeLeft/components/MachineNestTotal.vue 8 ●●●● patch | view | raw | blame | history
src/views/Home/RSide.vue 177 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue 9 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineMonitor.vue 37 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineStatus/MachineStatus.vue 19 ●●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/MachineTableDetails.vue 74 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/SignMachineNest.vue 3 ●●●● patch | view | raw | blame | history
src/api/home/index.js
@@ -111,7 +111,7 @@
export const flyByJobId = jobId => {
    return request({
        url: '/drone-device-core/wayline/api/v1/workspaces/flyByJobId?jobId=' + jobId,
        method: 'get',
        method: 'post',
    })
}
// 再次执行
src/api/home/machineNest.js
@@ -27,9 +27,9 @@
    })
}
// 机巢直播/无人机直播 均可使用
export const liveStart = deviceSn => {
export const liveStart = (deviceSn, quality) => {
    return request({
        url: `/drone-device-core/manage/api/v1/live/streams/liveStart?deviceSn=${deviceSn}`,
        url: `/drone-device-core/manage/api/v1/live/streams/liveStart?deviceSn=${deviceSn}&quality=${quality}`,
        method: 'post',
        data: {},
        headers: {
src/styles/element-ui.scss
@@ -167,7 +167,7 @@
    color: var(--text-color);
  }
  .el-select__wrapper {
    background-color: transparent;
    background-color: #021022;
    box-shadow: none;
    border: var(--border);
  }
@@ -270,3 +270,57 @@
    }
  }
}
// 弹框-dialog
.ztzf-dialog {
    background: #0f1929;
    box-shadow: inset 0px -50px 50px 0px rgba(27, 148, 255, 0.13);
    border-radius: 20px 0px 0px 0px;
    border: 2px solid;
    padding: 0 !important;
    border-image: linear-gradient(
            180deg,
            rgba(81, 168, 255, 0),
            rgba(48, 111, 202, 1),
            rgba(255, 255, 255, 1),
            rgba(27, 148, 255, 1)
        )
        2 2;
  // 头部
  .el-dialog__header {
        width: 100%;
        height: 47px;
        margin-bottom: 14px;
        background: url('/src/assets/images/home/homeLeft/inspection-vector.png') no-repeat center;
        background-size: 100% 100%;
        font-weight: bold;
        font-size: 16px;
        line-height: 47px;
    }
  .el-dialog__title {
        width: 112px;
        height: 19px;
        font-family: Segoe UI, Segoe UI;
        font-weight: bold;
        font-size: 16px;
        line-height: 16px;
        text-shadow: 0px 0px 5px rgba(154, 218, 255, 0.6);
        text-align: left;
        font-style: normal;
        text-transform: none;
        background: linear-gradient(90deg, #fbfdff 0%, #86d4ff 100%);
        margin-left: 16px;
        -webkit-background-clip: text; /* 背景被裁剪成文字的前景色 */
        -webkit-text-fill-color: transparent; /* 文字填充颜色变透明 */
    }
  .el-dialog .el-dialog__header {
        /* margin: 0px !important; */
        padding: 0px !important;
        padding-left: 0px !important;
    }
  .el-scrollbar__thumb {
        background: #13c6ff !important;
    }
}
src/views/Home/HomeLeft/MachineNestList.vue
@@ -16,27 +16,29 @@
                <div
                    :class="[index % 2 === 1 ? 'table-itemeven' : 'table-item']"
                    v-for="(item, index) in tableList"
                    :key="index"
                    :key="index" @click="signMachineNestClick(item)"
                >
                    <img src="/src/assets/images/home/homeLeft/machinenestlist-sign.png" alt="" />
                    <div class="middle">
                        <div class="title">{{ item.nickname }}</div>
                        <div class="number">
                            飞行次数:<span>{{ item.fly_count }}</span
                            >次 飞行里程:<span>{{ item.flight_mileage }}</span
                            >km
                            飞行次数:
                            <span>{{ item.fly_count }}</span>
                            次 飞行里程:
                            <span>{{ item.flight_mileage }}</span>
                            km
                        </div>
                        <div class="result">
                            任务成果:<span>{{ item.accumulate_data }}</span
                            >个 飞行时长:<span>{{ item.hour }}</span
                            >h
                            任务成果:
                            <span>{{ item.accumulate_data }}</span>
                            个 飞行时长:
                            <span>{{ item.hour }}</span>
                            h
                        </div>
                    </div>
                    <div
                        class="right"
                        :class="item.status === 'WORKING' ? 'atcive' : item.status === 'OFFLINE' ? 'offine' : 'freetime'"
                        @click="signMachineNestClick(item)"
                    >
                        :class="item.status === 'WORKING' ? 'atcive' : item.status === 'OFFLINE' ? 'offine' : 'freetime'">
                        {{ item.status === 'OFFLINE' ? '离线中' : item.status === 'WORKING' ? '作业中' : '空闲中' }}
                    </div>
                </div>
src/views/Home/HomeLeft/components/MachineNestTotal.vue
@@ -7,7 +7,7 @@
        <div class="name">机巢总数</div>
      </div>
      <div class="status">
        <div class="item" v-for="(item, index) in listNum">
        <div class="item" v-for="(item, index) in listNum" >
          <div>
            <div :style="{ color: item.color }" class="value">{{ item.value }}</div>
            <div class="name">{{ item.name }}</div>
@@ -34,9 +34,9 @@
let total = ref(0);
// 机巢统计
let listNum = ref([
  { name: '空闲中', value: 89, color: '#FFA768' },
  { name: '作业中', value: 100, color: '#8EFFAC' },
  { name: '离线中', value: 66, color: '#FFFFFF' },
  { name: '空闲中', value: 0, color: '#FFA768' },
  { name: '作业中', value: 0, color: '#8EFFAC' },
  { name: '离线中', value: 0, color: '#FFFFFF' },
  // { name: '异常', value: 10, color: '#FF8E8E' },
]);
// 获取机巢列表
src/views/Home/RSide.vue
@@ -1,35 +1,33 @@
<template>
  <div class="ai-chat">
    <el-popover
      placement="bottom"
      :visible="visible"
      :width="200"
      trigger="click"
    >
      <template #reference>
        <div class="chat" id="chatID" v-drag:chatID  @mousedown="handleMouseDown"
             @mouseup="handleMouseUp"/>
      </template>
      <div>
        快和我对话吧
        <el-input/>
      </div>
    </el-popover>
    <img class="chat-bottom" src="../../assets/images/chat-bottom.png" alt="">
  </div>
  <div class="r-side">
        <img v-for="(item, index) in images"
                 :key="index" :class="item.class"
                 :src="activeIndex === index ? item.activeSrc : item.src" alt=""
                 @click="activeChange(index)"
                 @mouseenter="enterHover(index)"
                 @mouseleave="logIndex=3"
        >
  </div>
  <div v-if="logIndex===0" class="r-side-positioning">返回当前位置</div>
  <div v-if="logIndex===1" class="r-side-measuring">量尺</div>
  <div v-if="logIndex===2" class="r-side-layer">切换地图模式</div>
  <MeasuringDistance v-if="activeIndex === 1"/>
    <div class="ai-chat">
        <el-popover placement="bottom" :visible="visible" :width="200" trigger="click">
            <template #reference>
                <div class="chat" id="chatID" v-drag:chatID @mousedown="handleMouseDown" @mouseup="handleMouseUp" />
            </template>
            <div>
                快和我对话吧
                <el-input />
            </div>
        </el-popover>
        <img class="chat-bottom" src="../../assets/images/chat-bottom.png" alt="" />
    </div>
    <div class="r-side">
        <img
            v-for="(item, index) in images"
            :key="index"
            :class="item.class"
            :src="activeIndex === index ? item.activeSrc : item.src"
            alt=""
            @click="activeChange(index)"
            @mouseenter="enterHover(index)"
            @mouseleave="logIndex = 3"
        />
    </div>
    <div v-if="logIndex === 0" class="r-side-positioning">返回当前位置</div>
    <div v-if="logIndex === 1" class="r-side-measuring">量尺</div>
    <div v-if="logIndex === 2" class="r-side-layer">切换地图模式</div>
    <MeasuringDistance v-if="activeIndex === 1" />
</template>
<script setup>
@@ -43,45 +41,48 @@
import tc1 from '@/assets/images/rSide/tc1.png'
import cesiumOperation from '@/utils/cesium-tsa'
let logIndex = ref(3);
const enterHover = (value) => {
    logIndex.value = value;
let logIndex = ref(3)
const enterHover = value => {
    logIndex.value = value
}
const { flyTo } = cesiumOperation()
const store = useStore();
const currentAreaPosition = computed(() => store.state.home.currentAreaPosition);
const store = useStore()
const currentAreaPosition = computed(() => store.state.home.currentAreaPosition)
let activeIndex = ref(null);
const activeChange = (value) => {
    if(value === 0) {
let activeIndex = ref(null)
const activeChange = value => {
    if (value === 0) {
        flyTo(currentAreaPosition.value, 0, currentAreaPosition.value.height)
    }
    if (value === 1){
        activeIndex.value = activeIndex.value === 1 ? null : value;
    if (value === 1) {
        activeIndex.value = activeIndex.value === 1 ? null : value
    }
    if (value === 2) {
        activeIndex.value = activeIndex.value === 2 ? null : value
    }
}
const visible = ref(false);
let pressStart = 0;
const visible = ref(false)
let pressStart = 0
const handleMouseDown = () => {
  pressStart = Date.now(); // 记录按下时间
};
    pressStart = Date.now() // 记录按下时间
}
const handleMouseUp = () => {
  const pressDuration = Date.now() - pressStart; // 计算按下时长
  if (pressDuration < 150) {
    // 如果按下时间小于200ms,认为是点击
    visible.value = !visible.value;
  }
};
    const pressDuration = Date.now() - pressStart // 计算按下时长
    if (pressDuration < 150) {
        // 如果按下时间小于200ms,认为是点击
        visible.value = !visible.value
    }
}
// 添加: 定义图片数组
const images = [
    { class: 'positioning', src: dw,activeSrc:dw1 },
    { class: 'measuring-scale', src: lc,activeSrc:lc1 },
    { class: 'layer', src: tc,activeSrc:tc1 }
];
    { class: 'positioning', src: dw, activeSrc: dw1 },
    { class: 'measuring-scale', src: lc, activeSrc: lc1 },
    { class: 'layer', src: tc, activeSrc: tc1 },
]
</script>
<style scoped lang="scss">
@@ -106,45 +107,47 @@
  }
}
.r-side {
  position: absolute;
  bottom: 122px;
  right: 463px;
  cursor: pointer;
  img {
    display: block;
    width: 48px;
    height: 48px;
  }
  .positioning {
    position: relative;
    bottom: 24px;
  }
  .measuring-scale {
    position: relative;
    bottom: 12px;
  }
    position: absolute;
    bottom: 122px;
    right: 463px;
    cursor: pointer;
    img {
        display: block;
        width: 48px;
        height: 48px;
    }
    .positioning {
        position: relative;
        bottom: 24px;
    }
    .measuring-scale {
        position: relative;
        bottom: 12px;
    }
}
.r-side-positioning, .r-side-measuring, .r-side-layer {
  position: absolute;
  right: 514px;
  width: 130px;
  height: 48px;
  font-family: Source Han Sans CN, Source Han Sans CN;
  font-weight: 400;
  font-size: 18px;
  color: #FFFFFF;
  line-height: 48px;
  text-align: center;
  background: url('@/assets/images/cursor.png');
.r-side-positioning,
.r-side-measuring,
.r-side-layer {
    position: absolute;
    right: 514px;
    width: 130px;
    height: 48px;
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 18px;
    color: #ffffff;
    line-height: 48px;
    text-align: center;
    background: url('@/assets/images/cursor.png');
}
.r-side-positioning {
  bottom: 242px;
    bottom: 242px;
}
.r-side-measuring {
  bottom: 182px;
    bottom: 182px;
}
.r-side-layer {
  bottom: 122px;
    bottom: 122px;
}
</style>
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue
@@ -34,11 +34,11 @@
              <img src="../../../assets/images/signMachineNest/machineRight/name.png" alt="" />{{ item.creator_name || '' }}
            </div>
          </div>
          <div class="right" v-if="tabIndex===1" @click="returnImmediately(item.id)">
          <div class="right" v-if="tabIndex===1" @click="reExecute(item.dock_sn)">
            <span>立即返航</span>
            <img src="../../../assets/images/signMachineNest/machineRight/return-fly.png" alt="">
          </div>
          <div class="right" v-else @click="reExecute(item.dock_sn)">
          <div class="right" v-else @click="returnImmediately(item.job_id)">
            <span>再次执行</span>
            <img src="../../../assets/images/signMachineNest/machineRight/return-fly.png" alt="">
          </div>
@@ -55,6 +55,7 @@
<script setup>
import { Search } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import CommonTitle from '@/components/CommonTitle.vue';
import { getBeforeJob, getTodayJob, flyByJobId, returnHome } from '@/api/home';
import { useStore } from 'vuex';
@@ -148,7 +149,7 @@
const returnImmediately = (id) => {
  flyByJobId(id).then(result => {
    if (result.data.code === 0) {
      ElMessage.success('返航成功');
      ElMessage.success('执行成功');
    } else {
      ElMessage.error(result.data.message);
    }
@@ -158,7 +159,7 @@
const reExecute = (dock_sn) => {
  returnHome(dock_sn).then(result => {
    if (result.data.code === 0) {
      ElMessage.success('执行成功');
      ElMessage.success('返航成功');
    } else {
      ElMessage.error(result.data.message);
    }
src/views/SignMachineNest/MachineRight/MachineMonitor.vue
@@ -1,6 +1,6 @@
<!-- 机巢监控 -->
<template>
  <CommonTitle title="机巢监控" />
  <CommonTitle title="视频监控" />
  <div :style="{ marginLeft: pxToRem(14) }">
    <div class="machine-monitor">
      <LiveVideo :videoUrl="airPortUrl" />
@@ -22,14 +22,43 @@
// 直播地址
let airPortUrl = ref('');
// 获取直播地址
const getVideoUrl = () => {
  liveStart(singleUavHome.value.device_sn).then(res => {
const getVideoUrl = (sn,quality) => {
  liveStart(sn, quality).then(res => {
    if (res.data.code !== 0) return;
    airPortUrl.value = res.data.data.rtcs_url;
  });
};
let isCurrentSn = ref(false);
let CurrentSn = ref('');
// 监听ws消息
watch(
  () => store.state.home.deviceState.deviceInfo,
  (newValue) => {
    CurrentSn.value = Object.keys(newValue)[0];
    const currentDevice = newValue[CurrentSn.value];
    if (currentDevice.mode_code > 0) {
      isCurrentSn.value = true;
    } else if (currentDevice.mode_code === 14) {
      isCurrentSn.value = false;
    } else {
      isCurrentSn.value = false;
    }
  },
  {
    immediate: true,
    deep: true,
  }
);
// 监听 isCurrentSn
watch(isCurrentSn, (newVal) => {
  if (newVal) {
    getVideoUrl(CurrentSn.value, 2);
  } else {
    getVideoUrl(singleUavHome.value.device_sn,1);
  }
}, { immediate: true,deep: true });
onMounted(() => {
  getVideoUrl();
  getVideoUrl(singleUavHome.value.device_sn,1);
});
</script>
src/views/SignMachineNest/MachineRight/MachineStatus/MachineStatus.vue
@@ -6,7 +6,8 @@
      <div class="info">
        <img src="../../../../assets/images/signMachineNest/machineRight/wrj.png" alt="">
        <div class="info-right">
          <div class="name">{{ osdVisible?.callsign || '--' }}</div>
          <!-- <div class="name">{{ osdVisible?.callsign || '--' }}</div> -->
          <div class="name">{{ singleUavHome?.nickname || '--' }}</div>
          <div class="wz">
            <span class="left">当前位置:</span>
            <span class="right">{{ detailInfo.longitude }},{{ detailInfo.latitude }}</span>
@@ -80,6 +81,7 @@
const store = useStore();
// 获取机巢信息
let osdVisible = computed(() => store.state.home.osdVisible);
const singleUavHome = computed(() => store.state.home.singleUavHome);
// 单个机巢统计数据
const singleTotal = computed(() => store.state.home.singleTotal);
// 是否展示机机巢状态详情
@@ -141,11 +143,11 @@
  (newValue) => {
    if (newValue.mode_code === 14) return
    if (Object.keys(newValue).length === 0) return
    // console.log(newValue, '顶顶顶111')
    detailInfo.value.longitude = newValue?.longitude.toFixed(6) || '--';
        detailInfo.value.latitude = newValue?.latitude.toFixed(6) || '--';
    getLnglatAltitude(Number(detailInfo.value.longitude), Number(detailInfo.value.latitude)).then((res) => {
      console.log(res, '顶顶顶')
    getLnglatAltitude(Number(detailInfo.value.longitude), Number(detailInfo.value.latitude),window.$viewer).then((res) => {
      // console.log('333333333', res);
      const height = newValue?.height - res?.height;
      //针对西安实时高度进行降低
      const wId = localStorage.getItem('bs_workspace_id');
@@ -166,9 +168,9 @@
// 获取最新机场状态
watch(store.state.home.deviceState, (newValue) => {
    // if (data.currentSn !== osdVisible.gateway_sn) return;
    if (newValue.currentType === EDeviceTypeName.Dock && newValue.dockInfo[newValue.currentSn]) {
    if (newValue.currentType === EDeviceTypeName.Dock && newValue?.dockInfo[newValue.currentSn]) {
      // 机场状态
      mode_code.value = EDockModeText[newValue.dockInfo[newValue.currentSn]?.basic_osd?.mode_code];
      mode_code.value = EDockModeText[newValue?.dockInfo[newValue.currentSn]?.basic_osd?.mode_code];
      // this.$emit('updateModeCode', mode_code.value);
      // 舱内状态
      AircraftStatus.value =
@@ -178,7 +180,7 @@
          ]?.mode_code
        ];
      // 舱内关机时显示的电量
      let child_sn = newValue.dockInfo[newValue.currentSn].basic_osd.sub_device.device_sn;
      let child_sn = newValue?.dockInfo[newValue.currentSn].basic_osd.sub_device.device_sn;
      // 飞机在线时取飞机中的电量
      if(newValue.deviceInfo[child_sn]) {
        drone_charge_state.value = {
@@ -296,6 +298,9 @@
          font-size: 24px;
          color: #0BE5F5;
          line-height: 28px;
          overflow: hidden;          // 添加溢出隐藏
          text-overflow: ellipsis;   // 显示省略号
          white-space: nowrap;       // 不换行
        }
        .wz {
          font-family: Source Han Sans CN, Source Han Sans CN;
src/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/MachineTableDetails.vue
@@ -1,12 +1,11 @@
<!-- 机巢列表详情 -->
<template>
    <el-dialog
        class="machineTableDetails"
        class="machineTableDetails ztzf-dialog"
        v-model="isShowDetails"
        :width="pxToRem(1500)"
        :close-on-click-modal="false"
        :destroy-on-close="true"
    >
        <template #header="{ titleId, titleClass }">
            <div class="my-header">
@@ -18,25 +17,28 @@
            <div class="machineTableDetailsTitle"><span>详情</span></div>
            <div class="infoBox">
                <div class="itemBox" v-for="(item, index) in infoList" :key="index">
                    <div class="itemTitle">{{ item.name }} : </div>
                    <div class="itemTitle">{{ item.name }} :</div>
                    <div v-if="item.name == '任务成果'" class="missionOutcomes">
                        <span>{{item.value ? item.value :0}}</span>
                        个
                    </div>
                    <div
                        v-if="item.name == '机巢状态'"
                        :class="{
                            active: item.value === 'WORKING',
                            freetime: item.value === 'LEISURE',
                            offine: item.value === 'OFFLINE',
                        }"
                        v-if="item.name == '机巢状态'"
                    >
                        {{ item.value === 'OFFLINE' ? '离线中' : item.value === 'WORKING' ? '作业中' : '空闲中' }}
                    </div>
                    <div class="itemValue" v-else> {{ item.value }}</div>
                    <div class="itemValue" v-else>{{ item.value }}</div>
                </div>
            </div>
        </div>
        <DeviceJob v-if="isShowDetails" />
        <DeviceEvent v-if="isShowDetails" />
    </el-dialog>
</template>
<script setup>
@@ -68,22 +70,9 @@
<style lang="scss">
.machineTableDetails {
width: 1270px;
    width: 1270px;
    height: 856px;
    background: #0f1929;
    box-shadow: inset 0px -50px 50px 0px rgba(27, 148, 255, 0.13);
    border-radius: 20px 0px 0px 0px;
    border: 2px solid;
    padding: 0 !important;
    border-image: linear-gradient(
            180deg,
            rgba(81, 168, 255, 0),
            rgba(48, 111, 202, 1),
            rgba(255, 255, 255, 1),
            rgba(27, 148, 255, 1)
        )
        2 2;
    .el-pagination {
        text-align: left;
        padding: 20px 20px 0 20px;
@@ -93,46 +82,10 @@
        vertical-align: middle;
        margin-left: 12px;
    }
    /* 头部 */
    .el-dialog__header {
        width: 1270px;
        height: 47px;
        margin-bottom: 14px;
        background: url('/src/assets/images/home/homeLeft/inspection-vector.png') no-repeat center;
        background-size: 100% 100%;
        font-weight: bold;
        font-size: 16px;
        line-height: 47px;
    }
    .el-dialog .el-dialog__header {
        /* margin: 0px !important; */
        padding: 0px !important;
        padding-left: 0px !important;
    }
    /* 头部 */
    .el-dialog__title {
        width: 112px;
        height: 19px;
        font-family: Segoe UI, Segoe UI;
        font-weight: bold;
        font-size: 16px;
        line-height: 16px;
        text-shadow: 0px 0px 5px rgba(154, 218, 255, 0.6);
        text-align: left;
        font-style: normal;
        text-transform: none;
        background: linear-gradient(90deg, #fbfdff 0%, #86d4ff 100%);
        margin-left: 16px;
        -webkit-background-clip: text; /* 背景被裁剪成文字的前景色 */
        -webkit-text-fill-color: transparent; /* 文字填充颜色变透明 */
    }
}
</style>
<style lang="scss" scoped>
.infoBox {
    display: flex;
    justify-content: space-between;
@@ -144,7 +97,7 @@
        display: flex;
        align-items: center;
        .itemTitle {
        margin-right: 5px;
            margin-right: 5px;
        }
    }
@@ -175,6 +128,13 @@
        margin-bottom: 8px;
    }
}
.missionOutcomes {
    span {
        color: #ffa500;
        font-size: 14px;
        font-weight: bold;
    }
}
// 离线中
.offine {
    width: 53px;
src/views/SignMachineNest/SignMachineNest.vue
@@ -38,6 +38,7 @@
      break
    }
    case EBizCode.DeviceOsd: {
      // console.log(payload, 'DeviceOsd')
      store.commit('setDeviceInfo', payload)
      store.commit('setWsMessage', payload)
      break
@@ -82,8 +83,6 @@
  getFlightStatistics(singleUavHome.value.device_sn).then(res => {
    if (res.data.code !== 0) return;
    const result = res.data.data;
    console.log(result);
    console.log(result);
    store.commit('setSingleTotal', result);
  })
};