无人机管理后台前端(已迁走)
chenyao
2025-11-28 cf55e57ac8e892c321cc9eeb79e14b39e7d80e82
Merge branch 'feature/v8.0/8.0.3' into prod
18 files modified
9 files added
1 files deleted
18479 ■■■■■ changed files
src/api/home/index.js 8 ●●●●● patch | view | raw | blame | history
src/api/tickets/orderLog.js 8 ●●●●● patch | view | raw | blame | history
src/api/tickets/ticket.js 10 ●●●● patch | view | raw | blame | history
src/assets/images/layerManagement/closeBtn.svg 24 ●●●●● patch | view | raw | blame | history
src/styles/element-ui.scss 30 ●●●● patch | view | raw | blame | history
src/utils/date.js 27 ●●●●● patch | view | raw | blame | history
src/views/authority/role.vue 14 ●●●●● patch | view | raw | blame | history
src/views/dataCenter/dataCenter.vue 23 ●●●●● patch | view | raw | blame | history
src/views/job/components/DeviceJobDetails.vue 1046 ●●●● patch | view | raw | blame | history
src/views/layerManagement/components/folderFile.vue 23 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/components/leftList.vue 46 ●●●● patch | view | raw | blame | history
src/views/layerManagement/components/rightEdit.vue 80 ●●●● patch | view | raw | blame | history
src/views/layerManagement/index.vue 111 ●●●● patch | view | raw | blame | history
src/views/resource/components/spotDetails.vue 27 ●●●● patch | view | raw | blame | history
src/views/system/user.vue 12 ●●●● patch | view | raw | blame | history
src/views/tickets/component/AddEditDetails.vue 550 ●●●●● patch | view | raw | blame | history
src/views/tickets/component/SearchBox.vue 313 ●●●●● patch | view | raw | blame | history
src/views/tickets/orderLog-copy1.vue 1402 ●●●●● patch | view | raw | blame | history
src/views/tickets/orderLog.vue 2111 ●●●● patch | view | raw | blame | history
src/views/tickets/orderLogCopy.vue 1666 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket-copy.vue 4069 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue 4325 ●●●● patch | view | raw | blame | history
src/views/tickets/ticketComponent/CreateTicketDialog.vue 710 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticketComponent/DispatchDialog.vue 244 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticketComponent/RecheckDialog.vue 117 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticketComponent/TicketDetailDialog.vue 1439 ●●●●● patch | view | raw | blame | history
src/views/wel/components/backlog.vue 2 ●●●●● patch | view | raw | blame | history
src/views/wel/components/proportionStatic.vue 42 ●●●● patch | view | raw | blame | history
src/api/home/index.js
@@ -6,6 +6,14 @@
    method: 'get',
  });
};
// 大屏首页=>事件状态数量
export const getEventStatusNum = data => {
  return request({
    url: '/drone-device-core/jobEvent/eventStatusNum',
    method: 'post',
    data,
  })
}
// 事件概况分类数
export const getJobEventByStatus = data => {
  return request({
src/api/tickets/orderLog.js
@@ -68,6 +68,14 @@
  });
};
// 智飞工单 统计数替换上面接口
// export const jobStatusNum = (data) => {
//   return request({
//     url: '/drone-device-core/wayline/orderLog/jobStatusNum',
//     method: 'post',
//     data
//   });
// };
// 草稿转发布
export const userPublish = (id) => {
src/api/tickets/ticket.js
@@ -73,14 +73,20 @@
}
// 新增接口:获取状态统计数据
export const getstatusCount = (params) => {
export const getstatusCount1 = (params) => {
  return request({
    url: '/drone-device-core/jobEvent/getstatusCount',
    method: 'get',
    params,
  })
}
export const getstatusCount = (data) => {
  return request({
    url: '/drone-device-core/jobEvent/getstatusCount',
    method: 'post',
    data,
  })
}
export const getStepInfo = (eventNum) => {
  return request({
    url: '/drone-device-core/jobEvent/getStepInfo',
src/assets/images/layerManagement/closeBtn.svg
New file
@@ -0,0 +1,24 @@
<svg width="135" height="52" viewBox="0 0 135 52" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 1321316178">
<g id="Rectangle 34624972" filter="url(#filter0_d_2725_2773)">
<rect x="5" width="125" height="39" rx="4" fill="#FF9011"/>
</g>
<path id="&#229;&#133;&#179;&#233;&#151;&#173;" d="M76.16 21.406H71.442C72.506 23.044 74.256 24.206 76.664 24.724C76.258 25.102 75.74 25.872 75.488 26.362C73.164 25.718 71.526 24.486 70.35 22.694C69.538 24.108 67.886 25.452 64.68 26.432C64.456 26.054 63.896 25.326 63.448 24.934C66.85 23.954 68.264 22.68 68.796 21.406H63.868V19.754H69.132V19.614V17.85H64.792V16.198H67.116C66.85 15.554 66.346 14.728 65.87 14.112L67.27 13.412C67.886 14.112 68.474 15.064 68.74 15.722L67.886 16.198H71.26C71.778 15.372 72.324 14.238 72.66 13.384L74.48 13.986C74.046 14.742 73.556 15.526 73.108 16.198H75.432V17.85H71.092V19.642V19.754H76.16V21.406ZM81.298 15.54L79.898 16.464C79.604 15.834 78.82 14.896 78.176 14.224L79.478 13.398C80.15 14.014 80.948 14.91 81.298 15.54ZM77.966 26.432V16.73H79.632V26.432H77.966ZM87.892 19.516H86.296V23.478C86.296 24.318 86.128 24.738 85.54 25.004C84.98 25.256 84.154 25.298 83.034 25.298C82.964 24.822 82.726 24.136 82.502 23.688C83.216 23.716 84.014 23.716 84.252 23.716C84.49 23.702 84.574 23.646 84.574 23.436V20.748C83.58 22.022 82.292 23.1 80.864 23.912C80.626 23.562 80.08 22.96 79.73 22.68C81.242 21.91 82.628 20.748 83.552 19.516H80.36V17.934H84.574V16.128H86.296V17.934H87.892V19.516ZM90.104 14.028V24.654C90.104 25.452 89.95 25.844 89.446 26.11C88.956 26.362 88.242 26.404 87.22 26.39C87.164 25.97 86.94 25.298 86.73 24.892C87.304 24.92 87.99 24.92 88.186 24.92C88.396 24.906 88.466 24.836 88.466 24.626V15.568H81.844V14.028H90.104Z" fill="white"/>
<g id="Frame">
<path id="Vector" d="M54.4599 25.1179C54.2915 25.1179 54.1232 25.0537 53.9946 24.9251L44.0678 14.9981C43.8107 14.7413 43.8107 14.3244 44.0678 14.0676C44.3249 13.8105 44.7413 13.8105 44.9983 14.0676L54.9252 23.9946C55.1822 24.2514 55.1822 24.6683 54.9252 24.9251C54.7967 25.0537 54.6283 25.1179 54.4599 25.1179Z" fill="white"/>
<path id="Vector_2" d="M44.5331 25.1179C44.3647 25.1179 44.1963 25.0537 44.0678 24.9251C43.8107 24.6683 43.8107 24.2514 44.0678 23.9946L53.9946 14.0676C54.2517 13.8105 54.6681 13.8105 54.9252 14.0676C55.1822 14.3244 55.1822 14.7413 54.9252 14.9981L44.9983 24.9251C44.8698 25.0537 44.7014 25.1179 44.5331 25.1179Z" fill="white"/>
</g>
</g>
<defs>
<filter id="filter0_d_2725_2773" x="1" y="0" width="133" height="47" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="4"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2725_2773"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2725_2773" result="shape"/>
</filter>
</defs>
</svg>
src/styles/element-ui.scss
@@ -184,25 +184,37 @@
  .ztzf-layer-search {
    color: #fff !important;
    background-color: transparent !important;
    .el-select__wrapper {
      background-color: transparent !important;
    }
    .el-select__popper.el-popper {
      background-color: transparent !important;
    }
    .el-popper.is-light {
      background-color: transparent !important;
    }
      .el-select__placeholder {
          &.is-transparent {
              color: rgba(255,255,255,0.37) !important;
          }
          &.el-select__selected-item {
              color: #ffffff;
          }
      }
    
    .el-select__placeholder {
      &.is-transparent {
        color: rgba(255,255,255,0.37) !important;
      }
      &.is-disabled {
        color: rgba(255,255,255,0.37) !important;
      }
    }
    .el-select__selected-item {
      color: #ffffff !important;
      &.is-disabled {
        color: rgba(255,255,255,0.37) !important;
        z-index: 99;
      }
    }
  }
  .ztzf-select-popper.el-select__popper {
      border:#2D2D2D !important;
src/utils/date.js
@@ -1,3 +1,11 @@
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn'
import weekday from 'dayjs/plugin/weekday'
// 配置 dayjs
dayjs.locale('zh-cn')
dayjs.extend(weekday)
export const calcDate = (date1, date2) => {
  let date3 = date2 - date1;
@@ -56,3 +64,22 @@
export function dateNow() {
  return dateFormat(new Date(), 'yyyyMMddhhmmss');
}
export const getDateRange = (unit, formatStr = 'YYYY-MM-DD HH:mm:ss') => {
  const todayDate = dayjs()
  let returnArr = [dayjs(), todayDate]
  if (unit === 'today') {
    returnArr = [dayjs(), todayDate]
  }
  if (unit === 'week') {
    returnArr = [dayjs().weekday(0), todayDate]
  }
  if (unit === 'month') {
    returnArr = [dayjs().startOf('month'), todayDate]
  }
  if (unit === 'year') {
    returnArr = [dayjs().startOf('year'), todayDate]
  }
  returnArr = [returnArr[0].startOf('day').format(formatStr), returnArr[1].endOf('day').format(formatStr)]
  return returnArr
}
src/views/authority/role.vue
@@ -24,6 +24,11 @@
                   :props="props">
          </el-tree>
        </el-tab-pane>
        <el-tab-pane label="大屏菜单">
          <el-tree :data="menuGrantListOld" show-checkbox node-key="id" ref="treeMenuApp" :default-checked-keys="menuTreeObjOld"
                   :props="props">
          </el-tree>
        </el-tab-pane>
        <!-- <el-tab-pane label="数据权限">
          <el-tree
            :data="dataScopeGrantList"
@@ -82,11 +87,13 @@
      },
      menuGrantList: [],
      menuGrantListApp: [],
      menuGrantListOld: [],
      dataScopeGrantList: [],
      apiScopeGrantList: [],
      apiGrantList: [],
      menuTreeObj: [],
      menuTreeObjApp: [],
      menuTreeObjOld: [],
      dataScopeTreeObj: [],
      apiScopeTreeObj: [],
      selectionList: [],
@@ -350,6 +357,13 @@
          this.box = true
        })
      })
      grantTree({sysType: 2}).then(res => {
        this.menuGrantListOld = res.data.data.menu
        getRole(this.ids).then(res => {
          this.menuTreeObjOld = res.data.data.menu
          this.box = true
        })
      })
    },
    handleDelete () {
      if (this.selectionList.length === 0) {
src/views/dataCenter/dataCenter.vue
@@ -205,7 +205,7 @@
              class="videoItem"
              ref="videoRefs"
              controls
              autoplay
              :src="dialogDetailList.link"
            ></video>
            <!-- 地图 -->
@@ -308,7 +308,9 @@
            class="videoBox"
            ref="videoRefs"
            controls
            autoplay
           preload="auto"
              @play="handleVideoPlay"
              @ended="handleVideoEnded(index)"
            :src="currentVideoUrl"
          ></video>
        </div>
@@ -364,6 +366,23 @@
const currentAreaPosition = ref({ height: 1987280, latitude: 27.636112, longitude: 115.732975 });
let handler = null;
const switchFolders = ref(false)
// 视频播放事件处理
const handleVideoPlay = (event) => {
  if (event.target.playbackRate !== 0.75) {
    event.target.playbackRate = 0.75
  }
}
const handleVideoEnded = index => {
  // 获取当前视频
  const video = videoRefs.value[index]
  // 重置视频播放时间为 0
  video.currentTime = 0
  // 重新加载视频
  video.load()
}
function bytesToMB(bytes, decimalPlaces = 2) {
  if (typeof bytes !== 'number' || bytes < 0) return '0';
  return (bytes / 1048576).toFixed(decimalPlaces) + ' MB';
src/views/job/components/DeviceJobDetails.vue
@@ -1,148 +1,150 @@
<!-- 历史任务详情 -->
<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">
        <div class="content">
            <div class="contentLeft">
                <div class="machineTableDetailsTitle"><img src="/src/assets/images/task/sign.svg" alt=""><span>详情</span></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 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>
                                    </template>
                                    <div class="itemValue" v-else>--</div>
                                </div>
                            </div>
                        </div>
                    </div>
  <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="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 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>
                  </template>
                  <div class="itemValue" v-else>--</div>
                </div>
              </div>
            </div>
          </div>
                </div>
                <JobRelatedEvents :jobTimes="jobTimes" :batchNo="batchNo" />
                <div class="devicetitle" v-if="isShow">
                <div>
                        <img src="/src/assets/images/task/sign.svg" alt="">
                    <p>
                        成果数据
                        <span>{{ total }}</span>
                        个
                    </p>
                </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" />
                            <div class="downloadBtnText">
                                <span v-if="htlsrwxq === 100">下载</span>
                                <template v-else>
                                    处理中<el-icon @click="cancelDownload"><CircleClose /></el-icon>
                                </template>
                            </div>
                        </div>
                        <div class="downloadBtn" @click="showAll = !showAll" v-if="total > 5">
                            {{ showAll ? '收起' : '更多' }}
                        </div>
                    </div>
                </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
                            />
                            <!-- 全景 -->
                            <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=""
                                />
                            </div>
                                <!-- 图片 -->
                            <el-image
                                v-else
                                :src="item.smallUrl"
                                :preview-src-list="[item.showUrl]"
                                preview-teleported
                                :initial-index="index"
                                fit="cover"
                            />
        </div>
        <JobRelatedEvents :jobTimes="jobTimes" :batchNo="batchNo" />
        <div class="devicetitle" v-if="isShow">
          <div>
            <img src="/src/assets/images/task/sign.svg" alt="">
            <p>
              成果数据
              <span>{{ total }}</span>
              个
            </p>
          </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" />
              <div class="downloadBtnText">
                <span v-if="htlsrwxq === 100">下载</span>
                <template v-else>
                  处理中<el-icon @click="cancelDownload"><CircleClose /></el-icon>
                </template>
              </div>
            </div>
            <div class="downloadBtn" @click="showAll = !showAll" v-if="total > 5">
              {{ showAll ? '收起' : '更多' }}
            </div>
          </div>
        </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
              />
              <!-- 全景 -->
              <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=""
                />
              </div>
              <!-- 图片 -->
              <el-image
                v-else
                :src="item.smallUrl"
                :preview-src-list="[item.showUrl]"
                preview-teleported
                :initial-index="index"
                fit="cover"
              />
                        <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(1600), height: '80vh' }"
                        controls
                        autoplay
                    ></video>
                </div>
            </el-dialog>
                        </div>
              <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>
            </div>
                    </template>
          </template>
                </div>
                <el-image-viewer
                    v-if="showViewer"
                    :url-list="previewUrls"
                    :initial-index="activeIndex"
                    @close="showViewer = false"
                />
            </div>
        </div>
        <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"
                />
                <div class="content-map-popups"></div>
            </div>
        </div>
    </el-dialog>
      <div class="content-right" v-if="isShow">
        <DeviceJobDetailsMap
          :detailsData="detailsData"
          :yuanImages="yuanImages"
          @showImageeclick="showImageeclick"
          :jobId="props.jobId"
        />
        <div class="content-map-popups"></div>
      </div>
    </div>
  </el-dialog>
  <!-- 全景360 -->
  <PanoramaPopup
    v-if="'全景'"
@@ -169,46 +171,66 @@
const VideoShow = ref(false)
const showAll = ref(false) // 是否展示全部
let loadingData
const fullscreenVideo = ref(null)
// 视频播放事件处理
const handleVideoPlay = (event) => {
  if (event.target.playbackRate !== 0.75) {
    event.target.playbackRate = 0.75
  }
}
const handleVideoEnded = index => {
  // 获取当前视频
  const video = fullscreenVideo.value
  // 重置视频播放时间为 0
  video.currentTime = 0
  // 重新加载视频
  video.load()
}
// 原图
const yuanImages = ref([])
function convertVideoUrlToThumbnail(videoUrl) {
    // 检查是否是有效的视频URL
    if (!videoUrl || typeof videoUrl !== 'string') {
        return videoUrl
    }
    // 替换文件扩展名
    return videoUrl.replace(/\.mp4$/, '_small.jpg')
  // 检查是否是有效的视频URL
  if (!videoUrl || typeof videoUrl !== 'string') {
    return videoUrl
  }
  // 替换文件扩展名
  return videoUrl.replace(/\.mp4$/, '_small.jpg')
}
const infoList = ref([
    { name: '任务编号', value: '', field: 'job_info_num' },
    { name: '任务名称', value: '', field: 'name' },
    { name: '任务类型', value: '', field: 'industry_type_str' },
    { name: '飞行事件', value: '', field: 'event_number' },
    { name: '所属机巢', value: '', field: 'device_names' },
    { name: '所属部门', value: '', field: 'dept_name' },
    { name: '任务时间', value: '', field: 'cycle_time_value' },
    { name: '关联算法', value: '', field: 'ai_type_str' },
    { name: '任务描述', value: '', field: 'remark' },
  { name: '任务编号', value: '', field: 'job_info_num' },
  { name: '任务名称', value: '', field: 'name' },
  { name: '任务类型', value: '', field: 'industry_type_str' },
  { name: '飞行事件', value: '', field: 'event_number' },
  { name: '所属机巢', value: '', field: 'device_names' },
  { name: '所属部门', value: '', field: 'dept_name' },
  { name: '任务时间', value: '', field: 'cycle_time_value' },
  { name: '关联算法', value: '', field: 'ai_type_str' },
  { name: '自定义识别区', value: '', field: 'enable_custom_area' },
    // { name: '任务执行次数', value: '', field: 'job_num' },
  { name: '创建人', value: '', field: 'creator_name' },
  { name: '任务描述', value: '', field: 'remark' },
  // { name: '任务执行次数', value: '', field: 'job_num' },
])
// 机巢状态
const flystatus = ref('')
const detailsData = ref({
    id: null,
    remark: null,
    is_monitoring: null,
    industry_type_str: null,
    area_code: null,
    ai_type_str: null,
    begin_time: null,
    end_time: null,
    device_names: null,
    create_time: null,
    name: null,
    event_number: null,
    creator_name: null,
    way_lines: [],
  id: null,
  remark: null,
  is_monitoring: null,
  industry_type_str: null,
  area_code: null,
  ai_type_str: null,
  enable_custom_area:null,
  begin_time: null,
  end_time: null,
  device_names: null,
  create_time: null,
  name: null,
  event_number: null,
  creator_name: null,
  way_lines: [],
})
// 任务成果
const total = ref(0)
@@ -218,10 +240,10 @@
provide('wayLineJodInfoId', wayLineJodInfoId)
// 可见数量
const visibleCount = computed(() =>
    showAll.value ? achievementList.value.length : Math.min(5, achievementList.value.length)
  showAll.value ? achievementList.value.length : Math.min(5, achievementList.value.length)
)
const getAchievement = () => {
    if (!props.jobId) return
  if (!props.jobId) return
  const attachmentsParams = {
    'wayLineJobId': props.jobId,
@@ -230,83 +252,83 @@
    current: 1,
    size: 2000,
  }
    const pageParams = {
        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)
    })
    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('正在加急开发中...')
  ElMessage.warning('正在加急开发中...')
}
const flightEvents = ref([])
const jobTimes = ref([])
const getDetails = () => {
    const params = {
        wayLineJobInfoId: props.wayLineJodInfoId,
        waylineJobId: props.waylineJobId,
        batchNo: props.batchNo
    }
    getJobDetails(params).then(res => {
        detailsData.value = res.data.data
        jobTimes.value = res.data.data.job_times
        infoList.value.forEach(item => {
            if (item.name === '任务频次') {
                const { rep_rule_type = '', rep_rule_val = '' } = detailsData?.value || {}
                item.value = rep_rule_type + ' -- ' + rep_rule_val
            } else {
                item.value = detailsData.value?.[item.field] || '--'
            }
            if (item.name === '飞行事件') {
                const { action_modes = [] } = detailsData?.value || {}
                flightEvents.value = action_modes.flatMap(({ actionActuatorFunc }) =>
                    droneEventList.filter(({ value }) => actionActuatorFunc === value)
                )
            }
  const params = {
    wayLineJobInfoId: props.wayLineJodInfoId,
    waylineJobId: props.waylineJobId,
    batchNo: props.batchNo
  }
  getJobDetails(params).then(res => {
    detailsData.value = res.data.data
    jobTimes.value = res.data.data.job_times
    infoList.value.forEach(item => {
      if (item.name === '任务频次') {
        const { rep_rule_type = '', rep_rule_val = '' } = detailsData?.value || {}
        item.value = rep_rule_type + ' -- ' + rep_rule_val
      } else {
        item.value = detailsData.value?.[item.field] || '--'
      }
      if (item.name === '飞行事件') {
        const { action_modes = [] } = detailsData?.value || {}
        flightEvents.value = action_modes.flatMap(({ actionActuatorFunc }) =>
          droneEventList.filter(({ value }) => actionActuatorFunc === value)
        )
      }
      if (item.name === '自定义识别区') {
        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],
        }).then(result => {
            if (result.data.code !== 200) return
            yuanImages.value = result.data.data.records
      getJobsAllFiles({
        wayLineJobId: detailsData.value.way_lines.map(item => item.job_id).join(','),
        resultTypes: [0, 2, 4, 5],
      }).then(result => {
        if (result.data.code !== 200) return
        yuanImages.value = result.data.data.records
        })
        })
        // flystatus.value = res.data.data.ai_type_str
    })
      })
    })
    // flystatus.value = res.data.data.ai_type_str
  })
}
// 播放
const videoRef = ref(null);
const currentVideoIndex = ref(-1)
const fullscreenVideo = ref(null)
const enterFullScreen = (index) => {
    currentVideoIndex.value = index
    VideoShow.value = true
  currentVideoIndex.value = index
  VideoShow.value = true
}
// 计算当前视频URL
const currentVideoUrl = computed(() => {
    return achievementList.value[currentVideoIndex.value]?.link || ''
  return achievementList.value[currentVideoIndex.value]?.link || ''
})
// 关闭弹窗处理
const handleClose = () => {
    if (fullscreenVideo.value) {
        fullscreenVideo.value.pause()
        fullscreenVideo.value.currentTime = 0
    }
  if (fullscreenVideo.value) {
    fullscreenVideo.value.pause()
    fullscreenVideo.value.currentTime = 0
  }
}
// 下载
const downloadFun = async () => {
@@ -334,353 +356,353 @@
const panoramaParamsShow = ref(false)
const panoramaParamsUrl = ref(null)
const clickpanorama = val => {
    panoramaParamsShow.value = true
    panoramaParamsUrl.value = val.link
  panoramaParamsShow.value = true
  panoramaParamsUrl.value = val.link
}
// 从地图点击图片预览
const showViewer = ref(false)
const activeIndex = ref(0)
const previewUrls = ref([])
const showImageeclick = (list, index, categoriestype) => {
    if (categoriestype === 5) {
        panoramaParamsShow.value = true
        panoramaParamsUrl.value = list[0]
    } else {
        previewUrls.value = list
        activeIndex.value = 0
        showViewer.value = true
    }
  if (categoriestype === 5) {
    panoramaParamsShow.value = true
    panoramaParamsUrl.value = list[0]
  } else {
    previewUrls.value = list
    activeIndex.value = 0
    showViewer.value = true
  }
}
// 取消下载
function cancelDownload() {
    cancelDownloadApi({ type:'htlsrwxq' }).then(res =>{
        ElMessage.success('取消成功')
    }).catch(e =>{
  cancelDownloadApi({ type:'htlsrwxq' }).then(res =>{
    ElMessage.success('取消成功')
  }).catch(e =>{
    EventBus.emit('useGlobalWS-messageHandler', {biz_code: 'DOWNLOAD_PROGRESS',data:{status: 'CANCELLED',type:'htlsrwxq'}})
  })
}
onMounted(() => {
    getDetails()
    getAchievement()
  getDetails()
  getAchievement()
})
</script>
<style lang="scss">
.detailsOfHistoricalTasks {
    .el-dialog {
        --el-dialog-margin-top: 5vh;
    }
  .el-dialog {
    --el-dialog-margin-top: 5vh;
  }
    .el-pagination {
        justify-content: center;
        padding: 20px;
    }
  .el-pagination {
    justify-content: center;
    padding: 20px;
  }
    .el-dialog__body {
        height: 80vh;
    }
  .el-dialog__body {
    height: 80vh;
  }
}
</style>
<style lang="scss" scoped>
.content {
    display: flex;
    height: 100%;
.contentLeft {
        margin-left: 35px;
        margin-right: 24px;
        width: 1150px;
        overflow: auto;
  display: flex;
  height: 100%;
  .contentLeft {
    margin-left: 35px;
    margin-right: 24px;
    width: 1150px;
    overflow: auto;
        .machineTableDetailsTitle {
            margin-bottom: 10px;
            // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
            border-bottom: 2px solid #e4e7ed;
            background-size: 100% 100%;
            img{
            width: 15px;
            height: 15px;}
            span {
                display: inline-block;
                margin-left: 10px;
                font-size: 16px;
                // color: #0282ff;
                color: #303133;
                line-height: 20px;
                text-align: left;
                margin-bottom: 8px;
            }
        }
    .machineTableDetailsTitle {
      margin-bottom: 10px;
      // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
      border-bottom: 2px solid #e4e7ed;
      background-size: 100% 100%;
      img{
        width: 15px;
        height: 15px;}
      span {
        display: inline-block;
        margin-left: 10px;
        font-size: 16px;
        // color: #0282ff;
        color: #303133;
        line-height: 20px;
        text-align: left;
        margin-bottom: 8px;
      }
    }
        .devicetitle {
            margin-bottom: 16px;
            // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
            border-bottom: 2px solid #e4e7ed;
            background-size: 100% 100%;
            display: flex;
            justify-content: space-between;
            align-content: center;
            .rightBox{
                display: flex;
                align-items: center;
                padding-right: 10px;
                gap: 0 10px;
    .devicetitle {
      margin-bottom: 16px;
      // background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
      border-bottom: 2px solid #e4e7ed;
      background-size: 100% 100%;
      display: flex;
      justify-content: space-between;
      align-content: center;
      .rightBox{
        display: flex;
        align-items: center;
        padding-right: 10px;
        gap: 0 10px;
                .downloadBtn{
                    position: relative;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                    height: 28px;
                    padding: 0 15px;
                    background: rgba(28, 92, 255, 0.14);
                    border-radius: 4px 4px 4px 4px;
                    border: 1px solid #1c5cff;
                    cursor: pointer;
                        color: #1C5CFF;
        .downloadBtn{
          position: relative;
          display: flex;
          align-items: center;
          justify-content: center;
          height: 28px;
          padding: 0 15px;
          background: rgba(28, 92, 255, 0.14);
          border-radius: 4px 4px 4px 4px;
          border: 1px solid #1c5cff;
          cursor: pointer;
          color: #1C5CFF;
                    .downloadBtnText{
                        display: flex;
                        align-items: center;
                        gap: 5px;
                        z-index: 5;
                    }
                    .el-progress{
                        position: absolute;
                        left: 0;
                        top: 0;
                        width: 100%;
          .downloadBtnText{
            display: flex;
            align-items: center;
            gap: 5px;
            z-index: 5;
          }
          .el-progress{
            position: absolute;
            left: 0;
            top: 0;
            width: 100%;
                        :deep(){
                            .el-progress-bar__outer{
                                height: 28px !important;
                                border-radius: 0 !important;
                                background: transparent !important;
            :deep(){
              .el-progress-bar__outer{
                height: 28px !important;
                border-radius: 0 !important;
                background: transparent !important;
                                .el-progress-bar__inner{
                                    border-radius: 0 !important;
                                }
                            }
                        }
                    }
                .el-progress-bar__inner{
                  border-radius: 0 !important;
                }
              }
            }
          }
                }
            }
            img{
            width: 15px;
            height: 15px;}
            p {
                display: inline-block;
                margin-left: 10px;
                font-size: 16px;
                color: #363636;
                line-height: 20px;
                text-align: left;
                margin-bottom: 8px;
        }
      }
      img{
        width: 15px;
        height: 15px;}
      p {
        display: inline-block;
        margin-left: 10px;
        font-size: 16px;
        color: #363636;
        line-height: 20px;
        text-align: left;
        margin-bottom: 8px;
                span {
                    font-size: 26px;
                    color: #0282ff;
                    font-weight: bold;
                }
            }
        span {
          font-size: 26px;
          color: #0282ff;
          font-weight: bold;
        }
      }
            .more {
                color: #2f9dff;
                font-size: 14px;
                padding-top: 5px;
                cursor: pointer;
            }
        }
      .more {
        color: #2f9dff;
        font-size: 14px;
        padding-top: 5px;
        cursor: pointer;
      }
    }
        .infoBox {
            display: flex;
            justify-content: space-between;
    .infoBox {
      display: flex;
      justify-content: space-between;
            .itemBoxLeft {
                flex: 1;
                display: grid;
                grid-template-columns: repeat(2, 1fr);
                column-gap: 10px;
                /* 只设置列间距 */
                row-gap: 0;
                /* 取消行间距 */
                padding: 10px;
                font-size: 14px;
      .itemBoxLeft {
        flex: 1;
        display: grid;
        grid-template-columns: repeat(2, 1fr);
        column-gap: 10px;
        /* 只设置列间距 */
        row-gap: 0;
        /* 取消行间距 */
        padding: 10px;
        font-size: 14px;
                .itemBox {
                    display: flex;
                    align-items: center;
                    background: #fff;
                    height: 43px;
                    border-top: 2px solid #EFEFEF;
        .itemBox {
          display: flex;
          align-items: center;
          background: #fff;
          height: 43px;
          border-top: 2px solid #EFEFEF;
                }
        }
            }
      }
            .itemCon:nth-last-child(-n+2) {
                border-bottom: 2px solid #EFEFEF;
            }
      .itemCon:nth-last-child(-n+2) {
        border-bottom: 2px solid #EFEFEF;
      }
            .itemBox:nth-last-child(-n+2) .itemTitle {
                border-bottom: 1px solid #EFEFEF;
            }
      .itemBox:nth-last-child(-n+2) .itemTitle {
        border-bottom: 1px solid #EFEFEF;
      }
            .itemTitle {
                color: #0B1D38;
                width: 26%;
                text-align: right;
                background: #F3F6FF;
                height: 43px;
                line-height: 43px;
                padding-right: 10px;
                border-top: 1px solid #EFEFEF;
            }
      .itemTitle {
        color: #0B1D38;
        width: 26%;
        text-align: right;
        background: #F3F6FF;
        height: 43px;
        line-height: 43px;
        padding-right: 10px;
        border-top: 1px solid #EFEFEF;
      }
            .itemValue {
                font-size: 14px;
                color: #747e91;
                text-align: center;
                width: 74%;
      .itemValue {
        font-size: 14px;
        color: #747e91;
        text-align: center;
        width: 74%;
            }
      }
            .flightEvents {
                display: flex;
                flex-wrap: wrap;
                gap: 10px 10px;
                width: 74%;
                justify-content: center;
      .flightEvents {
        display: flex;
        flex-wrap: wrap;
        gap: 10px 10px;
        width: 74%;
        justify-content: center;
                img {
                    width: 30px;
                    height: 30px;
                }
            }
        img {
          width: 30px;
          height: 30px;
        }
      }
        }
    }
        .imgListBox {
            display: grid;
            grid-template-columns: repeat(5, 1fr);
            gap: 10px;
            margin-bottom: 49px;
    .imgListBox {
      display: grid;
      grid-template-columns: repeat(5, 1fr);
      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;
                background: rgba(37, 36, 36, 0.63) !important;
                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%;
                cursor: pointer;
                position: relative;
            }
      > * {
        width: 100%;
        height: 200px;
        position: relative;
        border-radius: 0px 0px 4px 4px;
        overflow: hidden;
      }
      :deep(.el-checkbox__inner) {
        width: 17px !important;
        height: 17px !important;
        background: rgba(37, 36, 36, 0.63) !important;
        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%;
        cursor: pointer;
        position: relative;
      }
            .quanjing {
                width: 100%;
                height: 100%;
                object-fit: cover;
                cursor: pointer;
                position: relative;
            }
      .quanjing {
        width: 100%;
        height: 100%;
        object-fit: cover;
        cursor: pointer;
        position: relative;
      }
            .videotime {
                width: 100%;
                height: 100%;
                position: relative;
      .videotime {
        width: 100%;
        height: 100%;
        position: relative;
                .videoDisplay {
                    width: 100%;
                    height: 100%;
                    object-fit: cover;
                }
            }
            .itemName {
                position: absolute;
                bottom: 0;
                left: 0;
                height: 23px;
                width: 100%;
                background: rgba(22, 22, 22, 0.68);
                border-radius: 0px 0px 4px 4px;
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 14px;
                color: #ffffff;
                line-height: 23px;
                z-index: 9;
                padding: 0 6px;
            }
        }
        .videoDisplay {
          width: 100%;
          height: 100%;
          object-fit: cover;
        }
      }
      .itemName {
        position: absolute;
        bottom: 0;
        left: 0;
        height: 23px;
        width: 100%;
        background: rgba(22, 22, 22, 0.68);
        border-radius: 0px 0px 4px 4px;
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 400;
        font-size: 14px;
        color: #ffffff;
        line-height: 23px;
        z-index: 9;
        padding: 0 6px;
      }
    }
.videobutton {
            position: absolute;
            top: 40%;
            left: 50%;
            transform: translate(-50%);
            cursor: pointer;
            width: 40px;
            height: 40px;
            display: inline-block;
            background: #999;
            border-radius: 50%;
        }
        .videoDisplay {
            display: flex;
            flex-wrap: wrap;
            width: 200px;
            height: 200px;
    .videobutton {
      position: absolute;
      top: 40%;
      left: 50%;
      transform: translate(-50%);
      cursor: pointer;
      width: 40px;
      height: 40px;
      display: inline-block;
      background: #999;
      border-radius: 50%;
    }
    .videoDisplay {
      display: flex;
      flex-wrap: wrap;
      width: 200px;
      height: 200px;
        }
    }
        .videotime {
            position: relative;
        }
    .videotime {
      position: relative;
    }
    }
  }
        .content-right {
        position: relative;
        width: 0;
        flex-grow: 1;
        background: #19ad8d;
        margin-right: 17px;
        overflow: hidden;
  .content-right {
    position: relative;
    width: 0;
    flex-grow: 1;
    background: #19ad8d;
    margin-right: 17px;
    overflow: hidden;
        .content-map-popups {
            pointer-events: none;
    .content-map-popups {
      pointer-events: none;
            > * {
                pointer-events: auto !important;
            }
        }
    }
      > * {
        pointer-events: auto !important;
      }
    }
  }
}
</style>
src/views/layerManagement/components/folderFile.vue
@@ -49,13 +49,13 @@
          <div class="title">{{ layerParams.fileType === 1 ? '识别区' : '禁飞区' }}总数量</div>
          
          <div class="num">
            <span>{{ formData.total_count }}</span> 个
            <span class="totalQuantity">{{ formData.total_count }}</span> 个
          </div>
        </div>
        <div class="itemData">
          <div class="title">{{ layerParams.fileType === 1 ? '识别区' : '禁飞区' }}总面积</div>
          <div class="num">
            <span class="areaStyle">{{ formData.total_area }}</span> m²
            <span class="areaStyle">{{ formData.total_area.toFixed(2) }}</span> m²
          </div>
        </div>
      </div>
@@ -82,7 +82,7 @@
</template>
<script setup>
const emit = defineEmits(['refreshMethod']);
const emit = defineEmits(['refreshMethod','callParentMethod']);
import EventBus from '@/utils/eventBus';
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
@@ -152,6 +152,7 @@
};
const cancelHandel = () => {
  clearAllData();
   emit('callParentMethod');
};
const submitHandle = () => {
  if (!formData.value.name) {
@@ -185,6 +186,7 @@
        EventBus.emit('gettreeDataApi');
          emit('refreshMethod');
      clearAllData();
       emit('callParentMethod');
    });
  } else {
    //文件夹编辑
@@ -192,6 +194,7 @@
      ElMessage.success('编辑成功');
        EventBus.emit('gettreeDataApi');
      clearAllData();
       emit('callParentMethod');
    });
  }
};
@@ -279,11 +282,17 @@
          font-size: 14px;
          color: #ffffff;
        }
         .areaStyle {
        .totalQuantity {
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: bold;
        font-size: 20px;
        color: #FFFFFF;
                    font-weight: bold;
                    font-size: 20px;
        }
         .areaStyle {
                    font-family: Source Han Sans CN, Source Han Sans CN;
                    font-weight: bold;
                    font-size: 20px;
                    color: #FFFFFF;
                }
      }
      .itemData {
src/views/layerManagement/components/leftList.vue
@@ -44,7 +44,7 @@
    <div
      class="tableListBox"
      v-loading="loading"
      element-loading-background="rgba(0, 0, 0, 0.5)"
      element-loading-background="rgba(0, 0, 0, 0.2)"
      element-loading-text="加载中..."
    >
      <el-tree
@@ -167,25 +167,28 @@
  );
};
const selectAll = () => {
 layerParams.value.currentLocationFolderId = null;
layerParams.value.isSingleLocating = false
  if (treeRef.value) {
    const allFenceKeys = getAllFenceKeys();
    const allFenceNodes = treeData.value.flatMap(folder => folder.children);
    if (checkedKeys.value.length === allFenceKeys.length) {
      // 取消全选
      treeRef.value.setCheckedKeys([]);
      checkedKeys.value = [];
      checkedNodes.value = [];
      coverData.value = [];
      emit('update:coverData', coverData.value);
    } else {
      // 全选
      treeRef.value.setCheckedKeys(allFenceKeys);
      checkedKeys.value = allFenceKeys;
      checkedNodes.value = allFenceNodes;
      coverData.value = allFenceNodes;
      emit('update:coverData', coverData.value);
    }
    emit('update:coverData', coverData.value);
  }
};
// 定位按钮事件
@@ -402,7 +405,7 @@
  left: 0;
height: calc(100% - 32px);
  z-index: 99;
  width: 382px;
  width: 332px;
  overflow: hidden;
  background: rgba(0,0,0,0.51);
  backdrop-filter: blur(4px);
@@ -417,7 +420,7 @@
  margin-top: 10px;
padding-right: 12px;
  .inputName {
    width: 184px;
    width: 140px;
    height: 32px;
  }
  .searchAndReset {
@@ -426,34 +429,7 @@
    justify-content: right;
    align-items: center;
    margin-right: 10px;
    .reset,
    .search {
      display: flex;
      align-items: center;
      justify-content: center;
      width: 46px;
      height: 32px;
      cursor: pointer;
    }
    .reset {
      margin-left: 12px;
      background: #0d2b38;
      border-radius: 4px 4px 4px 4px;
      border: 1px solid #84cbd3;
      font-weight: 400;
      font-size: 14px;
      color: #ffffff;
    }
    .search {
      background: #1e2d51;
      border-radius: 4px 4px 4px 4px;
      border: 1px solid #c5d6ff;
      font-weight: 400;
      font-size: 14px;
      color: #ffffff;
    }
  }
}
.btnGroups {
@@ -471,8 +447,10 @@
  flex-grow: 1;
  padding-right: 12px;
  :deep(.el-tree-node__content) {
  height: 35px !important;
  line-height: 35px !important;
  height: 45px !important;
  line-height: 45px !important;
  // padding-left: 0 !important;
  border-bottom: 1px dotted  rgba(255,255,255,0.1);
  }
  .isTheTerritory {
src/views/layerManagement/components/rightEdit.vue
@@ -1,5 +1,5 @@
<template>
  <div class="rightContainer">
  <div class="rightContainer"  >
    <div class="contenttitle">{{ layerParams.fenceType === 1 ? '识别区' : '禁飞区' }}信息</div>
    <div class="centerBox">
      <div class="itemRow">
@@ -122,8 +122,10 @@
      </div>
    </div>
    <div class="btnGroups">
      <img src="/src/assets/images/layerManagement/savebtn.svg" alt="" @click="submitHandle" />
      <img src="/src/assets/images/layerManagement/cancelbtn.svg" @click="cancelHandel" alt="" />
      <img v-if="!layerParams.editingIsProhibited" src="/src/assets/images/layerManagement/savebtn.svg" alt="" @click="submitHandle" />
     <img v-if="layerParams.editingIsProhibited" src="/src/assets/images/layerManagement/closeBtn.svg" @click="cancelHandel" alt="" />
      <img v-else src="/src/assets/images/layerManagement/cancelbtn.svg" @click="cancelHandel" alt="" />
    </div>
  </div>
</template>
@@ -133,7 +135,7 @@
import dayjs from 'dayjs';
import { deitFenceApi, addFenceApi } from '@/api/layer/index';
import EventBus from '@/utils/eventBus';
const emit = defineEmits(['callParentMethod']);
const emit = defineEmits(['callParentMethod','update:loading']);
const layerParams = inject('layerParams');
const options = ref([]);
const timeValue = ref('');
@@ -150,6 +152,7 @@
  geo_data: '',
});
const loading = ref(false);
const detailData = ref(null);
const fenceArea = ref(0);
detailData.value = layerParams.value.editDetailData;
@@ -237,11 +240,16 @@
  layerParams.value.editingIsProhibited = false;
};
const cancelHandel = () => {
loading.value = false
 emit('update:loading', false);
  clearAllData();
  emit('callParentMethod');
  EventBus.emit('gettreeDataApi');
};
const submitHandle = () => {
if(layerParams.value.crossSurface){
  return ElMessage.warning('测区不支持交叉面')
}
  if (!formData.value.folder_id) {
    ElMessage.error('请选择文件夹');
    return;
@@ -258,6 +266,8 @@
    ElMessage.error('请绘制区域');
    return;
  }
  loading.value = true
  emit('update:loading', true);
  const params = {
    folder_id: formData.value.folder_id,
    name: formData.value.name,
@@ -277,22 +287,45 @@
  }
  if (layerParams.value.decideWhetherToAddOrEdit === 1) {
    //围栏新增
    addFenceApi(params).then(res => {
      ElMessage.success('新增成功');
      EventBus.emit('gettreeDataApi');
      clearAllData();
      emit('callParentMethod');
    });
    addFenceApi(params)
      .then(res => {
       if(res.data.code ===0 ){
          ElMessage.success('新增成功');
          EventBus.emit('gettreeDataApi');
          loading.value = false;
          emit('update:loading', false);
          clearAllData();
          emit('callParentMethod');
 }
      })
      .catch(err => {
        ElMessage.error('新增失败,请重试');
      })
      .finally(() => {
        loading.value = false;
         emit('update:loading', false);
      });
  } else if (layerParams.value.decideWhetherToAddOrEdit === 2) {
    //围栏编辑
    deitFenceApi(params).then(res => {
      ElMessage.success('编辑成功');
      EventBus.emit('gettreeDataApi');
      clearAllData();
      emit('callParentMethod');
    });
  }
      deitFenceApi(params)
      .then(res => {
      if(res.data.code === 0){
        ElMessage.success('编辑成功');
        EventBus.emit('gettreeDataApi');
        clearAllData();
        emit('callParentMethod');
        loading.value = false;
         emit('update:loading', false);
      }
      })
      .catch(err => {
        ElMessage.error('编辑失败,请重试');
      })
      .finally(() => {
        loading.value = false;
         emit('update:loading', false);
      });
        }
};
onMounted(() => {
  // 获取判断状态(默认设为 1)
@@ -344,11 +377,17 @@
  backdrop-filter: blur(4px);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
:deep(.el-select__wrapper.is-disabled .el-select__selected-item){
color: rgba(255,255,255,0.37) !important;
}
  :deep(.el-textarea__inner) {
    background: transparent !important;
    color: #ffffff !important;
    color: #ffffff;
  }
  :deep(.el-textarea.is-disabled .el-textarea__inner){
    color: var(--el-disabled-text-color) !important;
}
  .contenttitle {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: bold;
@@ -420,6 +459,7 @@
      height: 39px;
      cursor: pointer;
    }
  }
}
</style>
src/views/layerManagement/index.vue
@@ -8,8 +8,18 @@
        </el-tabs>
      </div>
      <!-- 地图 -->
      <div class="mapContainer">
        <div class="tool-tip" v-if="layerParams.addNest">点击地图生成测绘区域</div>
      <div class="mapContainer" v-loading="isPageLoading"
      element-loading-background="rgba(0, 0, 0, 0.5)"
      element-loading-text="加载中...">
      <div class="tool-tip warning" v-show="isShowWaringTip">
          <span class="icon">
            <el-icon><WarningFilled /></el-icon>
          </span>
          <span>测区不支持交叉面</span>
        </div>
         <div class="tool-tip" v-if="!isShowWaringTip && layerParams.addNest">
          {{ showToolTipText }}
        </div>
        <div id="layMap" class="ztzf-cesium"></div>
        <leftList
          v-if="
@@ -23,15 +33,17 @@
          @update:editFolder="handleFolder"
          :activeName="activeName"
          @newFencesMethods="newFencesMethods"
          :reset-check="resetCheck"
          :reset-check="resetCheck"
        ></leftList>
        <rightEdit
          v-if="layerParams.addNest || layerParams.editNest"
          @callParentMethod="parentMethod"
          @update:loading="handleLoadingChange"
        ></rightEdit>
        <folderFile
          @refreshMethod="refreshMethod"
          :activeName="activeName"
           @callParentMethod="parentMethod"
          v-if="layerParams.addFolder || layerParams.editFolder"
        ></folderFile>
      </div>
@@ -52,12 +64,14 @@
import * as Cesium from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import _, { cloneDeep, throttle } from 'lodash';
const isProd = import.meta.env.VITE_APP_ENV === 'production'
import { provide } from 'vue';
import { useStore } from 'vuex';
const isProd = import.meta.env.VITE_APP_ENV === 'production'
const store = useStore();
const userInfo = computed(() => store.getters.userInfo);
const areaCode = userInfo.value?.detail?.areaCode || '';
const showToolTipText = ref('点击地图生成测绘区域')
// 红色警告交叉面提示窗
const isShowWaringTip = ref(false);
const activeName = ref('自定义识别区');
@@ -81,6 +95,7 @@
      type: '2',
    },
  ];
  // 如果是生产环境,只返回自定义识别区
  if (isProd) {
    return allTabs.filter(tab => tab.type === '0');
@@ -107,6 +122,7 @@
  isDetailShow: false,
  isSingleLocating:false , //是否是单个定位
  currentLocationFolderId:null,
  crossSurface:false,//是否交叉面
});
const resetCheck = ref(false);
const handleClick = tab => {
@@ -121,7 +137,7 @@
     layerParams.value.addFolder = false
    layerParams.value.editFolder = false
    resetCheck.value = !resetCheck.value;
};
const refreshMethod = () => {
  getdataFolderApi();
@@ -150,7 +166,7 @@
  viewer.entities.removeAll();
  layerParams.value.editDetailData = val;
  drawPolygonExample.editThePatch(true);
  drawPolygonExample.drawTheArea(true);
  drawPolygonExample.drawTheArea(true);
  let geoDataArr = JSON.parse(val.geo_data);
  if (geoDataArr.length > 0) {
    geoDataArr.pop();
@@ -164,6 +180,12 @@
// 编辑文件夹
const handleFolder = val => {
parentMethod()
  selectDataList.value = val.children;
  if (selectDataList.value?.length > 0) {
    loadDataToMap(selectDataList.value);
  } else {
    viewer.entities.removeAll();
  }
  layerParams.value.editFolderData = val;
};
// 新增开启绘制
@@ -199,7 +221,7 @@
      console.warn(`数据 ${item.name} 坐标点不足,无法绘制`);
      return;
    }
    tbJwdList.push(...positions);
    let degreesArray = [];
    positions.forEach(pos => {
@@ -214,20 +236,20 @@
    border2: Cesium.Color.fromCssColorString('#ff0000'),
    border3: Cesium.Color.fromCssColorString('#00ffea'),
  };
   const fillColor = item.category_id === 1
      ? colorMap[1]
      : item.category_id === 2
        ? colorMap[2]
        : item.category_id === 3
          ? colorMap[3]
   const fillColor = item.category_id === 1
      ? colorMap[1]
      : item.category_id === 2
        ? colorMap[2]
        : item.category_id === 3
          ? colorMap[3]
          : Cesium.Color.YELLOW.withAlpha(0.5);
    const borderColor = item.category_id === 1
      ? colorMap.border1
      : item.category_id === 2
        ? colorMap.border2
        : item.category_id === 3
          ? colorMap.border3
    const borderColor = item.category_id === 1
      ? colorMap.border1
      : item.category_id === 2
        ? colorMap.border2
        : item.category_id === 3
          ? colorMap.border3
          : Cesium.Color.YELLOW;
      viewer.entities.add({
      id: `polygon_${item.id}`,
@@ -250,17 +272,17 @@
        material:borderColor,
        clampToGround: true,
      },
      zIndex: 99,
      zIndex: 99,
    });
  });
  });
 if (!layerParams.value.isSingleLocating) {
    focusOnAllFeatures();
    focusOnAllFeatures();
  }
  viewInstance.value?.addLeftClickEvent(null, handleFenceClick);
};
const focusOnAllFeatures = () => {
  if (tbJwdList.length === 0 || !viewer) return;
  // 判断是否有指定定位的文件夹
  if (layerParams.value.currentLocationFolderId) {
    // 筛选当前文件夹下的所有子节点坐标
@@ -270,7 +292,7 @@
        const positions = parseGeoDataToPositions(item.geo_data, item.altitude);
        return positions.length >= 3 ? positions : [];
      });
    if (targetFolderPositions.length > 0) {
      const positionsData = targetFolderPositions.map(pos => [
        pos.lng,
@@ -304,16 +326,16 @@
const handleFenceClick = movement => {
  if (!viewer) return;
  layerParams.value.editDetailData = null;
if(activeName.value !== '国土空间规划') {
    const pickedObjects = viewer.scene.drillPick(movement.position, 10);
    const pickedObjects = viewer.scene.drillPick(movement.position, 10);
  // 遍历所有被点击的实体,找到围栏类型(customType: 'fence_polygon')
  let selectedEntity = null;
  for (let i = 0; i < pickedObjects.length; i++) {
    const pick = pickedObjects[i];
    if (Cesium.defined(pick.id) && pick.id?.customType === 'fence_polygon') {
      selectedEntity = pick.id;
      break;
      break;
    }
  }
    // viewer.entities.values.forEach(entity => {
@@ -338,9 +360,6 @@
      layerParams.value.editingIsProhibited = true;
    }
  }
}else {
}
};
@@ -379,13 +398,12 @@
};
// 编辑/绘制
const loadPlanarRoute = async (positions = null, save = false) => {
  isShowWaringTip.value = false;
  if (positions) {
    curPolygonPosition = positions.map(item => {
      let cartographic = Cesium.Cartographic.fromCartesian(item);
      let lng = Cesium.Math.toDegrees(cartographic.longitude); // 经度
      let lat = Cesium.Math.toDegrees(cartographic.latitude); // 纬度
      let height = Cesium.Math.toDegrees(cartographic.height); //高度
      let height = Cesium.Math.toDegrees(cartographic.height); //高度
      return {
        lng: _.round(lng, 6),
        lat: _.round(lat, 6),
@@ -394,6 +412,15 @@
    });
  }
  let polygon = curPolygonPosition.map(item => [item?.lng, item?.lat]);
  if(polygon.length ===0){
    isShowWaringTip.value = false
  }
  if(polygon.length > 0 && !isShowWaringTip.value){
    showToolTipText.value = '测绘区域已绘制完成'
  }else {
    showToolTipText.value = '点击地图生成测绘区域'
  }
 if(polygon.length > 0){
      polygon.push([curPolygonPosition[0]?.lng, curPolygonPosition[0]?.lat])
 }
@@ -416,6 +443,7 @@
};
drawPolygonExample.subscribe('getShowWaringTip', data => {
  isShowWaringTip.value = data;
  layerParams.value.crossSurface = data
});
const throttleLoadPlanarRoute = throttle(loadPlanarRoute, 200);
drawPolygonExample.subscribe('getPolygonPositions', data => {
@@ -444,6 +472,11 @@
  } else {
    cesium.removeEventListener('contextmenu', preventDefault);
  }
};
// 添加loading
const isPageLoading = ref(false);
const handleLoadingChange = (status) => {
  isPageLoading.value = status;
};
provide('layerParams', layerParams);
onMounted(() => {
@@ -517,6 +550,18 @@
  position: relative;
  width: 100%;
  height: 80vh;
    .warning {
    position: absolute;
    top: 234px;
    color: #fff;
    background: rgba(140, 0, 0, 0.4) !important;
    .icon {
      display: flex;
      align-items: center;
      color: #ff1f1f;
    }
  }
  #layMap {
    width: 100%;
    height: 100%;
src/views/resource/components/spotDetails.vue
@@ -81,14 +81,30 @@
              </el-table-column>
              <el-table-column label="操作"  align="center" v-if="props.title === '图斑编辑'">
                <template #default="scope">
                  <span class="operationspan" @click="handleDelete(scope.row)">删除</span>
                  <span
                  <!-- <span class="operationspan" @click="handleDelete(scope.row)">删除</span> -->
            <el-button icon="el-icon-delete" link @click="handleDelete(scope.row)"></el-button>
                  <!-- <span
                    class="operationspan"
                    v-if="scope.row.is_exception == 2"
                    @click.stop="handleSelectionChange(scope.row)"
                  >
                    {{ isEditing && selectionIds === scope.row.id ? '取消编辑' : '编辑' }}
                  </span>
                  </span> -->
               <template v-if="scope.row.is_exception == 2">
                    <el-button
                      v-if="!isEditing || selectionIds !== scope.row.id"
                      icon="el-icon-edit"
                      link
                      @click.stop="handleSelectionChange(scope.row)"
                    />
                    <el-button
                      v-else
                      icon="el-icon-circle-close"
                      link
                      @click.stop="handleSelectionChange(scope.row)"
                    />
                  </template>
                </template>
              </el-table-column>
            </el-table>
@@ -913,4 +929,9 @@
  height: 100%;
  width: 100%;
}
.el-button {
 padding: 0;
      color: #fff;
      width: 17px;
}
</style>
src/views/system/user.vue
@@ -490,7 +490,7 @@
                label: '所属部门',
                prop: 'deptId',
                type: 'tree',
                multiple: true,
                multiple: false,
                dicData: [],
                // props: {
                //   label: 'title',
@@ -793,7 +793,7 @@
        }
      }
      row.deptId = func.join(row.deptId)
      // row.deptId = func.join(row.deptId)
      row.roleId = func.join(row.roleId)
      row.postId = func.join(row.postId)
      row.name = row.realName
@@ -836,7 +836,7 @@
        }
      }
      row.deptId = func.join(row.deptId)
      // row.deptId = func.join(row.deptId)
      row.roleId = func.join(row.roleId)
      row.postId = func.join(row.postId)
      row.name = row.realName
@@ -1061,9 +1061,9 @@
      if (['edit', 'view'].includes(type)) {
        getUser(this.form.id).then(res => {
          this.form = res.data.data
          if (this.form.hasOwnProperty('deptId')) {
            this.form.deptId = func.split(this.form.deptId)
          }
          // if (this.form.hasOwnProperty('deptId')) {
          //   this.form.deptId = func.split(this.form.deptId)
          // }
          if (this.form.hasOwnProperty('roleId')) {
            this.form.roleId = func.split(this.form.roleId)
          }
src/views/tickets/component/AddEditDetails.vue
New file
@@ -0,0 +1,550 @@
<template>
  <el-dialog
    class="ztzf-dialog-mange"
    modal-class="add-edit-details"
    v-model="isShowAddEditDetails"
    :title="txtTitle"
    width="70%"
    :close-on-click-modal="false"
    :destroy-on-close="true"
    @close="cancel"
    :before-close="handleBeforeClose"
  >
    <div class="add-edit-details">
      <div class="process" v-if="txtTitle !=='新建工单'">
        <div class="order_title">{{ formParams.name }}</div>
        <div class="custom-steps-container">
          <!-- 标题行 -->
          <div class="steps-titles">
            <div class="step-title" v-for="(record, index) in formParams.record_list" :class="{ active: record.user_id >= 0 }" :key="index">
              {{ record.status_str }}
            </div>
          </div>
          <!-- Element Steps 组件 -->
          <el-steps :active="formParams.active" align-center class="custom-steps">
            <el-step v-for="(record, index) in formParams.record_list" :key="index">
              <template #description>
                <span class="step-description" style="position: relative; display: inline-block">
                  {{ record.user_name }}
                </span>
                <span class="step-timer">
                  {{ record.interval_time_str }}
                </span>
                <div class="step-description">
                  {{ record.create_time_str }}
                </div>
              </template>
            </el-step>
          </el-steps>
        </div>
      </div>
      <el-form ref="orderFormRef" :model="formParams" :rules="rules" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="formParams.name" placeholder="请输入工单名称" maxlength="100" show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="formParams.file_id" placeholder="请选择航线" filterable @change="getFlyingNest" :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
                <el-option v-for="item in lineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number v-model="formParams.rth_altitude" :min="50" :max="500" :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="formParams.device_sns" placeholder="请选择机巢" multiple :disabled="(txtTitle === '工单审核' || txtTitle === '工单详情')">
                <el-option v-for="item in drone_sns" :key="item.device_sn" :label="item.nickname" :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="formParams.ai_types" placeholder="请选择关联算法" multiple :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
                <el-option v-for="item in aiTypeList" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12"  v-if="txtTitle === '新建工单'">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="formParams.content" rows="4" placeholder="请输入工单内容" maxlength="255" show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12" prop="statusStr" v-else>
            <el-form-item label="当前状态">
              {{ getOrderStatus(formParams.status) }}
            </el-form-item>
          </el-col>
          <el-col :span="7">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"
                v-model="formParams.date_range"
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                :disabled-date="disabledDate" />
            </el-form-item>
          </el-col>
          <el-col :span="2">
            <el-select v-model="formParams.rep_fre_type" placeholder="请选择频次" :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'">
              <el-option v-for="item in weeks" :key="item" :label="item" :value="item" />
            </el-select>
          </el-col>
          <el-col :span="3">
            <el-time-picker :style="{ width: pxToRem(140) }"
                            v-model="formParams.deal_time"
                            :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"
                            prop="deal_time"
                            value-format="HH:mm"
                            :picker-options="{ selectableRange: '00:00 - 23:59'}" />
          </el-col>
        </el-row>
        <el-row :gutter="20" v-if="txtTitle === '工单审核' || txtTitle === '工单详情' || txtTitle === '工单编辑'">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="formParams.content" rows="4" placeholder="请输入工单内容" maxlength="255"
                        show-word-limit :disabled="txtTitle === '工单审核' || txtTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="isShowAddEditDetails" ref="mapContainerRef"></map-container>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <template #footer v-if="txtTitle === '新建工单'">
      <div class="dialog-footer">
        <el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
<!--        <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button>-->
        <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
      </div>
    </template>
    <template #footer v-if="txtTitle === '工单审核'">
      <div class="dialog-footer">
        <el-button type="primary"
                   v-if="formParams.status == 1 && permission.orderLogpass"
                   @click="orderPass(formParams.id)">通过
        </el-button>
        <el-button type="danger"
                   v-if="formParams.status == 1 && permission.rejection_btn"
                   @click="orderReject(formParams.id)">驳回
        </el-button>
        <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
      </div>
    </template>
    <template #footer v-if="txtTitle === '工单编辑'">
      <div class="dialog-footer">
        <el-button type="danger"
                   v-if="formParams.status == 0 || (formParams.status == 2 && userInfo.user_id === formParams.create_user)"
                   @click="submitForm(1)" icon="el-icon-position">发布
        </el-button>
        <el-button @click="cancel" icon="el-icon-circle-close">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { pxToRem, pxToRemNum } from '@/utils/rem'
import { ref, shallowRef, onMounted, computed } from 'vue';
import { newGetWorkspacesPage } from '@/api/resource/wayline'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getFlyingNestBy } from '@/api/device/device'
import {
  saveUpdateOrderLog,
  orderLogDetails,
  orderLogPass,
  orderLogReject,
  orderLogRecall, getWaylineMaxTerrainHeight
} from '@/api/tickets/orderLog';
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn' // 导入中文语言包
import _ from 'lodash'
import { ElMessage, ElMessageBox } from 'element-plus';
import weekday from 'dayjs/plugin/weekday'
dayjs.extend(weekday)
dayjs.locale('zh-cn')
import { useStore } from 'vuex'
const emit = defineEmits(['refresh']);
const store = useStore()
const userInfo = computed(() => store.state.user.userInfo)
const permission = computed(() => store.state.user.permission);
// safeHeight 计算属性
const safeHeight = computed(() => {
  const height = store.state.common.safeHeight
  return Number(height) || 0 // 确保是数字类型
})
// positionsArr 计算属性
const positionsArr = computed(() => {
  const arr = store.state.common.positionsArr
  return arr || [] // 确保是数组类型
})
const txtTitle = inject('txtTitle')
const rowObj = inject('rowObj')
const isShowAddEditDetails = defineModel('show')
const mapContainerRef = ref(null)
const orderFormRef = ref(null)
const formParams = ref({})
const rules = ref({
  name: [
    { required: true, message: '请输入工单名称', trigger: 'blur' },
    { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
  ],
    file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
    device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
    content: [
    { required: true, message: '请输入工单内容', trigger: 'blur' },
    { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
  ],
    date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
    rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
    deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
})
//周期
const weeks = ref(['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'])
let lineList = inject('lineList')
let aiTypeList = inject('aiTypeList')
// 获取航线
// let lineList = ref([])
// function getLines() {
//   const formData = {
//     name: '',
//     waylineType: undefined,
//     current: 1,
//     size: 99999999,
//     descs: 'update_time'
//   }
//   newGetWorkspacesPage(formData).then(res => {
//     lineList.value = res.data.data.records
//   })
// }
function getOrderStatus (status) {
  const statusTextMap = {
    0: '草稿',
    1: '待审核',
    2: '已驳回',
    3: '已通过'
  }
  return statusTextMap[status] || '未知状态'
}
function disabledDate (time) {
  return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
}
// 关联机巢
const drone_sns = ref([])
function getFlyingNest (waylineId) {
  console.log('选择航线', lineList.value)
  // 选中航线对象
  initMapLine({}, polygon => {
    const currentLine = lineList.value.find(item => item.wayline_id === waylineId)
    const params = {
      type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
      wayline_id: waylineId,
      polygon
    }
    // 重置
    formParams.value.device_sns = []
    drone_sns.value = []
    getFlyingNestBy(params).then(res => {
      drone_sns.value = res.data.data
      initMapLine()
    })
  })
}
// 获取算法
// let aiTypeList = ref([])
// function aitypeList() {
//   getDictionaryByCode('SF').then(res => {
//     aiTypeList.value = res.data.data['SF']
//   })
// }
// initMapLine 方法
const initMapLine = async (infos = {}, cb = () => {}) => {
  const currentLine = lineList.value.find(item => item.wayline_id == formParams.value.file_id)
  if (!currentLine) return
  await nextTick()
  if (mapContainerRef.value?.initAddEntity) {
    mapContainerRef.value.initAddEntity('polyline', {
      url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key}?_t=${new Date().getTime()}`,
      type: currentLine.wayline_type,
      cb,
      infos
    })
  }
}
function formatDate (date) {
  if (!date) return undefined
  const d = new Date(date)
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(
    d.getDate()
  ).padStart(2, '0')} 00:00:00`
}
async function returnHeight() {
  let checkedList = drone_sns.value.filter(item => formParams.value.device_sns.includes(item.device_sn));
  const maxItem = checkedList.reduce((max, item) => {
    return item.drone_height > max.drone_height ? item : max;
  });
  // 返航绝对高度
  let backHeight = formParams.value.rth_altitude // + maxItem.drone_height
  console.log(backHeight,'backHeight')
  // 安全起飞高度
  let positions = positionsArr.value
  positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
  positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
  let resultHeight = 0
  await getWaylineMaxTerrainHeight(positions).then(res => {
    resultHeight = safeHeight.value > (res.data.data + 30) ? safeHeight.value : (res.data.data + 30)
  })
  let valueHeight = _.round(backHeight - resultHeight, 2)
  return _.round(backHeight - resultHeight, 2)
}
function submitForm(status) {
  orderFormRef.value?.validate(async (valid, fields) => {
    if (valid) {
      console.log('表单验证通过')
      formParams.value.begin_time = formatDate(formParams.value.date_range[0])
      formParams.value.end_time = formatDate(formParams.value.date_range[1])
      // 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
      if (formParams.value.deal_time) {
        const selectedDate = dayjs(formParams.value.date_range[0]).format('YYYY-MM-DD')
        const selectedTime = dayjs(selectedDate + ' ' + formParams.value.deal_time).toDate()
        const now = new Date()
        if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
          return ElMessage({message: '任务时间不能小于当前时间', type: 'warning'})
        }
      }
      let checkedList = drone_sns.value.filter(item => formParams.value.device_sns.includes(item.device_sn));
      const maxItem = checkedList.reduce((max, item) => {
        return item.drone_height > max.drone_height ? item : max;
      });
      // 返航绝对高度
      let backHeight = formParams.value.rth_altitude // + maxItem.drone_height
      console.log(backHeight,'backHeight')
      // 安全起飞高度
      let positions = positionsArr.value
      positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
      positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
      let resultHeight = 0
      let valueHeight = 0
      await getWaylineMaxTerrainHeight(positions).then(res => {
        resultHeight = (safeHeight.value + maxItem.drone_height) > (res.data.data + 30) ? (safeHeight.value + maxItem.drone_height) : (res.data.data + 30)
        console.log(resultHeight,maxItem.drone_height, '999')
        valueHeight = (resultHeight - maxItem.drone_height) > backHeight ? _.round(resultHeight - maxItem.drone_height) : 0
      })
      if (valueHeight > 0) {
        await ElMessageBox.confirm(`当前返航高度存在安全隐患,建议调整为${valueHeight}米以上后进行发布`, '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
      }
      const submitData = {
        ...formParams.value,
        status: status,
        ai_types: formParams.value.ai_types?.length ? formParams.value.ai_types : []
      }
      await saveUpdateOrderLog(submitData)
      if (txtTitle === '新建工单') {
        ElMessage({message: '工单创建成功', type: 'success'})
      } else {
        ElMessage({message: '工单发布成功', type: 'success'})
      }
      cancel()
    } else {
      console.log('表单验证失败', fields)
    }
  })
}
// 审核通过
function orderPass(id) {
  orderLogPass(id).then(res => {
    ElMessage.success('审核通过')
    cancel()
  })
}
// 驳回
const orderReject = async (id) => {
  try {
    const { value } = await ElMessageBox.prompt('', '驳回原因', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      inputPlaceholder: '请输入驳回原因',
      inputValidator: (value) => {
        if (!value) {
          return '驳回原因不能为空'
        }
      }
    })
    // 用户输入了内容并点击确定
    await orderLogReject(id, value)
    ElMessage.success('驳回成功')
    cancel()
  } catch (error) {
    // 用户点击取消或关闭弹窗
    if (error !== 'cancel' && error !== 'close') {
      ElMessage.error('驳回失败')
      console.error('驳回错误:', error)
    }
  }
}
const initMapPosition = async () => {
  await nextTick()
  if (mapContainerRef.value?.initAddEntity) {
    mapContainerRef.value.initAddEntity('initPosition')
  }
}
// 监听txtTitle
watch(isShowAddEditDetails, async (newVal, oldVal) => {
  console.log('txtTitle 发生变化:', newVal, oldVal)
  formParams.value = {rth_altitude:120}
  if (txtTitle.value === '工单审核' || txtTitle.value === '工单详情' || txtTitle.value === '工单编辑') {
    const response = await orderLogDetails(rowObj.value.id)
    const data = response.data.data
    formParams.value = {
      ...data
    }
    console.log(formParams.value, '898789')
    drone_sns.value = data.device_list
    initMapLine(data.device_map_infos)
  } else {
    initMapPosition()
  }
}, {
  immediate: true // 立即执行一次
})
function handleBeforeClose() {
  cancel()
}
function cancel() {
  formParams.value = {}
  isShowAddEditDetails.value = false
  emit('refresh');
}
// onMounted(async () => {
//   aitypeList()
//   getLines()
// });
</script>
<style lang="scss" scoped>
.add-edit-details {
  .safe-height {
    :deep(.el-form-item__label) {
      width: 120px !important;
    }
    :deep(.el-input-number) {
      width: 180px !important;
    }
  }
  .week_pc_one {
    flex: 1;
    .week-r {
      margin-right: 4px;
    }
    &> ::v-deep(div) {
      width: 100% !important;
    }
  }
  .process {
    .order_title {
      height: 30px;
      line-height: 30px;
      text-align: center;
      font-size: 20px;
      font-weight: bold;
      color: #333;
    }
    .custom-steps-container {
      width: 100%;
      margin: 7px 0;
    }
    .steps-titles {
      display: flex;
      justify-content: space-between;
      margin-bottom: 16px;
      position: relative;
    }
    .step-title {
      text-align: center;
      flex: 1;
      font-size: 16px;
      color: #999;
      position: relative;
      padding-bottom: 10px;
    }
    .step-title.active {
      color: #409eff;
      font-weight: bold;
    }
    .custom-steps {
      margin-top: -20px;
      /* 向上移动与标题重叠 */
    }
    .el-step__head {
      margin-top: 0;
    }
    .el-step__description {
      margin-top: 8px;
      padding: 0 20px;
    }
    .step-description {
      font-size: 14px;
      color: #666;
      line-height: 2.2;
    }
  }
}
</style>
src/views/tickets/component/SearchBox.vue
New file
@@ -0,0 +1,313 @@
<template>
  <div class="search-box-test">
    <el-form :model="searchForm" inline>
      <div class="search-first">
        <el-form-item label="关键字:">
          <el-input v-model="searchForm.key_word" placeholder="编号/名称/内容/姓名" clearable />
        </el-form-item>
        <el-form-item label="选择日期:" :style="{ width: pxToRem(460) }">
          <el-date-picker
            v-model="dateRange"
            type="daterange"
            range-separator="至" start-placeholder="开始日期"
            end-placeholder="结束日期"
            :default-value="datePickerDefaultVal" @change="handleSearch" />
        </el-form-item>
        <el-form-item label="关联航线:">
          <el-select
            :teleported="false"
            v-model="searchForm.file_id"
            placeholder="请选择关联航线"
            clearable
            @change="handleSearch"
          >
            <el-option
              v-for="item in lineList"
              :key="item.wayline_id"
              :label="item.name"
              :value="item.wayline_id"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="关联算法:">
          <el-select
            :teleported="false"
            v-model="searchForm.ai_typesValue"
            placeholder="请选择关联算法"
            clearable @change="handleSearch">
            <el-option
              v-for="item in aiTypeList"
              :key="item.dictKey"
              :label="item.dictValue"
              :value="item.dictKey"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="工单状态:">
          <el-select
            :teleported="false"
            :disabled="activeTab === 'WAIT_AUDIT' || activeTab === 'REJECTED' || activeTab === 'PASS'"
            v-model="searchForm.status"
            placeholder="请选择单状态"
            @change="handleSearch"
            clearable>
            <el-option
              v-for="item in orderStatus"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="工单周期:" :style="{ width: pxToRem(460) }">
          <el-date-picker
            v-model="cycleDateRange"
            type="daterange"
            range-separator="至" start-placeholder="开始日期"
            end-placeholder="结束日期"
            :default-value="datePickerDefaultVal" @change="handleSearch"/>
        </el-form-item>
        <el-form-item label="选择频次:">
          <el-select v-model="searchForm.rep_fre_type" placeholder="请选择频次" clearable @change="handleSearch">
            <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
          </el-select>
        </el-form-item>
        <el-form-item label="执行时间:">
          <el-time-picker @change="handleSearch"
            v-model="searchForm.deal_time"
            placeholder="请选择执行时间"
            prop="deal_time"
            value-format="HH:mm"
            :picker-options="{ selectableRange: '00:00 - 23:59'}" />
        </el-form-item>
        <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
        <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
      </div>
    </el-form>
  </div>
</template>
<script setup>
import { pxToRem } from '@/utils/rem';
import { calculateDefaultRange } from '@/utils/util'
import { newGetWorkspacesPage } from '@/api/resource/wayline'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import dayjs from 'dayjs';
// 选择日期
const dateRange = ref('')
let datePickerDefaultVal = calculateDefaultRange()
// 选择周期
const cycleDateRange = ref('')
const searchForm = reactive({
  ai_types: [],
  ai_typesValue: '',
  deal_time: "",
  file_id: "",
  key_word: "",
  status: '',
  rep_fre_type: '',
})
const emit = defineEmits(['search']);
const rowObj = inject('rowObj')
const activeTab = inject('activeTab')
const lineList = inject('lineList')
const aiTypeList = inject('aiTypeList')
watch(activeTab, (newTab, oldTab) => {
  if (newTab === 'all') {
    searchForm.status = ''
  }
  if(newTab === 'WAIT_AUDIT') {
    searchForm.status = '1'
  }
  if(newTab === 'REJECTED') {
    searchForm.status = '2'
  }
  if(newTab === 'PASS') {
    searchForm.status = '3'
  }
  emit('search', searchForm);
})
// 周期
const cycles = ref(['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'])
// 工单状态
const orderStatus = ref([
  { label: '待审核', value: '1' },
  { label: '已驳回', value: '2' },
  { label: '已通过', value: '3' }
])
const timeFormat = 'YYYY-MM-DD HH:mm:ss';
// 搜索
const handleSearch = () => {
  if (!dateRange.value) {
    dateRange.value = [];
  }
  if (!cycleDateRange.value) {
    cycleDateRange.value = [];
  }
  // if (dateRange.value && dateRange.value.length) {
  //   checked.value = '';
  //   searchForm.date_enum = '';
  // }
  // 提交至store
  let params = {
    ...searchForm,
    create_start_date: dateRange.value.length
      ? dayjs(dateRange?.value[0]).startOf('day').format(timeFormat)
      : null,
    create_end_date: dateRange?.value.length
      ? dayjs(dateRange?.value[1]).endOf('day').format(timeFormat)
      : null,
    start_date: cycleDateRange?.value.length
      ? dayjs(cycleDateRange?.value[0]).startOf('day').format(timeFormat)
      : null,
    end_date: cycleDateRange?.value.length
      ? dayjs(cycleDateRange?.value[1]).startOf('day').format(timeFormat)
      : null,
  };
  emit('search', params);
};
function handleReset () {
  searchForm.ai_types = []
  searchForm.ai_typesValue = ''
  searchForm.deal_time = ""
  searchForm.file_id = ""
  searchForm.key_word = ""
  if (activeTab.value === 'all') {
    searchForm.status = ''
  }
  searchForm.rep_fre_type = ''
  dateRange.value = ''
  searchForm.create_start_date = null
  searchForm.create_end_date = null
  cycleDateRange.value = ''
  searchForm.start_date = null
  searchForm.emd_date = null
  emit('search', searchForm);
}
// onMounted(() => {
//   aitypeList()
//   getLines()
// });
</script>
<style lang="scss" scoped>
// .taskAlgorithm {
//     :deep() {
//         .task-algorithm > div {
//             width: 233px !important;
//         }
//     }
// }
.search-box-test {
  margin-bottom: 16px;
  transition: all 0.3s;
  .search-first {
    position: relative;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 20px 15px; // 设置行间距和列间距
    .more {
      cursor: pointer;
      width: 32px;
      height: 32px;
      color: #1c5cff;
      line-height: 32px;
      font-size: 16px;
      margin: 0 28px;
    }
    .search-btn {
      // position: absolute;
      // right: 0;
      display: flex;
      justify-content: space-between;
      cursor: pointer;
      font-size: 14px;
      .btn {
        width: 80px;
        height: 32px;
        border-radius: 4px 4px 4px 4px;
        line-height: 32px;
        display: flex;
        justify-content: center;
        align-items: center;
        // margin-left: px;
        img {
          width: 14px;
          height: 14px;
        }
      }
      .search {
        background: #409eff;
        color: #fff;
        margin-right: 10px;
      }
      .clear {
        border: 1px solid #d2d1d1;
        color: #676767;
        margin-right: 10px;
      }
      .add {
        background: #ff9243;
        color: #fff;
      }
    }
    .time-card {
      text-align: center;
      background: #ffffff;
      border-radius: 4px 0px 0px 4px;
      border: 1px solid #e5e5e5;
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 400;
      font-size: 14px;
      color: #7c8091;
      display: flex;
      height: 32px;
      .card-item {
        width: 60px;
        height: 100%;
        line-height: 32px;
        cursor: pointer;
      }
      .active {
        background: #ffffff;
        border-radius: 0px 0px 0px 0px;
        border: 1px solid #1c5cff;
        color: #1441ff;
      }
    }
  }
  :deep(.el-form) {
    :deep(.el-input__wrapper.is-disabled) {
      box-shadow: 0 0 0 1px #026ad6;
    }
    .el-form-item__label {
      width: 80px;
    }
    .el-form-item {
      margin-bottom: 0;
      width: 280px;
      margin-right: 20px;
      .el-form-item__label {
        color: #363636;
        line-height: 32px;
      }
    }
  }
}
</style>
src/views/tickets/orderLog-copy1.vue
File was deleted
src/views/tickets/orderLog.vue
@@ -1,1708 +1,511 @@
<!-- 任务统计表格 -->
<template>
  <basic-container>
  <div class="order-log-update">
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane v-for="tab in filteredTabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
        <basic-main-content>
          <!-- 查询条件筛选栏 -->
          <div class="ztzf-form-search">
            <el-form :model="filters" inline>
              <el-row :gutter="24">
                <el-col :span="4">
                  <el-form-item label="关键字:">
                    <el-input v-model="filters.key_word" placeholder="编号/名称/内容/姓名" clearable
                              @keyup.enter="handleSearch" />
                  </el-form-item>
                  <!--              <div class="search-bar-box-item">-->
                  <!--                <el-select placeholder="请选择所属单位" v-model="filters.create_dept" clearable>-->
                  <!--                  <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />-->
                  <!--                </el-select>-->
                  <!--              </div>-->
                </el-col>
                <el-col :span="8">
                  <el-form-item label="选择日期:">
                    <el-date-picker @change="handleSearch" v-model="filters.dateRange" type="daterange"
                                    range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
                                    :default-value="datePickerDefaultVal" />
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="关联航线:">
                    <el-select @change="handleSearch" v-model="filters.file_id" placeholder="请选择关联航线" filterable
                               clearable>
                      <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                                 :value="item.wayline_id" />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="关联算法:">
                    <el-select @change="handleSearch" v-model="filters.ai_types" placeholder="关联算法" clearable>
                      <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue"
                                 :value="item.dictKey" />
                    </el-select>
                  </el-form-item>
                  <!-- <div class="search-bar-box-item">
                    <el-select v-model="filters.type" placeholder="请选择工单类型" clearable>
                      <el-option v-for="item in types" :key="item.dictValue" :label="item.dictValue"
                        :value="item.dictKey" />
                    </el-select>
                  </div> -->
                </el-col>
                <el-col :span="4">
                  <el-form-item label="工单状态:">
                    <el-select @change="handleSearch" v-model="filters.status" placeholder="请选择工单状态" clearable
                               :disabled="activeTab !== 'all'">
                      <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" />
                    </el-select>
                  </el-form-item>
                </el-col>
              </el-row>
              <el-row :gutter="24">
                <el-col :span="8">
                  <el-form-item label="工单周期:">
                    <el-date-picker @change="handleSearch" v-model="filters.cycleDateRange" type="daterange"
                                    range-separator="至" start-placeholder="工单周期开始日期" end-placeholder="工单周期结束日期"
                                    :default-value="datePickerDefaultVal" />
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="选择频次:">
                    <el-select @change="handleSearch" v-model="filters.rep_fre_type" placeholder="请选择频次" clearable>
                      <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="执行时间:">
                    <el-time-picker @change="handleSearch" v-model="filters.deal_time" placeholder="请选择执行时间"
                                    prop="deal_time" value-format="HH:mm" :picker-options="{
                        selectableRange: '00:00 - 23:59',
                      }" />
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索
                  </el-button>
                  <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
                </el-col>
              </el-row>
            </el-form>
          </div>
          <!-- 表格部分 -->
          <avue-crud class="ztzf-public-general-avue-crud" :data="tableData" :option="option" v-model:page="page"
                     ref="crud" :table-loading="loading" @current-change="currentChange" @refresh-change="refreshChange"
                     @on-load="onLoad" @search-change="searchChange" @size-change="sizeChange" v-if="activeTab === tab.name">
            <template #job_info_num="{ row }">
              <el-tooltip-copy :content="row.job_info_num" :showCopyText="true" textAlign="left">
                {{ row.job_info_num }}
              </el-tooltip-copy>
            </template>
            <template #name="{ row }">
              <el-tooltip-copy :content="row.name" :showCopyText="true" textAlign="left">
                {{ row.name }}
              </el-tooltip-copy>
            </template>
            <template #menu-left>
              <el-button v-if="hasAddBtnPermission() && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus"
                         @click="handleAdd">新建工单
              </el-button>
              <el-button type="success" plain icon="el-icon-download" @click="exportData">导出
              </el-button>
            </template>
            <template #menu="{ row }">
              <div class="menu-custom-box">
                <template v-if="row.status == 1">
                  <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view"
                             @click="handleCheckDetail(row)">审核
                  </el-button>
                </template>
                <!-- v-if="
                    (userInfo.user_id == row.create_user || hasRecallPaddingBtnPermission()) &&
                    row.status == 1
                  " -->
                <template v-if="row.status == 1">
      <el-tab-pane v-for="tab in tabsList" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
        <SearchBox @search="searchClick"></SearchBox>
        <el-button v-if="permission?.order_log_add && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus" @click="handleAddOrder">新建工单
        </el-button>
        <el-button type="success" plain icon="el-icon-download" @click="exportData">导出
        </el-button>
        <div class="task-table">
          <el-table border :data="orderListTable" class="custom-header" @cell-click="handleCellClick">
            <el-table-column label="序号" type="index" width="60">
              <template #default="{ $index }">
                {{ ($index + 1 + (orderListParams.current - 1) * orderListParams.size).toString().padStart(2,
                '0') }}
              </template>
            </el-table-column>
            <el-table-column prop="job_info_num" label="工单编号" width="150" show-overflow-tooltip>
              <template #default="scope">
                <el-tooltip-copy :content="scope.row.job_info_num" :showCopyText="true">
                  {{scope.row.job_info_num}}
                </el-tooltip-copy>
              </template>
            </el-table-column>
            <el-table-column prop="name" label="工单名称" width="100" show-overflow-tooltip>
              <template #default="scope">
                <el-tooltip-copy :content="scope.row.name" :showCopyText="true">
                  {{scope.row.name}}
                </el-tooltip-copy>
              </template>
            </el-table-column>
            <el-table-column prop="dept_name" label="工单状态" width="88">
              <template #default="scope">
                <el-tag :type="getStatusTagType(scope.row.status)">{{ getOrderStatus(scope.row.status) }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="dept_name" label="所属单位" width="88" show-overflow-tooltip/>
            <el-table-column prop="create_time" label="创建时间" width="120" show-overflow-tooltip/>
            <el-table-column prop="job_num" label="已执行次数" width="100" align="center" />
            <el-table-column prop="content" label="工单内容" show-overflow-tooltip align="center"/>
            <el-table-column prop="wayline_name" label="关联航线" show-overflow-tooltip />
            <el-table-column prop="ai_type_str" label="关联算法" width="100" align="center" show-overflow-tooltip/>
            <el-table-column prop="device_names" label="关联机巢" width="100" align="center" show-overflow-tooltip/>
            <el-table-column prop="creator_name" label="创建人" width="100" align="center" show-overflow-tooltip />
            <el-table-column prop="cycle_time_value" label="工单周期频次" width="110" align="center" show-overflow-tooltip />
            <el-table-column label="操作" width="200" fixed="right" align="center">
              <template #default="scope">
<!--                {{scope.row.status}}-->
                <template v-if="scope.row.status === 1">
                  <el-button type="text" icon="el-icon-view" v-if="permission.order_log_review" @click="handleCheckDetail(scope.row, '工单审核')">审核</el-button>
                  <!--待审核状态-->
                  <el-button type="text" icon="el-icon-warning" v-if="hasRecallPaddingBtnPermission()"
                             @click="orderLogRecall(row.id)">撤回
                  </el-button>
                  <!-- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button> -->
                  <el-button type="text" icon="el-icon-warning" v-if="permission.order_log_recall" @click="revokeOrder(scope.row.id)">撤回</el-button>
                  <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
                </template>
                <!--已驳回-->
                <template v-if="row.status == 2">
                  <el-button type="text" icon="el-icon-warning" @click="rejectDetail(row.id)">驳回原因
                  </el-button>
                  <el-button v-if="userInfo.user_id == row.create_user" type="text" icon="el-icon-view"
                             @click="handleViewDetail(row)">编辑
                  </el-button>
                  <el-button v-else type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
                  </el-button>
                <template v-if="scope.row.status === 2">
                  <el-button type="text" icon="el-icon-warning" @click="rejectReason(scope.row.id, scope.row)">驳回原因</el-button>
                  <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
<!--                  <el-button type="text" icon="el-icon-view" v-if="userInfo.user_id === scope.row.create_user" @click="handleViewDetail(scope.row, '工单编辑')">编辑</el-button>-->
<!--                  <el-button type="text" icon="el-icon-view" v-else @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>-->
                </template>
                <!-- 已通过 -->
                <template v-if="row.status == 3">
                  <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
                  </el-button>
                <template v-if="scope.row.status === 3">
                  <el-button type="text" icon="el-icon-view" @click="handleViewDetail(scope.row, '工单详情')">详情</el-button>
                </template>
                <!--草稿-->
                <template v-if="row.status == 0">
                  <el-button type="text" icon="el-icon-edit" @click="handleViewDetail(row)">编辑
                  </el-button>
                  <el-button type="text" icon="el-icon-position" @click="userPublishPush(row.id)">发布
                  </el-button>
                  <el-button type="text" icon="el-icon-delete" @click="deleteOrderLog(row.id)">删除
                  </el-button>
                </template>
              </div>
            </template>
            <template #status="{ row }">
              <el-tag :type="getStatusTagType(row.status)">{{ mapStatus(row.status) }}</el-tag>
            </template>
            <template #keyData="{ row }">
              <el-tooltip :content="row.address" placement="top" effect="light">
                <span>{{ row.keyData }}</span>
              </el-tooltip>
            </template>
          </avue-crud>
        </basic-main-content>
<!--                <template v-if="scope.row.status === 0">-->
<!--                  <el-button type="text" icon="el-icon-edit" @click="handleViewDetail(scope.row, '工单编辑')">编辑</el-button>-->
<!--                  <el-button type="text" icon="el-icon-position" @click="userPublishPush(scope.row.id)">发布</el-button>-->
<!--                  <el-button type="text" icon="el-icon-delete" @click="deleteOrder(scope.row.id)">删除</el-button>-->
<!--                </template>-->
              </template>
            </el-table-column>
          </el-table>
        </div>
        <div class="pagination">
          <el-pagination class="ztzf-pagination" popper-class="custom-pagination-dropdown" background
                         :page-sizes="[10, 20, 30, 40, 50, 100]" v-model:current-page="orderListParams.current"
                         v-model:page-size="orderListParams.size" layout="total, sizes, prev, pager, next, jumper" :total="total"
                         @size-change="handleSizeChange" @current-change="handleCurrentChange" />
        </div>
      </el-tab-pane>
    </el-tabs>
    <!-- 新建工单对话框 -->
    <el-dialog class="ztzf-dialog-mange" v-model="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false"
               @close="resetForm">
      <el-form :model="form" :rules="rules" ref="testform" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入工单名称" maxlength="100" show-word-limit></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="form.file_id" placeholder="请选择航线" filterable @change="getFlyingNestBy">
                <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="!device_sns.length">
                <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname"
                           :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple>
                <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="自定义识别区" prop="enable_custom_area" class="safe-height">
              <el-checkbox v-model="form.enable_custom_area"></el-checkbox>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255"
                        show-word-limit></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="7">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
                              end-placeholder="结束日期" :disabled-date="disabledDate" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <div class="flex">
              <div class="flex-1 flex-3">
                <el-select v-model="form.rep_fre_type" placeholder="请选择频次">
                  <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
                </el-select>
              </div>
              <div class="flex-1">
                <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" value-format="HH:mm"
                                :picker-options="{
                    selectableRange: '00:00 - 23:59',
                  }" />
              </div>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="dialogVisible" ref="MapContainer"></map-container>
          </el-col>
        </el-row>
        <!--        <el-row>-->
        <!--          <div class="add-box-btns">-->
        <!--            <el-button type="danger" @click="submitForm(1)">发起</el-button>-->
        <!--            <el-button type="primary" @click="submitForm(0)">存草稿</el-button>-->
        <!--            <el-button @click="dialogVisible = false">取消</el-button>-->
        <!--          </div>-->
        <!--        </el-row>-->
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
          <!-- <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button> -->
          <el-button @click="dialogVisible = false" icon="el-icon-circle-close">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 工单详情对话框 -->
    <el-dialog class="ztzf-dialog-mange" align-center v-model="detailVisible" :title="detailTitle" width="70%"
               :close-on-click-modal="false" @close="resetForm">
      <div class="event-title-center">{{ form.name }}</div>
      <el-form :model="form" :rules="rules" ref="testform" label-width="100px">
        <div class="custom-steps-container">
          <!-- 标题行 -->
          <div class="steps-titles" v-if="filters.status !== '0'">
            <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index"
                 class="step-title">
              {{ record.status_str }}
            </div>
          </div>
          <!-- Element Steps 组件 -->
          <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
            <el-step v-for="(record, index) in form.record_list" :key="index">
              <template #description>
                <span class="step-description" style="position: relative; display: inline-block">
                  {{ record.user_name }}
                </span>
                <span class="step-timer">
                  {{ record.interval_time_str }}
                </span>
                <div class="step-description">
                  {{ record.create_time_str }}
                </div>
              </template>
            </el-step>
          </el-steps>
        </div>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="detailTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="form.file_id" placeholder="请选择航线" @change="getFlyingNestBy" filterable
                         :disabled="detailTitle === '工单详情'">
                <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number :disabled="detailTitle === '工单详情'" v-model="form.rth_altitude" :min="50"
                               :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="detailTitle === '工单详情'">
                <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname"
                           :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple :disabled="detailTitle === '工单详情'">
                <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="自定义识别区" prop="enable_custom_area" class="safe-height">
              <el-checkbox v-model="form.enable_custom_area" :disabled="detailTitle === '工单详情'"></el-checkbox>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12" prop="statusStr">
            <el-form-item label="当前状态">
              {{ mapStatus(form.status) }}
            </el-form-item>
          </el-col>
          <el-col :span="7">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
                              end-placeholder="结束日期" :disabled-date="disabledDate" :disabled="detailTitle === '工单详情'" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <div class="flex">
              <div class="flex-1 flex-3">
                <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="detailTitle === '工单详情'">
                  <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
                </el-select>
              </div>
              <div class="flex-1">
                <el-time-picker v-model="form.deal_time" prop="deal_time"
                                value-format="HH:mm" :picker-options="{
                selectableRange: '00:00 - 23:59',
              }" :disabled="detailTitle === '工单详情'" />
              </div>
            </div>
          </el-col>
          <!--          <el-col :span="3">-->
          <!--            <el-time-picker class="timeStyle" :style="{ width: pxToRem(140) }" v-model="form.deal_time" prop="deal_time"-->
          <!--                            value-format="HH:mm" :picker-options="{-->
          <!--                selectableRange: '00:00 - 23:59',-->
          <!--              }" :disabled="detailTitle === '工单详情'" />-->
          <!--          </el-col>-->
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255"
                        show-word-limit :disabled="detailTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="detailVisible" ref="MapContainer"></map-container>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
                     @click="submitForm(1)" icon="el-icon-position">发布
          </el-button>
          <el-button type="primary"
                     v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)" @click="submitForm(0)"
                     icon="el-icon-plus">保存
          </el-button>
          <!-- <el-button type="primary" v-if="form.status == 0 || userInfo.user_id == form.create_user"
                          @click="submitForm(0)">保存</el-button> -->
          <el-button type="primary" v-if="form.status == 1 && hasPaddingBtnPermission()"
                     @click="orderLogPass(form.id)">通过
          </el-button>
          <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
                     @click="orderLogReject(form.id)">驳回
          </el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 工单详情 -->
    <el-dialog class="ztzf-dialog-mange" align-center v-model="detailVisibleCopy" title="工单详情" width="70%"
               :close-on-click-modal="false" @close="resetForm">
      <div class="event-title-center">{{ form.name }}</div>
      <el-form :model="form" ref="testform" label-width="100px">
        <div class="custom-steps-container">
          <!-- 标题行 -->
          <div class="steps-titles" v-if="filters.status !== '0'">
            <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index"
                 class="step-title">
              {{ record.status_str }}
            </div>
          </div>
          <!-- Element Steps 组件 -->
          <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
            <el-step v-for="(record, index) in form.record_list" :key="index">
              <template #description>
                <span class="step-description" style="position: relative; display: inline-block">
                  {{ record.user_name }}
                </span>
                <span class="step-timer">
                  {{ record.interval_time_str }}
                </span>
                <div class="step-description">
                  {{ record.create_time_str }}
                </div>
              </template>
            </el-step>
          </el-steps>
        </div>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="true"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="form.file_id" placeholder="请选择航线" :disabled="true">
                <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number :disabled="true" v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="true">
                <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname"
                           :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple :disabled="true">
                <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="自定义识别区" prop="enable_custom_area" class="safe-height">
              <el-checkbox v-model="form.enable_custom_area" :disabled="true"></el-checkbox>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12" prop="statusStr">
            <el-form-item label="当前状态">
              {{ mapStatus(form.status) }}
            </el-form-item>
          </el-col>
          <el-col :span="7">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
                              end-placeholder="结束日期" :disabled="true" />
            </el-form-item>
          </el-col>
          <el-col :span="5">
            <div class="flex">
              <div class="flex-1 flex-3">
                <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="true">
                  <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
                </el-select>
              </div>
              <div class="flex-1">
                <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" :disabled="true"
                                value-format="HH:mm" :picker-options="{
                selectableRange: '00:00 - 23:59',
              }" />
              </div>
            </div>
          </el-col>
          <!--          <el-col :span="3">-->
          <!--            <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" :disabled="true"-->
          <!--                            value-format="HH:mm" :picker-options="{-->
          <!--                selectableRange: '00:00 - 23:59',-->
          <!--              }" />-->
          <!--          </el-col>-->
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="form.content" rows="2" placeholder="请输入工单内容" maxlength="255"
                        show-word-limit :readonly="true" :disabled="true"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="detailVisibleCopy" ref="MapContainer"></map-container>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
                     @click="submitForm(1)" icon="el-icon-position">发布
          </el-button>
          <!-- <el-button type="primary" v-if="form.status == 0 || userInfo.user_id == form.create_user"
                          @click="submitForm(0)">保存</el-button> -->
          <el-button type="primary" v-if="form.status == 1 && hasPaddingBtnPermission()" @click="orderLogPass(form.id)"
                     icon="el-icon-check">通过
          </el-button>
          <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
                     @click="orderLogReject(form.id)" icon="el-icon-close">驳回
          </el-button>
          <el-button @click="detailVisibleCopy = false" icon="el-icon-circle-close">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </basic-container>
  </div>
  <AddEditDetails v-model:show="isShowAddEditDetails" @refresh="refreshGetOrderList" />
</template>
<script>
import { pxToRem, pxToRemNum } from '@/utils/rem'
import { calculateDefaultRange } from '@/utils/util'
<script setup>
import SearchBox from './component/SearchBox.vue'
import AddEditDetails from './component/AddEditDetails.vue'
import NProgress from 'nprogress'
import { useStore } from 'vuex'
import { useRoute } from 'vue-router'
import { downloadXls } from '@/utils/util'
import {
  getList,
  saveUpdateOrderLog,
  orderLogDetails,
  orderLogRecall,
  orderLogReject,
  orderLogPass,
  orderLogExport,
  jobStatusNum,
  userPublish,
  deleteOrderLog,
  getWaylineMaxTerrainHeight
} from '@/api/tickets/orderLog'
import { getTicketInfo } from '@/api/tickets/ticket'
import { newGetWorkspacesPage } from '@/api/resource/wayline'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getWaylineFileListByArea, newGetWorkspacesPage } from '@/api/resource/wayline'
import { export_json_to_excel } from '@/utils/exportExcel'
import { getFlyingNestBy } from '@/api/device/device'
import { mapGetters } from 'vuex'
import NProgress from 'nprogress'
import { downloadXls } from '@/utils/util'
import 'nprogress/nprogress.css'
import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
import { cloneDeep } from 'lodash';
import { computed, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn' // 导入中文语言包
import weekday from 'dayjs/plugin/weekday'
import elTooltipCopy from '@/components/ElTooltipCopy.vue'
import { CircleClose, Promotion, Select } from '@element-plus/icons-vue'
import { useStore } from 'vuex'
import _ from 'lodash'
// const store = useStore()
dayjs.extend(weekday)
dayjs.locale('zh-cn')
import { aiImagesPage } from '@/api/dataCenter/dataCenter';
// const lastHeight = computed(() => store.state.common.lastHeight)
const store = useStore()
const route = useRoute()
export default {
  components: { elTooltipCopy },
  name: 'TicketPage',
  data () {
    return {
      activeTab: 'all',
      getlastHeight: 0,
      tabs: [
        { label: '全部工单', name: 'all', value: null, count: 0 },
        { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
        { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
        { label: '已通过', name: 'PASS', value: 3, count: 0 },
        // { label: '草稿', name: 'DRAFT', value: 0, count: 0 }
      ],
      filters: {
        key_word: '',
        create_dept: '',
        start_date: null,
        end_date: null,
        file_id: '',
        ai_types: [],
        status: '',
        dateRange: []
      },
      departments: [],
      types: [],
      device_sns: [],
      ai_types: [],
      wayLineList: [],
      detailTitle: '编辑工单',
      detailVisibleCopy: false,
const userInfo = computed(() => store.state.user.userInfo)
const permission = computed(() => store.state.user.permission);
      statuses: [
        // { label: '草稿', value: '0' },
        { label: '待审核', value: '1' },
        { label: '已驳回', value: '2' },
        { label: '已通过', value: '3' }
      ],
      //周期
      cycles: ['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'],
const isShowAddEditDetails = ref(false)
      tableData: [],
      option: {
        index: true,
        indexWidth: 66,
        indexLabel: '序号',
        indexFixed: true,
const activeTab = ref('all')
provide('activeTab', activeTab)
let tabsList = ref([
  // { label: '我的工单', name: 'my_order', value: null, count: 0 },
  // { label: '全部工单', name: 'all', value: null, count: 0 },
  // { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
  // { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
  // { label: '已通过', name: 'PASS', value: 3, count: 0 },
  // { label: '草稿', name: 'DRAFT', value: 0, count: 0 }
])
function filteredTabs () {
  if (permission?.value.orderLog_tab_allOrders) {
    tabsList.value.push({ label: '全部工单', name: 'all', value: null, count: 0 })
  }
  if (permission?.value.orderLog_tab_pending) {
    tabsList.value.push({ label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 })
  }
  if (permission?.value.orderLog_tab_reject) {
    tabsList.value.push({ label: '已驳回', name: 'REJECTED', value: 2, count: 0 })
  }
  if (permission?.value.orderLog_tab_pass) {
    tabsList.value.push({ label: '已通过', name: 'PASS', value: 3, count: 0 })
  }
  activeTab.value = tabsList.value[0].name
  // tab值传
  orderListParams.status = tabsList.value[0].value
  // tabsList.value
  //   .map(tab => {
  //     if (tab.name === 'all') {
  //       return { ...tab, isShow: permission?.value.orderLog_tab_allOrders }
  //     }
  //     if (tab.name === 'WAIT_AUDIT') {
  //       return { ...tab, isShow: permission?.value.orderLog_tab_pending }
  //     }
  //     if (tab.name === 'REJECTED') {
  //       return { ...tab, isShow: permission?.value.orderLog_tab_reject }
  //     }
  //     if (tab.name === 'PASS') {
  //       return { ...tab, isShow: permission?.value.orderLog_tab_pass }
  //     }
  //     // return { ...tab, isShow: true }
  //   })
  //   .filter(tab => tab.isShow)
}
        align: 'left',
        border: true,
        stripe: false,
        searchShow: true,
        searchBtnText: '搜索',
        searchMenuSpan: 6,
        addBtnText: '新建工单',
        addBtn: false,
        viewBtn: false,
        editBtn: false,
        delBtn: false,
        cancelBtn: true,
        cancelBtnText: '取消',
// tab切换
function handleTabChange (tab) {
  activeTab.value = tab.paneName
  // orderListParams.status = ''
  // if (tab.paneName === 'WAIT_AUDIT') {
  //   orderListParams.status = '1'
  // } else if (tab.paneName === 'REJECTED') {
  //   orderListParams.status = '2'
  // } else if (tab.paneName === 'PASS') {
  //   orderListParams.status = '3'
  // } else if (tab.paneName === 'DRAFT') {
  //   orderListParams.status = '0'
  // }
  // refreshGetOrderList()
}
        menu: true,
        menuWidth: 210,
        menuClassName: 'cur-menu',
        height: 'auto',
        calcHeight: 196,
        column: [
          {
            label: '工单编号',
            prop: 'job_info_num',
            width: 150
          },
          {
            label: '工单名称',
            prop: 'name',
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '工单状态',
            prop: 'status',
            width: 88,
            showOverflowTooltip: true
          },
          {
            label: '所属单位',
            prop: 'dept_name',
            ellipsis: true,
            showOverflowTooltip: true
          },
          {
            label: '创建时间',
            prop: 'create_time',
            width: 144,
            ellipsis: true,
            showOverflowTooltip: true
          },
          {
            label: '已执行次数',
            prop: 'job_num',
            width: 70,
            ellipsis: true,
            showOverflowTooltip: true
          },
          {
            label: '工单内容',
            prop: 'content',
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '关联航线',
            prop: 'wayline_name',
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '关联算法',
            prop: 'ai_type_str',
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '关联机巢',
            prop: 'device_names',
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '创建人',
            prop: 'creator_name',
            width: 96,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          // {
          //   label: '关联机巢', prop: 'device_names', width: 112, ellipsis: true, overHidden: true,
          //   showOverflowTooltip: true,
          // },
          {
            label: '工单周期频次',
            prop: '',
            width: 110,
            formatter: row => this.formatCycleTime(row),
            html: true,
            ellipsis: true,
            showOverflowTooltip: true
            // overHidden: true
          }
        ]
      },
      page: {
        pageSize: 10,
        currentPage: 1,
        total: 0
      },
      dialogVisible: false,
      detailVisible: false,
      currentDetail: {},
      form: {},
      rules: {
        name: [
          { required: true, message: '请输入工单名称', trigger: 'blur' },
          { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
        ],
        file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
        device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
        content: [
          { required: true, message: '请输入工单内容', trigger: 'blur' },
          { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
        ],
        date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
        rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
        deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
      },
      loading: false,
      globalCounts: {},
      mapLoaded: false,
      // 配置时间选择器默认配置
      datePickerDefaultVal: calculateDefaultRange()
// 更新统计数
async function updateGlobalCounts() {
  let res = await jobStatusNum()
  res.data.data.forEach(item => {
    const tab = tabsList.value.find(t => t.name === item.dict_key)
    if (tab) {
      tab.count = item.num
    }
  },
  async created () {
    var response = await getDictionaryByCode('SF')
    var word_order_typeResponse = await getDictionaryByCode('WORK_ORDER_TYPE')
    this.ai_types = response.data.data['SF']
    this.types = word_order_typeResponse.data.data['WORK_ORDER_TYPE']
    //获取航线
    this.asyncgetWaylineFileListByArea()
    const response2 = await getTicketInfo()
    const { dept_data, event_type, ai_type } = response2.data.data
    this.departments = dept_data.map(item => ({
      label: item.dept_name,
      value: item.id
    }))
  },
  mounted () {
    this.fetchTableData()
    const id = this.$route.query.id
    console.log('idddddd', id)
  })
}
    if (id) {
      // 确保 id 存在
      this.handleViewDetail({ id })
      const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
      find && (find.query = {})
    } else {
      console.error('工单ID不存在!')
// 获取航线
let lineList = ref([])
provide('lineList', lineList)
function getLines() {
  const formData = {
    name: '',
    waylineType: undefined,
    current: 1,
    size: 99999999,
    descs: 'update_time'
  }
  newGetWorkspacesPage(formData).then(res => {
    lineList.value = res.data.data.records
  })
}
// 获取算法
let aiTypeList = ref([])
provide('aiTypeList', aiTypeList)
function aitypeList() {
  getDictionaryByCode('SF').then(res => {
    aiTypeList.value = res.data.data['SF']
  })
}
const total = ref(0)
const orderListParams = reactive({
  current: 1,
  size: 10,
  searchParams: {},
  status: ''
})
const orderListTable = ref([])
function getTableList() {
  orderListParams.searchParams.ai_types =  orderListParams.searchParams.ai_typesValue ? [ orderListParams.searchParams.ai_typesValue] : null
  const apiParams = {
    ...cloneDeep(orderListParams),
    ...cloneDeep(orderListParams.searchParams)
  }
  getList(apiParams, orderListParams.current, orderListParams.size).then(res => {
    orderListTable.value = res.data.data.records
    total.value = res.data.data.total
    // updateGlobalCounts()
  })
}
// 分页大小改变
const handleSizeChange = val => {
  orderListParams.size = val
  getTableList()
}
// 页码改变
const handleCurrentChange = val => {
  orderListParams.current = val
  getTableList()
}
// 传参查询条件
const searchClick = params => {
  orderListParams.current = 1
  orderListParams.size = 10
  orderListParams.searchParams = params
  getTableList()
}
const refreshGetOrderList = () => {
  orderListParams.current = 1
  orderListParams.size = 10
  getTableList()
}
function getOrderStatus (status) {
  const statusTextMap = {
    // 0: '草稿',
    1: '待审核',
    2: '已驳回',
    3: '已通过'
  }
  return statusTextMap[status] || '未知状态'
}
function getStatusTagType (status) {
  const statusMap = {
    1: 'warning',
    2: 'info',
    3: 'primary',
    4: 'success',
    5: 'danger'
  }
  return statusMap[status] || 'info'
}
// 新增按钮 和 导出按钮
// function hasAddBtnPermission () {
//   return permission && permission?.value.order_log_add
// }
// 新建工单、编辑、详情
const txtTitle = ref('新建工单')
const rowObj = ref({})
provide('txtTitle', txtTitle)
provide('rowObj', rowObj)
function handleAddOrder() {
  txtTitle.value = '新建工单'
  isShowAddEditDetails.value = true
}
// 详情、编辑
function handleViewDetail(row, txt) {
  rowObj.value = row
  txtTitle.value = txt
  isShowAddEditDetails.value = true
}
// 审核
function handleCheckDetail(row, txt) {
  rowObj.value = row
  txtTitle.value = txt
  isShowAddEditDetails.value = true
}
// 导出
async function exportData() {
  try {
    await ElMessageBox.confirm('是否导出智飞工单数据?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning',
    })
    NProgress.start()
    orderListParams.searchParams.ai_types =  orderListParams.searchParams.ai_typesValue ? [ orderListParams.searchParams.ai_typesValue] : null
    const apiParams = {
      ...cloneDeep(orderListParams),
      ...cloneDeep(orderListParams.searchParams)
    }
  },
  computed: {
    CircleClose () {
      return CircleClose
    },
    ...mapGetters(['userInfo', 'permission']),
    safeHeight () {
      const safeHeight = this.$store.state.common.safeHeight
      return Number(safeHeight) || 0 // 确保是数字类型
    },
    positionsArr () {
      const positionsArr = this.$store.state.common.positionsArr
      return positionsArr || [] // 确保是数字类型
    },
    filteredTabs () {
      // rejection_and_draft 权限控制“已驳回”和“草稿”tab
      // console.log(this.permission, '权限信息')
      // console.log(this.userInfo, '权限信息22')
      const canShowRejectAndDraft = this.permission?.rejection_and_draft === true
      return this.tabs
        .map(tab => {
          if (tab.name === 'DRAFT') {
            return { ...tab, isShow: canShowRejectAndDraft }
          }
          if (tab.name === 'REJECTED') {
            return { ...tab, isShow: canShowRejectAndDraft }
          }
          return { ...tab, isShow: true }
        })
        .filter(tab => tab.isShow)
    const res = await orderLogExport(apiParams)
    downloadXls(res.data, `智飞工单${dayjs().format('YYYY-MM-DD')}.xlsx`)
    ElMessage.success('导出成功')
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('导出失败')
      console.error('导出错误:', error)
    }
  },
  } finally {
    NProgress.done()
  }
}
  methods: {
    handleCellClick (row, column) {
      if (column.no === 1) {
        navigator.clipboard.writeText(row.job_info_num).then(() => {
          this.$message.success('复制工单编号成功')
        })
      } else if (column.no === 2) {
        navigator.clipboard.writeText(row.name).then(() => {
          this.$message.success('复制工单名称成功')
        })
      } else if (column.no === 4) {
        navigator.clipboard.writeText(row.dept_name).then(() => {
          this.$message.success('复制所属单位成功')
        })
      } else if (column.no === 8) {
        navigator.clipboard.writeText(row.wayline_name).then(() => {
          this.$message.success('复制关联航线成功')
        })
      } else if (column.no === 9) {
        navigator.clipboard.writeText(row.ai_type_str).then(() => {
          this.$message.success('复制关联算法成功')
        })
      }
    },
    disabledDate (time) {
      return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
    },
    searchChange (params, done) {
      // console.log('searchChange')
      this.query = params
      this.parentId = ''
      this.page.currentPage = 1
      this.onLoad(this.page, params)
      done()
    },
    async onLoad (page, params = {}) {
      this.loading = true
      getList(
        null,
        this.page.currentPage,
        this.page.pageSize,
        Object.assign(params, this.query)
      ).then(res => {
        this.tableData = res.data.data
        this.loading = false
        this.selectionClear()
      })
    },
    selectionClear () {
      this.selectionList = []
      this.$refs.crud.toggleSelection()
    },
    async loadAMapScripts () {
      try {
        // await loadAMap();
        // await loadAMapUI();
        this.mapLoaded = true
      } catch (error) {
        console.error('Failed to load AMap scripts:', error)
        this.$message.error('地图加载失败,请检查网络或API Key配置')
      }
    },
    formatCycleTime (row) {
      return `${row.cycle_time_value}`
    },
    async fetchTableData () {
      this.loading = true
      try {
        let params = this.getQueryParam()
        // console.log('发送的参数:', params)
        const response = await getList(params, this.page.currentPage, this.page.pageSize)
        if (!response?.data?.data?.records) {
          throw new Error('接口返回数据格式不正确')
        }
        const { total, records } = response.data.data
        this.tableData = records.map(item => {
          return item
        })
        // console.log('权限检查:', this.permission)
        this.page.total = total || 0
        this.updateGlobalCounts()
      } catch (error) {
        // console.error('获取数据失败:', error)
        this.$message.error(error.message || '获取数据失败')
        this.tableData = []
        this.page.total = 0
      } finally {
        this.loading = false
      }
    },
    getQueryParam () {
      const currentTab = this.tabs.find(tab => tab.name === this.activeTab)
      if (this.filters.dateRange) {
        // console.log(
        //   'this.formatDate(this.filters.dateRange[0])',
        //   this.formatDate(this.filters.dateRange[0])
        // )
      }
      const params = {
        key_word: this.filters.key_word || undefined,
        create_dept: this.filters.create_dept || undefined,
        ai_types:
          this.filters.ai_types && this.filters.ai_types.length > 0
            ? [this.filters.ai_types]
            : null,
        file_id: this.filters.file_id || undefined,
        status: this.filters.status !== '' ? Number(this.filters.status) : currentTab?.value,
        dept_id: this.filters.department || undefined,
        create_start_date: this.filters.dateRange?.[0]
          ? this.formatDate(this.filters.dateRange[0])
          : undefined,
        create_end_date: this.filters.dateRange?.[1]
          ? this.formatDate(this.filters.dateRange[1]).replace('00:00:00', '23:59:59')
          : undefined,
        start_date: this.filters.cycleDateRange?.[0]
          ? this.formatDate(this.filters.cycleDateRange[0])
          : undefined,
        end_date: this.filters.cycleDateRange?.[1]
          ? this.formatDate(this.filters.cycleDateRange[1]).replace('00:00:00', '23:59:59')
          : undefined,
        rep_fre_type: this.filters.rep_fre_type || undefined,
        deal_time: this.filters.deal_time || undefined,
        current: this.page.currentPage,
        size: this.page.pageSize
      }
      return params
    },
    sizeChange (pageSize) {
      this.page.pageSize = pageSize
    },
    async submitForm (status) {
      this.$refs.testform.validate(async valid => {
        if (valid) {
          let dateRange = this.form.date_range
          // console.log('dateRange' + dateRange)
          this.form.begin_time = this.formatDate(dateRange[0])
          this.form.end_time = this.formatDate(dateRange[1])
          // 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
          if (this.form.deal_time) {
            const selectedDate = dayjs(this.form.date_range[0]).format('YYYY-MM-DD')
            const selectedTime = dayjs(selectedDate + ' ' + this.form.deal_time).toDate()
            const now = new Date()
            if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
              return this.$message.warning('任务时间不能小于当前时间')
            }
          }
          let checkedList = this.device_sns.filter(item => this.form.device_sns.includes(item.device_sn));
          const maxItem = checkedList.reduce((max, item) => {
            return item.drone_height > max.drone_height ? item : max;
          });
          // 返航绝对高度
          let backHeight = this.form.rth_altitude // + maxItem.drone_height
          console.log(backHeight,'backHeight')
          // 安全起飞高度
          let positions = this.positionsArr
          positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
          positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
          let resultHeight = 0
          let valueHeight = 0
          await getWaylineMaxTerrainHeight(positions).then(res => {
            resultHeight = (this.safeHeight+maxItem.drone_height) > (res.data.data + 30) ? (this.safeHeight+maxItem.drone_height) : (res.data.data + 30)
            valueHeight = (resultHeight - maxItem.drone_height) > backHeight ? _.round(resultHeight - maxItem.drone_height) : 0
          })
          console.log('打印比较值', valueHeight)
          if (valueHeight > 0) {
            this.$confirm(`当前返航高度存在安全隐患,建议调整为${valueHeight}米以上后进行发布`, '提示', {
              confirmButtonText: '确定',
              cancelButtonText: '取消',
              type: 'warning'
            }).then(async () => {
              const submitData = {
                ...this.form,
                status: status,
                ai_types: this.form.ai_types?.length ? this.form.ai_types : []
              }
              await saveUpdateOrderLog(submitData)
              let id = this.form.id
              if (id) {
                this.$message.success('工单发布成功')
              } else {
                this.$message.success('工单创建成功')
              }
              this.dialogVisible = false
              this.detailVisible = false;
              (this.device_sns = []), (this.wayLineList = []), this.fetchTableData()
            }).catch(() => {
            })
          } else {
            const submitData = {
              ...this.form,
              status: status,
              ai_types: this.form.ai_types?.length ? this.form.ai_types : []
            }
            await saveUpdateOrderLog(submitData)
            let id = this.form.id
            if (id) {
              this.$message.success('工单发布成功')
            } else {
              this.$message.success('工单创建成功')
            }
            this.dialogVisible = false
            this.detailVisible = false;
            (this.device_sns = []), (this.wayLineList = []), this.fetchTableData()
          }
        }
      })
    },
    //驳回原因显示
    async rejectDetail (id) {
      const response = await orderLogDetails(id)
      let data = response.data.data
      this.$confirm(data.remark, '驳回原因', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.form = {
          ...response.data.data
        }
        // this.detailVisible = true
        this.handleViewDetail(data)
      })
    },
    formatDate (date) {
      if (!date) return undefined
      const d = new Date(date)
      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(
        d.getDate()
      ).padStart(2, '0')} 00:00:00`
    },
    mapStatus (status) {
      const statusTextMap = {
        0: '草稿',
        1: '待审核',
        2: '已驳回',
        3: '已通过'
      }
      return statusTextMap[status] || '未知状态'
    },
    getStatusTagType (status) {
      const statusMap = {
        1: 'warning',
        2: 'info',
        3: 'primary',
        4: 'success',
        5: 'danger'
      }
      return statusMap[status] || 'info'
    },
    handleTabChange (tab) {
      this.activeTab = tab.props?.name || tab.name
      this.filters.status = ''
      if (tab.props?.name === 'WAIT_AUDIT') {
        this.filters.status = '1'
      } else if (tab.props?.name === 'REJECTED') {
        this.filters.status = '2'
      } else if (tab.props?.name === 'PASS') {
        this.filters.status = '3'
      } else if (tab.props?.name === 'DRAFT') {
        this.filters.status = '0'
      }
      this.page.currentPage = 1
      this.fetchTableData()
    },
    handleSearch () {
      this.page.currentPage = 1
      this.fetchTableData()
    },
    handleReset () {
      this.filters = {
        keyword: '',
        department: '',
        type: '',
        dateRange: [],
        status: ''
      }
      this.page.currentPage = 1
      this.fetchTableData()
    },
    currentChange (currentPage) {
      this.page.currentPage = currentPage
    },
    async updateGlobalCounts () {
      const counts = {
        all: 0,
        DRAFT: 0,
        WAIT_AUDIT: 0,
        REJECTED: 0,
        PASS: 0
      }
      var reponse = await jobStatusNum()
      // console.log('统计' + reponse.data.data)
      reponse.data.data.forEach(item => {
        const tab = this.tabs.find(t => t.name === item.dict_key)
        if (tab) {
          tab.count = item.num
        }
      })
    },
    handleAdd () {
      this.form = {}
      this.form.rth_altitude = 120
      this.dialogVisible = true
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
          this.$refs.MapContainer.initAddEntity('initPosition')
        }
      })
      //航线列表
      this.asyncgetWaylineFileListByArea()
    },
    resetForm () {
      this.form = {
        name: '',
        type: '',
        handler: '',
        algorithm: '',
        location: '',
        address: '',
        content: '',
        photos: []
      }
      if (this.$refs.testform) {
        this.$refs.testform.resetFields()
      }
    },
    formatLocation (location) {
      if (!Array.isArray(location)) {
        return '未知位置'
      }
      return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`
    },
    async handleViewDetail (row) {
      const response = await orderLogDetails(row.id)
      const data = response.data.data
      this.form = {
        ...data
      }
      // 更新机巢列表
      this.device_sns = data.device_map_infos;// data.device_list;
      this.permission &&
      (this.permission.order_log_review || this.permission.order_log_recall) &&
      (data.status == 1 ||
        data.status == 3 ||
        (data.status == 2 && this.userInfo.user_id !== this.form.create_user))
        ? (this.detailTitle = '工单详情')
        : (this.detailTitle = '编辑工单')
      this.detailVisible = true
      // 更新航线列表,追加 wayline_file_region_vo 数据
      if (data.wayline_file_region_vo) {
        const newWayline = data.wayline_file_region_vo
        // 检查是否已经存在于 this.wayLineList 中
        const isDuplicate = this.wayLineList.some(
          item => item.wayline_id === newWayline.wayline_id
        )
        if (!isDuplicate) {
          this.wayLineList.push(newWayline)
        }
      }
      this.initMapLine(data.device_map_infos)
      const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
      find && (find.query = {})
    },
    async handleCheckDetail (row) {
      const response = await orderLogDetails(row.id)
      const data = response.data.data
      this.form = {
        ...data
      }
      // 更新航线列表,追加 wayline_file_region_vo 数据
      // 更新航线列表,追加 wayline_file_region_vo 数据
      if (data.wayline_file_region_vo) {
        const newWayline = data.wayline_file_region_vo
        // 检查是否已经存在于 this.wayLineList 中
        const isDuplicate = this.wayLineList.some(
          item => item.wayline_id === newWayline.wayline_id
        )
        if (!isDuplicate) {
          this.wayLineList.push(newWayline)
        }
      }
      // 更新机巢列表
      this.device_sns = data.device_list
      this.detailVisibleCopy = true
      this.initMapLine(data.device_map_infos)
    },
    //导出
    async exportData () {
      this.$confirm('是否智飞工单数据?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        NProgress.start()
        let params = this.getQueryParam()
        orderLogExport(params).then(res => {
          downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`)
          NProgress.done()
        })
      })
    },
    hasAddBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('this.permission.order_log_add :', this.permission.order_log_add)
      return this.permission && this.permission.order_log_add === true
    },
    hasPaddingBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.order_log_review === true
    },
    hasRecallPaddingBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      // 智飞工单撤回
      return this.permission && this.permission.order_log_recall === true
    },
    //驳回按钮权限
    hasRejectionBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.rejection_btn === true
    },
    //自己点发布
    userPublishPush (id) {
      this.$confirm('确定发布吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        let response = userPublish(id)
        this.$message.success('发布成功')
        this.fetchTableData()
      })
    },
    //删除
    deleteOrderLog (id) {
      this.$confirm('确定删除吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        let response = deleteOrderLog(id)
        this.$message.success('删除')
        this.fetchTableData()
      })
    },
    refreshChange () {
      this.fetchTableData()
    },
    //获取航线列表
    async asyncgetWaylineFileListByArea (name) {
      const formData = {
        name: '',
        waylineType: undefined,
        current: 1,
        size: 99999999,
        descs: 'update_time'
      }
      var wayLineListResponse = await newGetWorkspacesPage(formData)
      this.wayLineList = wayLineListResponse.data.data.records
      this.initMapLine()
    },
    initMapLine (infos = {}, cb = () => {
    }) {
      let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id)
      if (!currentLine) return
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
          this.$refs.MapContainer.initAddEntity('polyline', {
            url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key
            }?_t=${new Date().getTime()}`,
            type: currentLine.wayline_type,
            cb,
            infos
          })
        }
      })
    },
    //可飞行机巢列表
    async getFlyingNestBy (waylineId) {
      const that = this
      // 先清空
      that.form.device_sns = []
      that.device_sns = []
      this.initMapLine({}, async polygon => {
        const currentLine = that.wayLineList.find(item => item.wayline_id === waylineId)
        //按照航线来
        const params = {
          type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
          wayline_id: waylineId,
          polygon
        }
        var wayLineListResponse = await getFlyingNestBy(params)
        // 再赋值
        that.device_sns = wayLineListResponse.data.data
      })
    },
    //撤回
    async orderLogRecall (id) {
      this.$confirm('确定撤回则到草稿箱。', '是否撤回?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        let reposne = await orderLogRecall(id)
        this.handleSearch()
      })
    },
    onLoad () {
      this.fetchTableData()
    },
    /**
     * 通过
     */
    async orderLogPass (id) {
      let response = await orderLogPass(id)
      let data = response.data.data
      this.$message.success('审核通过')
      this.detailVisibleCopy = false
      this.detailVisible = false
      this.page.currentPage = 1
      this.onLoad(this.page, this.query)
      // this.handleViewDetail()
    },
    /**
     * 驳回
     */
    async orderLogReject (id) {
      this.$prompt('', '驳回原因', {
        confirmButtonText: '确定',
        cancelButtonText: '取消'
      }).then(async ({ value }) => {
        let response = await orderLogReject(id, value)
        let data = response.data.data
        this.$message.success('驳回成功')
        this.detailVisibleCopy = false
        // this.detailVisible = false
        this.page.currentPage = 1
        this.onLoad(this.page, this.query)
      })
    }
  },
  watch: {
    // lastHeight(newVal, oldVal) {
    //   console.log('lastHeight', newVal, oldVal);
    //   this.getlastHeight = newVal;
    // },
    tableData: {
      handler () {
        // this.updateTabCounts()
      },
      deep: true
// 撤回工单
async function revokeOrder(id) {
  try {
    await ElMessageBox.confirm('撤回会删除本工单,需重新提交?', '是否撤回', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    // 用户点击确定,执行撤回操作
    // await orderLogRecall(id)
    await deleteOrderLog(id)
    // 显示成功消息
    ElMessage.success('撤回成功')
    // 触发搜索刷新
    refreshGetOrderList()
  } catch (error) {
    // 用户点击取消,不执行任何操作
    if (error !== 'cancel') {
      ElMessage.error('撤回失败')
      console.error('撤回错误:', error)
    }
  }
}
// 驳回原因
async function rejectReason(id, row) {
  try {
    const res = await orderLogDetails(id)
    const data = res.data.data
    await ElMessageBox.confirm(data.remark, '驳回原因', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    // 用户点击确定后的逻辑 TODO
    // form.value = { ...res.data.data }
    handleViewDetail(row, userInfo.value.user_id === row.create_user ? '工单编辑':'工单详情')
  } catch (error) {
    // 用户点击取消,不执行任何操作
    if (error !== 'cancel') {
      ElMessage.error('操作失败')
      console.error('错误:', error)
    }
  }
}
function handleCellClick (row, column) {
  if (column.no === 1) {
    navigator.clipboard.writeText(row.job_info_num).then(() => {
      ElMessage.success('复制工单编号成功')
    })
  } else if (column.no === 2) {
    navigator.clipboard.writeText(row.name).then(() => {
      ElMessage.success('复制工单名称成功')
    })
  } else if (column.no === 4) {
    navigator.clipboard.writeText(row.dept_name).then(() => {
      ElMessage.success('复制所属单位成功')
    })
  } else if (column.no === 8) {
    navigator.clipboard.writeText(row.wayline_name).then(() => {
      ElMessage.success('复制关联航线成功')
    })
  } else if (column.no === 9) {
    navigator.clipboard.writeText(row.ai_type_str).then(() => {
      ElMessage.success('复制关联算法成功')
    })
  }
}
// 删除
async function deleteOrder(id) {
  try {
    await ElMessageBox.confirm('确定删除吗?', '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    deleteOrderLog(id)
    ElMessage.success('删除成功')
    // 触发搜索刷新
    refreshGetOrderList()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
      console.error('删除错误:', error)
    }
  }
}
onMounted(() => {
  getLines()
  aitypeList()
  filteredTabs()
  getTableList()
  updateGlobalCounts()
  if (route.query.id) {
    route.query.status === '1' ? handleCheckDetail({ id:  route.query.id}, '工单审核') : handleCheckDetail({ id:  route.query.id}, '工单详情')
  }
})
</script>
<style lang="scss" scoped>
//:deep(.el-input-number) {
//  width: 200px;
//}
//:deep(.el-form-item__label) {
//  width: 100px !important;
//}
::v-deep(.el-tabs) {
  height: 100%;
.order-log-update {
  height: 0;
  flex: 1;
  margin: 0 10px 10px 10px;
  background-color: #ffffff;
  padding: 10px 20px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  .el-tabs__header {
    order: 1;
  // 表格
  .task-table {
    //height: 0;
    //flex: 1;
    height: calc(100vh - 380px);
    margin-top: 18px;
    overflow: auto;
  }
  .el-tabs__content {
    order: 2;
  .btnItem {
    height: 27px;
    line-height: 27px;
    border-radius: 0px 0px 0px 0px;
    border: 1px solid #51a8ff;
    font-family: Segoe UI, Segoe UI;
    font-weight: 400;
    font-size: 14px;
    color: #1C5CFF;
    padding: 0 10px;
    display: inline-block;
    margin-right: 10px;
    cursor: pointer;
    &.turnBack {
      border: 1px solid #ffa500;
      color: #ffa500;
    }
    &.cancelTask {
      border: 1px solid #00AA2D;
      color: #00AA2D;
    }
  }
  // 分页
  :deep(.custom-header th.el-table__cell) {
    color: rgba(0, 0, 0, 0.85);
  }
  .el-tabs__content {
    height: 0;
    flex: 1;
  :deep(.el-table td.el-table__cell) {
    color: #606266;
  }
  :deep(.el-pagination) {
    display: flex;
    flex-direction: column;
    .el-tab-pane {
      height: 0;
      flex: 1;
      display: flex;
      flex-direction: column;
    }
  }
}
.filter-bar {
  // align-items: center;
  .search-bar-box {
    margin-bottom: 15px;
    display: flex;
    justify-content: space-between;
    gap: 8px;
    &-item {
      flex: 1;
      &> ::v-deep(.el-date-editor) {
        width: 100%;
        box-sizing: border-box;
      }
    }
    .search-btn {
      flex: 1;
      display: flex;
      justify-content: flex-end;
    }
    .flex-2 {
      min-width: 454px;
    }
  }
}
:deep(.el-pagination .el-select) {
  width: 128px !important;
}
.step-timer {
  position: absolute;
  left: 80%;
  top: 50%;
  transform: translateY(-50%);
  width: 100px;
  margin-left: 4px;
  color: #666;
  font-size: 12px;
}
:deep(.el-textarea__inner) {
  padding: 8px 12px;
}
:deep(.el-input__inner),
:deep(.el-select),
:deep(.el-select .el-input) {
  width: 100%; // 确保所有输入框和选择框宽度一致
}
:deep(.el-textarea__inner) {
  resize: none;
}
.action-bar {
  margin-bottom: 16px;
}
.el-tabs {
  :deep(.el-tabs__content) {
    overflow: visible;
  }
}
.el-dialog {
  .el-form-item {
    margin-bottom: 10px;
  }
}
.el-upload {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  :deep(.el-upload-list__item) {
    transition: all 0.3s ease;
    justify-content: right;
  }
  :deep(.el-upload-list__item:hover) {
    background-color: #f5f7fa;
  :deep(.el-pagination button) {
    background: center center no-repeat none !important;
    color: #8eb8ea !important;
  }
}
.el-upload__tip {
  font-size: 12px;
  color: #909399;
  margin-top: 7px;
}
.custom-steps-container {
  width: 100%;
  margin: 20px 0;
}
.steps-titles {
  display: flex;
  justify-content: space-between;
  margin-bottom: 16px;
  position: relative;
}
.step-title {
  text-align: center;
  flex: 1;
  font-size: 16px;
  color: #999;
  position: relative;
  padding-bottom: 10px;
}
.step-title.active {
  color: #409eff;
  font-weight: bold;
}
.custom-steps {
  margin-top: -20px;
  /* 向上移动与标题重叠 */
}
.el-step__head {
  margin-top: 0;
}
.el-step__description {
  margin-top: 8px;
  padding: 0 20px;
}
.step-description {
  font-size: 14px;
  color: #666;
  line-height: 1.5;
}
.event-title-center {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 12px;
  color: #333;
}
.cur-menu {
  .menu-custom-box {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    &> ::v-deep(.el-button) {
      flex: 1;
      margin-left: 0;
      margin-right: 10px;
    }
  }
}
.add-box-btns {
  width: 100%;
  display: flex;
  justify-content: center;
}
.safe-height {
  :deep(.el-form-item__label) {
    width: 120px !important;
  }
  :deep(.el-input-number) {
    width: 180px !important;
  }
}
.flex {
  display: flex;
}
.flex-1 {
  flex: 1;
  &> ::v-deep(div) {
    width: 100% !important;
  }
}
.flex-3 {
  margin-right: 4px;
}
</style>
src/views/tickets/orderLogCopy.vue
New file
@@ -0,0 +1,1666 @@
<template>
  <basic-container>
    <el-tabs v-model="activeTab" @tab-click="handleTabChange">
      <el-tab-pane v-for="tab in filteredTabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
        <basic-main-content>
          <!-- 查询条件筛选栏 -->
          <div class="ztzf-form-search">
            <el-form :model="filters" inline>
              <el-row :gutter="24">
                <el-col :span="4">
                  <el-form-item label="关键字:">
                    <el-input v-model="filters.key_word" placeholder="编号/名称/内容/姓名" clearable
                              @keyup.enter="handleSearch" />
                  </el-form-item>
                  <!--              <div class="search-bar-box-item">-->
                  <!--                <el-select placeholder="请选择所属单位" v-model="filters.create_dept" clearable>-->
                  <!--                  <el-option v-for="dept in departments" :key="dept.value" :label="dept.label" :value="dept.value" />-->
                  <!--                </el-select>-->
                  <!--              </div>-->
                </el-col>
                <el-col :span="8">
                  <el-form-item label="选择日期:">
                    <el-date-picker @change="handleSearch" v-model="filters.dateRange" type="daterange"
                                    range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
                                    :default-value="datePickerDefaultVal" />
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="关联航线:">
                    <el-select @change="handleSearch" v-model="filters.file_id" placeholder="请选择关联航线" filterable
                               clearable>
                      <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                                 :value="item.wayline_id" />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="关联算法:">
                    <el-select @change="handleSearch" v-model="filters.ai_types" placeholder="关联算法" clearable>
                      <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue"
                                 :value="item.dictKey" />
                    </el-select>
                  </el-form-item>
                  <!-- <div class="search-bar-box-item">
                    <el-select v-model="filters.type" placeholder="请选择工单类型" clearable>
                      <el-option v-for="item in types" :key="item.dictValue" :label="item.dictValue"
                        :value="item.dictKey" />
                    </el-select>
                  </div> -->
                </el-col>
                <el-col :span="4">
                  <el-form-item label="工单状态:">
                    <el-select @change="handleSearch" v-model="filters.status" placeholder="请选择工单状态" clearable
                               :disabled="activeTab !== 'all'">
                      <el-option v-for="item in statuses" :key="item.value" :label="item.label" :value="item.value" />
                    </el-select>
                  </el-form-item>
                </el-col>
              </el-row>
              <el-row :gutter="24">
                <el-col :span="8">
                  <el-form-item label="工单周期:">
                    <el-date-picker @change="handleSearch" v-model="filters.cycleDateRange" type="daterange"
                                    range-separator="至" start-placeholder="工单周期开始日期" end-placeholder="工单周期结束日期"
                                    :default-value="datePickerDefaultVal" />
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="选择频次:">
                    <el-select @change="handleSearch" v-model="filters.rep_fre_type" placeholder="请选择频次" clearable>
                      <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
                    </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="执行时间:">
                    <el-time-picker @change="handleSearch" v-model="filters.deal_time" placeholder="请选择执行时间"
                                    prop="deal_time" value-format="HH:mm" :picker-options="{
                        selectableRange: '00:00 - 23:59',
                      }" />
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索
                  </el-button>
                  <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
                </el-col>
              </el-row>
            </el-form>
          </div>
          <!-- 表格部分 -->
          <avue-crud class="ztzf-public-general-avue-crud" :data="tableData" :option="option" v-model:page="page"
                     ref="crud" :table-loading="loading" @current-change="currentChange" @refresh-change="refreshChange"
                     @on-load="onLoad" @search-change="searchChange" @size-change="sizeChange" v-if="activeTab === tab.name">
            <template #job_info_num="{ row }">
              <el-tooltip-copy :content="row.job_info_num" :showCopyText="true" textAlign="left">
                {{ row.job_info_num }}
              </el-tooltip-copy>
            </template>
            <template #name="{ row }">
              <el-tooltip-copy :content="row.name" :showCopyText="true" textAlign="left">
                {{ row.name }}
              </el-tooltip-copy>
            </template>
            <template #menu-left>
              <el-button v-if="hasAddBtnPermission() && activeTab != 'WAIT_AUDIT'" type="primary" icon="el-icon-plus"
                         @click="handleAdd">新建工单
              </el-button>
              <el-button type="success" plain icon="el-icon-download" @click="exportData">导出
              </el-button>
            </template>
            <template #menu="{ row }">
              <div class="menu-custom-box">
                <template v-if="row.status == 1">
                  <el-button v-if="hasPaddingBtnPermission()" type="text" icon="el-icon-view"
                             @click="handleCheckDetail(row)">审核
                  </el-button>
                </template>
                <!-- v-if="
                    (userInfo.user_id == row.create_user || hasRecallPaddingBtnPermission()) &&
                    row.status == 1
                  " -->
                <template v-if="row.status == 1">
                  <!--待审核状态-->
                  <el-button type="text" icon="el-icon-warning" v-if="hasRecallPaddingBtnPermission()"
                             @click="orderLogRecall(row.id)">撤回
                  </el-button>
                  <!-- <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情</el-button> -->
                </template>
                <!--已驳回-->
                <template v-if="row.status == 2">
                  <el-button type="text" icon="el-icon-warning" @click="rejectDetail(row.id)">驳回原因
                  </el-button>
                  <el-button v-if="userInfo.user_id == row.create_user" type="text" icon="el-icon-view"
                             @click="handleViewDetail(row)">编辑
                  </el-button>
                  <el-button v-else type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
                  </el-button>
                </template>
                <!-- 已通过 -->
                <template v-if="row.status == 3">
                  <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)">详情
                  </el-button>
                </template>
                <!--草稿-->
                <template v-if="row.status == 0">
                  <el-button type="text" icon="el-icon-edit" @click="handleViewDetail(row)">编辑
                  </el-button>
                  <el-button type="text" icon="el-icon-position" @click="userPublishPush(row.id)">发布
                  </el-button>
                  <el-button type="text" icon="el-icon-delete" @click="deleteOrderLog(row.id)">删除
                  </el-button>
                </template>
              </div>
            </template>
            <template #status="{ row }">
              <el-tag :type="getStatusTagType(row.status)">{{ mapStatus(row.status) }}</el-tag>
            </template>
            <template #keyData="{ row }">
              <el-tooltip :content="row.address" placement="top" effect="light">
                <span>{{ row.keyData }}</span>
              </el-tooltip>
            </template>
          </avue-crud>
        </basic-main-content>
      </el-tab-pane>
    </el-tabs>
    <!-- 新建工单对话框 -->
    <el-dialog class="ztzf-dialog-mange" v-model="dialogVisible" title="新建工单" width="70%" :close-on-click-modal="false"
               @close="resetForm">
      <el-form :model="form" :rules="rules" ref="testform" label-width="100px">
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入工单名称" maxlength="100" show-word-limit></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="form.file_id" placeholder="请选择航线" filterable @change="getFlyingNestBy">
                <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="!device_sns.length">
                <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname"
                           :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple>
                <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255"
                        show-word-limit></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
                              end-placeholder="结束日期" :disabled-date="disabledDate" />
            </el-form-item>
          </el-col>
          <el-col :span="4">
            <div class="flex">
              <div class="flex-1">
                <el-select v-model="form.rep_fre_type" placeholder="请选择频次">
                  <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
                </el-select>
              </div>
              <div class="flex-1">
                <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" value-format="HH:mm"
                                :picker-options="{
                    selectableRange: '00:00 - 23:59',
                  }" />
              </div>
            </div>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="dialogVisible" ref="MapContainer"></map-container>
          </el-col>
        </el-row>
        <!--        <el-row>-->
        <!--          <div class="add-box-btns">-->
        <!--            <el-button type="danger" @click="submitForm(1)">发起</el-button>-->
        <!--            <el-button type="primary" @click="submitForm(0)">存草稿</el-button>-->
        <!--            <el-button @click="dialogVisible = false">取消</el-button>-->
        <!--          </div>-->
        <!--        </el-row>-->
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" @click="submitForm(1)" icon="el-icon-position">发起</el-button>
          <el-button type="primary" @click="submitForm(0)" icon="el-icon-document-add">存草稿</el-button>
          <el-button @click="dialogVisible = false" icon="el-icon-circle-close">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 工单详情对话框 -->
    <el-dialog class="ztzf-dialog-mange" align-center v-model="detailVisible" :title="detailTitle" width="70%"
               :close-on-click-modal="false" @close="resetForm">
      <div class="event-title-center">{{ form.name }}</div>
      <el-form :model="form" :rules="rules" ref="testform" label-width="100px">
        <div class="custom-steps-container">
          <!-- 标题行 -->
          <div class="steps-titles" v-if="filters.status !== '0'">
            <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index"
                 class="step-title">
              {{ record.status_str }}
            </div>
          </div>
          <!-- Element Steps 组件 -->
          <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
            <el-step v-for="(record, index) in form.record_list" :key="index">
              <template #description>
                <span class="step-description" style="position: relative; display: inline-block">
                  {{ record.user_name }}
                </span>
                <span class="step-timer">
                  {{ record.interval_time_str }}
                </span>
                <div class="step-description">
                  {{ record.create_time_str }}
                </div>
              </template>
            </el-step>
          </el-steps>
        </div>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="detailTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="form.file_id" placeholder="请选择航线" @change="getFlyingNestBy" filterable
                         :disabled="detailTitle === '工单详情'">
                <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number :disabled="detailTitle === '工单详情'" v-model="form.rth_altitude" :min="50"
                               :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="detailTitle === '工单详情'">
                <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname"
                           :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple :disabled="detailTitle === '工单详情'">
                <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12" prop="statusStr">
            <el-form-item label="当前状态">
              {{ mapStatus(form.status) }}
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
                              end-placeholder="结束日期" :disabled-date="disabledDate" :disabled="detailTitle === '工单详情'" />
            </el-form-item>
          </el-col>
          <el-col :span="3">
            <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="detailTitle === '工单详情'">
              <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
            </el-select>
          </el-col>
          <el-col :span="3">
            <el-time-picker class="timeStyle" :style="{ width: pxToRem(140) }" v-model="form.deal_time" prop="deal_time"
                            value-format="HH:mm" :picker-options="{
                selectableRange: '00:00 - 23:59',
              }" :disabled="detailTitle === '工单详情'" />
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="form.content" rows="4" placeholder="请输入工单内容" maxlength="255"
                        show-word-limit :disabled="detailTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="detailVisible" ref="MapContainer"></map-container>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
                     @click="submitForm(1)" icon="el-icon-position">发布
          </el-button>
          <el-button type="primary"
                     v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)" @click="submitForm(0)"
                     icon="el-icon-plus">保存
          </el-button>
          <!-- <el-button type="primary" v-if="form.status == 0 || userInfo.user_id == form.create_user"
                          @click="submitForm(0)">保存</el-button> -->
          <el-button type="primary" v-if="form.status == 1 && hasPaddingBtnPermission()"
                     @click="orderLogPass(form.id)">通过
          </el-button>
          <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
                     @click="orderLogReject(form.id)">驳回
          </el-button>
        </div>
      </template>
    </el-dialog>
    <!-- 工单详情 -->
    <el-dialog class="ztzf-dialog-mange" align-center v-model="detailVisibleCopy" title="工单详情" width="70%"
               :close-on-click-modal="false" @close="resetForm">
      <div class="event-title-center">{{ form.name }}</div>
      <el-form :model="form" ref="testform" label-width="100px">
        <div class="custom-steps-container">
          <!-- 标题行 -->
          <div class="steps-titles" v-if="filters.status !== '0'">
            <div v-for="(record, index) in form.record_list" :class="{ active: record.user_id >= 0 }" :key="index"
                 class="step-title">
              {{ record.status_str }}
            </div>
          </div>
          <!-- Element Steps 组件 -->
          <el-steps :active="form.active" align-center class="custom-steps" v-if="filters.status !== '0'">
            <el-step v-for="(record, index) in form.record_list" :key="index">
              <template #description>
                <span class="step-description" style="position: relative; display: inline-block">
                  {{ record.user_name }}
                </span>
                <span class="step-timer">
                  {{ record.interval_time_str }}
                </span>
                <div class="step-description">
                  {{ record.create_time_str }}
                </div>
              </template>
            </el-step>
          </el-steps>
        </div>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input v-model="form.name" placeholder="请输入工单名称" :disabled="true"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="关联航线" prop="file_id">
              <el-select v-model="form.file_id" placeholder="请选择航线" :disabled="true">
                <el-option v-for="item in wayLineList" :key="item.wayline_id" :label="item.name"
                           :value="item.wayline_id" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="安全返航真高" prop="rth_altitude" class="safe-height">
              <el-input-number :disabled="true" v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="关联机巢" prop="device_sns">
              <el-select v-model="form.device_sns" placeholder="请选择机巢" multiple :disabled="true">
                <el-option v-for="item in device_sns" :key="item.device_sn" :label="item.nickname"
                           :value="item.device_sn" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="关联算法" prop="ai_types">
              <el-select v-model="form.ai_types" placeholder="请选择关联算法" multiple :disabled="true">
                <el-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12" prop="statusStr">
            <el-form-item label="当前状态">
              {{ mapStatus(form.status) }}
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="周期频次" prop="date_range">
              <el-date-picker v-model="form.date_range" type="daterange" range-separator="至" start-placeholder="开始日期"
                              end-placeholder="结束日期" :disabled="true" />
            </el-form-item>
          </el-col>
          <el-col :span="3">
            <el-select v-model="form.rep_fre_type" placeholder="请选择频次" :disabled="true">
              <el-option v-for="item in cycles" :key="item" :label="item" :value="item" />
            </el-select>
          </el-col>
          <el-col :span="3">
            <el-time-picker style="width: 100px" v-model="form.deal_time" prop="deal_time" :disabled="true"
                            value-format="HH:mm" :picker-options="{
                selectableRange: '00:00 - 23:59',
              }" />
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input type="textarea" v-model="form.content" rows="2" placeholder="请输入工单内容" maxlength="255"
                        show-word-limit :readonly="true" :disabled="true"></el-input>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20" style="height: 400px">
          <el-col :span="24">
            <map-container v-if="detailVisibleCopy" ref="MapContainer"></map-container>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
                     @click="submitForm(1)" icon="el-icon-position">发布
          </el-button>
          <!-- <el-button type="primary" v-if="form.status == 0 || userInfo.user_id == form.create_user"
                          @click="submitForm(0)">保存</el-button> -->
          <el-button type="primary" v-if="form.status == 1 && hasPaddingBtnPermission()" @click="orderLogPass(form.id)"
                     icon="el-icon-check">通过
          </el-button>
          <el-button type="danger" v-if="form.status == 1 && hasRejectionBtnPermission()"
                     @click="orderLogReject(form.id)" icon="el-icon-close">驳回
          </el-button>
          <el-button @click="detailVisibleCopy = false" icon="el-icon-circle-close">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </basic-container>
</template>
<script>
import { pxToRem, pxToRemNum } from '@/utils/rem'
import { calculateDefaultRange } from '@/utils/util'
import {
  getList,
  saveUpdateOrderLog,
  orderLogDetails,
  orderLogRecall,
  orderLogReject,
  orderLogPass,
  orderLogExport,
  jobStatusNum,
  userPublish,
  deleteOrderLog,
  getWaylineMaxTerrainHeight
} from '@/api/tickets/orderLog'
import { getTicketInfo } from '@/api/tickets/ticket'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getWaylineFileListByArea, newGetWorkspacesPage } from '@/api/resource/wayline'
import { export_json_to_excel } from '@/utils/exportExcel'
import { getFlyingNestBy } from '@/api/device/device'
import { mapGetters } from 'vuex'
import NProgress from 'nprogress'
import { downloadXls } from '@/utils/util'
import 'nprogress/nprogress.css'
import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
import dayjs from 'dayjs'
import 'dayjs/locale/zh-cn' // 导入中文语言包
import weekday from 'dayjs/plugin/weekday'
import elTooltipCopy from '@/components/ElTooltipCopy.vue'
import { CircleClose, Promotion, Select } from '@element-plus/icons-vue'
import { useStore } from 'vuex'
import _ from 'lodash'
// const store = useStore()
dayjs.extend(weekday)
dayjs.locale('zh-cn')
// const lastHeight = computed(() => store.state.common.lastHeight)
export default {
  components: { elTooltipCopy },
  name: 'TicketPage',
  data () {
    return {
      activeTab: 'all',
      getlastHeight: 0,
      tabs: [
        { label: '全部工单', name: 'all', value: null, count: 0 },
        { label: '待审核', name: 'WAIT_AUDIT', value: 1, count: 0 },
        { label: '已驳回', name: 'REJECTED', value: 2, count: 0 },
        { label: '已通过', name: 'PASS', value: 3, count: 0 },
        { label: '草稿', name: 'DRAFT', value: 0, count: 0 }
      ],
      filters: {
        key_word: '',
        create_dept: '',
        start_date: null,
        end_date: null,
        file_id: '',
        ai_types: [],
        status: '',
        dateRange: []
      },
      departments: [],
      types: [],
      device_sns: [],
      ai_types: [],
      wayLineList: [],
      detailTitle: '编辑工单',
      detailVisibleCopy: false,
      statuses: [
        { label: '草稿', value: '0' },
        { label: '待审核', value: '1' },
        { label: '已驳回', value: '2' },
        { label: '已通过', value: '3' }
      ],
      //周期
      cycles: ['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'],
      tableData: [],
      option: {
        index: true,
        indexWidth: 66,
        indexLabel: '序号',
        indexFixed: true,
        align: 'left',
        border: true,
        stripe: false,
        searchShow: true,
        searchBtnText: '搜索',
        searchMenuSpan: 6,
        addBtnText: '新建工单',
        addBtn: false,
        viewBtn: false,
        editBtn: false,
        delBtn: false,
        cancelBtn: true,
        cancelBtnText: '取消',
        menu: true,
        menuWidth: 210,
        menuClassName: 'cur-menu',
        height: 'auto',
        calcHeight: 196,
        column: [
          {
            label: '工单编号',
            prop: 'job_info_num',
            width: 150
          },
          {
            label: '工单名称',
            prop: 'name',
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '工单状态',
            prop: 'status',
            width: 88,
            showOverflowTooltip: true
          },
          {
            label: '所属单位',
            prop: 'dept_name',
            ellipsis: true,
            showOverflowTooltip: true
          },
          {
            label: '创建时间',
            prop: 'create_time',
            width: 144,
            ellipsis: true,
            showOverflowTooltip: true
          },
          {
            label: '已执行次数',
            prop: 'job_num',
            width: 70,
            ellipsis: true,
            showOverflowTooltip: true
          },
          {
            label: '工单内容',
            prop: 'content',
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '关联航线',
            prop: 'wayline_name',
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '关联算法',
            prop: 'ai_type_str',
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '关联机巢',
            prop: 'device_names',
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          {
            label: '创建人',
            prop: 'creator_name',
            width: 96,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true
          },
          // {
          //   label: '关联机巢', prop: 'device_names', width: 112, ellipsis: true, overHidden: true,
          //   showOverflowTooltip: true,
          // },
          {
            label: '工单周期频次',
            prop: '',
            width: 110,
            formatter: row => this.formatCycleTime(row),
            html: true,
            ellipsis: true,
            showOverflowTooltip: true
            // overHidden: true
          }
        ]
      },
      page: {
        pageSize: 10,
        currentPage: 1,
        total: 0
      },
      dialogVisible: false,
      detailVisible: false,
      currentDetail: {},
      form: {},
      rules: {
        name: [
          { required: true, message: '请输入工单名称', trigger: 'blur' },
          { max: 100, message: '工单名称不能超过100个字', trigger: 'blur' }
        ],
        file_id: [{ required: true, message: '需要选择航线', trigger: 'change' }],
        device_sns: [{ required: true, message: '请选择机巢', trigger: 'change' }],
        content: [
          { required: true, message: '请输入工单内容', trigger: 'blur' },
          { max: 255, message: '工单内容不能超过255个字', trigger: 'blur' }
        ],
        date_range: [{ required: true, message: '请选择时间不能为空', trigger: 'blur' }],
        rep_fre_type: [{ required: true, message: '请选择周期', trigger: 'blur' }],
        deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
      },
      loading: false,
      globalCounts: {},
      mapLoaded: false,
      // 配置时间选择器默认配置
      datePickerDefaultVal: calculateDefaultRange()
    }
  },
  async created () {
    var response = await getDictionaryByCode('SF')
    var word_order_typeResponse = await getDictionaryByCode('WORK_ORDER_TYPE')
    this.ai_types = response.data.data['SF']
    this.types = word_order_typeResponse.data.data['WORK_ORDER_TYPE']
    //获取航线
    this.asyncgetWaylineFileListByArea()
    const response2 = await getTicketInfo()
    const { dept_data, event_type, ai_type } = response2.data.data
    this.departments = dept_data.map(item => ({
      label: item.dept_name,
      value: item.id
    }))
  },
  mounted () {
    this.fetchTableData()
    const id = this.$route.query.id
    console.log('idddddd', id)
    if (id) {
      // 确保 id 存在
      this.handleViewDetail({ id })
      const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
      find && (find.query = {})
    } else {
      console.error('工单ID不存在!')
    }
  },
  computed: {
    CircleClose () {
      return CircleClose
    },
    ...mapGetters(['userInfo', 'permission']),
    safeHeight () {
      const safeHeight = this.$store.state.common.safeHeight
      return Number(safeHeight) || 0 // 确保是数字类型
    },
    positionsArr () {
      const positionsArr = this.$store.state.common.positionsArr
      return positionsArr || [] // 确保是数字类型
    },
    filteredTabs () {
      // rejection_and_draft 权限控制“已驳回”和“草稿”tab
      // console.log(this.permission, '权限信息')
      // console.log(this.userInfo, '权限信息22')
      const canShowRejectAndDraft = this.permission?.rejection_and_draft === true
      return this.tabs
        .map(tab => {
          if (tab.name === 'DRAFT') {
            return { ...tab, isShow: canShowRejectAndDraft }
          }
          if (tab.name === 'REJECTED') {
            return { ...tab, isShow: canShowRejectAndDraft }
          }
          return { ...tab, isShow: true }
        })
        .filter(tab => tab.isShow)
    }
  },
  methods: {
    handleCellClick (row, column) {
      if (column.no === 1) {
        navigator.clipboard.writeText(row.job_info_num).then(() => {
          this.$message.success('复制工单编号成功')
        })
      } else if (column.no === 2) {
        navigator.clipboard.writeText(row.name).then(() => {
          this.$message.success('复制工单名称成功')
        })
      } else if (column.no === 4) {
        navigator.clipboard.writeText(row.dept_name).then(() => {
          this.$message.success('复制所属单位成功')
        })
      } else if (column.no === 8) {
        navigator.clipboard.writeText(row.wayline_name).then(() => {
          this.$message.success('复制关联航线成功')
        })
      } else if (column.no === 9) {
        navigator.clipboard.writeText(row.ai_type_str).then(() => {
          this.$message.success('复制关联算法成功')
        })
      }
    },
    disabledDate (time) {
      return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
    },
    searchChange (params, done) {
      // console.log('searchChange')
      this.query = params
      this.parentId = ''
      this.page.currentPage = 1
      this.onLoad(this.page, params)
      done()
    },
    async onLoad (page, params = {}) {
      this.loading = true
      getList(
        null,
        this.page.currentPage,
        this.page.pageSize,
        Object.assign(params, this.query)
      ).then(res => {
        this.tableData = res.data.data
        this.loading = false
        this.selectionClear()
      })
    },
    selectionClear () {
      this.selectionList = []
      this.$refs.crud.toggleSelection()
    },
    async loadAMapScripts () {
      try {
        // await loadAMap();
        // await loadAMapUI();
        this.mapLoaded = true
      } catch (error) {
        console.error('Failed to load AMap scripts:', error)
        this.$message.error('地图加载失败,请检查网络或API Key配置')
      }
    },
    formatCycleTime (row) {
      return `${row.cycle_time_value}`
    },
    async fetchTableData () {
      this.loading = true
      try {
        let params = this.getQueryParam()
        // console.log('发送的参数:', params)
        const response = await getList(params, this.page.currentPage, this.page.pageSize)
        if (!response?.data?.data?.records) {
          throw new Error('接口返回数据格式不正确')
        }
        const { total, records } = response.data.data
        this.tableData = records.map(item => {
          return item
        })
        // console.log('权限检查:', this.permission)
        this.page.total = total || 0
        this.updateGlobalCounts()
      } catch (error) {
        // console.error('获取数据失败:', error)
        this.$message.error(error.message || '获取数据失败')
        this.tableData = []
        this.page.total = 0
      } finally {
        this.loading = false
      }
    },
    getQueryParam () {
      const currentTab = this.tabs.find(tab => tab.name === this.activeTab)
      if (this.filters.dateRange) {
        // console.log(
        //   'this.formatDate(this.filters.dateRange[0])',
        //   this.formatDate(this.filters.dateRange[0])
        // )
      }
      const params = {
        key_word: this.filters.key_word || undefined,
        create_dept: this.filters.create_dept || undefined,
        ai_types:
          this.filters.ai_types && this.filters.ai_types.length > 0
            ? [this.filters.ai_types]
            : null,
        file_id: this.filters.file_id || undefined,
        status: this.filters.status !== '' ? Number(this.filters.status) : currentTab?.value,
        dept_id: this.filters.department || undefined,
        create_start_date: this.filters.dateRange?.[0]
          ? this.formatDate(this.filters.dateRange[0])
          : undefined,
        create_end_date: this.filters.dateRange?.[1]
          ? this.formatDate(this.filters.dateRange[1]).replace('00:00:00', '23:59:59')
          : undefined,
        start_date: this.filters.cycleDateRange?.[0]
          ? this.formatDate(this.filters.cycleDateRange[0])
          : undefined,
        end_date: this.filters.cycleDateRange?.[1]
          ? this.formatDate(this.filters.cycleDateRange[1]).replace('00:00:00', '23:59:59')
          : undefined,
        rep_fre_type: this.filters.rep_fre_type || undefined,
        deal_time: this.filters.deal_time || undefined,
        current: this.page.currentPage,
        size: this.page.pageSize
      }
      return params
    },
    sizeChange (pageSize) {
      this.page.pageSize = pageSize
    },
    async submitForm (status) {
      this.$refs.testform.validate(async valid => {
        if (valid) {
          let dateRange = this.form.date_range
          // console.log('dateRange' + dateRange)
          this.form.begin_time = this.formatDate(dateRange[0])
          this.form.end_time = this.formatDate(dateRange[1])
          // 如果选中日期包含当前天,那么选中的时间点不能小于当前时间
          if (this.form.deal_time) {
            const selectedDate = dayjs(this.form.date_range[0]).format('YYYY-MM-DD')
            const selectedTime = dayjs(selectedDate + ' ' + this.form.deal_time).toDate()
            const now = new Date()
            if (selectedDate === dayjs().format('YYYY-MM-DD') && selectedTime < now) {
              return this.$message.warning('任务时间不能小于当前时间')
            }
          }
          let checkedList = this.device_sns.filter(item => this.form.device_sns.includes(item.device_sn));
          const maxItem = checkedList.reduce((max, item) => {
            return item.drone_height > max.drone_height ? item : max;
          });
          // 返航绝对高度
          let backHeight = this.form.rth_altitude // + maxItem.drone_height
          console.log(backHeight,'backHeight')
          // 安全起飞高度
          let positions = this.positionsArr
          positions.unshift({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height })
          positions.push({latitude: maxItem.latitude, longitude: maxItem.longitude, height:maxItem.drone_height})
          let resultHeight = 0
          await getWaylineMaxTerrainHeight(positions).then(res => {
            resultHeight = this.safeHeight > (res.data.data + 30) ? this.safeHeight : (res.data.data + 30)
          })
          let valueHeight = _.round(backHeight - resultHeight, 2)
          console.log('打印比较值', valueHeight)
          if (valueHeight < 0) {
            this.$confirm(`当前返航高度存在安全隐患,建议调整为${resultHeight}米以上后进行发布`, '提示', {
              confirmButtonText: '确定',
              cancelButtonText: '取消',
              type: 'warning'
            }).then(async () => {
              const submitData = {
                ...this.form,
                status: status,
                ai_types: this.form.ai_types?.length ? this.form.ai_types : []
              }
              await saveUpdateOrderLog(submitData)
              let id = this.form.id
              if (id) {
                this.$message.success('工单发布成功')
              } else {
                this.$message.success('工单创建成功')
              }
              this.dialogVisible = false
              this.detailVisible = false;
              (this.device_sns = []), (this.wayLineList = []), this.fetchTableData()
            }).catch(() => {
            })
          } else {
            const submitData = {
              ...this.form,
              status: status,
              ai_types: this.form.ai_types?.length ? this.form.ai_types : []
            }
            await saveUpdateOrderLog(submitData)
            let id = this.form.id
            if (id) {
              this.$message.success('工单发布成功')
            } else {
              this.$message.success('工单创建成功')
            }
            this.dialogVisible = false
            this.detailVisible = false;
            (this.device_sns = []), (this.wayLineList = []), this.fetchTableData()
          }
        }
      })
    },
    //驳回原因显示
    async rejectDetail (id) {
      const response = await orderLogDetails(id)
      let data = response.data.data
      this.$confirm(data.remark, '驳回原因', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        this.form = {
          ...response.data.data
        }
        // this.detailVisible = true
        this.handleViewDetail(data)
      })
    },
    formatDate (date) {
      if (!date) return undefined
      const d = new Date(date)
      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(
        d.getDate()
      ).padStart(2, '0')} 00:00:00`
    },
    mapStatus (status) {
      const statusTextMap = {
        0: '草稿',
        1: '待审核',
        2: '已驳回',
        3: '已通过'
      }
      return statusTextMap[status] || '未知状态'
    },
    getStatusTagType (status) {
      const statusMap = {
        1: 'warning',
        2: 'info',
        3: 'primary',
        4: 'success',
        5: 'danger'
      }
      return statusMap[status] || 'info'
    },
    handleTabChange (tab) {
      this.activeTab = tab.props?.name || tab.name
      this.filters.status = ''
      if (tab.props?.name === 'WAIT_AUDIT') {
        this.filters.status = '1'
      } else if (tab.props?.name === 'REJECTED') {
        this.filters.status = '2'
      } else if (tab.props?.name === 'PASS') {
        this.filters.status = '3'
      } else if (tab.props?.name === 'DRAFT') {
        this.filters.status = '0'
      }
      this.page.currentPage = 1
      this.fetchTableData()
    },
    handleSearch () {
      this.page.currentPage = 1
      this.fetchTableData()
    },
    handleReset () {
      this.filters = {
        keyword: '',
        department: '',
        type: '',
        dateRange: [],
        status: ''
      }
      this.page.currentPage = 1
      this.fetchTableData()
    },
    currentChange (currentPage) {
      this.page.currentPage = currentPage
    },
    async updateGlobalCounts () {
      const counts = {
        all: 0,
        DRAFT: 0,
        WAIT_AUDIT: 0,
        REJECTED: 0,
        PASS: 0
      }
      var reponse = await jobStatusNum()
      // console.log('统计' + reponse.data.data)
      reponse.data.data.forEach(item => {
        const tab = this.tabs.find(t => t.name === item.dict_key)
        if (tab) {
          tab.count = item.num
        }
      })
    },
    handleAdd () {
      this.form = {}
      this.form.rth_altitude = 120
      this.dialogVisible = true
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
          this.$refs.MapContainer.initAddEntity('initPosition')
        }
      })
      //航线列表
      this.asyncgetWaylineFileListByArea()
    },
    resetForm () {
      this.form = {
        name: '',
        type: '',
        handler: '',
        algorithm: '',
        location: '',
        address: '',
        content: '',
        photos: []
      }
      if (this.$refs.testform) {
        this.$refs.testform.resetFields()
      }
    },
    formatLocation (location) {
      if (!Array.isArray(location)) {
        return '未知位置'
      }
      return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`
    },
    async handleViewDetail (row) {
      const response = await orderLogDetails(row.id)
      const data = response.data.data
      this.form = {
        ...data
      }
      // 更新机巢列表
      this.device_sns = data.device_map_infos;// data.device_list;
      this.permission &&
      (this.permission.order_log_review || this.permission.order_log_recall) &&
      (data.status == 1 || data.status == 3 ||  (data.status == 2 && this.userInfo.user_id !== this.form.create_user))
        ? (this.detailTitle = '工单详情')
        : (this.detailTitle = '编辑工单')
      this.detailVisible = true
      // 更新航线列表,追加 wayline_file_region_vo 数据
      if (data.wayline_file_region_vo) {
        const newWayline = data.wayline_file_region_vo
        // 检查是否已经存在于 this.wayLineList 中
        const isDuplicate = this.wayLineList.some(
          item => item.wayline_id === newWayline.wayline_id
        )
        if (!isDuplicate) {
          this.wayLineList.push(newWayline)
        }
      }
      this.initMapLine(data.device_map_infos)
      const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
      find && (find.query = {})
    },
    async handleCheckDetail (row) {
      const response = await orderLogDetails(row.id)
      const data = response.data.data
      this.form = {
        ...data
      }
      // 更新航线列表,追加 wayline_file_region_vo 数据
      // 更新航线列表,追加 wayline_file_region_vo 数据
      if (data.wayline_file_region_vo) {
        const newWayline = data.wayline_file_region_vo
        // 检查是否已经存在于 this.wayLineList 中
        const isDuplicate = this.wayLineList.some(
          item => item.wayline_id === newWayline.wayline_id
        )
        if (!isDuplicate) {
          this.wayLineList.push(newWayline)
        }
      }
      // 更新机巢列表
      this.device_sns = data.device_list
      this.detailVisibleCopy = true
      this.initMapLine(data.device_map_infos)
    },
    //导出
    async exportData () {
      this.$confirm('是否智飞工单数据?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        NProgress.start()
        let params = this.getQueryParam()
        orderLogExport(params).then(res => {
          downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`)
          NProgress.done()
        })
      })
    },
    hasAddBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('this.permission.order_log_add :', this.permission.order_log_add)
      return this.permission && this.permission.order_log_add === true
    },
    hasPaddingBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.order_log_review === true
    },
    hasRecallPaddingBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      // 智飞工单撤回
      return this.permission && this.permission.order_log_recall === true
    },
    //驳回按钮权限
    hasRejectionBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.rejection_btn === true
    },
    //自己点发布
    userPublishPush (id) {
      this.$confirm('确定发布吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        let response = userPublish(id)
        this.$message.success('发布成功')
        this.fetchTableData()
      })
    },
    //删除
    deleteOrderLog (id) {
      this.$confirm('确定删除吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        let response = deleteOrderLog(id)
        this.$message.success('删除')
        this.fetchTableData()
      })
    },
    refreshChange () {
      this.fetchTableData()
    },
    //获取航线列表
    async asyncgetWaylineFileListByArea (name) {
      const formData = {
        name: '',
        waylineType: undefined,
        current: 1,
        size: 99999999,
        descs: 'update_time'
      }
      var wayLineListResponse = await newGetWorkspacesPage(formData)
      this.wayLineList = wayLineListResponse.data.data.records
      this.initMapLine()
    },
    initMapLine (infos = {}, cb = () => {
    }) {
      let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id)
      if (!currentLine) return
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
          this.$refs.MapContainer.initAddEntity('polyline', {
            url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key
            }?_t=${new Date().getTime()}`,
            type: currentLine.wayline_type,
            cb,
            infos
          })
        }
      })
    },
    //可飞行机巢列表
    async getFlyingNestBy (waylineId) {
      const that = this
      // 先清空
      that.form.device_sns = []
      that.device_sns = []
      this.initMapLine({}, async polygon => {
        const currentLine = that.wayLineList.find(item => item.wayline_id === waylineId)
        //按照航线来
        const params = {
          type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
          wayline_id: waylineId,
          polygon
        }
        var wayLineListResponse = await getFlyingNestBy(params)
        // 再赋值
        that.device_sns = wayLineListResponse.data.data
      })
    },
    //撤回
    async orderLogRecall (id) {
      this.$confirm('确定撤回则到草稿箱。', '是否撤回?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(async () => {
        let reposne = await orderLogRecall(id)
        this.handleSearch()
      })
    },
    onLoad () {
      this.fetchTableData()
    },
    /**
     * 通过
     */
    async orderLogPass (id) {
      let response = await orderLogPass(id)
      let data = response.data.data
      this.$message.success('审核通过')
      this.detailVisibleCopy = false
      this.detailVisible = false
      this.page.currentPage = 1
      this.onLoad(this.page, this.query)
      // this.handleViewDetail()
    },
    /**
     * 驳回
     */
    async orderLogReject (id) {
      this.$prompt('', '驳回原因', {
        confirmButtonText: '确定',
        cancelButtonText: '取消'
      }).then(async ({ value }) => {
        let response = await orderLogReject(id, value)
        let data = response.data.data
        this.$message.success('驳回成功')
        this.detailVisibleCopy = false
        // this.detailVisible = false
        this.page.currentPage = 1
        this.onLoad(this.page, this.query)
      })
    }
  },
  watch: {
    // lastHeight(newVal, oldVal) {
    //   console.log('lastHeight', newVal, oldVal);
    //   this.getlastHeight = newVal;
    // },
    tableData: {
      handler () {
        // this.updateTabCounts()
      },
      deep: true
    }
  }
}
</script>
<style lang="scss" scoped>
//:deep(.el-input-number) {
//  width: 200px;
//}
//:deep(.el-form-item__label) {
//  width: 100px !important;
//}
::v-deep(.el-tabs) {
  height: 100%;
  display: flex;
  flex-direction: column;
  .el-tabs__header {
    order: 1;
  }
  .el-tabs__content {
    order: 2;
  }
  .el-tabs__content {
    height: 0;
    flex: 1;
    display: flex;
    flex-direction: column;
    .el-tab-pane {
      height: 0;
      flex: 1;
      display: flex;
      flex-direction: column;
    }
  }
}
.filter-bar {
  // align-items: center;
  .search-bar-box {
    margin-bottom: 15px;
    display: flex;
    justify-content: space-between;
    gap: 8px;
    &-item {
      flex: 1;
      &> ::v-deep(.el-date-editor) {
        width: 100%;
        box-sizing: border-box;
      }
    }
    .search-btn {
      flex: 1;
      display: flex;
      justify-content: flex-end;
    }
    .flex-2 {
      min-width: 454px;
    }
  }
}
:deep(.el-pagination .el-select) {
  width: 128px !important;
}
.step-timer {
  position: absolute;
  left: 80%;
  top: 50%;
  transform: translateY(-50%);
  width: 100px;
  margin-left: 4px;
  color: #666;
  font-size: 12px;
}
:deep(.el-textarea__inner) {
  padding: 8px 12px;
}
:deep(.el-input__inner),
:deep(.el-select),
:deep(.el-select .el-input) {
  width: 100%; // 确保所有输入框和选择框宽度一致
}
:deep(.el-textarea__inner) {
  resize: none;
}
.action-bar {
  margin-bottom: 16px;
}
.el-tabs {
  :deep(.el-tabs__content) {
    overflow: visible;
  }
}
.el-dialog {
  .el-form-item {
    margin-bottom: 10px;
  }
}
.el-upload {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  :deep(.el-upload-list__item) {
    transition: all 0.3s ease;
  }
  :deep(.el-upload-list__item:hover) {
    background-color: #f5f7fa;
  }
}
.el-upload__tip {
  font-size: 12px;
  color: #909399;
  margin-top: 7px;
}
.custom-steps-container {
  width: 100%;
  margin: 20px 0;
}
.steps-titles {
  display: flex;
  justify-content: space-between;
  margin-bottom: 16px;
  position: relative;
}
.step-title {
  text-align: center;
  flex: 1;
  font-size: 16px;
  color: #999;
  position: relative;
  padding-bottom: 10px;
}
.step-title.active {
  color: #409eff;
  font-weight: bold;
}
.custom-steps {
  margin-top: -20px;
  /* 向上移动与标题重叠 */
}
.el-step__head {
  margin-top: 0;
}
.el-step__description {
  margin-top: 8px;
  padding: 0 20px;
}
.step-description {
  font-size: 14px;
  color: #666;
  line-height: 1.5;
}
.event-title-center {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 12px;
  color: #333;
}
.cur-menu {
  .menu-custom-box {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    &> ::v-deep(.el-button) {
      flex: 1;
      margin-left: 0;
      margin-right: 10px;
    }
  }
}
.add-box-btns {
  width: 100%;
  display: flex;
  justify-content: center;
}
.safe-height {
  :deep(.el-form-item__label) {
    width: 120px !important;
  }
  :deep(.el-input-number) {
    width: 180px !important;
  }
}
.flex {
  display: flex;
}
.flex-1 {
  flex: 1;
  &> ::v-deep(div) {
    width: 100% !important;
  }
}
</style>
src/views/tickets/ticket-copy.vue
New file
Diff too large
src/views/tickets/ticket.vue
Diff too large
src/views/tickets/ticketComponent/CreateTicketDialog.vue
New file
@@ -0,0 +1,710 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    title="新建工单"
    width="70%"
    :close-on-click-modal="false"
    @close="handleClose"
  >
    <el-form
      :model="form"
      :rules="rules"
      ref="formRef"
      label-width="90px"
      class="create-ticket-form"
      @submit.prevent
    >
      <div class="form-section">
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="工单名称" prop="name">
              <el-input
                v-model="form.name"
                placeholder="请输入工单名称"
                clearable
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="工单类型" prop="type">
              <el-select
                @change="handleTypeChange"
                v-model="form.type"
                placeholder="请选择工单类型"
                class="full-width"
                clearable
              >
                <el-option
                  v-for="item in types"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="处理部门" prop="department">
              <el-select
                v-model="form.department"
                placeholder="请选择处理部门"
                @change="handleDepartmentChange"
                class="full-width"
                clearable
              >
                <el-option
                  v-for="dept in departments"
                  :key="dept.value"
                  :label="dept.label"
                  :value="dept.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="处理人员" prop="handler">
              <el-select
                v-model="form.handler"
                placeholder="请先选择处理人员"
                :disabled="!form.department"
                class="full-width"
                clearable
              >
                <el-option
                  v-for="user in availableHandlers"
                  :key="user.id"
                  :label="user.name"
                  :value="user.id"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="关联算法" prop="algorithm">
              <el-select
                v-model="form.algorithm"
                multiple
                placeholder="请选择关联算法"
                class="full-width"
                :disabled="!form.type"
                clearable
              >
                <el-option
                  v-for="item in availableAlgorithms"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="事件位置" prop="location">
              <div class="location-wrapper">
                <avue-input-map
                  v-model="form.location"
                  :clearable="false"
                  :params="mapParams"
                  @change="handleLocationChange"
                  type="button"
                >
                  <el-button type="primary" plain class="map-button">
                    <i class="el-icon-map-location"></i> 地图选点
                  </el-button>
                </avue-input-map>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="工单内容" prop="content">
              <el-input
                type="textarea"
                v-model="form.content"
                :rows="4"
                placeholder="请输入工单内容描述"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="事件图片" prop="photos" required class="upload-wrapper">
              <el-upload
                ref="uploadRef"
                :action="'#'"
                :auto-upload="false"
                list-type="picture-card"
                :on-change="handleFileChange"
                :on-remove="handleUploadRemove"
                :before-upload="beforeUpload"
                :file-list="form.photos"
                :limit="1"
                accept="image/*"
                class="create-upload"
              >
                <template v-if="form.photos.length < 1">
                  <div class="el-icon-plus">
                    <span>+</span>
                  </div>
                </template>
              </el-upload>
              <div class="upload-tip">需上传含有地址信息的照片(jpg、jpeg、png),且不超过5M</div>
            </el-form-item>
          </el-col>
        </el-row>
      </div>
    </el-form>
    <template #footer>
      <div class="dialog-footer-new">
        <el-button
          type="danger"
          :loading="submitLoading"
          @click="handleSubmit"
          icon="el-icon-position"
        >
          发布
        </el-button>
        <el-button @click="handleClose" icon="el-icon-circle-close">取 消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script>
import { createTicket } from '@/api/tickets/ticket';
import { gcj02ToWgs84, wgs84ToGcj02 } from '@/utils/coordinateTransformation';
export default {
  name: 'CreateTicketDialog',
  props: {
    modelValue: {
      type: Boolean,
      default: false
    },
    departments: {
      type: Array,
      default: () => []
    },
    departmentUsers: {
      type: Object,
      default: () => ({})
    },
    types: {
      type: Array,
      default: () => []
    },
    allAlgorithms: {
      type: Array,
      default: () => []
    },
    editData: {
      type: Object,
      default: null
    },
    mapCenter: {
      type: Array,
      default: () => [115.861365, 28.621311]
    }
  },
  emits: ['update:modelValue', 'create-success', 'draft-success', 'create-error'],
  data() {
    return {
      form: {
        name: '',
        type: '',
        department: '',
        handler: '',
        algorithm: [],
        location: [],
        address: '',
        content: '',
        photos: []
      },
      rules: {
        name: [{ required: true, message: '请输入工单名称', trigger: 'blur' }],
        type: [{ required: true, message: '请选择工单类型', trigger: 'change' }],
        department: [{ required: true, message: '请选择所属部门', trigger: 'change' }],
        handler: [{ required: true, message: '请选择处理人员', trigger: 'change' }],
        content: [{ required: true, message: '请输入工单内容', trigger: 'blur' }],
        algorithm: [{ required: true, message: '请选择关联算法', trigger: 'change' }],
        location: [
          {
            required: true,
            validator: (rule, value, callback) => {
              if (!value || value.length < 2) {
                callback(new Error('请选择位置信息'));
              } else if (!value[0] || !value[1]) {
                callback(new Error('请选择位置信息'));
              } else {
                callback();
              }
            },
            trigger: 'change',
          },
        ],
        photos: [
          {
            validator: (rule, value, callback) => {
              if (!this.form.photos || this.form.photos.length === 0) {
                callback(new Error('请上传工单图片'));
              } else {
                callback();
              }
            },
            trigger: 'change',
          },
        ],
      },
      availableAlgorithms: [],
      mapParams: {
        zoom: 15,
        center: null
      },
      submitLoading: false,
      draftLoading: false
    };
  },
  computed: {
    dialogVisible: {
      get() {
        return this.modelValue;
      },
      set(value) {
        this.$emit('update:modelValue', value);
      }
    },
    availableHandlers() {
      return this.form.department ? this.departmentUsers[this.form.department] || [] : [];
    },
    isEditMode() {
      return !!this.editData;
    }
  },
  watch: {
    modelValue(newVal) {
      if (newVal) {
        this.initForm();
        this.mapParams.center = [...this.mapCenter];
      }
    }
  },
  methods: {
    initForm() {
      if (this.isEditMode && this.editData) {
        // 编辑模式:填充表单数据
        this.loadEditData();
      } else {
        // 新建模式:重置表单
        this.resetForm();
      }
    },
    loadEditData() {
      const data = this.editData;
      // 获取部门ID
      let deptId = data.dept_id;
      if (!deptId) {
        const deptInfo = this.departments.find(
          dept => dept.label && dept.label.trim() === data.department.trim()
        );
        deptId = deptInfo?.value;
      }
      // 获取工单类型值
      const typeValue = this.types.find(t => t.value === data.type)?.value || data.work_order_type_dict_key;
      // 处理算法数组
      let algorithmArr = [];
      if (Array.isArray(data.aiType)) {
        algorithmArr = data.aiType;
      } else if (typeof data.aiType === 'string' && data.aiType) {
        algorithmArr = data.aiType.split(/[,、]/).map(item => item.trim());
      }
      // 确保算法值与选项匹配
      algorithmArr = algorithmArr
        .map(item => {
          if (typeof item === 'string') {
            const matchedAlgorithm = this.allAlgorithms.find(
              algo => algo.dict_value === item || algo.dict_key === item
            );
            return matchedAlgorithm ? matchedAlgorithm.dict_key : item;
          }
          return item;
        })
        .filter(Boolean);
      // 处理位置数据
      let curLocation = [];
      if (Array.isArray(data.location) && data.location.length >= 2) {
        let [lng, lat] = this.disposeLocation(false, data);
        curLocation = [lng, lat, data.location[2] || data.address || ''];
      }
      this.form = {
        id: data.id,
        name: data.orderName,
        type: typeValue,
        department: deptId,
        handler: data.handler,
        algorithm: algorithmArr,
        location: curLocation,
        address: curLocation[2] || '',
        content: data.content,
        photos: data.photo_url ? [{
          name: 'existing-photo.jpg',
          url: data.photo_url,
          status: 'success',
          raw: null,
          existingUrl: data.photo_url,
        }] : []
      };
      // 更新算法选项
      this.handleTypeChange(typeValue);
    },
    resetForm() {
      this.form = {
        name: '',
        type: '',
        department: '',
        handler: '',
        algorithm: [],
        location: [],
        address: '',
        content: '',
        photos: []
      };
      this.availableAlgorithms = [];
      if (this.$refs.formRef) {
        this.$refs.formRef.clearValidate();
      }
    },
    handleClose() {
      this.dialogVisible = false;
      this.resetForm();
    },
    handleTypeChange(typeValue) {
      this.form.algorithm = [];
      if (!typeValue) {
        this.availableAlgorithms = [];
        return;
      }
      const matchedCategory = this.allAlgorithms.find(category => category.dict_key === typeValue);
      if (!matchedCategory || !matchedCategory.algorithms || matchedCategory.algorithms.length === 0) {
        this.availableAlgorithms = [];
        this.$message.info('该工单类型暂无关联算法');
        return;
      }
      this.availableAlgorithms = matchedCategory.algorithms.map(algo => ({
        label: algo.dict_value,
        value: algo.dict_key,
        dict_key: algo.dict_key,
        dict_value: algo.dict_value,
      }));
    },
    handleDepartmentChange() {
      this.form.handler = '';
    },
    handleLocationChange(val) {
      let locationValue = val.value;
      if (locationValue && locationValue.length >= 2) {
        const [lng, lat] = gcj02ToWgs84(locationValue[0], locationValue[1]);
        this.form.location = [Number(lng), Number(lat), locationValue[2] || ''];
        this.form.address = locationValue[2] || '';
      } else {
        this.form.location = [];
        this.form.address = '';
      }
    },
    handleFileChange(file, fileList) {
      this.form.photos = fileList.map(item => ({
        ...item,
        existingUrl: item.url && !item.raw ? item.url : null,
      }));
    },
    handleUploadRemove(file, fileList) {
      this.form.photos = fileList;
    },
    beforeUpload(file) {
      const isImage = file.type.includes('image');
      const isLt5M = file.size / 1024 / 1024 < 5;
      if (!isImage) {
        this.$message.error('只能上传图片文件!');
        return false;
      }
      if (!isLt5M) {
        this.$message.error('图片大小不能超过5MB!');
        return false;
      }
      return true;
    },
    async handleSubmit() {
      if (this.submitLoading) return;
      try {
        await this.$refs.formRef.validate();
        // 验证位置信息
        if (!this.form.location || this.form.location.length < 2) {
          this.$message.warning('请在地图上选择位置');
          return;
        }
        // 验证图片
        if (!this.form.photos || this.form.photos.length === 0) {
          this.$message.warning('请上传工单图片');
          return;
        }
        this.submitLoading = true;
        let [lng, lat] = this.disposeLocation(true, this.form);
        const submitData = {
          eventName: this.form.name,
          content: this.form.content,
          workType: '1',
          longitude: lng,
          latitude: lat,
          address: this.form.address,
          workOrderTypeDictKey: this.form.type,
          aiType: Array.isArray(this.form.algorithm) ? this.form.algorithm : [this.form.algorithm],
          updateUser: this.form.handler,
          createDept: this.form.department,
          isDraft: 0,
        };
        if (this.form.id) {
          submitData.id = this.form.id;
        }
        // 获取文件
        let file = null;
        const photoInfo = this.form.photos[0];
        if (photoInfo.raw) {
          file = photoInfo.raw;
        } else if (photoInfo.existingUrl) {
          submitData.photoUrl = photoInfo.existingUrl;
        } else {
          this.$message.warning('图片文件无效,请重新上传');
          return;
        }
        const response = await createTicket(submitData, file);
        if (response.data.code === 0) {
          this.$message.success(this.isEditMode ? '工单编辑成功' : '工单创建成功');
          this.$emit('create-success');
          this.handleClose();
        } else {
          throw new Error(response.data.msg || '操作失败');
        }
      } catch (error) {
        if (error.message.includes('验证未通过')) {
          this.$message.warning('请填写完整的工单信息');
        } else {
          this.$message.error(error.message || '操作失败,请稍后重试');
          this.$emit('create-error', error);
        }
      } finally {
        this.submitLoading = false;
      }
    },
    disposeLocation(goWgs84 = false, data) {
      let lng = '', lat = '';
      if (Array.isArray(data.location) && data.location.length > 0) {
        if (goWgs84) {
          lng = data.location?.[0] ? String(data.location[0]) : undefined;
          lat = data.location?.[1] ? String(data.location[1]) : undefined;
          if (lng && lat) {
            [lng, lat] = gcj02ToWgs84(Number(lng), Number(lat));
          }
        } else {
          lng = Number(data.location[0]);
          lat = Number(data.location[1]);
          if (lng && lat) {
            [lng, lat] = this.wgs84ToGcj02(Number(lng), Number(lat));
          }
        }
      }
      return [String(lng), String(lat)];
    },
  }
};
</script>
<style lang="scss" scoped>
.full-width {
  width: 100%;
}
.location-wrapper {
  width: 100%;
  height: auto;
}
.map-button {
  width: 100%;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.create-ticket-form {
  padding: 20px 10px;
  .form-section {
    background-color: #fff;
    border-radius: 4px;
    .el-row {
      margin-bottom: 16px;
      &:last-child {
        margin-bottom: 0;
      }
    }
  }
  .location-wrapper {
    width: 100%; // 修改为100%以适应父容器
    height: auto; // 修改为auto以自适应内容
    .map-button {
      width: 100%; // 让按钮填满容器宽度
      height: 36px; // 与其他输入框保持一致的高度
      display: flex;
      align-items: center;
      justify-content: center;
      i {
        margin-right: 4px;
      }
    }
    .location-text {
      margin-top: 8px;
      padding: 8px 12px;
      background-color: #f5f7fa;
      border-radius: 4px;
      color: #606266;
      font-size: 13px;
      line-height: 1.4;
    }
  }
  .upload-wrapper {
    .uploader {
      :deep(.el-upload--picture-card) {
        width: 120px;
        height: 100px;
        line-height: 128px;
      }
      :deep(.el-upload-list__item) {
        width: 120px;
        height: 120px;
      }
    }
    .upload-tip {
      font-size: 12px;
      color: #909399;
      line-height: 1.4;
      margin-top: 8px;
    }
  }
  .el-form-item {
    margin-bottom: 18px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  :deep(.el-form-item__label) {
    font-weight: 500;
    color: #606266;
  }
  // :deep(.el-input__inner) {
  //   height: 36px;
  //   line-height: 36px;
  // }
  :deep(.el-textarea__inner) {
    padding: 8px 12px;
  }
  // :deep(.el-input__inner),
  // :deep(.el-select),
  // :deep(.el-select .el-input) {
  //   width: 100%; // 确保所有输入框和选择框宽度一致
  // }
  .full-width {
    width: 100%;
  }
}
:deep(.el-dialog__body) {
  border-top: 0.1rem solid #f0f0f0;
}
/* 新建工单和处理工单的上传组件样式 */
.create-upload,
.detail-upload {
  :deep(.el-upload--picture-card) {
    width: 120px;
    height: 100px;
    line-height: 100px;
  }
  /* 隐藏额外的上传按钮 */
  :deep(.el-upload.el-upload--picture-card) {
    display: none;
  }
  /* 当没有图片时显示上传按钮 */
  :deep(.el-upload.el-upload--picture-card:first-child) {
    display: flex;
  }
  /* 上传组件的预览图片样式 */
  :deep(.el-upload-list--picture-card .el-upload-list__item) {
    width: 120px;
    height: 100px;
  }
}
</style>
src/views/tickets/ticketComponent/DispatchDialog.vue
New file
@@ -0,0 +1,244 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    title="派发工单"
    width="40%"
    :close-on-click-modal="false"
    @close="handleClose"
  >
    <el-form
      :model="form"
      :rules="rules"
      ref="formRef"
      label-width="100px"
      @submit.prevent
    >
      <el-form-item label="选择部门" prop="department">
        <el-tree-select
          v-model="form.department"
          placeholder="请选择部门"
          :data="deptTreeData"
          :props="treeProps"
          :filterable="true"
          :allow-clear="true"
          :check-strictly="true"
          @change="handleDepartmentChange"
          class="full-width"
        />
      </el-form-item>
      <el-form-item label="选择处理人" prop="handler">
        <el-select
          filterable
          v-model="form.handler"
          placeholder="请选择处理人"
          :disabled="!form.department"
          class="full-width"
        >
          <el-option
            v-for="user in availableHandlers"
            :key="user.id"
            :label="user.name"
            :value="user.id"
          />
        </el-select>
      </el-form-item>
    </el-form>
    <template #footer>
      <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
      <el-button
        type="primary"
        :loading="loading"
        @click="handleSubmit"
        icon="el-icon-check"
      >
        确认派发
      </el-button>
    </template>
  </el-dialog>
</template>
<script setup>
import { getDeptTree, getDeptLazyTree } from '@/api/system/dept'
import { ElMessage } from 'element-plus';
import { flowEvent } from '@/api/tickets/ticket';
import { onMounted } from 'vue';
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  currentDetail: {
    type: Object,
    default: () => ({})
  },
  departmentUsers: {
    type: Object,
    default: () => ({})
  },
  algorithms: {
    type: Array,
    default: () => []
  },
  types: {
    type: Array,
    default: () => []
  }
});
const emit = defineEmits(['update:modelValue', 'dispatch-success', 'dispatch-error']);
const formRef = ref(null); // 表单引用
const loading = ref(false);
const form = ref({
  department: '',
  handler: ''
});
const deptTreeData =ref([])
// 树形选择器配置
const treeProps = ref({
  value: 'id', // 对应部门数据的id字段(作为选中值)
  label: 'name', // 对应部门数据的name字段(作为显示文本)
  children: 'children' // 对应子部门字段
});
// 获取树形数据
    const getDepartmentTree=() =>{
    const tenantId = '000000'
    getDeptTree(tenantId).then(res => {
    deptTreeData.value = res.data.data
      })
}
// 表单校验规则
const rules = ref({
  department: [{ required: true, message: '请选择部门', trigger: 'change' }],
  handler: [{ required: true, message: '请选择处理人', trigger: 'change' }]
});
const dialogVisible = computed({
  get() {
    return props.modelValue;
  },
  set(value) {
    emit('update:modelValue', value);
  }
});
// 处理人列表
const availableHandlers = computed(() => {
  return form.value.department ? props.departmentUsers[form.value.department] || [] : [];
});
watch(
  () => props.modelValue,
  (newVal) => {
    if (newVal) {
      resetForm();
    }
  },
  { immediate: true }
);
// 重置表单
const resetForm = () => {
  form.value = {
    department: '',
    handler: ''
  };
  if (formRef.value) {
    formRef.value.clearValidate();
  }
};
// 关闭对话框
const handleClose = () => {
  dialogVisible.value = false;
  resetForm();
};
// 部门切换时清空处理人
const handleDepartmentChange = () => {
  form.value.handler = '';
};
// 验证必填字段(currentDetail 中的必填项)
const validateRequiredFields = () => {
  if (!props.currentDetail.orderName || !props.currentDetail.orderName.trim()) {
    ElMessage.warning('请填写工单名称');
    return false;
  }
  if (!props.currentDetail.type) {
    ElMessage.warning('请选择工单类型');
    return false;
  }
  if (!props.currentDetail.content || !props.currentDetail.content.trim()) {
    ElMessage.warning('请填写工单内容');
    return false;
  }
  return true;
};
// 获取算法值
const getAlgorithmValue = () => {
  const { aiType } = props.currentDetail;
  // 检查是否直接匹配 value
  const isValueMatch = props.algorithms.some(item => item.value === aiType);
  if (isValueMatch) {
    return aiType;
  }
  // 尝试匹配 dict_value 或 label
  const matched = props.algorithms.find(
    item => item.dict_value === aiType || item.label === aiType
  );
  return matched ? matched.value : null;
};
// 提交派发
const handleSubmit = async () => {
  try {
    const valid = await formRef.value.validate();
    if (!valid) return;
    if (!validateRequiredFields()) {
      return;
    }
    loading.value = true;
    const algorithmValue = getAlgorithmValue();
    const data = {
      id: props.currentDetail.id,
      status: props.currentDetail.status,
      isPass: 0, // 0 表示通过
      eventName: props.currentDetail.orderName,
      eventNum: props.currentDetail.orderNumber,
      workOrderTypeDictKey: props.currentDetail.type,
      content: props.currentDetail.content,
      createDept: form.value.department,
      updateUser: form.value.handler,
      aiType: algorithmValue,
    };
    // 发送请求
    const response = await flowEvent(data);
    if (response.data.code === 0) {
      ElMessage.success('工单已成功派发');
      emit('dispatch-success');
      handleClose();
    } else {
      throw new Error(response.data.msg || '派发失败');
    }
  } catch (error) {
    console.error('派发失败:', error);
    emit('dispatch-error', error);
    ElMessage.error(error.message || '派发失败,请稍后重试');
  } finally {
    loading.value = false;
  }
};
onMounted(()=>{
  getDepartmentTree()
})
</script>
<style scoped>
.full-width {
  width: 100%;
}
.el-form {
margin-top: 20px;}
</style>
src/views/tickets/ticketComponent/RecheckDialog.vue
New file
@@ -0,0 +1,117 @@
<template>
  <el-dialog
    v-model="dialogVisible"
    title="工单复核"
    width="30%"
    append-to-body
    class="ztzf-dialog-mange"
    @close="handleClose"
  >
    <div class="dialog-footer">
      <el-button type="primary" @click="handleConfirm(1)">人工复核</el-button>
      <el-button type="primary" @click="handleConfirm(2)">无人机复核</el-button>
    </div>
  </el-dialog>
</template>
<script>
import { ElMessageBox, ElLoading } from 'element-plus';
import { getReviewById, getCreateEventJob } from '@/api/tickets/ticket';
export default {
  name: 'RecheckDialog',
  props: {
    modelValue: {
      type: Boolean,
      default: false
    },
    recheckData: {
      type: Object,
      default: () => ({})
    }
  },
  emits: ['update:modelValue', 'recheck-success'],
  computed: {
    dialogVisible: {
      get() {
        return this.modelValue;
      },
      set(value) {
        this.$emit('update:modelValue', value);
      }
    }
  },
  methods: {
    handleClose() {
      this.dialogVisible = false;
    },
    handleConfirm(key) {
      if (key === 1) {
        this.handleManualRecheck();
      } else {
        this.handleUAVRecheck();
      }
    },
    async handleManualRecheck() {
      try {
        await getReviewById(this.recheckData.id);
        this.$message.success('人工复核成功');
        this.$emit('recheck-success');
        this.dialogVisible = false;
      } catch (error) {
        this.$message.error('人工复核失败');
        console.error('人工复核失败:', error);
      }
    },
    async handleUAVRecheck() {
      const loading = ElLoading.service({
        lock: true,
        text: '复核任务创建中……',
        background: 'rgba(0, 0, 0, 0.7)',
      });
      try {
        const response = await getCreateEventJob(this.recheckData.id);
        const estimatedTime = response.data.data;
        loading.close();
        await ElMessageBox.confirm(
          `预计复核执行时间为${estimatedTime}`,
          '提示',
          {
            confirmButtonText: '确定',
            showCancelButton: false,
            closeOnClickModal: false,
            closeOnPressEscape: false,
            type: 'warning',
          }
        );
        this.$message.success('无人机复核任务创建成功');
        this.$emit('recheck-success');
        this.dialogVisible = false;
      } catch (error) {
        loading.close();
        if (error !== 'cancel') {
          this.$message.error('无人机复核任务创建失败');
          console.error('无人机复核失败:', error);
        }
        this.dialogVisible = false;
      }
    }
  }
};
</script>
<style scoped>
.dialog-footer {
  display: flex;
  justify-content: center;
  gap: 20px;
  padding: 20px 0;
}
</style>
src/views/tickets/ticketComponent/TicketDetailDialog.vue
New file
@@ -0,0 +1,1439 @@
<template>
  <el-dialog
    class="custom-dialog"
    align-center
    v-model="dialogVisible"
    :title="isEditMode ? '编辑工单' : '工单详情'"
    width="80%"
    append-to-body
    @close="handleClose"
  >
    <div class="detail-container">
      <div class="detail-top-title">
        <div class="event-title-center event-orderNumber">
          {{ currentDetail.orderNumber || '工单编号' }}
        </div>
        <div class="event-title-center">{{ currentDetail.orderName || '事件名称' }}</div>
      </div>
      <div v-if="totalTime" class="event-total-time">总耗时:{{ totalTime }}</div>
      <!-- 工单状态流程 -->
      <div class="custom-steps-container">
        <!-- 标题行 -->
        <div class="steps-titles">
          <div
            v-for="(status, index) in stepStatusList"
            :key="index"
            :class="{
              'step-title': true,
              active: index <= stepStatusList.indexOf(String(currentDetail.status)),
            }"
          >
            {{ mapStatus(status) }}
          </div>
        </div>
        <!-- Element Steps 组件 -->
        <el-steps :active="getActiveStep() - 1" align-center class="custom-steps">
          <el-step v-for="(status, index) in stepStatusList" :key="index">
            <template #description>
              <span class="step-description">
                {{ getStepHandler(status) }}
              </span>
              <div class="step-description" v-if="getStepTime(status)">
                <span class="step-timer"> 耗时:{{ getStepTime(status) }} </span>
              </div>
              <div class="step-description">
                {{ getStepCreateTime(status) }}
              </div>
            </template>
          </el-step>
        </el-steps>
      </div>
      <div class="PopUpTableScrolls">
        <!-- 基本信息表格 -->
        <el-table :show-header="false" :data="formattedDetailFields" border class="tableCss">
          <el-table-column prop="label1" label="基本信息" width="150">
            <template #default="{ row }">
              <span
                v-if="
                  currentDetail.status === 0 &&
                  (row.label1 === '关联算法' || row.label1 === '工单名称')
                "
                class="required-label"
              >
                <span class="required-star">*</span>{{ row.label1 }}
              </span>
              <span v-else>{{ row.label1 }}</span>
            </template>
          </el-table-column>
          <el-table-column>
            <template #default="{ row }">
              <template
                v-if="
                  currentDetail.status === 0 &&
                  row.label1 === '工单名称' &&
                  hasProcessingBtnPermission()
                "
              >
                <el-input
                  v-model="currentDetail.orderName"
                  placeholder="请输入工单名称"
                  class="required-input"
                />
              </template>
              <template
                v-else-if="
                  currentDetail.status === 0 &&
                  row.label1 === '关联算法' &&
                  hasProcessingBtnPermission()
                "
              >
                <el-select
                  v-model="currentDetail.aiType"
                  placeholder="请选择关联算法"
                  class="required-input"
                >
                  <el-option
                    v-for="item in algorithms"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
              </template>
              <template v-else>{{ row.value1 }}</template>
            </template>
          </el-table-column>
          <el-table-column prop="label2" label="基本信息" width="150">
            <template #default="{ row }">
              <span
                v-if="currentDetail.status === 0 && row.label2 === '工单内容'"
                class="required-label"
              >
                <span class="required-star">*</span>{{ row.label2 }}
              </span>
              <span v-else>{{ row.label2 }}</span>
            </template>
          </el-table-column>
          <el-table-column>
            <template #default="{ row }">
              <template
                v-if="
                  currentDetail.status === 0 &&
                  row.label2 === '工单内容' &&
                  hasProcessingBtnPermission()
                "
              >
                <el-input
                  type="textarea"
                  :maxlength="200"
                  show-word-limit
                  v-model="currentDetail.content"
                  placeholder="请输入工单内容"
                  class="required-input"
                />
              </template>
              <template v-else>{{ row.value2 }}</template>
            </template>
          </el-table-column>
        </el-table>
        <!-- 事件处理详情 -->
        <div v-if="[3, 4].includes(currentDetail.status)" class="form-section">
          <div class="section-title">
            <template v-if="currentDetail.status === 3">
              <span class="required-label">
                <span class="required-star">*</span>事件处理详情
              </span>
            </template>
            <template v-else> 事件处理详情</template>
          </div>
          <!-- 处理中状态显示输入框 -->
          <template v-if="currentDetail.status === 3 && hasProcessedAndOverBtnPermission()">
            <el-input
              type="textarea"
              v-model="currentDetail.processingDetail"
              placeholder="请输入事件处理详情"
              :rows="4"
              :maxlength="200"
              show-word-limit
              style="width: 100%; margin-bottom: 10px"
              :disabled="!isCurrentUserAssigned"
            />
          </template>
          <!-- 已完成和已完结状态显示只读文本 -->
          <template v-else>
            <div class="readonly-processing-detail">
              {{ currentDetail.processingDetail }}
            </div>
          </template>
        </div>
        <!-- 上传图片 -->
        <div v-if="[3].includes(currentDetail.status)" class="form-section uploadImg">
          <div class="section-title" v-if="hasProcessedAndOverBtnPermission()">
            <template v-if="currentDetail.status === 3">
              <span class="required-label"> <span class="required-star">*</span>上传图片 </span>
            </template>
            <template v-else> 上传图片</template>
          </div>
          <el-upload
          :disabled="!isCurrentUserAssigned"
            v-if="hasProcessedAndOverBtnPermission()"
            ref="uploadRef"
            :action="'#'"
            :auto-upload="false"
            list-type="picture-card"
            :on-change="handleFileChange"
            :on-remove="handleUploadRemove"
            :before-upload="beforeUpload"
            :file-list="currentDetail.photos || []"
            :limit="1"
            accept="image/*"
            class="detail-upload"
          >
            <template v-if="!currentDetail.photos || currentDetail.photos.length < 1">
              <div class="el-icon-plus">
                <span>+</span>
              </div>
            </template>
          </el-upload>
          <div class="el-upload__tip" v-if="hasProcessedAndOverBtnPermission()">
            (上传照片即可完结工单,只能上传jpg、jpeg、png照片,且不超过5M)
          </div>
        </div>
        <!-- 图片和地图部分 -->
        <div class="media-section">
          <el-row :gutter="20">
            <el-col :span="12">
              <div class="media-box">
                <div class="media-title">事件图片</div>
                <div class="media-content">
                  <el-image
                    v-if="currentDetail.mediaUrl"
                    :src="getThumbUrl(currentDetail.mediaUrl)"
                    :preview-src-list="[getPreviewUrl(currentDetail.mediaUrl)]"
                    fit="contain"
                    style="cursor: pointer"
                  >
                    <template #placeholder>
                      <div class="image-placeholder">
                        <i class="el-icon-picture-outline"></i>
                        <span>加载中...</span>
                      </div>
                    </template>
                    <template #error>
                      <div class="image-error">
                        <i class="el-icon-picture-outline"></i>
                        <span>加载失败</span>
                      </div>
                    </template>
                  </el-image>
                  <div v-else class="no-media">暂无图片/视频</div>
                </div>
              </div>
            </el-col>
            <el-col :span="12">
              <div class="media-box">
                <template v-if="currentDetail.status === 4">
                  <div class="media-title">工单处理图片</div>
                  <div class="media-content">
                    <el-image
                      v-if="currentDetail.updatePhotoUrl"
                      :src="getThumbUrl(currentDetail.updatePhotoUrl)"
                      :preview-src-list="[getPreviewUrl(currentDetail.updatePhotoUrl)]"
                      fit="fill"
                    >
                      <template #placeholder>
                        <div class="image-placeholder">
                          <i class="el-icon-picture-outline"></i>
                          <span>加载中...</span>
                        </div>
                      </template>
                      <template #error>
                        <div class="image-error">
                          <i class="el-icon-picture-outline"></i>
                          <span>加载失败</span>
                        </div>
                      </template>
                    </el-image>
                    <div v-else class="no-media">暂无处理图片</div>
                  </div>
                </template>
                <template v-else>
                  <div class="media-title">
                    地图标记事件点
                    <el-popover
                      v-if="currentDetail.status === 3 || currentDetail.status === 0"
                      popper-class="custom-qrcode-popover"
                      :width="120"
                      :visible="currentDetail.showQR && dialogVisible"
                      placement="top"
                      title=""
                      trigger="click"
                    >
                      <template #reference>
                        <img
                          @click.stop="handleQRCode(currentDetail)"
                          class="QRCodeImg"
                          src="@/assets/images/dataCenter/qrCode.svg"
                          alt=""
                          title="事件导航"
                        />
                      </template>
                      <div class="qrcode-content">
                        <div class="close-btn" @click.stop="currentDetail.showQR = false">×</div>
                        <CreateQRcode
                          v-if="currentDetail.showQR && dialogVisible"
                          :lat-and-lon="currentDetail.location"
                        />
                      </div>
                    </el-popover>
                  </div>
                  <div class="media-content">
                    <map-container v-if="dialogVisible" ref="mapContainerRef" />
                  </div>
                </template>
              </div>
            </el-col>
          </el-row>
        </div>
      </div>
      <!-- 操作按钮 -->
      <div class="dialog-footer1-new">
        <div
          :class="currentIndex === 0 ? 'disableds' : ''"
          class="leftBtn"
          @click="handlePrev"
        >
          上一页
        </div>
        <div class="btngroups">
          <template v-if="currentDetail.status === 2">
            <!-- 待审核 -->
            <el-button
              v-if="hasReviewBtnPermission()"
              type="primary"
              :loading="approveLoading"
              @click="handleApprove"
              icon="el-icon-check"
            >
              通过
            </el-button>
            <el-button
              v-if="hasReviewBtnPermission()"
              type="danger"
              :loading="rejectLoading"
              @click="handleReject"
              icon="el-icon-close"
            >
              不通过
            </el-button>
            <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
          </template>
          <template v-else-if="currentDetail.status === 0">
            <el-button
              v-if="hasProcessingBtnPermission()"
              type="primary"
              :loading="dispatchLoading"
              @click="handleApproveAndDispatch"
              icon="el-icon-check"
            >
              受理
            </el-button>
            <el-button
              v-if="hasProcessingBtnPermission()"
              type="danger"
              :loading="rejectLoading"
              @click="handleReject"
              icon="el-icon-close"
            >
              不受理
            </el-button>
            <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
          </template>
          <template v-if="currentDetail.status === 3">
            <!-- 处理中 -->
            <el-button
              v-if="hasProcessedAndOverBtnPermission()"
              type="primary"
              :loading="completeLoading"
              @click="handleComplete"
              icon="el-icon-circle-check"
              :disabled="!isCurrentUserAssigned"
            >
              完成工单
            </el-button>
            <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
          </template>
          <template v-else-if="currentDetail.status === 4">
            <!-- 已完成 -->
            <el-button @click="handleClose" icon="el-icon-circle-close">取消</el-button>
          </template>
        </div>
        <div
          :class="currentIndex === totalItems - 1 ? 'disableds' : ''"
          class="leftBtn"
          @click="handleNext"
        >
          下一页
        </div>
      </div>
    </div>
  </el-dialog>
</template>
<script setup>
import { useStore } from 'vuex'
import { ref, computed, watch, nextTick } from 'vue';
import { flowEvent, getStepInfo } from '@/api/tickets/ticket';
import { getSmallImg, getShowImg } from '@/utils/util';
import CreateQRcode from '@/components/CreateQRcode/CreateQRcode.vue';
import { ElMessage} from 'element-plus';
const store = useStore()
const userInfo = computed(() => store.state.user.userInfo)
const isLoading = ref(false);
// console.log('userInfo',userInfo.value);
// 定义props
const props = defineProps({
  modelValue: {
    type: Boolean,
    default: false
  },
  currentDetail: {
    type: Object,
    default: () => ({})
  },
  currentIndex: {
    type: Number,
    default: 0
  },
  totalItems: {
    type: Number,
    default: 0
  },
  algorithms: {
    type: Array,
    default: () => []
  },
  types: {
    type: Array,
    default: () => []
  },
  permission: {
    type: Object,
    default: () => ({})
  },
  stepStatusList: {
    type: Array,
    default: () => []
  },
  workType: {
    type: Number,
    default: 0
  }
});
// 定义emits
const emit = defineEmits([
  'update:modelValue',
  'detail-success',
  'detail-error',
  'prev-item',
  'next-item',
  'update:currentDetail',
  'approve-and-dispatch'
]);
// 响应式数据
const stepInfos = ref([]);
const totalTime = ref('');
const approveLoading = ref(false);
const rejectLoading = ref(false);
const dispatchLoading = ref(false);
const completeLoading = ref(false);
const finalizeLoading = ref(false);
const uploadRef = ref(null);
const mapContainerRef = ref(null);
// 计算属性
const dialogVisible = computed({
  get() {
    return props.modelValue;
  },
  set(value) {
    emit('update:modelValue', value);
  }
});
const isEditMode = computed(() => {
  return props.currentDetail.status === -1;
});
// 是否有权限完成工单
const isCurrentUserAssigned  =computed(()=>{
  const handleUserId = props.currentDetail?.handle_user_id;
  const currentUserId = userInfo.value?.user_id;
   return !!handleUserId && !!currentUserId && String(handleUserId) === String(currentUserId);
})
const formattedDetailFields = computed(() => {
  const fields = [
    { label: '工单名称', value: props.currentDetail.orderName },
    {
      label: '工单类型',
      value: props.types.find(t => t.value === props.currentDetail.type)?.label || props.currentDetail.type,
    },
    { label: '关联任务', value: props.currentDetail.job_name || '/' },
    { label: '工单创建人', value: props.currentDetail.creator },
    { label: '当前状态', value: mapStatus(props.currentDetail.status) },
    { label: '事件地址', value: props.currentDetail.address || props.currentDetail.latAndLon },
    {
      label: '关联算法',
      value: props.algorithms.find(t => t.value === props.currentDetail.aiType)?.label || props.currentDetail.aiType,
    },
    { label: '发起单位', value: props.currentDetail.department },
    { label: '发起任务时间', value: props.currentDetail.startTime },
    { label: '工单内容', value: props.currentDetail.content },
  ];
  const filteredFields = fields.filter(field => field.value !== '/');
  const formattedFields = [];
  for (let i = 0; i < filteredFields.length; i += 2) {
    formattedFields.push({
      label1: filteredFields[i]?.label || '',
      value1: filteredFields[i]?.value || (filteredFields[i]?.label ? '暂无数据' : ''),
      label2: filteredFields[i + 1]?.label || '',
      value2: filteredFields[i + 1]?.value || (filteredFields[i + 1]?.label ? '暂无数据' : ''),
    });
  }
  return formattedFields;
});
// 监听
watch(
  () => props.modelValue,
  (newVal) => {
    if (newVal) {
      loadStepInfo();
      initMap();
    }
  }
);
watch(
  () => props.currentDetail,
  (newVal) => {
    if (newVal && dialogVisible.value) {
    isLoading.value = true;
     setTimeout(() => {
        loadStepInfo();
        initMap();
        isLoading.value = false;
      }, 50);
    }
  },
  { deep: true }
);
// 方法
const handleClose = () => {
  dialogVisible.value = false;
};
const handlePrev = () => {
  if (props.currentIndex > 0) {
    emit('prev-item');
  }
};
const handleNext = () => {
  if (props.currentIndex < props.totalItems - 1) {
    emit('next-item');
  }
};
const loadStepInfo = async () => {
  try {
    const stepResponse = await getStepInfo(props.currentDetail.orderNumber);
    const steps = Array.isArray(stepResponse.data.data)
      ? stepResponse.data.data
      : stepResponse.data.data?.steps || [];
    const finishedStep = steps.find(s => String(s.status) === '4');
    totalTime.value = finishedStep && finishedStep.total_time ? finishedStep.total_time : '';
    stepInfos.value = steps.map(step => ({
      status: String(step.status),
      name: step.name,
      time: step.time,
      create_time: step.create_time,
    }));
  } catch (error) {
    stepInfos.value = [];
  }
};
const initMap = () => {
  nextTick(() => {
    if (mapContainerRef.value && mapContainerRef.value.initAddEntity && props.currentDetail.location) {
      mapContainerRef.value.initAddEntity('point', props.currentDetail.location);
    }
  });
};
const mapStatus = (status) => {
  const statusTextMap = {
    2: '待审核',
    0: '待处理',
    3: '处理中',
    4: '已完成',
  };
  return statusTextMap[status] || '未知状态';
};
const getStepHandler = (status) => {
  const step = stepInfos.value.find(step => step.status === status);
  return step ? step.name : '';
};
const getStepTime = (status) => {
  const step = stepInfos.value.find(step => step.status === status);
  return step && step.time && step.time !== '0' ? step.time : false;
};
const getStepCreateTime = (status) => {
  const step = stepInfos.value.find(step => step.status === status);
  return step ? step.create_time : null;
};
const getActiveStep = () => {
  const arr = props.stepStatusList;
  const index = arr.indexOf(String(props.currentDetail.status));
  return index !== -1 ? index + 2 : 1;
};
const handleQRCode = (val) => {
  emit('update:currentDetail', {
    ...props.currentDetail,
    showQR: !val.showQR
  });
};
const getThumbUrl = (url) => {
  if (!url) return '';
  return getSmallImg(url);
};
const getPreviewUrl = (url) => {
  if (!url) return '';
  return getShowImg(url);
};
const hasProcessingBtnPermission = () => {
  return props.permission && props.permission.tickets_processing_btn === true;
};
const hasProcessedAndOverBtnPermission = () => {
  return props.permission && props.permission.tickets_view_processedAndOver === true;
};
// 通过不通过
const hasReviewBtnPermission = () => {
  return props.permission && props.permission.tickets_review_btn === true;
};
// 文件操作方法
const handleFileChange = (file, fileList) => {
  emit('update:currentDetail', {
    ...props.currentDetail,
    photos: fileList
  });
};
const handleUploadRemove = (file, fileList) => {
  emit('update:currentDetail', {
    ...props.currentDetail,
    photos: fileList
  });
};
const beforeUpload = (file) => {
  const isImage = file.type.includes('image');
  const isLt5M = file.size / 1024 / 1024 < 5;
  if (!isImage) {
    ElMessage.error('只能上传图片文件!');
    return false;
  }
  if (!isLt5M) {
    ElMessage.error('图片大小不能超过5MB!');
    return false;
  }
  return true;
};
// 工单操作方法
const handleApprove = async () => {
  if (approveLoading.value) return;
  approveLoading.value = true;
  try {
    const data = {
      id: props.currentDetail.id,
      status: props.currentDetail.status,
      isPass: 0,
      eventNum: props.currentDetail.orderNumber,
    };
    const response = await flowEvent(data);
    if (response.data.code === 0) {
      ElMessage.success('工单已通过');
      emit('detail-success');
      handleClose();
    } else {
      throw new Error(response.data.msg || '操作失败');
    }
  } catch (error) {
    ElMessage.error(error.message || '操作失败,请稍后重试');
    emit('detail-error', error);
  } finally {
    approveLoading.value = false;
  }
};
const handleReject = async () => {
  if (rejectLoading.value) return;
  rejectLoading.value = true;
  try {
    const data = {
      id: props.currentDetail.id,
      status: props.currentDetail.status,
      isPass: 1,
    };
    const response = await flowEvent(data);
    if (response.data.code === 0) {
      ElMessage.success('工单未通过');
      emit('detail-success');
      handleClose();
    } else {
      throw new Error(response.data.msg || '操作失败');
    }
  } catch (error) {
    ElMessage.error(error.message || '操作失败,请稍后重试');
    emit('detail-error', error);
  } finally {
    rejectLoading.value = false;
  }
};
const handleApproveAndDispatch = () => {
  // 触发派发工单操作,由父组件处理
  emit('approve-and-dispatch', props.currentDetail);
};
const handleComplete = async () => {
  if (completeLoading.value) return;
  completeLoading.value = true;
    if (!props.currentDetail.processingDetail) {
      ElMessage.warning('请先填写事件处理详情');
      completeLoading.value = false;
      return;
    }
    if (!props.currentDetail.photos || props.currentDetail.photos.length === 0) {
      ElMessage.warning('请选择上传图片');
      completeLoading.value = false;
      return;
    }
  try {
    const data = {
      id: props.currentDetail.id,
      status: props.currentDetail.status,
      processingDetails: props.currentDetail.processingDetail,
      eventNum: props.currentDetail.orderNumber,
    };
    const file = props.currentDetail.photos?.[0]?.raw || null;
    const response = await flowEvent(data, file);
    if (response.data.code === 0) {
      ElMessage.success('工单已完成');
      emit('detail-success');
      handleClose();
    } else {
      throw new Error(response.data.msg || '操作失败');
    }
      handleClose();
  } catch (error) {
    console.error('完成工单失败:', error);
    ElMessage.error(error.message || '操作失败,请稍后重试');
    emit('detail-error', error);
      handleClose();
  } finally {
    completeLoading.value = false;
      handleClose();
  }
};
</script>
<style lang="scss">
.custom-dialog {
  height: 96vh;
// min-height: 50vh;
  .el-dialog__body {
    border-top: 0.1rem solid #f0f0f0;
  }
}
.custom-qrcode-popover {
  min-width: 160px !important;
  min-height: 140px !important;
  position: relative;
  .qrcode-content {
    margin-top: 8px;
    .close-btn {
      position: absolute;
      top: 2px;
      right: 2px;
      width: 16px;
      height: 16px;
      line-height: 16px;
      text-align: center;
      cursor: pointer;
      color: #999;
      font-size: 16px;
      z-index: 10;
    }
  }
}
</style>
<style lang="scss" scoped>
.uploadImg {
  margin-bottom: 32px;
}
.tableCss {
  width: 100%;
  margin-bottom: 10px;
}
.step-timer {
  position: absolute;
  right: 92%;
  top: 50%;
  transform: translateY(-50%);
  width: 100px;
  margin-left: 4px;
  color: #666;
  font-size: 12px;
  white-space: nowrap;
}
.event-total-time {
  text-align: center;
  color: #666;
  font-size: 15px;
  margin-bottom: 12px;
}
.action-bar {
  margin-bottom: 16px;
}
.el-dialog {
  .el-form-item {
    margin-bottom: 20px;
  }
}
.el-upload {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  :deep(.el-upload-list__item) {
    transition: all 0.3s ease;
  }
  :deep(.el-upload-list__item:hover) {
    background-color: #f5f7fa;
  }
}
.el-upload__tip {
  font-size: 12px;
  color: #909399;
  margin-top: 12px;
}
.create-ticket-form {
  padding: 20px 10px;
  .form-section {
    background-color: #fff;
    border-radius: 4px;
    .el-row {
      margin-bottom: 16px;
      &:last-child {
        margin-bottom: 0;
      }
    }
  }
  .location-wrapper {
    width: 100%; // 修改为100%以适应父容器
    height: auto; // 修改为auto以自适应内容
    .map-button {
      width: 100%; // 让按钮填满容器宽度
      height: 36px; // 与其他输入框保持一致的高度
      display: flex;
      align-items: center;
      justify-content: center;
      i {
        margin-right: 4px;
      }
    }
    .location-text {
      margin-top: 8px;
      padding: 8px 12px;
      background-color: #f5f7fa;
      border-radius: 4px;
      color: #606266;
      font-size: 13px;
      line-height: 1.4;
    }
  }
  .upload-wrapper {
    .uploader {
      :deep(.el-upload--picture-card) {
        width: 120px;
        height: 100px;
        line-height: 128px;
      }
      :deep(.el-upload-list__item) {
        width: 120px;
        height: 120px;
      }
    }
    .upload-tip {
      font-size: 12px;
      color: #909399;
      line-height: 1.4;
      margin-top: 8px;
    }
  }
  .el-form-item {
    margin-bottom: 18px;
    &:last-child {
      margin-bottom: 0;
    }
  }
  :deep(.el-form-item__label) {
    font-weight: 500;
    color: #606266;
  }
  :deep(.el-textarea__inner) {
    padding: 8px 12px;
  }
  .full-width {
    width: 100%;
  }
}
:deep(.el-dialog__body) {
  border-top: 0.1rem solid #f0f0f0;
}
.dialog-footer-new {
  text-align: right;
  padding-top: 16px;
  border-top: 1px solid #ebeef5;
  .el-button + .el-button {
    margin-left: 12px;
  }
  .el-button {
    padding: 9px 20px;
    &:last-child {
      margin-left: 12px;
    }
  }
}
.dialog-footer1-new {
  position: sticky;
  bottom: 28px;
  left: 0;
  right: 0;
  background: white;
  z-index: 10;
  padding: 16px 20px;
  border-top: 1px solid #ebeef5;
  display: flex;
  justify-content: space-between;
  align-items: center;
  .el-button + .el-button {
    margin-left: 12px;
  }
  .btngroups {
    margin-left: 12px;
  }
  .el-button {
    padding: 9px 20px;
    &:last-child {
      margin-left: 12px;
      margin-right: 12px;
    }
  }
}
.map-container {
  width: 100%;
  height: 400px;
  margin-bottom: 15px;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  overflow: hidden;
  :deep(.el-input-map) {
    height: 100%;
  }
}
.location-info {
  margin-top: 10px;
  padding: 10px;
  background-color: #f5f7fa;
  border-radius: 4px;
  p {
    margin: 5px 0;
    color: #606266;
    font-size: 14px;
  }
}
.map-select {
  display: flex;
  align-items: flex-start;
  gap: 15px;
  .selected-location {
    flex: 1;
    padding: 5px 10px;
    background-color: #f5f7fa;
    border-radius: 4px;
    p {
      margin: 5px 0;
      color: #606266;
      font-size: 14px;
    }
  }
}
.preview-image {
  border-radius: 4px;
  overflow: hidden;
  background-color: #f5f7fa;
}
.image-placeholder,
.image-error,
.no-media {
  height: 200px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  color: #909399;
  background-color: #f5f7fa;
  border-radius: 4px;
  i {
    font-size: 32px;
    margin-bottom: 8px;
  }
}
.no-media {
  border: 1px dashed #d9d9d9;
}
.detail-container {
  padding: 0 20px;
  .detail-top-title {
    display: flex;
    justify-content: center;
    align-items: center;
    .event-orderNumber {
      margin-right: 10px;
    }
  }
}
.status-flow {
  margin-bottom: 20px;
  .custom-steps {
    .el-step__description {
      position: relative;
      margin: 0;
      padding: 0;
    }
    // 添加发起任务步骤的特殊样式
    .init-step-info {
      display: flex;
      flex-direction: column;
      align-items: center;
      text-align: center;
      width: 130px;
      .creator-name {
        font-size: 14px;
        font-weight: bold;
        color: #303133;
        margin-bottom: 4px;
      }
      .create-time {
        font-size: 10px;
        color: #909399;
      }
    }
    // 保持其他步骤的原有样式
    .step-info {
      display: flex;
      margin: 0;
      padding: 0;
      justify-content: space-between;
      align-items: center;
      width: 200px;
      .process-time {
        font-size: 10px;
        color: #909399;
        text-align: left;
        margin-left: -50%;
        padding-left: 0%;
        flex: 1;
      }
      .handler-name {
        font-size: 14px;
        font-weight: bold;
        color: #303133;
        text-align: left;
        flex: 1;
      }
    }
  }
}
.basic-info {
  margin-bottom: 20px;
}
.media-section {
  // margin-bottom: 20px;
  .el-row {
    display: flex;
    align-items: center;
  }
}
.leftBtn {
  width: 70px;
  height: 32px;
  background-color: #999;
  border-radius: 5px;
  text-align: center;
  line-height: 32px;
  color: #fff;
  cursor: pointer;
  opacity: 0.8;
}
.disableds {
  background: #999 !important;
  cursor: not-allowed !important;
  pointer-events: none;
  opacity: 0.3 !important;
}
.PopUpTableScrolls {
  max-height: 600px;
  overflow-y: scroll;
  overflow-x: hidden;
}
.media-box {
  width: 100%;
  border: 1px solid #dcdfe6;
  border-radius: 4px;
  padding: 10px;
  background: #fff;
  box-sizing: border-box;
  .media-title {
    font-weight: bold;
    margin-bottom: 5px;
    display: flex;
    align-items: center;
    .QRCodeImg {
      width: 18px;
      height: 18px;
      cursor: pointer;
      padding-bottom: 1px;
    }
  }
  .media-content {
    position: relative;
    height: 500px;
    :deep(.el-image) {
      width: 100% !important;
      max-width: 100%;
      max-height: 100%;
      .el-image__inner {
        width: 100%;
        height: 100%;
        object-fit: cover !important;
      }
    }
  }
}
.image-placeholder,
.image-error,
.no-media {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: #909399;
  background-color: #f5f7fa;
  border-radius: 4px;
}
.image-placeholder i,
.image-error i {
  font-size: 32px;
  margin-bottom: 8px;
}
.no-media {
  border: 1px dashed #d9d9d9;
}
.info-table {
  margin-bottom: 20px;
}
.info-item {
  display: flex;
  margin-bottom: 10px;
}
.info-label {
  font-weight: bold;
  width: 120px;
  color: #606266;
}
.info-value {
  flex: 1;
  color: #303133;
  word-break: break-word;
}
.readonly-processing-detail {
  background-color: #f5f7fa;
  padding: 12px;
  border-radius: 4px;
  min-height: 30px;
  color: #606266;
  line-height: 1.5;
  display: block;
  text-align: left;
  margin-bottom: 5px;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
  }
}
// 添加删除按钮样式
.danger-button {
  color: #f56c6c;
}
.danger-button:hover {
  color: #f78989;
}
.custom-steps-container {
  width: 100%;
  margin: 10px 0;
}
.steps-titles {
  display: flex;
  justify-content: space-between;
  margin-bottom: 14px;
  position: relative;
}
.step-title {
  text-align: center;
  flex: 1;
  font-size: 14px;
  color: #999;
  position: relative;
  padding-bottom: 5px;
}
.step-title.active {
  color: #409eff;
  font-weight: bold;
}
.event-title-center {
  text-align: center;
  font-size: 20px;
  font-weight: bold;
  margin-bottom: 5px;
  color: #333;
}
.custom-steps {
  margin-top: -20px;
  :deep(.el-step__description) {
    margin-top: 8px;
    padding: 0 20px;
  }
}
.step-description {
  font-size: 14px;
  color: #666;
  line-height: 1.5;
  display: block;
  text-align: center;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
  }
}
// 覆盖其他相关样式
.status-flow {
  .custom-steps {
    .el-step__description {
      position: relative;
      margin: 0;
      padding: 0;
    }
    // 移除之前的样式
    .init-step-info,
    .step-info {
      display: block;
      width: auto;
      text-align: center;
    }
  }
}
/* 新建工单和处理工单的上传组件样式 */
.create-upload,
.detail-upload {
  :deep(.el-upload--picture-card) {
    width: 120px;
    height: 100px;
    line-height: 100px;
  }
  /* 隐藏额外的上传按钮 */
  :deep(.el-upload.el-upload--picture-card) {
    display: none;
  }
  /* 当没有图片时显示上传按钮 */
  :deep(.el-upload.el-upload--picture-card:first-child) {
    display: flex;
  }
  /* 上传组件的预览图片样式 */
  :deep(.el-upload-list--picture-card .el-upload-list__item) {
    width: 120px;
    height: 100px;
  }
}
/* 原有图片预览的样式 */
.el-image-viewer__wrapper {
  :deep(.el-image-viewer__img) {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
  }
}
/* 必填项样式 */
.required-label {
  position: relative;
  display: inline-block;
  .required-star {
    color: #f56c6c;
    margin-right: 4px;
  }
}
/* 必填输入框样式 */
.required-input {
  width: 100%;
  :deep(.el-input__inner),
  :deep(.el-textarea__inner) {
    &:focus {
      border-color: #409eff;
    }
  }
}
</style>
src/views/wel/components/backlog.vue
@@ -155,11 +155,13 @@
const jumporder = val => {
  if (checked.value === '智飞工单') {
    const id = val.id
    const status = val.status
    getaddOrderRecord(id)
    router.replace({
      path: `/tickets/orderLog`,
      query: {
        id,
        status,
      },
    })
  } else {
src/views/wel/components/proportionStatic.vue
@@ -46,22 +46,32 @@
<script setup>
import useEchartsResize from '@/hooks/useEchartsResize';
import { getJobEventByStatus } from '@/api/home/index';
import { getJobEventByStatus, getEventStatusNum } from '@/api/home/index';
import { getDateRange } from '@/utils/date'
import overviewImg2 from '@/assets/images/workbench/tc2.svg';
import overviewImg3 from '@/assets/images/workbench/tc3.svg';
import overviewImg4 from '@/assets/images/workbench/tc4.svg';
import overviewImg5 from '@/assets/images/workbench/tc5.svg';
import { useRouter } from 'vue-router'
import _ from 'lodash'
const router = useRouter()
let checked = ref('CURRENT_YEAR');
let timeListStr = ['本周', '本月', '本年'];
let timeListEnum = ['CURRENT_WEEK', 'CURRENT_MONTH', 'CURRENT_YEAR'];
const dateRanges = {
  today: getDateRange('today'),
  week: getDateRange('week'),
  month: getDateRange('month'),
  year: getDateRange('year'),
}
const params = ref({
  date_enum: 'CURRENT_YEAR',
  // date_enum: 'CURRENT_YEAR',
  device_sn: '',
  end_date: undefined,
  start_date: undefined,
   source:1,//数据来源
  end_date: dateRanges['year'][1], // undefined,
  start_date: dateRanges['year'][0], // undefined,
  source:1,//数据来源
});
const dateSelect = ref('CURRENT_YEAR');
const eventTypeList = ref([
@@ -72,13 +82,14 @@
]);
//  工单统计
const getTypeData = () => {
  getJobEventByStatus(params.value).then(res => {
  getEventStatusNum(params.value).then(res => {
    const resList = res?.data?.data || [];
    let totalNum = resList.reduce((sum, item) => sum + (item.num || 0), 0) - resList[0].num;
    resList.forEach(item => {
      eventTypeList.value.forEach(item1 => {
        if (item1.name === item.name) {
          item1.value = item.num;
          item1.rate = item.rate;
          item1.rate = _.round((item.num/totalNum)*100, 1);
        }
      });
    });
@@ -94,7 +105,18 @@
let timeClick = (item, index) => {
  checked.value = item;
  params.value.date_enum = item;
  // params.value.date_enum = item;
  if(item === 'CURRENT_WEEK') {
    params.value.start_date = dateRanges['week'][0]
    params.value.end_date = dateRanges['week'][1]
  } else if(item === 'CURRENT_MONTH') {
    params.value.start_date = dateRanges['month'][0]
    params.value.end_date = dateRanges['month'][1]
  } else {
    params.value.start_date = dateRanges['year'][0]
    params.value.end_date = dateRanges['year'][1]
  }
  dateSelect.value = item;
  getTypeData();
};
@@ -110,7 +132,7 @@
const echartsRef = ref(null);
let { chart } = useEchartsResize(echartsRef);
const initChart = val => {
  const totalNum = val.reduce((sum, item) => sum + item.num, 0);
  let totalNum = val.reduce((sum, item) => sum + item.num, 0);
  const data = {
    total: {
      title: '总计',
@@ -119,7 +141,7 @@
    data: val.map(item => ({
      value: item.num,
      name: item.name,
      rate: item.rate,
      rate: _.round((item.num/totalNum)*100, 1),
    })),
  };
  const containerWidth = chart.value.clientWidth;