无人机管理后台前端(已迁走)
shuishen
2025-12-08 02bccf3d373deba3988d7d7580bb937c6311f58f
Merge branch 'feature/v8.0/8.0.4' into feature/v9.0/9.0.1
1 files modified
270 ■■■■■ changed files
src/views/job/components/DeviceJobDetails.vue 270 ●●●●● patch | view | raw | blame | history
src/views/job/components/DeviceJobDetails.vue
@@ -1,7 +1,7 @@
<!-- 历史任务详情 -->
<template>
  <el-dialog class="ztzf-dialog" append-to-body modal-class="detailsOfHistoricalTasks" v-model="isShow" title="历史任务详情"
             :width="pxToRem(1800)" :close-on-click-modal="false" :destroy-on-close="true">
    :width="pxToRem(1800)" :close-on-click-modal="false" :destroy-on-close="true">
    <div class="content">
      <div class="contentLeft">
        <div class="machineTableDetailsTitle"><img src="/src/assets/images/task/sign.svg" alt=""><span>详情</span></div>
@@ -11,14 +11,14 @@
              <div class="itemBox">
                <div class="itemTitle">{{ item.name }}:</div>
                <div truncated class="itemValue" v-if="item.name !== '飞行事件' && item.name !== '任务执行次数'">{{
                    item.value ? item.value : '--' }}</div>
                  item.value ? item.value : '--' }}</div>
                <div class="itemValue" v-if="item.name === '任务执行次数'">{{ item.value ? item.value : '0'
                  }}次</div>
                }}次</div>
                <!-- 飞行事件 -->
                <div class="flightEvents" v-if="item.name === '飞行事件'">
                  <template v-if="flightEvents.length">
                    <img v-for="(item, index) in flightEvents" alt="" :src="item.img"
                         :title="item.name" :key="index"></img>
                    <img v-for="(item, index) in flightEvents" alt="" :src="item.img" :title="item.name"
                      :key="index"></img>
                  </template>
                  <div class="itemValue" v-else>--</div>
                </div>
@@ -39,11 +39,14 @@
          </div>
          <div class="rightBox" v-if="total">
            <div class="downloadBtn" @click="htlsrwxq === 100 && downloadFun()">
              <el-progress v-if="htlsrwxq !== 100" :percentage="htlsrwxq" :show-text="false" striped striped-flow :duration="1" />
              <el-progress v-if="htlsrwxq !== 100" :percentage="htlsrwxq" :show-text="false" striped striped-flow
                :duration="1" />
              <div class="downloadBtnText">
                <span v-if="htlsrwxq === 100">下载</span>
                <template v-else>
                  处理中<el-icon @click="cancelDownload"><CircleClose /></el-icon>
                  处理中<el-icon @click="cancelDownload">
                    <CircleClose />
                  </el-icon>
                </template>
              </div>
            </div>
@@ -59,114 +62,65 @@
              <el-checkbox v-model="item.checked" />
              <div class="itemName">{{ item.createTime }}</div>
              <!-- 正射 -->
              <el-image
                v-if="item.resultType === 4"
                :src="item.smallUrl"
                :preview-src-list="[item.showUrl]"
                fit="cover"
                preview-teleported
              />
              <el-image v-if="item.resultType === 4" :src="item.smallUrl" :preview-src-list="[item.showUrl]" fit="cover"
                preview-teleported />
              <!-- 全景 -->
              <img
                v-else-if="item.resultType === 5"
                class="quanjing"
                @click="clickpanorama(item)"
                :src="item?.smallUrl"
                alt=""
              />
              <img v-else-if="item.resultType === 5" class="quanjing" @click="clickpanorama(item)" :src="item?.smallUrl"
                alt="" />
              <!-- 视频 -->
              <div v-else-if="item.resultType === 1" class="videotime">
                <img
                  class="videoDisplay"
                  :src="convertVideoUrlToThumbnail(item.link)"
                  alt=""
                  @click="enterFullScreen(index)"
                />
                <img
                  @click="enterFullScreen(index)"
                  class="videobutton"
                  src="@/assets/images/task/videoshow.png"
                  alt=""
                />
                <img class="videoDisplay" :src="convertVideoUrlToThumbnail(item.link)" alt=""
                  @click="enterFullScreen(index)" />
                <img @click="enterFullScreen(index)" class="videobutton" src="@/assets/images/task/videoshow.png"
                  alt="" />
              </div>
              <!-- 图片 -->
              <el-image
                v-else
                :src="item.smallUrl"
                :preview-src-list="[item.showUrl]"
                preview-teleported
                :initial-index="index"
                fit="cover"
              />
              <el-image v-else :src="item.smallUrl" :preview-src-list="[item.showUrl]" preview-teleported
                :initial-index="index" fit="cover" />
            </div>
          </template>
        </div>
        <el-image-viewer
          v-if="showViewer"
          :url-list="previewUrls"
          :initial-index="activeIndex"
          @close="showViewer = false"
        />
        <el-image-viewer v-if="showViewer" :url-list="previewUrls" :initial-index="activeIndex"
          @close="showViewer = false" />
      </div>
      <div class="content-right" v-if="isShow">
        <DeviceJobDetailsMap
          :detailsData="detailsData"
          :yuanImages="yuanImages"
          @showImageeclick="showImageeclick"
          :jobId="props.jobId"
        />
        <DeviceJobDetailsMap :detailsData="detailsData" :yuanImages="yuanImages" @showImageeclick="showImageeclick"
          :jobId="props.jobId" />
        <div class="content-map-popups"></div>
      </div>
    </div>
  </el-dialog>
    <el-dialog
        class="ztzf-dialog"
        append-to-body
        modal-class="detailsOfHistoricalTasks"
        v-model="VideoShow"
        :width="pxToRem(1600)"
        :close-on-click-modal="false"
        :destroy-on-close="true"
      >
        <div class="fullscreen">
          <video
            ref="fullscreenVideo"
            class="fullscreen-video"
            :src="currentVideoUrl"
            :style="{ width: pxToRem(1567), height: '80vh' }"
            controls
            preload="auto"
            @play="handleVideoPlay"
            @ended="handleVideoEnded"
          ></video>
        </div>
      </el-dialog>
  <el-dialog class="ztzf-dialog" append-to-body modal-class="detailsOfHistoricalTasks" v-model="VideoShow"
    :width="pxToRem(1600)" :close-on-click-modal="false" :destroy-on-close="true">
    <div class="fullscreen">
      <video ref="fullscreenVideo" class="fullscreen-video" :src="currentVideoUrl"
        :style="{ width: pxToRem(1567), height: '80vh' }" controls preload="auto" @play="handleVideoPlay"
        @ended="handleVideoEnded"></video>
    </div>
  </el-dialog>
  <!-- 全景360 -->
  <PanoramaPopup
    v-if="'全景'"
    v-model:panoramaParamsShow="panoramaParamsShow"
    v-model:panoramaParamsUrl="panoramaParamsUrl"
  />
  <PanoramaPopup v-if="'全景'" v-model:panoramaParamsShow="panoramaParamsShow"
    v-model:panoramaParamsUrl="panoramaParamsUrl" />
</template>
<script setup>
import PanoramaPopup from '@/components/PanoramaPopup/PanoramaPopup.vue';
import { getShowImg, getSmallImg, getzsSmallImg, getzsShowImg, aLinkDownloadUtil } from '@/utils/util';
import { getaiImagesPageAPI,cancelDownloadApi,getDownloadStatusApi,attachDownload,aiImagesPage} from '@/api/dataCenter/dataCenter';
import PanoramaPopup from '@/components/PanoramaPopup/PanoramaPopup.vue'
import { getShowImg, getSmallImg, getzsSmallImg, getzsShowImg, aLinkDownloadUtil } from '@/utils/util'
import { getaiImagesPageAPI, cancelDownloadApi, getDownloadStatusApi, attachDownload, aiImagesPage } from '@/api/dataCenter/dataCenter'
import { pxToRem } from '@/utils/rem'
import JobRelatedEvents from './JobRelatedEvents.vue'
import { getEventMediaListApi, getJobDetails, getJobInfoFiles,getJobsAllFiles } from '@/api/job/task'
import { getEventMediaListApi, getJobDetails, getJobInfoFiles, getJobsAllFiles } from '@/api/job/task'
import DeviceJobDetailsMap from './DeviceJobDetailsMap.vue'
import { droneEventList } from '../const/drc'
import { ElMessage,ElLoading } from 'element-plus'
import { useStore } from 'vuex';
import EventBus from '@/utils/eventBus';
const store = useStore();
import { ElMessage, ElLoading } from 'element-plus'
import { useStore } from 'vuex'
import EventBus from '@/utils/eventBus'
const store = useStore()
const htlsrwxq = computed(() => store.state.common.downloadProgress?.htlsrwxq || 100)
const isShow = defineModel('show')
const VideoShow = ref(false)
@@ -192,7 +146,7 @@
}
// 原图
const yuanImages = ref([])
function convertVideoUrlToThumbnail(videoUrl) {
function convertVideoUrlToThumbnail (videoUrl) {
  // 检查是否是有效的视频URL
  if (!videoUrl || typeof videoUrl !== 'string') {
    return videoUrl
@@ -223,7 +177,7 @@
  industry_type_str: null,
  area_code: null,
  ai_type_str: null,
  enable_custom_area:null,
  enable_custom_area: null,
  begin_time: null,
  end_time: null,
  device_names: null,
@@ -236,38 +190,14 @@
// 任务成果
const total = ref(0)
const achievementList = ref([])
const props = defineProps(['wayLineJobInfoId', 'waylineJobId', 'batchNo','jobId'])
const props = defineProps(['wayLineJobInfoId', 'waylineJobId', 'batchNo', 'jobId'])
const wayLineJobInfoId = computed(() => props.wayLineJobInfoId)
provide('wayLineJobInfoId', wayLineJobInfoId)
// 可见数量
const visibleCount = computed(() =>
  showAll.value ? achievementList.value.length : Math.min(5, achievementList.value.length)
)
const getAchievement = () => {
  if (!props.jobId) return
  const attachmentsParams = {
    'wayLineJobId': props.jobId,
    'resultTypes': [0, 1, 2, 4, 5],
    'orderByCreateTime': true,
    current: 1,
    size: 2000,
  }
  const pageParams = {
    current: 1,
    size: 2000,
  }
  aiImagesPage(attachmentsParams ).then(res => {
    achievementList.value = res.data.data.records.map(i => ({
      ...i,
      checked: false,
      smallUrl: i.resultType === 4 ? getzsSmallImg(i.link) : getSmallImg(i.link),
      showUrl: i.resultType === 4 ? getzsShowImg(i.link) : getShowImg(i.link),
    }))
    total.value = res.data.data.total
    // console.log('成果数据', res.data.data)
  })
}
const jumpMore = () => {
  ElMessage.warning('正在加急开发中...')
}
@@ -285,7 +215,7 @@
    jobTimes.value = res.data.data.job_times
    infoList.value.forEach(item => {
      if (item.name === '任务频次') {
        const { rep_rule_type = '',final_cycle_frequency = '' } = detailsData?.value || {}
        const { rep_rule_type = '', final_cycle_frequency = '' } = detailsData?.value || {}
        item.value = rep_rule_type ? final_cycle_frequency : '1次'
      } else {
        item.value = detailsData.value?.[item.field] || '--'
@@ -297,23 +227,30 @@
        )
      }
      if (item.name === '自定义识别区') {
        item.value = res.data.data.enable_custom_area ? '是': '否'
        item.value = res.data.data.enable_custom_area ? '是' : '否'
      }
      getJobsAllFiles({
        wayLineJobId: detailsData.value.way_lines.map(item => item.job_id).join(','),
        resultTypes: [0, 2, 4, 5],
        resultTypes: [0, 1, 2, 4, 5],
        orderByCreateTime: true,
      }).then(result => {
        if (result.data.code !== 200) return
        yuanImages.value = result.data.data.records
        achievementList.value = res.data.data.records.map(i => ({
          ...i,
          checked: false,
          smallUrl: i.resultType === 4 ? getzsSmallImg(i.link) : getSmallImg(i.link),
          showUrl: i.resultType === 4 ? getzsShowImg(i.link) : getShowImg(i.link),
        }))
        total.value = res.data.data.total
        yuanImages.value = res.data.data.records.filter(item => item.resultType !== 1)
      })
    })
    // flystatus.value = res.data.data.ai_type_str
  })
}
// 播放
const videoRef = ref(null);
const videoRef = ref(null)
const currentVideoIndex = ref(-1)
const enterFullScreen = (index) => {
@@ -333,25 +270,25 @@
}
// 下载
const downloadFun = async () => {
    const list = achievementList.value.filter(i => i.checked)
    console.log('list',list);
    if (!list?.length) return ElMessage.warning('请选择文件')
    if (list.length === 1) {
        list.forEach((item, index) => {
            const suffix = item.link.split('.').pop()
            aLinkDownloadUtil(item.link, item.nickName + '.' + suffix)
        })
    } else {
        loadingData = ElLoading.service({ background: 'rgba(0, 0, 0, 0.5)', text: '打包中,请稍等...' })
        const fileIds = list.map(i => i.id)
        const res = await getDownloadStatusApi({type: 'htlsrwxq'})
        if (!['CANCELLED','COMPLETED'].includes(res.data.data?.status || 'COMPLETED')){
            return ElMessage.warning('还有正在处理的')
        }
        attachDownload({ attachIds: fileIds,type:'htlsrwxq' }).finally(()=>{
            loadingData.close()
        })
    }
  const list = achievementList.value.filter(i => i.checked)
  console.log('list', list)
  if (!list?.length) return ElMessage.warning('请选择文件')
  if (list.length === 1) {
    list.forEach((item, index) => {
      const suffix = item.link.split('.').pop()
      aLinkDownloadUtil(item.link, item.nickName + '.' + suffix)
    })
  } else {
    loadingData = ElLoading.service({ background: 'rgba(0, 0, 0, 0.5)', text: '打包中,请稍等...' })
    const fileIds = list.map(i => i.id)
    const res = await getDownloadStatusApi({ type: 'htlsrwxq' })
    if (!['CANCELLED', 'COMPLETED'].includes(res.data.data?.status || 'COMPLETED')) {
      return ElMessage.warning('还有正在处理的')
    }
    attachDownload({ attachIds: fileIds, type: 'htlsrwxq' }).finally(() => {
      loadingData.close()
    })
  }
}
// 全景预览
const panoramaParamsShow = ref(false)
@@ -375,16 +312,15 @@
  }
}
// 取消下载
function cancelDownload() {
  cancelDownloadApi({ type:'htlsrwxq' }).then(res =>{
function cancelDownload () {
  cancelDownloadApi({ type: 'htlsrwxq' }).then(res => {
    ElMessage.success('取消成功')
  }).catch(e =>{
    EventBus.emit('useGlobalWS-messageHandler', {biz_code: 'DOWNLOAD_PROGRESS',data:{status: 'CANCELLED',type:'htlsrwxq'}})
  }).catch(e => {
    EventBus.emit('useGlobalWS-messageHandler', { biz_code: 'DOWNLOAD_PROGRESS', data: { status: 'CANCELLED', type: 'htlsrwxq' } })
  })
}
onMounted(() => {
  getDetails()
  getAchievement()
})
</script>
@@ -409,6 +345,7 @@
.content {
  display: flex;
  height: 100%;
  .contentLeft {
    margin-left: 35px;
    margin-right: 24px;
@@ -420,9 +357,12 @@
      // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
      border-bottom: 2px solid #e4e7ed;
      background-size: 100% 100%;
      img{
      img {
        width: 15px;
        height: 15px;}
        height: 15px;
      }
      span {
        display: inline-block;
        margin-left: 10px;
@@ -443,13 +383,14 @@
      display: flex;
      justify-content: space-between;
      align-content: center;
      .rightBox{
      .rightBox {
        display: flex;
        align-items: center;
        padding-right: 10px;
        gap: 0 10px;
        .downloadBtn{
        .downloadBtn {
          position: relative;
          display: flex;
          align-items: center;
@@ -462,25 +403,26 @@
          cursor: pointer;
          color: #1C5CFF;
          .downloadBtnText{
          .downloadBtnText {
            display: flex;
            align-items: center;
            gap: 5px;
            z-index: 5;
          }
          .el-progress{
          .el-progress {
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
            :deep(){
              .el-progress-bar__outer{
            :deep() {
              .el-progress-bar__outer {
                height: 28px !important;
                border-radius: 0 !important;
                background: transparent !important;
                .el-progress-bar__inner{
                .el-progress-bar__inner {
                  border-radius: 0 !important;
                }
              }
@@ -489,9 +431,12 @@
        }
      }
      img{
      img {
        width: 15px;
        height: 15px;}
        height: 15px;
      }
      p {
        display: inline-block;
        margin-left: 10px;
@@ -590,13 +535,14 @@
      gap: 10px;
      margin-bottom: 49px;
      > * {
      >* {
        width: 100%;
        height: 200px;
        position: relative;
        border-radius: 0px 0px 4px 4px;
        overflow: hidden;
      }
      :deep(.el-checkbox__inner) {
        width: 17px !important;
        height: 17px !important;
@@ -604,21 +550,25 @@
        border-radius: 4px 4px 4px 4px;
        border: 1px solid #ffffff !important;
      }
      .el-checkbox {
        position: absolute;
        top: 0;
        left: 6px;
      }
      :deep(.el-checkbox__inner:after) {
        position: absolute;
        left: 50% !important;
        top: 40% !important;
        transform: translate(-50%, -50%) rotate(45deg) !important;
      }
      :deep(.el-checkbox.is-checked .el-checkbox__inner) {
        background-color: #09297b !important;
        border-color: #1c5cff !important;
      }
      .el-image {
        width: 100%;
        height: 100%;
@@ -645,6 +595,7 @@
          object-fit: cover;
        }
      }
      .itemName {
        position: absolute;
        bottom: 0;
@@ -675,6 +626,7 @@
      background: #999;
      border-radius: 50%;
    }
    .videoDisplay {
      display: flex;
      flex-wrap: wrap;
@@ -700,7 +652,7 @@
    .content-map-popups {
      pointer-events: none;
      > * {
      >* {
        pointer-events: auto !important;
      }
    }