forked from drone/command-center-dashboard

shuishen
2025-04-16 115bd56702e2ce09b7a7c1e6bb5e93e2f6174cdf
Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
22 files modified
16 files added
1 files deleted
2162 ■■■■ changed files
README.md 8 ●●●● patch | view | raw | blame | history
public/depend/ZLMRTCClient.js 2 ●●● patch | view | raw | blame | history
src/api/drc.js 16 ●●●●● patch | view | raw | blame | history
src/api/home/common.js 8 ●●●● patch | view | raw | blame | history
src/api/home/event.js 8 ●●●●● patch | view | raw | blame | history
src/api/payload.js 46 ●●●●● patch | view | raw | blame | history
src/assets/images/aiNowFly/photo.png patch | view | raw | blame | history
src/assets/images/aiNowFly/stop.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/close.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/expand.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/point-active.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/point.png patch | view | raw | blame | history
src/assets/images/taskManagement/taskIntermediateContent/controlCenter.png patch | view | raw | blame | history
src/assets/images/taskManagement/taskIntermediateContent/droneImg.png patch | view | raw | blame | history
src/assets/svg/autoControl.svg 11 ●●●●● patch | view | raw | blame | history
src/assets/svg/continueTask.svg 11 ●●●●● patch | view | raw | blame | history
src/assets/svg/manualControl.svg 17 ●●●●● patch | view | raw | blame | history
src/assets/svg/turnBack.svg 11 ●●●●● patch | view | raw | blame | history
src/assets/svg/user.svg 6 ●●●● patch | view | raw | blame | history
src/axios.js 3 ●●●● patch | view | raw | blame | history
src/components/LiveVideo.vue 6 ●●●● patch | view | raw | blame | history
src/components/SvgIcon.vue 26 ●●●●● patch | view | raw | blame | history
src/hooks/controlDrone/useManualControl.js 116 ●●●● patch | view | raw | blame | history
src/store/modules/common.js 4 ●●●● patch | view | raw | blame | history
src/utils/websocket/connect-websocket.js 1 ●●●● patch | view | raw | blame | history
src/views/Home/AINowFly.vue 188 ●●●●● patch | view | raw | blame | history
src/views/Home/EventOverviewDetail/EventOverviewDetailLeft/EventOverviewDetailLeft.vue 41 ●●●● patch | view | raw | blame | history
src/views/Home/EventOverviewDetail/EventOverviewDetailRight.vue 182 ●●●● patch | view | raw | blame | history
src/views/Home/Footer.vue 7 ●●●● patch | view | raw | blame | history
src/views/Home/Home.vue 1 ●●●● patch | view | raw | blame | history
src/views/Home/RSide.vue 6 ●●●●● patch | view | raw | blame | history
src/views/Home/SearchBox.vue 17 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue 338 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/BaseControl.vue 156 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue 604 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue 84 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue 129 ●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsRight.vue 99 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue 10 ●●●● patch | view | raw | blame | history
README.md
@@ -137,13 +137,9 @@
使用 `SvgIcon` 组件加载 SVG 图标,并支持**自定义颜色和尺寸**:
```
<SvgIcon name="user" color="red" :size="48" className="userClass" />
<SvgIcon name="user"  class="userClass" />
```
 🔹 `name`:对应 `/src/assets/svg/` 目录下的 SVG 文件名(无需后缀 `.svg`)。
 🔹 `color`:可选,设置图标颜色。
 🔹 `size`:可选,设置图标大小,单位 `px`。
 🔹 `className`:可选,设置图标类名。
 🔹 `class`:可选,设置图标类名。
public/depend/ZLMRTCClient.js
@@ -6423,7 +6423,7 @@
  }
  function log(message, ...optionalParams) {
    if (logger) {
      logger(message, ...optionalParams);
      // logger(message, ...optionalParams);
    }
  }
  function error(message, ...optionalParams) {
src/api/drc.js
@@ -40,3 +40,19 @@
    data,
  })
}
// 一键返航
export async function returnHome(sn) {
  return request({
    url: `/dp/home/${sn}/drc/returnHome`,
    method: 'post',
  })
}
// 取消返航
export async function returnHomeCancel(sn) {
  return request({
    url: `/dp/home/${sn}/drc/returnHomeCancel`,
    method: 'post',
  })
}
src/api/home/common.js
@@ -25,18 +25,18 @@
        params,
    })
}
// 机巢搜索
// 地址搜索
export const searchByKeyword = params => {
    return request({
        url: `/drone-device-core/map/amap/searchByKeyword?keyword=${params}`,
        method: 'get',
    })
}
// 地址搜索
export const selectDeviceList = params => {
// 机巢搜索
export const selectDeviceList = data => {
    return request({
        url: `/drone-device-core/manage/api/v1/devices/selectDeviceList`,
        method: 'post',
        params,
        data: data,
    })
}
src/api/home/event.js
@@ -55,3 +55,11 @@
        data,
    })
}
// 点击图片关联的事件历史图片列表
export const findImgHistory = id => {
    return request({
        url: '/drone-device-core/jobEvent/findImgHistory?eventId='+id,
        method: 'get',
    })
}
src/api/payload.js
New file
@@ -0,0 +1,46 @@
import request from '@/axios'
import { ElMessage } from 'element-plus'
const API_PREFIX = '/drone-device-core/control/api/v1'
// 获取负载控制权
export async function postPayloadAuth(sn, body) {
    return await request.post(`${API_PREFIX}/devices/${sn}/authority/payload`, body)
}
export const PayloadCommandsEnum = {
  CameraModeSwitch: 'camera_mode_switch',
  CameraPhotoTake: 'camera_photo_take',
  CameraRecordingStart: 'camera_recording_start',
  CameraScreenDrag: 'camera_screen_drag',
  CameraRecordingStop: 'camera_recording_stop',
  CameraFocalLengthSet: 'camera_focal_length_set',
  GimbalReset: 'gimbal_reset',
  CameraAim: 'camera_aim',
};
// 发送负载名称
export async function postPayloadCommands(sn, body, config = {}) {
    return await request.post(`${API_PREFIX}/devices/${sn}/payload/commands`, body, config)
}
// 拍照和录像
export async function callPhotoAndVideoCmd(sn, type) {
  return await request({
    url:`/droneAirport/liveStreamApi/${sn}/payload/photoAndVideoCmd/${type}`,
    method:'get',
  })
}
// 切换直播镜头
export const switchLivestream = (data) => {
  const url = `${API_PREFIX}/live/streams/switch`;
  return request({
    url,
    method: 'post',
    data,
  });
};
src/assets/images/aiNowFly/photo.png
src/assets/images/aiNowFly/stop.png
src/assets/images/home/useEventOperate/close.png
src/assets/images/home/useEventOperate/expand.png
src/assets/images/home/useEventOperate/point-active.png
src/assets/images/home/useEventOperate/point.png
src/assets/images/taskManagement/taskIntermediateContent/controlCenter.png
src/assets/images/taskManagement/taskIntermediateContent/droneImg.png
src/assets/svg/autoControl.svg
New file
@@ -0,0 +1,11 @@
<svg width="30" height="31" viewBox="0 0 30 31" xmlns="http://www.w3.org/2000/svg">
    <g id="Group 1321315343">
        <g id="Frame">
            <g id="Vector">
                <path id="Vector_2"
                      d="M19.326 16.6081L14.9118 5.12231H11.3986L7.1098 16.6319C3.67647 17.715 1.38379 19.6788 1.38379 21.9284C1.38379 25.3444 6.69917 28.1176 13.2464 28.1176C19.7937 28.1176 25.1091 25.3444 25.1091 21.9284C25.1091 19.6669 22.7822 17.6912 19.326 16.6081ZM13.121 9.1334L15.3908 15.513H10.8967L13.121 9.1334ZM13.2464 25.4634C7.39496 25.4634 2.63849 23.2852 2.63849 20.5953C2.63849 18.9647 4.39508 17.5126 7.09839 16.6319L4.9768 22.3093H8.50138L9.85874 18.4053H16.4402L17.8774 22.3093H21.4933L19.3032 16.62C22.0408 17.5007 23.8316 18.9528 23.8316 20.6072C23.8544 23.2853 19.0979 25.4634 13.2464 25.4634Z"
                />
            </g>
        </g>
    </g>
</svg>
src/assets/svg/continueTask.svg
New file
@@ -0,0 +1,11 @@
<svg width="31" height="31" viewBox="0 0 31 31" xmlns="http://www.w3.org/2000/svg">
    <g id="Group 1321315341">
        <g id="Group 1321315342">
            <g id="Vector">
                <path id="Vector_2"
                      d="M6.656 4.52682C5.4624 4.52682 4.41797 5.59926 4.41797 6.82513V23.6811C4.41797 24.907 5.4624 25.9794 6.656 25.9794C7.84961 25.9794 8.89404 24.907 8.89404 23.6811V6.82513C8.89174 5.59914 7.84742 4.52682 6.656 4.52682ZM26.1966 17.104L15.909 25.5045C15.0146 26.2347 13.7912 25.966 13.1764 24.9036C12.9512 24.5144 12.8307 24.0535 12.8307 23.5814V6.78025C12.8307 5.49121 13.7107 4.44617 14.796 4.44629C15.1935 4.44629 15.5815 4.58952 15.909 4.85693L26.1966 13.2574C27.091 13.9877 27.3174 15.441 26.7024 16.5032C26.5662 16.7386 26.3948 16.9424 26.1966 17.104Z"
                     />
            </g>
        </g>
    </g>
</svg>
src/assets/svg/manualControl.svg
New file
@@ -0,0 +1,17 @@
<svg width="31" height="31" viewBox="0 0 31 31" xmlns="http://www.w3.org/2000/svg">
    <g id="Group 1321315344">
        <g id="Frame" clip-path="url(#clip0_98_1338)">
            <path id="Vector"
                  d="M2.29292 6.26166C2.18247 5.92176 2.13281 5.70343 2.13281 5.70343L3.17479 5.4637C3.17735 5.47483 3.22016 5.65463 3.31006 5.93032L2.29292 6.26166ZM28.2943 11.3474C28.2168 11.0307 28.1193 10.7193 28.0024 10.415L29.0007 10.0323C29.1334 10.379 29.2455 10.7369 29.3329 11.0931L28.2943 11.3474ZM6.81699 10.3833L6.77675 9.31481C7.09914 9.29992 7.42012 9.26273 7.73738 9.20351L7.93002 10.2558C7.56234 10.3235 7.19046 10.3661 6.81699 10.3833ZM6.0156 10.3414C5.6079 10.2739 5.2161 10.132 4.85975 9.9227L5.39487 8.99717C5.64744 9.14357 5.91543 9.24032 6.19112 9.28656L6.0156 10.3414ZM8.64408 10.0991L8.38294 9.06138C8.69802 8.98261 9.02251 8.88757 9.353 8.78055L9.68092 9.7977C9.33878 9.90977 8.99298 10.0103 8.64408 10.0991ZM27.7438 9.81653C27.6028 9.52516 27.4417 9.24394 27.2618 8.97491L28.1496 8.38071C28.3543 8.68723 28.5409 9.01258 28.7044 9.35163L27.7438 9.81653ZM10.3556 9.56738L9.99685 8.56137C10.3119 8.44835 10.6347 8.32678 10.9609 8.20006L11.3488 9.1958C11.0131 9.3268 10.6818 9.45266 10.3556 9.56738ZM4.21077 9.45437C3.92167 9.19491 3.66211 8.90433 3.43678 8.58791L4.30323 7.9629C4.49673 8.23088 4.70393 8.46376 4.91883 8.65298L4.21077 9.45437ZM12.0029 8.93552L11.6022 7.94406C11.919 7.81563 12.2418 7.68378 12.5654 7.54936L12.9738 8.5374C12.6467 8.67353 12.3222 8.80709 12.0029 8.93552ZM26.8782 8.45863C26.6744 8.21113 26.452 7.97957 26.2129 7.76598L26.9253 6.96716C27.2018 7.21545 27.4647 7.48772 27.7036 7.77882L26.8782 8.45863ZM13.6176 8.27284L13.2101 7.2848C13.5312 7.15209 13.8557 7.01938 14.1819 6.88839L14.5817 7.8807C14.2581 8.00999 13.9361 8.14098 13.6176 8.27284ZM3.03095 7.95348C2.84786 7.63043 2.68598 7.29581 2.54635 6.95175L3.53524 6.54592C3.66367 6.85842 3.80665 7.15466 3.95991 7.42436L3.03095 7.95348ZM15.223 7.62727L14.836 6.63068C15.1656 6.50396 15.4952 6.37725 15.8266 6.25824L16.1888 7.26425C15.866 7.38155 15.5432 7.50227 15.223 7.62727ZM25.7138 7.36614C25.454 7.17852 25.1798 7.0119 24.8936 6.86784L25.3756 5.91405C25.7112 6.08272 26.0357 6.27964 26.3388 6.49797L25.7138 7.36614ZM16.8369 7.03822L16.4961 6.02365C16.8343 5.91063 17.1734 5.80189 17.5133 5.70343L17.8138 6.73C17.4867 6.82503 17.1614 6.92863 16.8369 7.03822ZM24.3054 6.60671C24.0031 6.49125 23.6937 6.39573 23.379 6.32074L23.623 5.28048C23.9865 5.36525 24.3436 5.47569 24.6915 5.61097L24.3054 6.60671ZM18.4671 6.55191L18.2034 5.51593C18.5544 5.42689 18.9054 5.34726 19.2556 5.28219L19.4534 6.33187C19.1246 6.39352 18.7967 6.468 18.4671 6.55191ZM20.1144 6.22314L19.9671 5.16489C20.3404 5.11267 20.706 5.07842 21.0553 5.06301L21.105 6.13067C20.777 6.14522 20.4457 6.1769 20.1144 6.22314ZM22.742 6.20173C22.4179 6.15577 22.0914 6.12918 21.7642 6.12211L21.789 5.05273C22.1649 5.06215 22.5356 5.09212 22.8927 5.14263L22.742 6.20173ZM29.5306 12.451H28.4613C28.4613 12.3508 28.4493 12.1942 28.4219 11.9981L29.4784 11.8448C29.5178 12.1077 29.5306 12.3166 29.5306 12.451Z"
            />
            <path id="Vector_2"
                  d="M24.3371 16.3578C23.9835 16.1369 23.5708 15.9023 23.1453 15.9023C22.9972 15.9023 22.8165 15.9297 22.6359 16.0291C22.641 16.0034 22.6478 15.9803 22.6538 15.9563L22.659 15.9349L22.6641 15.9135L22.7506 15.4272C22.8234 15.0196 22.8079 14.0453 21.129 13.7465C20.9584 13.7148 20.7854 13.6976 20.6118 13.6951C19.8944 13.6951 19.4277 14.0924 19.301 14.8124L19.2993 14.8201L19.2565 13.9006V13.892L19.2548 13.8818C19.218 13.4254 18.9705 12.6557 17.7393 12.6557C17.6409 12.6557 17.5381 12.6608 17.4251 12.6703C15.8917 12.7978 15.7247 13.6557 15.7641 14.1634L15.7693 14.2842C15.7128 14.4991 15.6554 14.5521 15.6066 14.5898L15.5801 8.27461V8.2592C15.5649 7.80539 15.3737 7.37532 15.0468 7.06013C14.72 6.74495 14.2832 6.56944 13.8292 6.57081L13.7675 6.57166C13.5374 6.57949 13.3112 6.63277 13.1018 6.72843C12.8924 6.82409 12.704 6.96024 12.5475 7.12904C12.2325 7.46586 12.0641 7.91389 12.0791 8.37478L12.1656 16.3758C12.1656 18.3407 12.0851 19.5317 11.9259 19.9187C11.9019 19.9016 11.8728 19.8819 11.8385 19.8553L9.45663 17.9829C9.19173 17.7607 8.86698 17.6219 8.52331 17.584C8.17965 17.5461 7.83246 17.6108 7.52552 17.77C7.21858 17.9291 6.96563 18.1756 6.79856 18.4783C6.63149 18.781 6.55777 19.1264 6.58671 19.4709C6.6261 19.9392 6.84357 20.3622 7.20145 20.6644L11.3103 24.1397C12.2778 25.852 13.3488 26.7262 13.8283 27.0593L13.8454 28.8898L13.8497 29.3795H22.1042L22.1119 28.8932L22.1418 26.9659L24.57 19.3074L24.5734 19.2937L24.5785 19.28L24.6402 19.0394C24.963 17.7954 25.195 16.8964 24.3371 16.3578Z"
            />
        </g>
    </g>
    <defs>
        <clipPath id="clip0_98_1338">
            <rect width="27.3913" height="27.3913" transform="translate(2.13281 3.52051)"/>
        </clipPath>
    </defs>
</svg>
src/assets/svg/turnBack.svg
New file
@@ -0,0 +1,11 @@
<svg width="31" height="31" viewBox="0 0 31 31" xmlns="http://www.w3.org/2000/svg">
    <g id="Group 1321315339">
        <g id="Group 1321315340">
            <g id="Vector">
                <path id="Vector_2"
                      d="M15.6081 17.7676C15.8605 18.02 16.2211 18.02 16.4735 17.7676L21.8467 12.3944C22.0992 12.1419 22.0992 11.7813 21.8467 11.5289H18.7094C18.6733 11.1322 18.3848 4.13624 26.6429 5.3984C23.9503 3.0664 21.1736 2.45335 18.3127 3.55925C16.762 4.38867 13.5525 5.3984 13.5165 11.5289H10.2709C10.0185 11.7813 10.0185 12.1419 10.2709 12.3944L15.6081 17.7676ZM20.5846 15.3514V16.0727C24.0104 16.9742 26.3544 18.8494 26.3544 20.9771C26.3544 24.0062 21.6664 26.4584 15.8965 26.4584C10.1267 26.4584 5.43865 24.0062 5.43865 20.9771C5.43865 18.8134 7.78266 16.9742 11.2085 16.0727V15.3514C6.15988 16.3612 2.55371 18.9215 2.55371 21.9147C2.55371 25.8093 8.53996 28.9467 15.8965 28.9467C23.2531 28.9467 29.2394 25.8093 29.2394 21.9147C29.2394 18.9215 25.6332 16.3612 20.5846 15.3514ZM11.1725 18.1282L10.4152 23.5374H12.8674L13.0477 21.0131H18.6372L18.6733 23.5374H21.1255L20.6206 18.1282H18.6012L18.6372 19.8231H13.1198L13.228 18.1282H11.1725Z"
                      />
            </g>
        </g>
    </g>
</svg>
src/assets/svg/user.svg
@@ -1 +1,5 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1681105122900" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2621" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M858.5 763.6c-18.9-44.8-46.1-85-80.6-119.5-34.5-34.5-74.7-61.6-119.5-80.6-0.4-0.2-0.8-0.3-1.2-0.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-0.4 0.2-0.8 0.3-1.2 0.5-44.8 18.9-85 46-119.5 80.6-34.5 34.5-61.6 74.7-80.6 119.5C146.9 807.5 137 854 136 901.8c-0.1 4.5 3.5 8.2 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c0.1 4.4 3.6 7.8 8 7.8h60c4.5 0 8.1-3.7 8-8.2-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z" p-id="2622"></path></svg>
<svg class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2621"
     xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
    <path d="M858.5 763.6c-18.9-44.8-46.1-85-80.6-119.5-34.5-34.5-74.7-61.6-119.5-80.6-0.4-0.2-0.8-0.3-1.2-0.5C719.5 518 760 444.7 760 362c0-137-111-248-248-248S264 225 264 362c0 82.7 40.5 156 102.8 201.1-0.4 0.2-0.8 0.3-1.2 0.5-44.8 18.9-85 46-119.5 80.6-34.5 34.5-61.6 74.7-80.6 119.5C146.9 807.5 137 854 136 901.8c-0.1 4.5 3.5 8.2 8 8.2h60c4.4 0 7.9-3.5 8-7.8 2-77.2 33-149.5 87.8-204.3 56.7-56.7 132-87.9 212.2-87.9s155.5 31.2 212.2 87.9C779 752.7 810 825 812 902.2c0.1 4.4 3.6 7.8 8 7.8h60c4.5 0 8.1-3.7 8-8.2-1-47.8-10.9-94.3-29.5-138.2zM512 534c-45.9 0-89.1-17.9-121.6-50.4S340 407.9 340 362c0-45.9 17.9-89.1 50.4-121.6S466.1 190 512 190s89.1 17.9 121.6 50.4S684 316.1 684 362c0 45.9-17.9 89.1-50.4 121.6S557.9 534 512 534z"
          p-id="2622"></path>
</svg>
src/axios.js
@@ -115,7 +115,7 @@
        //获取状态信息
        const status = res.data.error_code || res.data.code || res.status
        const statusWhiteList = website.statusWhiteList || []
        const message = res.data.msg || res.data.error_description || '系统错误'
        const message = res.data.msg || res.data.error_description || res.data.message || '系统错误'
        //如果在白名单里则自行catch逻辑处理
        if (statusWhiteList.includes(status)) return Promise.reject(res)
@@ -206,7 +206,6 @@
            }
            return Promise.reject(new Error(message))
        }
        // 如果请求为非200则默认统一处理
        if (status !== 200) {
            ElMessage({
src/components/LiveVideo.vue
@@ -1,7 +1,7 @@
<!--视频直播-->
<template>
  <div class="live-video" ref="JessibucaContainer">
    <video ref="liveVideoRef" class="video-player" controls autoplay muted playsinline>
    <video ref="liveVideoRef" class="video-player" :controls="props.controls" autoplay muted playsinline>
      Your browser is too old which doesn't support HTML5 video.
    </video>
  </div>
@@ -15,6 +15,10 @@
    type: String,
    default: '',
  },
    controls:{
        type:Boolean,
        default: true
    }
});
let liveVideoRef = ref(null);
src/components/SvgIcon.vue
@@ -1,28 +1,22 @@
<template>
  <svg
    :class="['svg-icon',props.className]"
    :style="{ width: size + 'px', height: size + 'px', color: color }"
    aria-hidden="true"
  >
    <use :xlink:href="`#icon-${name}`" />
  </svg>
    <svg :class="['svg-icon']" aria-hidden="true">
        <use :xlink:href="`#icon-${name}`" />
    </svg>
</template>
<script setup>
const props = defineProps({
  name: { type: String, required: true }, // svg 图标名称
  className: { type: String, default: '' }, // 指定的类样式
  size: { type: Number, default: 32 }, // 图标尺寸
  color: { type: String, default: '#000' } // 图标颜色
    name: { type: String, required: true }, // svg 图标名称
})
</script>
<style scoped lang="scss">
.svg-icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
  fill: currentColor;
  overflow: hidden;
    width: 1em;
    height: 1em;
    color: #000;
    vertical-align: -0.15em;
    fill: currentColor;
    overflow: hidden;
}
</style>
src/hooks/controlDrone/useManualControl.js
@@ -1,19 +1,38 @@
import { DRC_METHOD } from '@/const/drc.js'
import { useMqtt } from '@/hooks/controlDrone/useMqtt'
import { ElMessage } from 'element-plus'
import { postPayloadCommands } from '@/api/payload'
let myInterval
export const KeyCode = {
    KEY_W: 'KeyW',
    KEY_A: 'KeyA',
    KEY_S: 'KeyS',
    KEY_D: 'KeyD',
    KEY_Q: 'KeyQ',
    KEY_E: 'KeyE',
    ARROW_UP: 'ArrowUp',
    ARROW_DOWN: 'ArrowDown',
}
export const KeyCode =  {
        KEY_W: 'KeyW',
        KEY_A: 'KeyA',
        KEY_S: 'KeyS',
        KEY_D: 'KeyD',
        KEY_Q: 'KeyQ',
        KEY_E: 'KeyE',
        KEY_J: 'KeyJ',
        KEY_K: 'KeyK',
        KEY_L: 'KeyL',
        KEY_X: 'KeyX',
        KEY_Z: 'KeyZ',
        KEY_C: 'KeyC',
        KEY_I: 'KeyI',
        KEY_O: 'KeyO',
        ARROW_UP: 'ArrowUp',
        ARROW_DOWN: 'ArrowDown',
        ARROW_LEFT: 'ArrowLeft',
        ARROW_RIGHT: 'ArrowRight',
        NUMPAD_SUBTRACT: 'NumpadSubtract',
        NUMPAD_ADD: 'NumpadAdd',
        // 提升速度
        KEY_EQUAL: 'Equal',
        KEY_MINUS: 'Minus',
        // 录音
        KEY_N: 'KeyN',
        KEY_M: 'KeyM',
    }
export function useManualControl(mqttState,deviceTopicInfo, isCurrentFlightController) {
    const activeCodeKey = ref(null)
@@ -35,34 +54,63 @@
        }, 50)
    }
    function handleKeyup(keyCode) {
    function handleKeyup(keyCode,params) {
        const {sn,speed} = params
        let genPortOne = false //是一代机场
        if (!deviceTopicInfo.pubTopic) {
            ElMessage.error('请确保已经建立DRC链路')
            return
        }
        const SPEED = 5 //  check
        const HEIGHT = 5 //  check
        const W_SPEED = 20 // 机头角速度
        if (sn === '4TADKCM0010016' || sn === 'BA0BA1C3D38157A49E1B16574FA474F7') {
            genPortOne = true
        }
        const SPEED = genPortOne ? (speed || 5) : 500 //  check
        const HEIGHT = genPortOne ? 5 : 500; //  check
        const W_SPEED = genPortOne ? 20 : 500 // 机头角速度
        seq = 0
        switch (keyCode) {
            case 'KeyA':
                if (activeCodeKey.value === keyCode) return
                handlePublish({ y: -SPEED })
            case 'KeyQ':
                if (activeCodeKey === keyCode) return
                let objQ = { yaw: 1024-W_SPEED }
                if (genPortOne) {objQ = {w: -W_SPEED}}
                handlePublish(objQ)
                activeCodeKey.value = keyCode
                break
            case 'KeyW':
                if (activeCodeKey.value === keyCode) return
                handlePublish({ x: SPEED })
                if (activeCodeKey === keyCode) return
                let objW = { pitch: 1024+SPEED }
                if (genPortOne) {objW = {x: SPEED}}
                handlePublish(objW)
                activeCodeKey.value = keyCode
                break
            case 'KeyE':
                if (activeCodeKey === keyCode) return
                let objE = { yaw: 1024+W_SPEED }
                if (genPortOne) {objE = {w: W_SPEED}}
                handlePublish(objE)
                activeCodeKey.value = keyCode
                break
            case 'KeyA':
                if (activeCodeKey === keyCode) return
                let objA = { roll: 1024-SPEED }
                if (genPortOne) {objA = {y: -SPEED}}
                handlePublish(objA)
                activeCodeKey.value = keyCode
                break
            case 'KeyS':
                if (activeCodeKey.value === keyCode) return
                handlePublish({ x: -SPEED })
                if (activeCodeKey === keyCode) return
                let objS = { pitch: 1024-SPEED }
                if (genPortOne) {objS = {x: -SPEED}}
                handlePublish(objS)
                activeCodeKey.value = keyCode
                break
            case 'KeyD':
                if (activeCodeKey.value === keyCode) return
                handlePublish({ y: SPEED })
                if (activeCodeKey === keyCode) return
                let objD = { roll: 1024+SPEED }
                if (genPortOne) {objD = {y: SPEED}}
                handlePublish(objD)
                activeCodeKey.value = keyCode
                break
            case 'ArrowUp':
@@ -75,21 +123,23 @@
                handlePublish({ h: -HEIGHT })
                activeCodeKey.value = keyCode
                break
            case 'KeyQ':
                if (activeCodeKey.value === keyCode) return
                handlePublish({ w: -W_SPEED })
                activeCodeKey.value = keyCode
                break
            case 'KeyE':
                if (activeCodeKey.value === keyCode) return
                handlePublish({ w: W_SPEED })
                activeCodeKey.value = keyCode
                break
            default:
                break
        }
    }
    const handlePayloadTurn = (params) => {
        handleClearInterval()
        if (!this.myInterval) {
            postPayloadCommands(this.sn, params)
        }
        this.myInterval = setInterval(() => {
            postPayloadCommands(this.sn, params)
        }, 500)
    }
    function handleClearInterval() {
        clearInterval(myInterval)
        myInterval = undefined
src/store/modules/common.js
@@ -36,8 +36,12 @@
      visual: '3D',
      isDark: false,
    },
    isHideBottomIcon: true,
  },
  mutations: {
    setHideBottomIcon (state, data) {
      state.isHideBottomIcon = data
    },
    setMapSetting: (state, data) => {
      state.mapSetting = data
      loadLAYER()
src/utils/websocket/connect-websocket.js
@@ -4,6 +4,7 @@
/**
 * 接收一个message函数
 * @param messageHandler
 * @param url
 */
export function useConnectWebSocket(messageHandler, url) {
    const webSocket = new ConnectWebSocket(url || getWebsocketUrl())
src/views/Home/AINowFly.vue
@@ -14,21 +14,46 @@
                <el-input disabled type="text" v-model="di" placeholder="地名&经纬度"/>
            </el-form-item>
            <!--            todo-->
            <el-form-item label="事件">
            <el-form-item label="飞行事件">
                <div class="event">
                    <img src="@/assets/images/aiNowFly/picture-recording.png" alt="">
                    <div class="img-close" v-show="isPhoto">
                        <img @click="isPhoto=false" class="close" src="@/assets/images/aiNowFly/close.png" alt="">
                        <img src="@/assets/images/aiNowFly/photo.png" alt="">
                    </div>
                    <div class="img-close" v-show="isStop">
                        <img @click="isStop=false" class="close" src="@/assets/images/aiNowFly/close.png" alt="">
                        <img src="@/assets/images/aiNowFly/stop.png" alt="">
                    </div>
                    <div class="add-event" @click="isShowEvent = !isShowEvent">+
                        <div class="event-select" v-show="isShowEvent">
                            <div class="photo" @click="isPhoto=true">拍照</div>
                            <div class="stop" @click="isStop=true">悬停</div>
                        </div>
                    </div>
                </div>
            </el-form-item>
        </el-form>
        <div class="title-list">可飞行机巢列表:</div>
        <el-table :data="list" height="120" @selection-change="handleSelectionChange">
            <el-table-column type="selection" />
            <!-- <el-table-column type="index" label="序号" /> -->
            <el-table-column prop="nickname" label="机巢名称" />
            <el-table-column prop="minute" label="可飞行时间" />
            <el-table-column prop="exe_distance" label="可飞行距离" />
        </el-table>
        <div class="tabledata">
            <div class="table-content">
                <div class="tabler-item">
                    <el-checkbox v-model="isCheckAll" @change="checkedAll"/>
                </div>
                <div class="table-item">机巢名称</div>
                <div class="table-item">可飞行时间</div>
                <div class="table-item">可飞行距离</div>
            </div>
            <div class="table-content" v-for="item in list">
                <div class="tabler-item">
                    <el-checkbox v-model="item.checked" @change="handleItemCheck"/>
                </div>
                <div class="table-item">{{ item.nickname }}</div>
                <div class="table-item">{{ item.minute }}</div>
                <div class="table-item">{{ item.exe_distance }}</div>
            </div>
        </div>
        <div class="btn-submit" @click="nowFly"><img src="@/assets/images/aiNowFly/fly.png" alt=""></div>
    </div>
</template>
@@ -61,9 +86,17 @@
    type: 1,
    begin_time: nowTime,
    end_time: nowTime,
    action_modes: [{action_actuator_func: 'startRecord'}],
}
const params = ref(_.cloneDeep(paramsInit))
const di = computed(() => params.value.longitude + ',' + params.value.latitude)
// 事件默认隐藏
const isShowEvent = ref(false);
// 添加事件
const isPhoto = ref(false)
const isStop = ref(false)
const rules = ref({
    name: [
@@ -89,6 +122,12 @@
    await ruleFormRef.value.validate()
    if (!params.value.dock_sns.length) return ElMessage.warning('请选择将要飞行的机巢')
    if (!params.value.longitude) return ElMessage.warning('请选择飞行的点')
    if (isPhoto.value) {
        params.value.action_modes.push({action_actuator_func:'takePhoto'})
    }
    if (isStop.value) {
        params.value.action_modes.push({action_actuator_func:'hover'})
    }
    createTask(params.value).then(res => {
        ElMessage.success('起飞成功')
        if (params.value.dock_sns.length === 1) {
@@ -205,6 +244,7 @@
  size: 10
});
const isCheckAll = ref(false);
// 获取飞机列表
const getFJList = async () => {
    let pageParams = ref({
@@ -215,10 +255,27 @@
    const res = await getFlyingNestBy(pageParams.value, pagingParams.value)
    list.value = (res.data?.data || []).map(item => ({
        ...item,
        checked: false,
        minute: _.round(item.estimated_arrival_time / 60, 0),
    }))
    if (!list.value.length) ElMessage.warning('附近暂无可用无人机')
    renderingLine()
}
// 单个选择框变化
const handleItemCheck = () => {
  const allChecked = list.value.every(item => item.checked)
  const someChecked = list.value.some(item => item.checked)
  isCheckAll.value = allChecked
  params.value.dock_sns = list.value.filter(item => item.checked).map(item => item.device_sn)
}
// 全选
const checkedAll = () => {
    list.value.forEach(item => {
        item.checked = isCheckAll.value
    })
    params.value.dock_sns = isCheckAll.value ? list.value.map(item => item.device_sn) : []
}
const initMap = () => {
@@ -240,6 +297,8 @@
    list.value = []
    eventPoint = {}
    store.commit('setFootActiveIndex', 0)
    // 显示相关的按钮
    store.commit('setHideBottomIcon', true)
}
onBeforeUnmount(() => {
@@ -304,79 +363,82 @@
      box-shadow: 0 0 0 1px #026AD6;
    }
        .event {
            display: flex;
            img {
                width: 40px;
                height: 34px;
                margin-right: 10px;
            }
            .img-close {
                position: relative;
                .close {
                    cursor: pointer;
                    width: 8px;
                    height: 8px;
                    position: absolute;
                    top: -8px;
                    right: 1px;
                }
            }
            .add-event {
                width: 38px;
                height: 38px;
                background: rgba(83,179,255,0.16);
                border-radius: 8px 8px 8px 8px;
                border: 1px dashed;
                text-align: center;
                line-height: 38px;
                color: #11C4FF;
                position: relative;
                cursor: pointer;
                // border-image: linear-gradient(180deg, rgba(17, 196, 255, 1), rgba(255, 255, 255, 1)) 1 1;
                .event-select {
                    z-index: 10;
                    position: absolute;
                    top: 40px;
                    width: 72px;
                    height: 70px;
                    background: #0F1929;
                    box-shadow: inset 0px -50px 50px 0px rgba(27,148,255,0.13);
                    border-radius: 8px 8px 8px 8px;
                    border: 1px solid #51A8FF;
                    text-align: center;
                    color: #D3E9FF;
                    .photo {
                        height: 36px;
                        border-bottom: 1px solid #51A8FF;
                    }
                }
            }
        }
    }
    .title-list {
        padding: 0 0 14px 11px;
    }
    .el-table {
        width: 300px;
    .tabledata {
        height: 120px;
        margin: 0 0 0 11px;
        overflow: hidden;
        :deep(.el-scrollbar__wrap) {
            overflow-x: hidden !important;
        border: 1px dashed #fff;
        margin: 0px 10px;
        .table-content {
            padding: 0 8px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            border-bottom: 1px dashed #fff;
        }
        :deep(.el-table__body-wrapper) {
            overflow-x: hidden !important;
        }
        :deep(.el-table__header-wrapper) {
            th {
                font-size: 12px;
                color: #D3E9FF;
                background-color: #122348;
                font-weight: normal;
                border-bottom: none;
            }
        }
        :deep(.el-table__empty-block) {
            background-color: #0A1734;
            height: auto !important;  // 移除固定高度
        min-height: 60px;        // 设置最小高度
            .el-table__empty-text {
                color: #FFFFFF;
            }
        }
        :deep(.el-table__body) {
            background-color: #0A1734;
            tr {
                background-color: #0A1734 !important;
                color: #FFFFFF;
                &:hover > td {
                    background-color: #0A1734 !important;
                }
            }
        }
        :deep(.el-scrollbar) {
            background-color: #0A1734;
        .table-item {
            width: 100px;
            text-align: center;
            height: 32px;
            line-height: 32px;
        }
    }
    .btn-submit {
        cursor: pointer;
        margin-left: 88px;
        margin-top: 12px;
        img { width: 138px; height: 38px; }
    }
    :deep(.el-table__inner-wrapper) {
    border: 1px dashed #2F497D;
    &::before {
      display: none;
    }
  }
  :deep(.el-table__cell) {
    border: none;
  }
  :deep(.el-table__border) {
    display: none;
  }
}
</style>
src/views/Home/EventOverviewDetail/EventOverviewDetailLeft/EventOverviewDetailLeft.vue
@@ -1,12 +1,12 @@
<template>
    <!--时间 天气-->
    <common-weather></common-weather>
    <div class="event-overview-detail-left">
        <!--返回-->
        <div class="do-return" @click="goBack">
            <img src="@/assets/images/return.png" alt="" />
        </div>
        <common-title title="事件数据分析" />
    <!--返回-->
    <div class="do-return" :style="{left:isHideBottomIcon?pxToRem(430):pxToRem(46)}" @click="goBack">
        <img src="@/assets/images/return.png" alt="" />
    </div>
    <common-title v-show="isHideBottomIcon" :style="{left:pxToRem(14)}" title="事件数据分析" />
    <div v-show="isHideBottomIcon" class="event-overview-detail-left">
        <CommonDateTime class="dateTime" v-model="timeArr" @change="timeChange" />
        <EventDataAnalysis />
        <EventTrendAnalysis />
@@ -22,6 +22,7 @@
import { useStore } from 'vuex'
import EventTrendAnalysis from '@/views/Home/EventOverviewDetail/EventOverviewDetailLeft/EventTrendAnalysis.vue'
import EventTop5 from '@/views/Home/EventOverviewDetail/EventOverviewDetailLeft/EventTop5.vue'
import { pxToRem } from '@/utils/rem'
// 选中机巢默认日
// const today = dayjs().format('YYYY-MM-DD')
@@ -30,6 +31,8 @@
const startOfWeek = dayjs().startOf('week').add(1, 'day').format('YYYY-MM-DD')
const endOfWeek = dayjs().endOf('week').add(1, 'day').format('YYYY-MM-DD')
const timeArr = ref([startOfWeek, endOfWeek])
const isHideBottomIcon = computed(() => store.state.common.isHideBottomIcon)
const store = useStore()
@@ -49,6 +52,7 @@
    store.commit('setEventTimeType', [type, date_enum]);
}
const goBack = () => {
    store.commit('setHideBottomIcon', true)
    store.commit('setIsEventOverviewDetail', false)
}
</script>
@@ -56,8 +60,8 @@
.event-overview-detail-left {
    position: absolute;
    width: 390px;
    height: 920px;
    top: 120px;
    height: 870px;
    top: 166px;
    margin-left: 29px;
    background: linear-gradient(
    270deg,
@@ -72,20 +76,19 @@
    align-items: center;
    .dateTime {
        width: 400px;
        width: 356px;
        margin: 0 0 8px 0;
    }
}
.do-return {
    position: absolute;
    top: 136px;
    left: 430px;
    cursor: pointer;
    .do-return {
        position: absolute;
        right: -50px;
        cursor: pointer;
        img {
            width: 60px;
            height: 33px;
        }
    img {
        width: 60px;
        height: 33px;
    }
}
</style>
src/views/Home/EventOverviewDetail/EventOverviewDetailRight.vue
@@ -1,9 +1,9 @@
<template>
    <div class="event-overviewdetail-right" :class="{ isMore }">
        <CommonTitle title="工单列表" />
        <CommonTitle title="事件概况" :style="{right: isMore?pxToRem(410):pxToRem(0)}" />
        <div class="content">
            <img class="leftArrow" :src="isMore ? rightArrowImg : leftArrowImg" @click="leftArrowFun" alt="" />
            <img class="leftArrow" :class="isMore?'rightArrow':''" :src="isMore ? rightArrowImg : leftArrowImg" @click="leftArrowFun" alt="" />
            <el-date-picker
                class="ztzf-date-picker"
@@ -50,12 +50,12 @@
                    <img class="statusItemImg" :src="item.img" alt="" />
                    <div class="statusItemInfo">
                        <div class="statusItemName">{{ item.name }}</div>
                        <div class="statusItemNum">{{ item.num || 0 }}</div>
                        <div class="statusItemNum" :style="{ color:item.color }">{{ item.num || 0 }}</div>
                    </div>
                </div>
            </div>
            <el-checkbox-group v-model="params.event_keys" @change="getList">
            <el-checkbox-group v-show="isMore" v-model="params.event_keys" @change="getList">
                <el-checkbox v-for="item in eventList" :value="item.status" :key="item.dictKey">
                    <template #default>
                        <span class="eventListItemCheckboxName">{{ item.name }}</span>
@@ -70,6 +70,7 @@
                        class="eventListItemImg"
                        :src="getSmallImg(item.photo_url)"
                        alt=""
                        @click="getFindImgHistory(item.id)"
                    />
                    <div class="eventListItemPosition" :title="item.address" @click="positioning(item)">
                        <img :src="positioningImg" alt="" title="点击定位" />
@@ -95,6 +96,25 @@
            </div>
        </div>
    </div>
    <div class="image-list" v-if="isShowBigImg">
        <div class="title">
            <img @click="isShowBigImg=false" src="@/assets/images/home/useEventOperate/close.png" alt="">
        </div>
        <div class="content">
            <img :src="clickImgSrc" alt="">
        </div>
        <div class="card">
            <div v-for="(item,index) in imageList">
                <div class="time-top" :class="index===selectedImgIndex?'active':''" @click="clickWeekTime(item,index)">{{ item.create_time_str }}</div>
                <div class="time-point">
                    <img v-if="index===selectedImgIndex" src="@/assets/images/home/useEventOperate/point-active.png" alt="">
                    <img v-else src="@/assets/images/home/useEventOperate/point.png" alt="">
                </div>
                <div class="time-bottom">{{ item.create_time }}</div>
            </div>
            <div class="time-line"></div>
        </div>
    </div>
</template>
<script setup>
import status0Img from '@/assets/images/home/eventOverviewDetail/status0.png'
@@ -110,7 +130,7 @@
import dayjs from 'dayjs'
import { selectDevicePage } from '@/api/home/machineNest'
import { getMultipleDictionary } from '@/api/system/dictbiz'
import { getEvenNum, getEventPage, getEventStatusNum } from '@/api/home/event'
import { getEvenNum, getEventPage, getEventStatusNum, findImgHistory } from '@/api/home/event'
import cesiumOperation from '@/utils/cesium-tsa'
import CommonTitle from '@/components/CommonTitle.vue'
import { pxToRem } from '@/utils/rem'
@@ -148,6 +168,8 @@
    isMore.value = !isMore.value
    params.value.size = isMore.value ? 16 : 8
    params.value.current = 1
    // 下半部分隐藏 右侧隐藏
    store.commit('setHideBottomIcon', !isMore.value)
    getList()
}
@@ -181,12 +203,12 @@
// 事件状态+数量
const statusList = ref([
    { name: '全部状态', img: status0Img, id: undefined,few: true },
    { name: '待审核', img: status1Img, id: 2 },
    { name: '待处理', img: status2Img, id: 0, few: true },
    { name: '处理中', img: status3Img, id: 3, few: true },
    { name: '已完成', img: status4Img, id: 4, few: true },
    { name: '待分拨', img: status5Img, id: 1 },
    { name: '全部状态', img: status0Img, id: undefined,few: true, color: '#FFD509' },
    { name: '待审核', img: status1Img, id: 2, color: '#8CFEA7' },
    { name: '待处理', img: status2Img, id: 0, few: true, color: '#FF7411' },
    { name: '处理中', img: status3Img, id: 3, few: true, color: '#FFC398' },
    { name: '已完成', img: status4Img, id: 4, few: true, color: '#AFD9FB' },
    { name: '待分拨', img: status5Img, id: 1, color: '#11C4FF' },
])
const getEventStatusNumFun = () => {
@@ -244,6 +266,24 @@
        total.value = resData.total
    })
}
const isShowBigImg = ref(false)
const imageList = ref([]);
const clickImgSrc = ref('');
const selectedImgIndex = ref(0);
// 点击图片放大 显示详细信息
const getFindImgHistory = (id) => {
    findImgHistory(id).then((res) => {
        if (res.data.code !== 0) return
        imageList.value = res.data.data
        clickImgSrc.value = res.data.data[0]?.url;
        isShowBigImg.value = true
    })
}
// 点击周期
const clickWeekTime = (item,index) => {
    clickImgSrc.value = item.url
    selectedImgIndex.value = index
}
const getDateRange = unit => {
  if (unit === 'today') {
@@ -282,25 +322,31 @@
    height: 922px;
    width: 405px;
    font-size: 18px;
    z-index: 10;
    &.isMore {
        width: 807px;
        width: 808px;
        .content {
            width: 792px;
            padding-left: 44px;
        }
    }
    .content {
        width: 390px;
        height: 877px;
        background: linear-gradient(270deg, #1f3e7a 0%, rgba(31, 62, 122, 0.35) 79%, rgba(31, 62, 122, 0) 100%);
        background: linear-gradient(
    270deg,
    rgba(31, 62, 122, 0) 0%,
    rgba(31, 62, 122, 0.35) 21%,
    #1f3e7a 100%);
        display: flex;
        justify-content: space-between;
        align-content: start;
        flex-wrap: wrap;
        padding: 7px 15px 0 11px;
        gap: 10px;
        padding: 7px 13px 0 11px;
        gap: 16px;
        .leftArrow {
            position: absolute;
@@ -310,38 +356,42 @@
            cursor: pointer;
        }
        .rightArrow {
            position: absolute;
            top: 50%;
            left: 30px;
            transform: translateY(-50%);
            cursor: pointer;
        }
        .statusList {
            width: 100%;
            display: flex;
            justify-content: space-between;
            .statusItem {
                width: 95px;
                // width: 95px;
                height: 44px;
                display: flex;
                flex: 1;
                justify-content: center;
                position: relative;
                cursor: pointer;
                // margin-right: 4px;
                &.active {
                    background: linear-gradient(
                        180deg,
                        rgba(19, 80, 141, 0) 0%,
                        rgba(22, 56, 91, 0.48) 48%,
                        #053462 91%,
                        #259dff 91%,
                        #259dff 98%
                    );
                    background: linear-gradient( 180deg, rgba(19,80,141,0) 0%, rgba(22,56,91,0.48) 48%, #053462 98%, #259DFF 98%, #259DFF 98%);
                }
                .statusItemImg {
                    width: 35px;
                    height: 35px;
                    width: 34px;
                    height: 34px;
                    margin-right: 1px;
                }
                .statusItemInfo {
                    .statusItemName {
                        width: 59px;
                        // width: 59px;
                        height: 17px;
                        font-family: Segoe UI, Segoe UI;
                        font-weight: 400;
@@ -351,6 +401,7 @@
                        text-align: left;
                        font-style: normal;
                        text-transform: none;
                        margin-bottom: 4px;
                    }
                    .statusItemNum {
@@ -401,13 +452,14 @@
            width: 100%;
            display: flex;
            flex-wrap: wrap;
            justify-content: space-between;
            gap: 20px 0;
            // justify-content: space-between;
            gap: 20px 10px;
            .eventListItem {
                width: 175px;
                height: 140px;
                position: relative;
                cursor: pointer;
                .eventListItemImg {
                    width: 174px;
@@ -475,4 +527,76 @@
    display: flex;
    justify-content: center;
}
.image-list {
    position: absolute;
    left: 40%;
    transform: translate(-40%);
    bottom: 218px;
    width: 524px;
    height: 382px;
    background: #0F1929;
    box-shadow: inset 0px -50px 50px 0px rgba(27,148,255,0.13);
    border-radius: 0px 0px 0px 0px;
    border: 2px solid;
    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;
    .title {
        position: relative;
        text-align: right;
        right: 12px;
        top: 12px;
        img {
            width: 10px;
            height: 10px;
            cursor: pointer;
        }
    }
    .content {
        width: 475px;
        height: 231px;
        box-shadow: 1px 3px 6px 0px rgba(81,168,255,0.58);
        border-radius: 20px 20px 20px 20px;
        border-image: linear-gradient(180deg, rgba(81, 168, 255, 1), rgba(189, 228, 255, 1)) 2 2;
        margin: 28px 28px;
        img {
            width: 100%;
            height: 230px;
        }
    }
    .card {
        margin: 26px 26px;
        color: #BECBEA;
        display: flex;
        justify-content: space-between;
        position: relative;
        .time-top {
            width: 50px;
            height: 30px;
            line-height: 30px;
            text-align: center;
            color: #ffffff;
            background: linear-gradient( 180deg, rgba(13,30,70,0.72) 0%, #142E6B 100%);
            border: 1px solid #0054D3;
            cursor: pointer;
        }
        .active {
            background: linear-gradient( 180deg, rgba(13,48,131,0.72) 0%, #023DC8 100%);
            border: 1px solid;
            border-image: linear-gradient(180deg, rgba(0, 84, 211, 1), rgba(146, 186, 245, 1)) 1 1;
        }
        .time-point {
            text-align: center;
            img {
                width: 8px;
                height: 12px;
            }
        }
        .time-line {
            position: absolute;
            background: linear-gradient( 82deg, rgba(23,40,79,0) 0%, #576D9F 17%, #576D9F 86%, rgba(23,40,79,0) 100%);
            height: 2px;
            top: 38px;
            width: 100%;
        }
    }
}
</style>
src/views/Home/Footer.vue
@@ -1,5 +1,5 @@
<template>
    <div class="footer">
    <div class="footer" v-show="isHideBottomIcon">
        <img
            v-for="(item,index) in list.filter(i => !i.show)"
            :class="item.className"
@@ -37,6 +37,7 @@
const store = useStore()
const footActiveIndex = computed(() => store.state.home.footActiveIndex)
const isHideBottomIcon = computed(() => store.state.common.isHideBottomIcon)
// 机巢聚合
const { init, removeAll } = useMapAggregation('device')
@@ -76,6 +77,10 @@
    fromItem?.removeAll?.()
    nextTick(() => toItem?.init?.())
    list.value = list.value.map(item => ({ ...item, active: item.name === toItem?.name }))
    if (index === 5) {
        // 相关图标不显示
        store.commit('setHideBottomIcon', false)
    }
}
watch(
src/views/Home/Home.vue
@@ -5,6 +5,7 @@
        <!--        事件概述详细信息页面-->
        <EventOverviewDetail v-else-if="isEventOverviewDetail" />
        <!--    智引即飞    -->
        <AINowFly v-else-if="isAINowFly" />
        <!--            首页默认的页面-->
        <template v-else>
src/views/Home/RSide.vue
@@ -1,5 +1,5 @@
<template>
    <div class="ai-chat">
    <div class="ai-chat" v-show="isHideBottomIcon">
        <el-popover placement="bottom" :visible="visible" :width="200" trigger="click">
            <template #reference>
                <div class="chat" id="chatID" v-drag:chatID @mousedown="handleMouseDown" @mouseup="handleMouseUp" />
@@ -11,7 +11,7 @@
        </el-popover>
        <img class="chat-bottom" src="../../assets/images/chat-bottom.png" alt="" />
    </div>
    <div class="r-side">
    <div class="r-side" v-show="isHideBottomIcon">
        <img
            v-for="(item, index) in images"
            :key="index"
@@ -49,6 +49,8 @@
const store = useStore()
const currentAreaPosition = computed(() => store.state.home.currentAreaPosition)
// 事件概况 展开隐藏
const isHideBottomIcon = computed(() => store.state.common.isHideBottomIcon)
let activeIndex = ref(null)
const activeChange = value => {
src/views/Home/SearchBox.vue
@@ -93,9 +93,7 @@
// 获取机巢搜索结果
const getDeviceList = async () => {
  const res = await selectDeviceList({
    keyword: searchKey.value,
  });
  const res = await selectDeviceList({nickname: searchKey.value});
  if (res.data.code !== 0) return;
  machineNestList.value = res?.data?.data || [];
  downList.value = res?.data?.data || [];
@@ -144,11 +142,12 @@
watch(searchKey, async (newVal) => {
  if (optionsValue.value === '2') {
    await getAddressList();
  } else {
    await getDeviceList();
  }
}, { immediate: false });
onMounted(() => {
  getDeviceList();
  document.addEventListener('click', handleClickOutside);
});
@@ -166,8 +165,8 @@
.select-down-list {
  position: absolute;
  top: 188px;
  left: 45%;
  transform: translateX(-45%);
  left: 44%;
  transform: translateX(-40%);
  width: 220px;
  height: 256px;
  overflow-y: auto;
@@ -175,6 +174,7 @@
  border-radius: 0px 0px 8px 8px;
  border: 1px solid;
  border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1;
  opacity: 0.8;
  &::-webkit-scrollbar {
    width: 0;
    display: none;
@@ -187,6 +187,11 @@
    line-height: 32px;
    text-align: center;
    cursor: pointer;
    &:hover {
      background: linear-gradient( 90deg, rgba(0,122,255,0) 0%, rgba(0,98,204,0.6) 50%, rgba(0,73,153,0) 100%);
      border: 1px solid;
      border-image: linear-gradient(90deg, rgba(0, 199, 190, 0), rgba(48, 176, 199, 1), rgba(0, 199, 190, 0)) 1 1;
    }
  }
}
.searchBox {
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue
File was deleted
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/BaseControl.vue
New file
@@ -0,0 +1,156 @@
<template>
    <div class="manualControl">
        <div class="manualControlBg">
            <div class="manualControlBorder">
                <div class="manualControlCenter">
                    <img :src="controlCenterImg" alt="" />
                </div>
            </div>
            <div
                v-for="item in list3"
                :style="item.style"
                class="operateBtn"
                :title="item.name"
                :class="item.active ? 'active' : ''"
                @click="item.handle"
            ></div>
            <SvgIcon
                v-for="(item, index) in list3"
                :name="item.svg"
                :class="`${'btnIcon-' + index} btnIcon ${item.active ? 'active' : ''}`"
                :style="item.imgStyle"
            />
        </div>
    </div>
</template>
<script setup>
import controlCenterImg from '@/assets/images/taskManagement/taskIntermediateContent/controlCenter.png'
import SvgIcon from '@/components/SvgIcon.vue'
import EventBus from '@/event-bus'
const isAutoControl = inject('isAutoControl')
const list3 = computed(() => [
    { name: '自动控制', svg: 'autoControl', style: { top: '-70%' }, active: isAutoControl.value, handle: autoControl },
    { name: '继续任务', svg: 'continueTask', style: { left: '70%' }, active: false, handle: autoControl },
    {
        name: '手动控制',
        svg: 'manualControl',
        style: { top: '70%' },
        active: !isAutoControl.value,
        handle: manualControl,
    },
    { name: '返航/取消返航', svg: 'turnBack', style: { left: '-70%' }, active: false, handle: turnBack },
])
function autoControl() {
    isAutoControl.value = true
    EventBus.emit('controlPanel-cancelControl')
}
function manualControl() {
    EventBus.emit('controlPanel-control')
}
function turnBack() {
    EventBus.emit('controlPanel-returnOrCancelReturn')
}
</script>
<style scoped lang="scss">
.manualControl {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 188px;
    height: 188px;
    background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
    border-radius: 40px 40px 40px 40px;
    .manualControlBg {
        display: flex;
        align-items: center;
        justify-content: center;
        overflow: hidden;
        width: 165px;
        height: 165px;
        border-radius: 50%;
        position: relative;
        background: radial-gradient(circle, #5d5c5e 0%, #514f52 50%, #3d3b3d 100%);
        .operateBtn {
            position: absolute;
            width: 100%;
            height: 100%;
            transform: rotate(45deg);
            &:hover {
                cursor: pointer;
                box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.3);
            }
        }
        .btnIcon {
            position: absolute;
            font-size: 24px;
            pointer-events: none;
            color: #8e8e8f;
            &.active {
                color: #fff;
            }
            &-0 {
                left: 50%;
                top: 20px;
                transform: translateX(-50%);
            }
            &-1 {
                top: 50%;
                right: 20px;
                transform: translateY(-50%);
            }
            &-2 {
                bottom: 20px;
                left: 50%;
                transform: translateX(-50%);
            }
            &-3 {
                top: 50%;
                left: 20px;
                transform: translateY(-50%);
            }
        }
        .manualControlBorder {
            display: flex;
            align-items: center;
            justify-content: center;
            width: 135px;
            height: 135px;
            border-radius: 50%;
            border: 2px solid #ffffff;
            .manualControlCenter {
                z-index: 5;
                width: 50px;
                height: 50px;
                background: #404040;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                img {
                    width: 18px;
                    height: 18px;
                }
            }
        }
    }
}
</style>
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue
New file
@@ -0,0 +1,604 @@
<template>
    <div class="pointControl">
        <BaseControl />
        <div class="direction">
            <div class="boxTitle">
                飞
                <br />
                行
                <br />
                控
                <br />
                制
                <br />
                器
            </div>
            <div class="btnGroup">
                <div class="btnGroupT">
                    <div class="btnItem" v-for="item in list1">
                        <el-icon class="btnIcon">
                            <component :is="item.icon" />
                        </el-icon>
                        <div class="btn" @mousedown="onMouseDown(item.key)" @mouseup="onMouseUp">{{ item.text }}</div>
                    </div>
                </div>
                <div class="btnGroupB">
                    <div class="btnItem" v-for="item in list2">
                        <div class="btn" @mousedown="onMouseDown(item.key)" @mouseup="onMouseUp">{{ item.text }}</div>
                        <el-icon class="btnIcon">
                            <component :is="item.icon" />
                        </el-icon>
                    </div>
                </div>
            </div>
            <div class="speed">
                <el-icon class="btnIcon" @click="speed = speed + 1">
                    <Plus />
                </el-icon>
                <div>
                    {{ speed }}
                    <br />
                    m/s
                </div>
                <el-icon class="btnIcon" @click="speed = speed - 1">
                    <Minus />
                </el-icon>
            </div>
            <div class="upAndDown">
                <div class="btnGroupT">
                    <div class="btnItem">
                        <el-icon class="btnIcon">
                            <Top />
                        </el-icon>
                        <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_UP)" @mouseup="onMouseUp">C</div>
                    </div>
                </div>
                <div class="btnGroupT">
                    <div class="btnItem">
                        <div class="btn" @mousedown="onMouseDown(KeyCode.ARROW_DOWN)" @mouseup="onMouseUp">Z</div>
                        <el-icon class="btnIcon">
                            <Bottom />
                        </el-icon>
                    </div>
                </div>
            </div>
        </div>
        <div class="compass">
            <ControlComPass />
        </div>
        <div class="ptzControlBox">
            <div class="boxTitle">
                云
                <br />
                台
                <br />
                控
                <br />
                制
            </div>
            <div class="ptzControlBtnBox">
                <div class="ptzControlBtn b-r">
                    <div v-for="(item, index) in list5" :style="item.style" class="ptzControlItem"></div>
                    <div
                        class="ptzControlItemIcon"
                        v-for="(item, index) in list5"
                        :style="{ transform: `rotate(${index * 90}deg)` }"
                    >
                        <el-icon>
                            <CaretRight />
                        </el-icon>
                    </div>
                    <div class="circles1 b-r">
                        <div class="circles2 b-r">
                            <div class="circles3 b-r">
                                <div class="circles4 b-r"></div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="videoBox" v-if="valueTime !== '00:00:00'">
                    <div class="videoName">录像</div>
                    <div class="videoPoint"></div>
                    <div class="videoTime">{{ valueTime }}</div>
                </div>
            </div>
            <div class="divider"></div>
            <div v-for="arr in list4" class="info">
                <div v-for="item in arr" class="infoItem">
                    <div class="infoName">{{ item.name }}</div>
                    <div class="infoValue">{{ item.value }}</div>
                </div>
            </div>
        </div>
    </div>
</template>
<script setup>
import ControlComPass from '../ControlComPass/ControlComPass.vue'
import { KeyCode, useManualControl } from '@/hooks/controlDrone/useManualControl'
import { droneController, exitController, postDrc, postDrcExit, returnHome, returnHomeCancel } from '@/api/drc'
import { ElMessage } from 'element-plus'
import { useStore } from 'vuex'
import { UranusMqtt } from '@/mqtt'
import {
    ArrowDown,
    ArrowLeft,
    ArrowRight,
    ArrowUp,
    Bottom,
    CaretRight,
    CaretTop,
    Minus,
    Plus,
    RefreshLeft,
    RefreshRight,
} from '@element-plus/icons-vue'
import controlCenterImg from '@/assets/images/taskManagement/taskIntermediateContent/controlCenter.png'
import BaseControl from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/BaseControl.vue'
import EventBus from '@/event-bus'
import dayjs from 'dayjs'
const deviceOsdInfo = inject('deviceOsdInfo')
const taskDetails = inject('taskDetails')
const store = useStore()
const workspace_id = computed(() => taskDetails?.value?.workspace_id)
const dock_sn = computed(() => taskDetails.value.device_sns[0])
const list1 = [
    { key: KeyCode.KEY_Q, text: 'Q', icon: RefreshLeft },
    { key: KeyCode.KEY_W, text: 'W', icon: ArrowUp },
    { key: KeyCode.KEY_E, text: 'E', icon: RefreshRight },
]
const list2 = [
    { key: KeyCode.KEY_A, text: 'A', icon: ArrowLeft },
    { key: KeyCode.KEY_S, text: 'S', icon: ArrowDown },
    { key: KeyCode.KEY_D, text: 'D', icon: ArrowRight },
]
const speed = ref(5)
const list5 = [
    { name: '上', style: { top: '-70%' }, imgStyle: { top: '20%', left: '50%' } },
    { name: '右', style: { left: '70%' }, imgStyle: { right: '0', top: '50%' } },
    { name: '下', style: { top: '70%' }, imgStyle: { bottom: '0', left: '50%' } },
    { name: '左', style: { left: '-70%' }, imgStyle: { left: '20%', top: '50%' } },
]
const list4 = [
    [
        { name: '焦距倍数', value: '0' },
        { name: '俯仰角度', value: '0.0°' },
        { name: '横向角度', value: '0.0°' },
    ],
    [
        { name: '储存', value: '64.5G' },
        { name: '方向', value: '正北' },
        { name: '方向', value: '正北' },
    ],
]
let mqttState = null
const client_id = ref('')
const valueTime = ref('00:00:00')
let timer = null
let totalSeconds = 0
const deviceTopicInfo = ref({
    sn: deviceOsdInfo.value?.data?.sn,
    pubTopic: '',
    subTopic: '',
})
const flightController = ref(false)
// 控制对象
let manualControl = {}
const sn = computed(() => deviceOsdInfo?.value?.data?.sn)
const isAutoControl = inject('isAutoControl')
const timeStart = () => {
    stop() // 避免重复启动
    timer = setInterval(() => {
        totalSeconds++
        const hours = String(Math.floor(totalSeconds / 3600)).padStart(2, '0')
        const minutes = String(Math.floor((totalSeconds % 3600) / 60)).padStart(2, '0')
        const secs = String(totalSeconds % 60).padStart(2, '0')
        valueTime.value = `${hours}:${minutes}:${secs}`
    }, 1000)
}
const timeStop = () => {
    if (timer) {
        clearInterval(timer)
        timer = null
        totalSeconds = 0
        valueTime.value = '00:00:00'
    }
}
// 按下操作
function onMouseDown(type) {
    manualControl?.handleKeyup(type, { sn: sn.value, speed: speed.value })
}
// 弹起操作
function onMouseUp() {
    manualControl?.resetControlState()
}
// 取消手动控制
function cancelControl() {
    exitController({ client_id: client_id.value, dock_sn: dock_sn.value })
        .then(res => {
            flightController.value = false
            deviceTopicInfo.value.subTopic = ''
            deviceTopicInfo.value.pubTopic = ''
            ElMessage.success('退出飞行控制成功')
        })
        .catch(e => {})
}
// 手动控制
function control() {
    if (!client_id.value) return ElMessage.error('无人机不在空中,不能进入指挥飞行模式。')
    if (!dock_sn.value) return ElMessage.error('系统错误,未获取到dock_sn')
    droneController({ client_id: client_id.value, dock_sn: dock_sn.value }).then(res => {
        flightController.value = true
        const { data } = res.data
        if (data.sub && data.sub?.length > 0) {
            deviceTopicInfo.value.subTopic = data.sub[0]
        }
        if (data.pub && data.pub?.length > 0) {
            deviceTopicInfo.value.pubTopic = data.pub[0]
        }
        ElMessage.success('控制成功')
        isAutoControl.value = false
    })
}
// 返航
function onBackDock() {
    returnHome(dock_sn.value).then(res => {
        ElMessage.success('返航操作成功')
    })
}
// 取消返航
function cancelBackDock() {
    returnHomeCancel({ client_id: this.clientId, dock_sn: this.sn }).then(res => {
        ElMessage.success('取消返航成功')
    })
}
// 创建mqtt连接
const createConnect = async () => {
    const result = await postDrc({}, workspace_id.value)
    if (result?.code === 0) {
        const { address, client_id: clientId, username, password, expire_time } = result.data
        mqttState = new UranusMqtt(address, { clientId, username, password })
        mqttState?.initMqtt()
        client_id.value = clientId
    }
}
// 销毁连接
const destroyConnect = () => {
    if (mqttState) {
        mqttState?.destroyed()
        mqttState = null
        client_id.value = ''
    }
}
// 返航或取消返航
const returnOrCancelReturn = () => {
    if (deviceOsdInfo.value?.data?.host?.mode_code === 9) {
        cancelBackDock()
    } else {
        onBackDock()
    }
}
watch(
    () => workspace_id.value,
    async () => {
        if (workspace_id.value) {
            await createConnect()
            // 使用控制
            manualControl = useManualControl(mqttState, deviceTopicInfo.value, flightController)
        }
    }
)
onMounted(async () => {
    EventBus.on('controlPanel-control', control)
    EventBus.on('controlPanel-cancelControl', cancelControl)
    EventBus.on('controlPanel-returnOrCancelReturn', returnOrCancelReturn)
    EventBus.on('controlPanel-onMouseDown', onMouseDown)
    EventBus.on('controlPanel-timeStart', timeStart)
    EventBus.on('controlPanel-timeStop', timeStop)
})
onBeforeUnmount(() => {
    EventBus.off('controlPanel-control', control)
    EventBus.off('controlPanel-cancelControl', cancelControl)
    EventBus.off('controlPanel-returnOrCancelReturn', returnOrCancelReturn)
    EventBus.off('controlPanel-onMouseDown', onMouseDown)
    EventBus.off('controlPanel-timeStart', timeStart)
    EventBus.off('controlPanel-timeStop', timeStop)
    destroyConnect()
})
</script>
<style scoped lang="scss">
.f-c {
    display: flex;
    align-items: center;
}
.boxTitle {
    font-family: Segoe UI, Segoe UI;
    font-weight: 400;
    font-size: 14px;
    color: #d2e8fa;
}
.b-r {
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
}
.pointControl {
    position: absolute;
    bottom: 0;
    right: 0;
    width: 1540px;
    height: 217px;
    background: linear-gradient(196deg, rgba(23, 23, 23, 0.11) 0%, rgba(6, 6, 6, 0.11) 100%);
    backdrop-filter: blur(5px);
    border-radius: 40px 0px 40px 40px;
    display: flex;
    justify-content: center;
    align-items: center;
    color: white;
    gap: 0 10px;
    pointer-events: all;
    .direction {
        width: 476px;
        height: 188px;
        background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
        border-radius: 40px 40px 40px 40px;
        display: flex;
        align-items: center;
        justify-content: space-evenly;
        .btnGroup {
            display: flex;
            flex-direction: column;
            gap: 10px 0;
            .btnGroupT,
            .btnGroupB {
                width: 238px;
                height: 73px;
            }
        }
        .upAndDown {
            display: flex;
            flex-direction: column;
            gap: 10px 0;
            .btnGroupT,
            .btnGroupB {
                width: 58px;
                height: 73px;
            }
        }
        .speed {
            display: flex;
            flex-direction: column;
            justify-content: space-between;
            align-items: center;
            width: 58px;
            height: 155px;
            background: #37393f;
            box-shadow: 2px 4px 6px 0px rgba(0, 13, 26, 0.42);
            border-radius: 8px 8px 8px 8px;
            text-align: center;
            padding: 5px 0;
            .btnIcon {
                font-size: 20px;
            }
        }
    }
    .ptzControlBox {
        width: 406px;
        height: 188px;
        background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
        border-radius: 40px 40px 40px 40px;
        display: flex;
        align-items: center;
        justify-content: space-evenly;
        .ptzControlBtnBox {
            display: flex;
            flex-direction: column;
            align-items: center;
            .ptzControlBtn {
                position: relative;
                overflow: hidden;
                width: 154px;
                height: 154px;
                background: linear-gradient(180deg, #818181 0%, #222222 100%);
                box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
                .ptzControlItemIcon {
                    pointer-events: none;
                    width: 75px;
                    display: flex;
                    justify-content: right;
                    font-size: 18px;
                    position: absolute;
                }
                .ptzControlItem {
                    position: absolute;
                    width: 100%;
                    height: 100%;
                    transform: rotate(45deg);
                    &:hover {
                        cursor: pointer;
                        box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.3);
                    }
                }
                .circles1 {
                    width: 130px;
                    height: 130px;
                    background: linear-gradient(360deg, #282828 23%, #3a3a3a 70%, #3c3a3a 95%);
                    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25);
                    .circles2 {
                        width: 79px;
                        height: 79px;
                        background: linear-gradient(180deg, #484848 0%, #3f3f3f 100%);
                        box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.25), inset -1px -2px 4px 0px rgba(255, 255, 255, 0.25);
                        border: 1px solid rgba(0, 0, 0, 0.2);
                        .circles3 {
                            width: 42px;
                            height: 42px;
                            background: rgba(0, 0, 0, 0.31);
                            z-index: 1;
                            .circles4 {
                                width: 23px;
                                height: 23px;
                                background: #ffffff;
                                box-shadow: 2px 4px 6px 0px rgba(35, 37, 39, 0.26);
                            }
                        }
                    }
                }
            }
            .videoBox {
                display: flex;
                align-items: center;
                font-family: Segoe UI, Segoe UI;
                margin-top: 5px;
                .videoName {
                    font-weight: 400;
                    font-size: 12px;
                    color: rgba(210, 232, 250, 0.57);
                }
                .videoPoint {
                    width: 3px;
                    height: 3px;
                    background: #12ff7f;
                    border-radius: 50%;
                    margin: 0 5px;
                }
                .videoTime {
                    font-weight: 400;
                    font-size: 16px;
                    color: #ffffff;
                    line-height: 15px;
                }
            }
        }
        .divider {
            position: absolute;
            transform: translateX(90px);
            width: 0;
            height: 137px;
            border: 1px solid rgba(255, 255, 255, 0.07);
        }
        .info {
            display: flex;
            flex-direction: column;
            gap: 7px 0;
            width: 70px;
            .infoName {
                height: 25px;
                font-family: Segoe UI, Segoe UI, serif;
                font-weight: 400;
                font-size: 11px;
                color: rgba(210, 232, 250, 0.57);
                line-height: 15px;
            }
            .infoValue {
                height: 25px;
                font-family: Segoe UI, Segoe UI, serif;
                font-weight: 400;
                font-size: 16px;
                color: #ffffff;
                line-height: 15px;
            }
        }
    }
    .compass {
        width: 356px;
        height: 188px;
        background: rgba(157, 173, 189, 0.11);
        background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
        border-radius: 40px 40px 40px 40px;
    }
    .btnGroupT,
    .btnGroupB {
        background: #37393f;
        box-shadow: 2px 4px 6px 0px rgba(0, 13, 26, 0.42);
        border-radius: 8px 8px 8px 8px;
        display: flex;
        align-items: center;
        text-align: center;
        justify-content: center;
        gap: 0 45px;
        .btnItem {
            .btnIcon {
                font-size: 20px;
                &:first-child {
                    margin-bottom: 5px;
                }
            }
            .btn {
                width: 35px;
                height: 35px;
                background: #222324;
                line-height: 35px;
                border-radius: 5px;
                cursor: pointer;
                &:first-child {
                    margin-bottom: 5px;
                }
            }
        }
    }
}
</style>
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -9,16 +9,21 @@
        :destroy-on-close="true"
    >
        <div class="content-container" v-if="isShow">
            <TaskDetailsHead/>
            <TaskDetailsLeft/>
            <!-- 视频直播 -->
            <div class="video-container">
                <LiveVideo :videoUrl="currentLiveUrl" />
                <LiveVideo :videoUrl="currentLiveUrl" :controls="false" />
            </div>
            <!-- 展示地图 -->
            <RealTimeMap class="realTimeMap" />
            <ControlPanel />
<!--            <ControlPanel v-if="deviceOsdInfo?.data?.sn" />-->
            <TaskDetailsRight v-if="isAutoControl" />
            <template v-else>
                <TaskDetailsHead />
            </template>
            <TaskDetailsLeft />
            <!--    控制面板,里面有方法需要立即执行,不可用v-if        -->
            <ControlPanel v-show="!isAutoControl" />
        </div>
    </el-dialog>
</template>
@@ -33,10 +38,17 @@
import { getWebsocketUrl } from '@/websocket/util/config'
import { useConnectWebSocket } from '@/utils/websocket/connect-websocket'
import { EBizCode } from '@/utils/staticData/enums'
import ControlPanel from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel.vue'
import { KeyCode } from '@/hooks/controlDrone/useManualControl'
import ControlPanel from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/ControlPanel.vue'
import TaskDetailsHead from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsHead.vue'
import TaskDetailsLeft from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue'
import TaskDetailsRight from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsRight.vue'
import { ElMessage } from 'element-plus'
import EventBus from '@/event-bus'
const isAutoControl = ref(true)
const lineQuality = ref(1) //1流畅,2标清
provide('isAutoControl', isAutoControl)
provide('lineQuality', lineQuality)
const isShow = defineModel('show')
const props = defineProps({
@@ -46,28 +58,36 @@
        default: () => ({}),
    },
})
let taskDetails = ref({})
const currentLiveUrl = ref('')
const machineNestUrl = ref('')
const droneLiveUrl = ref('')
provide('taskDetails', taskDetails)
let taskDetails = ref({})
const deviceOsdInfo = ref({})
provide('taskDetails', taskDetails)
provide('deviceOsdInfo', deviceOsdInfo)
provide('dockSn', taskDetails?.value?.device_sns?.[0])
provide('droneSn', deviceOsdInfo?.value?.data?.sn)
// 机巢直播
const getDeviceLiveUrl = async () => {
    if (machineNestUrl.value) return machineNestUrl.value
    const res = await liveStart(taskDetails.value.device_sns[0], '')
    const res = await liveStart(taskDetails.value.device_sns[0], 2)
    machineNestUrl.value = res.data.data.rtcs_url
    return machineNestUrl.value
}
// 无人机直播
const getDockLiveUrl = async dockSn => {
const getDroneLiveUrl = async droneSn => {
    if (droneLiveUrl.value) return droneLiveUrl.value
    const res = await liveStart(dockSn, '')
    const res = await liveStart(droneSn, lineQuality.value)
    droneLiveUrl.value = res.data.data.rtcs_url
    return droneLiveUrl.value
}
// 无人机直播画质切换
const changeLineQuality = async () => {
    const res = await liveStart(droneSn, lineQuality.value)
    droneLiveUrl.value = res.data.data.rtcs_url
    return droneLiveUrl.value
}
@@ -76,11 +96,11 @@
const setCurrentLiveUrl = async () => {
    const data = deviceOsdInfo.value?.data
    const deviceInfo = data?.host
    const dockSn = data?.sn
    const drone = data?.sn
    if ([14, 0].includes(deviceInfo.mode_code)) {
        currentLiveUrl.value = await getDeviceLiveUrl()
    } else {
        currentLiveUrl.value = await getDockLiveUrl(dockSn)
        currentLiveUrl.value = await getDroneLiveUrl(drone)
    }
}
@@ -89,10 +109,12 @@
    getJobDetails({ wayLineJobInfoId: props.rowData.id }).then(async res => {
        taskDetails.value = res.data.data
        currentLiveUrl.value = await getDeviceLiveUrl()
        createWsConnect(taskDetails.value.way_lines[0].workspace_id)
        taskDetails.value.workspace_id = taskDetails.value.way_lines[0]?.workspace_id
        createWsConnect()
    })
}
// websocket 的消息
const messageHandler = result => {
    let payload = JSON.parse(result) // 为了兼容聊天消息
    switch (payload.biz_code) {
@@ -106,28 +128,36 @@
            break
    }
}
// 创建ws连接
let connectWs
const createWsConnect = workspaceId => {
const createWsConnect = () => {
    const workspaceId = taskDetails.value.workspace_id
    if (!workspaceId) return
    let webSocketUrl = getWebsocketUrl() + '&workspace-id=' + workspaceId
    // 监听ws 消息
    connectWs = useConnectWebSocket(messageHandler, webSocketUrl)
}
watch(
    () => taskDetails.value.workspace_id,
    () => {
        createWsConnect()
    }
)
const removeEvent = () => {
    connectWs?.close()
    deviceOsdInfo.value = {}
}
// 监听 rowData 变化
watch(isShow, newVal => {
    if (newVal) {
        getTaskDetails()
    } else {
        removeEvent()
    }
onMounted(() => {
    getTaskDetails()
    EventBus.on('CurrentTaskDetails-timeStop', changeLineQuality)
})
onBeforeUnmount(() => {
    EventBus.off('CurrentTaskDetails-timeStop', changeLineQuality)
    removeEvent()
})
</script>
@@ -144,10 +174,12 @@
        width: 1782px;
        height: 1002px;
        padding: 0;
        .el-dialog__body {
            width: 100%;
            height: 100%;
        }
        .el-dialog__header {
            height: 0;
            overflow: hidden;
@@ -178,8 +210,6 @@
        width: 100%;
        height: 100%;
    }
    .realTimeMap {
        position: absolute;
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -1,17 +1,19 @@
<template>
    <div class="taskDetailsLeft">
        <div class="title">负载控制</div>
        <div class="singleCol">广角相机</div>
        <div class="singleCol">变焦相机</div>
        <div class="singleCol">红外相机</div>
        <div class="singleCol" @click="cameraType('wide', '广角')">广角相机</div>
        <div class="singleCol" @click="cameraType('zoom', '变焦')">变焦相机</div>
        <div class="singleCol" @click="cameraType('ir', '红外')">红外相机</div>
        <div class="multiCol">
            <div>云台回中</div>
            <div>云台朝下</div>
        </div>
        <div class="singleCol">画质</div>
        <el-select class="qualityChange" v-model="lineQuality" @change="qualityChange">
            <el-option v-for="item in qualityList" :key="item.id" :label="item.name" :value="item.id" />
        </el-select>
        <div class="multiCol">
            <div>拍照</div>
            <div>录像</div>
            <div @click="takePictures">拍照</div>
            <div @click="recordFun">{{ isRecording ? '录像中...' : '录像' }}</div>
        </div>
        <div class="multiCol">
            <div>喊话</div>
@@ -20,7 +22,58 @@
    </div>
</template>
<script setup></script>
<script setup>
import EventBus from '@/event-bus'
import { callPhotoAndVideoCmd, switchLivestream } from '@/api/payload'
import { ElMessage } from 'element-plus'
const isRecording = ref(false)
const droneSn = inject('droneSn')
const lineQuality = inject('lineQuality')
const qualityList = [
    { name: '自适应', id: 0 },
    { name: '流畅', id: 1 },
    { name: '标清', id: 2 },
    { name: '高清', id: 3 },
    { name: '超清', id: 4 },
]
// todo
function cameraType(video_type, name) {
    switchLivestream({
        // video_id: this.videoId,
        video_type,
    }).then(res => {
        ElMessage.success(`切换为${name}相机成功`)
    })
}
// 画质切换
function qualityChange() {
    EventBus.emit('CurrentTaskDetails-timeStop')
}
// 录像
function recordFun() {
    const type = isRecording.value ? 'video_stop' : 'video_start'
    const msg = isRecording.value ? '停止录像' : '开始录像'
    const emitType = isRecording.value ? 'controlPanel-timeStop' : 'controlPanel-timeStart'
    callPhotoAndVideoCmd(droneSn.value, type).then(res => {
        ElMessage.success(msg)
        EventBus.emit(emitType)
        isRecording.value = !isRecording.value
    })
}
// 拍照
function takePictures() {
    // photo拍照,video_start开始录像,video_stop结束录像
    callPhotoAndVideoCmd(droneSn.value, 'photo').then(res => {
        ElMessage.success('拍照成功')
    })
}
</script>
<style scoped lang="scss">
.taskDetailsLeft {
@@ -40,45 +93,73 @@
    font-family: Segoe UI, Segoe UI;
    font-weight: 400;
    font-size: 14px;
    color: #FFFFFF;
    color: #ffffff;
    line-height: 18px;
    text-align: center;
    align-items: center;
    gap: 12px;
    .singleCol{
    .qualityChange{
        width: 146px;
        height: 40px;
        box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
        background: rgba(74, 72, 72, 0.67);
        border:none!important;
        display:flex;
        justify-content:center;
        box-shadow: 0 0.4rem 7.2rem 0 rgba(0, 0, 0, 0.25);
        border-radius: 0.8rem 0.8rem 0.8rem 0.8rem;
        :deep(){
            .el-select__wrapper{
                width: 100px;
                height: 100%;
                background: transparent;
                color: #3d3b3b;
                border: none;
                box-shadow: none;
            }
            .el-select__selected-item{
                font-family: Segoe UI, Segoe UI;
                color: #ffffff;
                font-size: 14px;
            }
        }
    }
    .singleCol {
        width: 146px;
        height: 40px;
        box-shadow: 0px 4px 72px 0px rgba(0, 0, 0, 0.25);
        border-radius: 8px 8px 8px 8px;
        line-height: 40px;
        cursor: pointer;
        background: rgba(74,72,72,0.67);
        background: rgba(74, 72, 72, 0.67);
        &:hover{
            background: rgba(255,255,255,0.78);
            color: #3D3B3B;
            border: 1px solid rgba(255,255,255,0.99);
        &:hover {
            background: rgba(255, 255, 255, 0.78);
            color: #3d3b3b;
            border: 1px solid rgba(255, 255, 255, 0.99);
        }
    }
    .multiCol{
    .multiCol {
        display: flex;
        gap: 12px;
        >div{
        > div {
            cursor: pointer;
            width: 67px;
            height: 40px;
            background: rgba(74,72,72,0.67);
            box-shadow: 0px 4px 72px 0px rgba(0,0,0,0.25);
            background: rgba(74, 72, 72, 0.67);
            box-shadow: 0px 4px 72px 0px rgba(0, 0, 0, 0.25);
            border-radius: 8px 8px 8px 8px;
            line-height: 40px;
            &:hover{
                background: rgba(255,255,255,0.78);
                color: #3D3B3B;
                border: 1px solid rgba(255,255,255,0.99);
            &:hover {
                background: rgba(255, 255, 255, 0.78);
                color: #3d3b3b;
                border: 1px solid rgba(255, 255, 255, 0.99);
            }
        }
    }
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/TaskDetailsRight.vue
New file
@@ -0,0 +1,99 @@
<template>
    <div class="task-details-right-container">
        <div  class="titleImg">
            <img :src="droneImg" alt="" />
        </div>
        <div class="taskInfo">
            <div v-for="item in list" class="itemBox">
                <div class="itemName">{{ item.name }}:</div>
                <div class="itemValue">{{ item.value }}</div>
            </div>
        </div>
        <BaseControl v-if="taskDetails.workspace_id"/>
    </div>
</template>
<script setup>
import droneImg from '@/assets/images/taskManagement/taskIntermediateContent/droneImg.png'
import BaseControl from '@/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails/ControlPanel/BaseControl.vue'
const taskDetails = inject('taskDetails')
watch(taskDetails, () => {
    list.value.forEach(item => {
        item.value = taskDetails?.value?.[item.field] || ''
    })
})
const list = ref([
    { name: '任务编号', value: '', field: 'job_info_num' },
    { name: '任务名称', value: '', field: 'name' },
    { name: '任务类型', value: '', field: 'industry_type_str' },
    { name: '飞行事件', value: '', field: '' },
    { name: '所属单位', value: '', field: 'dept_name' },
    { name: '任务时间', value: '', field: 'create_time' },
    { name: '任务频次', value: '', field: 'rep_rule_type rep_rule_val' },
    { name: '关联算法', value: '', field: 'ai_type_str' },
    { name: '任务描述', value: '', field: 'remark' },
])
</script>
<style scoped lang="scss">
.task-details-right-container {
    padding-top: 35px;
    position: absolute;
    right: 0;
    top: 0;
    width: 297px;
    height: 1002px;
    background: rgba(31, 31, 31, 0.15);
    border-radius: 0px 40px 40px 0px;
    display: flex;
    flex-direction: column;
    gap: 25px 0;
    align-items: center;
    .titleImg {
        width: 224px;
        height: 131px;
        background: rgba(74, 72, 72, 0.54);
        box-shadow: 0px 4px 72px 0px rgba(0, 0, 0, 0.25);
        border-radius: 12px 12px 12px 12px;
        border: 1px solid rgba(255, 255, 255, 0.99);
        display: flex;
        align-items: center;
        justify-content: center;
        img{
            width: 100px;
            height: 68px;
        }
    }
    .taskInfo {
        width: 220px;
        height: 520px;
        display: flex;
        overflow: auto;
        flex-direction: column;
        gap: 25px 0;
        .itemBox {
            display: flex;
            font-family: Segoe UI, Segoe UI;
            font-size: 14px;
            .itemName {
                flex-shrink: 0;
                font-weight: 400;
                color: #D3D3D3;
            }
            .itemValue {
                font-weight: bold;
                color: #ffffff;
            }
        }
    }
}
</style>
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue
@@ -59,7 +59,7 @@
  <!-- 添加任务 -->
  <AddTask v-model:show="isShowAddTask" @refresh="searchClick"/>
  <!-- 当前任务详情 -->
  <CurrentTaskDetails v-model:show="isShowCurrentTaskDetails" :rowData="rowData"/>
  <CurrentTaskDetails v-if="isShowCurrentTaskDetails"  v-model:show="isShowCurrentTaskDetails" :rowData="rowData"/>
</template>
<script setup>
@@ -102,8 +102,12 @@
let rowData = ref({});
const handleDetail = (row) => {
    if (row.device_sns.length === 1){
        isShowCurrentTaskDetails.value = true;
        rowData.value = row? row : {};
        if (row.status === 2 || row.status === 1){
            rowData.value = row? row : {};
            isShowCurrentTaskDetails.value = true;
        }else{
            ElMessage.warning('todo 跳历史任务详情')
        }
    }else{
        ElMessage.warning('即将跳转到集群调度');
    }