无人机管理后台前端(已迁走)
张含笑
2025-12-10 93fd50d4705b441125a05439b20a8531a2e453f7
src/views/job/components/DeviceJobDetails.vue
@@ -1,24 +1,61 @@
<!-- 历史任务详情 -->
<template>
  <el-dialog class="ztzf-dialog" append-to-body modal-class="detailsOfHistoricalTasks" v-model="isShow" title="历史任务详情"
  <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">
    <div class="content">
      <div class="contentLeft">
        <div class="machineTableDetailsTitle"><img src="/src/assets/images/task/sign.svg" alt=""><span>详情</span></div>
        <div class="machineTableDetailsTitle"><img src="/src/assets/images/task/sign.svg" alt="">
          <div class="title">
            详情
          </div>
          <div class="btn" v-show="videoData.length > 0">
            <el-button
              class="playback-video-btn"
              v-show="videoData.length == 1"
              type="primary"
              @click="openPlaybackVideoDialog(videoData[0])"
              size="small"
            >
              回放直播
            </el-button>
            <el-popover placement="bottom-start" popper-class="ztzf-custom-popover bottom">
              <template #reference>
                <el-button class="playback-video-btn" v-show="videoData.length > 1" type="primary" size="small">
                  回放直播
                </el-button>
              </template>
              <div class="playback-video-list">
                <div v-for="(item, index) in videoData" :key="index">
                  {{ formatTimestamp(item.execute_time) }}
                  <div class="play-btn" @click="openPlaybackVideoDialog(item)">播放</div>
                </div>
              </div>
            </el-popover>
          </div>
        </div>
        <div class="infoBox">
          <div class="itemBoxLeft">
            <div v-for="(item, index) in infoList" :key="index" class="itemCon">
              <div class="itemBox">
                <div class="itemTitle">{{ item.name }}:</div>
                <div truncated class="itemValue" v-if="item.name !== '飞行事件' && item.name !== '任务执行次数'">{{
                  item.value ? item.value : '--' }}</div>
                <div class="itemValue" v-if="item.name === '任务执行次数'">{{ item.value ? item.value : '0'
                }}次</div>
                <div truncated class="itemValue" v-if="item.name !== '飞行事件' && item.name !== '任务执行次数'">
                  {{ item.value ? item.value : '--' }}
                </div>
                <div class="itemValue" v-if="item.name === '任务执行次数'">{{ item.value ? item.value : '0' }}次</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.imgUrl"
                      :title="item.label"
                      :key="index"
                    />
                  </template>
                  <div class="itemValue" v-else>--</div>
                </div>
@@ -27,7 +64,7 @@
          </div>
        </div>
        <JobRelatedEvents :jobTimes="jobTimes" :batchNo="batchNo" />
        <JobRelatedEvents :jobTimes="jobTimes" :batchNo="batchNo" :waylineJobId="waylineJobId" :detailName="detailName" :isShowExportTheTask="isShowExportTheTask"/>
        <div class="devicetitle" v-if="isShow">
          <div>
            <img src="/src/assets/images/task/sign.svg" alt="">
@@ -55,37 +92,77 @@
            </div>
          </div>
        </div>
<!--        <div class="imgListBox">-->
<!--          &lt;!&ndash; 图片显示 &ndash;&gt;-->
<!--          <template v-for="(item, index) in achievementList.slice(0, visibleCount)" :key="index">-->
<!--            <div class="result-item">-->
<!--              <el-checkbox v-model="item.checked" />-->
<!--              <div class="itemName">{{ item.createTime }}</div>-->
<!--              &lt;!&ndash; 正射 &ndash;&gt;-->
<!--              <el-image v-if="item.resultType === 4" :src="item.smallUrl" :preview-src-list="[item.showUrl]" fit="cover"-->
<!--                preview-teleported />-->
<!--              &lt;!&ndash; 全景 &ndash;&gt;-->
<!--              <img v-else-if="item.resultType === 5" class="quanjing" @click="clickpanorama(item)" :src="item?.smallUrl"-->
<!--                alt="" />-->
<!--              &lt;!&ndash; 视频 &ndash;&gt;-->
<!--              <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="" />-->
<!--              </div>-->
<!--              &lt;!&ndash; 图片 &ndash;&gt;-->
<!--              <el-image v-else :src="item.smallUrl" :preview-src-list="[item.showUrl]" preview-teleported-->
<!--                :initial-index="index" fit="cover" />-->
<!--            </div>-->
<!--          </template>-->
<!--        </div>-->
        <div class="imgListBox">
          <!-- 图片显示 -->
          <template v-for="(item, index) in achievementList.slice(0, visibleCount)" :key="index">
            <div class="result-item">
              <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" fit="cover" preview-teleported @click="clickPlay(index)"/>
              <!-- 全景 -->
              <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="clickPlay(index)"
                :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="clickPlay(index)"
                />
                <img
                  @click="clickPlay(index)"
                  class="videobutton"
                  src="@/assets/images/signMachineNest/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" fit="cover" preview-teleported  @click="clickPlay(index)"/>
            </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 class="cg-empty" v-if="achievementList.length === 0">暂无数据</div>
      </div>
      <div class="content-right" v-if="isShow">
@@ -106,12 +183,19 @@
  <!-- 全景360 -->
  <PanoramaPopup v-if="'全景'" v-model:panoramaParamsShow="panoramaParamsShow"
    v-model:panoramaParamsUrl="panoramaParamsUrl" />
  <PlaybackVideo v-model:show="playbackVideo" v-model:detailData="openVideoDetail" :detailsData="detailsData" />
  <FileTypePlay v-model:show="carouselShow" :list="achievementList" :cIndex="currentIndex"></FileTypePlay>
</template>
<script setup>
import PanoramaPopup from '@/components/PanoramaPopup/PanoramaPopup.vue'
import PlaybackVideo from '@/components/PlaybackVideo/PlaybackVideo.vue'
import FileTypePlay from '@/views/job/components/FileTypePlay.vue';
import { getWaylinejobLiveRecordPage } from '@/api/playback'
import { getShowImg, getSmallImg, getzsSmallImg, getzsShowImg, aLinkDownloadUtil } from '@/utils/util'
import { getaiImagesPageAPI, cancelDownloadApi, getDownloadStatusApi, attachDownload, aiImagesPage } from '@/api/dataCenter/dataCenter'
import { getFindAction } from '@/views/RoutePlan/PointAirLine/pointWayLineUtils'
import { pxToRem } from '@/utils/rem'
import JobRelatedEvents from './JobRelatedEvents.vue'
import { getEventMediaListApi, getJobDetails, getJobInfoFiles, getJobsAllFiles } from '@/api/job/task'
@@ -127,6 +211,9 @@
const showAll = ref(false) // 是否展示全部
let loadingData
const fullscreenVideo = ref(null)
const playbackVideo = ref(false)
const carouselShow = ref(false) // 是否轮播
const currentIndex = ref(-1)
// 视频播放事件处理
const handleVideoPlay = (event) => {
  if (event.target.playbackRate !== 0.75) {
@@ -157,12 +244,13 @@
const infoList = ref([
  { name: '任务编号', value: '', field: 'job_info_num' },
  { name: '任务名称', value: '', field: 'name' },
  { name: '任务类型', value: '', field: 'industry_type_str' },
  { name: '任务类型', value: '', field: 'rep_rule_type' },
  { name: '飞行事件', value: '', field: 'event_number' },
  { name: '所属机巢', value: '', field: 'device_names' },
  { name: '创建人', value: '', field: 'creator_name' },
  { name: '所属部门', value: '', field: 'dept_name' },
  { name: '任务时间', value: '', field: 'cycle_time_value' },
  { name: '任务频次', value: '', field: 'rep_rule_type' },
  { name: '关联算法', value: '', field: 'ai_type_str' },
  { name: '自定义识别区', value: '', field: 'enable_custom_area' },
  { name: '任务描述', value: '', field: 'remark' },
@@ -192,7 +280,10 @@
const achievementList = ref([])
const props = defineProps(['wayLineJobInfoId', 'waylineJobId', 'batchNo', 'jobId'])
const wayLineJobInfoId = computed(() => props.wayLineJobInfoId)
const jobId = computed(() => props.jobId)
provide('wayLineJobInfoId', wayLineJobInfoId)
provide('jobId', jobId)
// 可见数量
const visibleCount = computed(() =>
  showAll.value ? achievementList.value.length : Math.min(5, achievementList.value.length)
@@ -204,51 +295,59 @@
const flightEvents = ref([])
const jobTimes = ref([])
const historyTime = ref('')
const jumpDataCenter = ref(null)
const cycle_time = ref(null)
const detailName = ref(null)
const isShowExportTheTask = ref(null)
const getDetails = () => {
  const params = {
    wayLineJobInfoId: props.wayLineJobInfoId,
    waylineJobId: props.waylineJobId,
    // waylineJobId: props.waylineJobId,
    batchNo: props.batchNo
  }
  getJobDetails(params).then(res => {
    detailsData.value = res.data.data
    jobTimes.value = res.data.data.job_times
    jumpDataCenter.value = res.data.data
    cycle_time.value = res.data.data.cycle_time_value
    detailName.value = detailsData.value.name
    const aiTypeStr = detailsData.value.ai_type_str || '';
    isShowExportTheTask.value = aiTypeStr && !/人|车|船/.test(aiTypeStr)
    infoList.value.forEach(item => {
      item.value = detailsData.value?.[item.field] || '--'
      if (item.name === '任务频次') {
        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] || '--'
        item.value = detailsData?.value.rep_rule_type ? detailsData?.value.final_cycle_frequency : '1次'
      }
      if (item.name === '任务类型') {
        const { rep_rule_type = '' } = detailsData?.value || {}
        item.value = rep_rule_type ? '周期任务' : '临时任务'
      }
      if (item.name === '飞行事件') {
        const { action_modes = [] } = detailsData?.value || {}
        flightEvents.value = action_modes.flatMap(({ actionActuatorFunc }) =>
          droneEventList.filter(({ value }) => actionActuatorFunc === value)
        )
        flightEvents.value = action_modes.flatMap(getFindAction)
      }
      if (item.name === '自定义识别区') {
      if (item.name === '自定义识别区' && infoList?.value[9].value !== '--') {
        item.value = res.data.data.enable_custom_area ? '是' : '否'
      }
    })
    getJobsAllFiles({
      wayLineJobId: detailsData.value.way_lines.map(item => item.job_id).join(','),
      resultTypes: [0, 1, 2, 4, 5],
      orderByCreateTime: true,
    }).then(res => {
      if (res.data.code !== 200) return
      achievementList.value = res.data.data.records.map(i => ({
    }).then(result => {
      if (result.data.code !== 200) return
      achievementList.value = result.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
      total.value = result.data.data.total
      yuanImages.value = res.data.data.records.filter(item => item.resultType !== 1)
      yuanImages.value = result.data.data.records.filter(item => item.resultType !== 1)
    })
    // flystatus.value = res.data.data.ai_type_str
  })
}
// 播放
@@ -321,8 +420,43 @@
    EventBus.emit('useGlobalWS-messageHandler', { biz_code: 'DOWNLOAD_PROGRESS', data: { status: 'CANCELLED', type: 'htlsrwxq' } })
  })
}
// 执行机巢详情
// const taskData = ref([])
// const getNestDetailApi = () => {
//   const params = {
//     jobInfoId: props.wayLineJobInfoId,
//     batchNo: props.batchNo,
//   }
//   NestDetailApi(params).then(res => {
//     taskData.value = res.data.data
//   })
// }
// 获取回放直播列表
const videoData = ref([])
const openVideoDetail = ref({})
const getVideoList = () => {
  getWaylinejobLiveRecordPage({ job_info_id: props.wayLineJobInfoId }).then(videoDetails => {
    videoData.value = videoDetails.data.data.records
  })
}
const openPlaybackVideoDialog = data => {
  playbackVideo.value = true
  openVideoDetail.value = data
}
function clickPlay(index) {
  carouselShow.value = true
  currentIndex.value = index
}
onMounted(() => {
  getDetails()
  getVideoList()
})
</script>
@@ -355,7 +489,8 @@
    overflow: auto;
    .machineTableDetailsTitle {
      margin-bottom: 10px;
      display: flex;
      //margin-bottom: 10px;
      // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
      border-bottom: 2px solid #e4e7ed;
      background-size: 100% 100%;
@@ -363,9 +498,10 @@
      img {
        width: 15px;
        height: 15px;
        margin-top: 6px;
      }
      span {
      .title {
        display: inline-block;
        margin-left: 10px;
        font-size: 16px;
@@ -375,6 +511,12 @@
        text-align: left;
        margin-bottom: 8px;
      }
      .btn {
        margin-left: 16px;
        margin-bottom: 8px;
        line-height: 20px;
      }
    }
    .devicetitle {