无人机管理后台前端(已迁走)
linwei
2025-11-15 d94baed69ebfa0347b321d6fee42dded181161fe
Merge remote-tracking branch 'origin/test' into test
14 files modified
361 ■■■■ changed files
src/api/tickets/orderLog.js 9 ●●●●● patch | view | raw | blame | history
src/api/tickets/ticket.js 9 ●●●●● patch | view | raw | blame | history
src/hooks/useRouteLine/useRouteLine.js 11 ●●●●● patch | view | raw | blame | history
src/store/modules/common.js 10 ●●●● patch | view | raw | blame | history
src/views/algorithmRepository/algorithmRepository.vue 4 ●●● patch | view | raw | blame | history
src/views/device/airport.vue 2 ●●●●● patch | view | raw | blame | history
src/views/job/components/DeviceJobDetails.vue 4 ●●●● patch | view | raw | blame | history
src/views/layerManagement/components/folderFile.vue 11 ●●●● patch | view | raw | blame | history
src/views/layerManagement/components/leftList.vue 82 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/components/rightEdit.vue 11 ●●●● patch | view | raw | blame | history
src/views/layerManagement/index.vue 99 ●●●●● patch | view | raw | blame | history
src/views/system/user.vue 12 ●●●● patch | view | raw | blame | history
src/views/tickets/orderLog.vue 63 ●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue 34 ●●●● patch | view | raw | blame | history
src/api/tickets/orderLog.js
@@ -86,6 +86,15 @@
  });
};
// 获取返航最高 高度
export const getWaylineMaxTerrainHeight = data => {
  return request({
    url: `/drone-odm/odmTaskInfo/odmTaskInfo/getWaylineMaxTerrainHeight`,
    method: 'post',
    data,
  })
}
src/api/tickets/ticket.js
@@ -104,3 +104,12 @@
    method: 'post',
  })
}
// 导出工单
export const exportTheTicket = params => {
    return request({
        url: `/drone-device-core/jobEvent/downloadEvent`,
        method: 'get',
        responseType: 'blob',
        params,
    })
}
src/hooks/useRouteLine/useRouteLine.js
@@ -51,7 +51,7 @@
        resetCurRouteLineData()
        const { pointPlacemark, polygonList, templateType, startPoint, execute_height_mode, auto_flight_speed } =
        const { pointPlacemark, polygonList, templateType, startPoint, execute_height_mode, auto_flight_speed, take_off_security_height } =
            await analysisPointLineKmz(kmzUrl)
        curRouteLineData.value = {
@@ -61,6 +61,7 @@
            execute_height_mode,
            auto_flight_speed,
            wayline_type,
            take_off_security_height,
            infos: infos
        }
@@ -194,9 +195,10 @@
                height: 50,
            },
        })
        // 获取最后一点高度
        console.log(pointList, '查看数组最后一点高度')
        store.commit('setLastHeight', Number(pointList[pointList.length - 1].height))
        // 获取航线拆分所有点
        store.commit('setPositionsArr', pointList)
        // 获取安全起飞高度
        store.commit('setSafeHeight', curRouteLineData.value.take_off_security_height)
        flyVisual({
            positionsData:pointList.map(i => [Number(i.longitude), Number(i.latitude), Number(i.height)]),
            viewer,
@@ -217,6 +219,7 @@
            execute_height_mode: '',
            auto_flight_speed: '',
            wayline_type: '',
            take_off_security_height: ''
        }
    }
src/store/modules/common.js
@@ -21,11 +21,15 @@
      htsjzx: 100, //数据中心
      htlsrwxq: 100, //历史任务详情
    },
    lastHeight: 0, // 获取最后一点高度
    safeHeight: 0, // 获取选择航线高度
    positionsArr: [], // 获取选择航线点数据
  },
  mutations: {
    setLastHeight(state, data) {
      state.lastHeight = data
    setSafeHeight (state, data) {
      state.safeHeight = data
    },
    setPositionsArr(state, data) {
      state.positionsArr = data
    },
    setDownloadProgress(state, data) {
      state.downloadProgress = data
src/views/algorithmRepository/algorithmRepository.vue
@@ -4,7 +4,7 @@
      <div class="algorithItem" v-if="!showDetail">
        <div class="item" v-for="(item, index) in AlgorithmData" :key="index"
          :class="{ 'active-bg': activeItem === item.dictValue }" @click="jumpDatail(item)">
          <img class="imgicon" :src="`${baseUrl}/后台-算法仓库/${item.dictValue}.png`" alt="" />
          <img class="imgicon" :src="`${baseUrl}/manage-sf-icon-source/${item.dictKey}.png`" alt="" />
          <div class="item-title">{{ item.dictValue }}</div>
          <div :class="!statusSign && item.dictValue === nameSign ? 'stopStatus' : 'normalStatus'"
            @click.stop="changeStatus(item)">
@@ -101,6 +101,8 @@
      // 处理数据
      AlgorithmData.value = res.data.data['SF']
    }
    console.log('算法',AlgorithmData.value);
  })
}
const activeItem = ref(null)
src/views/device/airport.vue
@@ -721,6 +721,7 @@
            prop: 'login_time',
            type: 'date',
            viewDisplay: true, //查看显示
            editDisabled: true,
            labelWidth: 145,
            overHidden: true,
            format: 'YYYY-MM-DD HH:mm:ss',
@@ -754,6 +755,7 @@
            type: 'date',
            hide: true,
            viewDisplay: true, //查看显示
            editDisabled: true,
            overHidden: true,
            labelWidth: 145,
            width: 160,
src/views/job/components/DeviceJobDetails.vue
@@ -189,6 +189,7 @@
    { name: '任务时间', value: '', field: 'cycle_time_value' },
    { name: '关联算法', value: '', field: 'ai_type_str' },
    { name: '任务描述', value: '', field: 'remark' },
  { name: '自定义识别区', value: '', field: 'enable_custom_area' },
    // { name: '任务执行次数', value: '', field: 'job_num' },
])
// 机巢状态
@@ -272,6 +273,9 @@
                    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],
src/views/layerManagement/components/folderFile.vue
@@ -8,7 +8,7 @@
          <el-input class="ztzf-layer-input inputName" v-model="formData.name" placeholder="请输入" />
        </div>
      </div>
      <div class="itemRow" v-if="layerParams.addAnEditingFolder === 1 && layerParams.fileType !==2 && !itSLandPlanning">
     <div class="itemRow" v-if="(layerParams.addAnEditingFolder === 1 && layerParams.fileType !==2) || (layerParams.addAnEditingFolder === 2 && layerParams.fileType !==2) && !itSLandPlanning">
        <div class="label">关联算法</div>
        <div>
          <el-select
@@ -28,7 +28,6 @@
          </el-select>
          <el-select
            v-model="formData.algorithm_name"
            class="ztzf-layer-search"
            placeholder="请选择"
            clearable
@@ -47,13 +46,14 @@
    <div class="detailInfo" v-if="layerParams.editFolder">
      <div class="infpItem" v-if="!itSLandPlanning">
        <div class="itemData">
          <div class="title">围栏总数量</div>
          <div class="title">{{ layerParams.fileType === 1 ? '识别区' : '禁飞区' }}总数量</div>
          <div class="num">
            <span>{{ formData.total_count }}</span> 个
          </div>
        </div>
        <div class="itemData">
          <div class="title">围栏总面积</div>
          <div class="title">{{ layerParams.fileType === 1 ? '识别区' : '禁飞区' }}总面积</div>
          <div class="num">
            <span class="areaStyle">{{ formData.total_area }}</span> m²
          </div>
@@ -233,7 +233,8 @@
  width: 324px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  background: rgba(0,0,0,0.51);
  backdrop-filter: blur(4px);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
src/views/layerManagement/components/leftList.vue
@@ -13,7 +13,7 @@
    </div>
    <div class="btnGroups">
      <el-button v-if="activeName !== '国土空间规划'" class="ztzf-button" @click="addFence">{{
        props.activeName === '电子围栏' ? '新增围栏' : '新增禁飞区'
        props.activeName === '自定义识别区' ? '新增识别区' : '新增禁飞区'
      }}</el-button>
      <el-button v-if="activeName !== '国土空间规划'" class="ztzf-button" @click="addFolder"
        >新增文件夹</el-button
@@ -36,7 +36,10 @@
        <el-button v-else class="ztzf-button" @click="synchronize"> 同步 </el-button>
      </template> -->
      <el-button class="ztzf-button" @click="selectAll">全选</el-button>
      <!-- <el-button class="ztzf-button" @click="selectAll">全选</el-button> -->
      <el-button class="ztzf-button" @click="selectAll">
  {{ checkedKeys.length === getAllFenceKeys().length ? '取消全选' : '全选' }}
</el-button>
    </div>
    <div
      class="tableListBox"
@@ -54,6 +57,7 @@
        node-key="id"
        default-expand-all
        :expand-on-click-node="false"
         :check-on-click-node="false"
        :filter-node-method="filterNode"
        @check="handleCheck"
        :props="{
@@ -147,28 +151,33 @@
  );
};
const handleCheck = (checkedNodesData, checkedKeysData) => {
layerParams.value.isSingleLocating = false
  const filteredNodes = checkedKeysData.checkedNodes.filter(node => node.level === 3);
  checkedNodes.value = checkedNodesData.checkedNodes;
  checkedNodes.value = filteredNodes
  checkedKeys.value = filteredNodes.map(node => node.id);
  coverData.value = filteredNodes;
  emit('update:coverData', coverData.value);
};
// 全选方法
const selectAll = () => {
  if (treeRef.value) {
    const allFenceKeys = treeData.value.flatMap(
      folder => folder.children.map(fence => fence.id) // folder 是 level 2,children 是 level 3
const getAllFenceKeys = () => {
  return treeData.value.flatMap(
    folder => folder.children.map(fence => fence.id)
    );
};
const selectAll = () => {
layerParams.value.isSingleLocating = false
  if (treeRef.value) {
    const allFenceKeys = getAllFenceKeys();
    const allFenceNodes = treeData.value.flatMap(folder => folder.children);
    if (checkedKeys.value.length === allFenceKeys.length) {
      // 取消全选:清空 keys 和数据
      // 取消全选
      treeRef.value.setCheckedKeys([]);
      checkedKeys.value = [];
      checkedNodes.value = [];
      coverData.value = [];
    } else {
      // 全选:仅设置 level 3 节点的 id
      // 全选
      treeRef.value.setCheckedKeys(allFenceKeys);
      checkedKeys.value = allFenceKeys;
      checkedNodes.value = allFenceNodes;
@@ -179,27 +188,44 @@
};
// 定位按钮事件
const handleLocation = node => {
  checkedKeys.value = [];
  checkedNodes.value = [];
  coverData.value = [];
 if (node.data.level === 2) {
    layerParams.value.isSingleLocating = false;
  } else if (node.data.level === 3) {
    layerParams.value.isSingleLocating = true;
  }
  const currentCheckedKeys = [...checkedKeys.value];
  const currentCheckedNodes = [...checkedNodes.value];
  EventBus.emit('focusOnNode', node.data);
  if (node.data.level === 2) {
    const folderChildren = node.data.children || [];
    // const childrenIds = folderChildren.map(child => child.id);
    // treeRef.value.setCheckedKeys(childrenIds);
    // checkedKeys.value = childrenIds;
    // checkedNodes.value = folderChildren;
    coverData.value = folderChildren;
    emit('update:coverData', coverData.value);
  } else {
    emit('update:coverData', [node.data]);
    const childrenIds = folderChildren.map(child => child.id);
    // 合并已选中的节点和当前文件夹的子节点
    const newCheckedKeys = [...new Set([...currentCheckedKeys, ...childrenIds])];
    const newCheckedNodes = [...currentCheckedNodes, ...folderChildren];
    treeRef.value.setCheckedKeys(newCheckedKeys);
    checkedKeys.value = newCheckedKeys;
    checkedNodes.value = newCheckedNodes;
    coverData.value = newCheckedNodes;
  }
  // 如果是单个节点(level 3),仅添加到选中列表(如果未选中)
  else if (!currentCheckedKeys.includes(node.data.id)) {
  layerParams.value.isSingleLocating = true
    currentCheckedKeys.push(node.data.id);
    currentCheckedNodes.push(node.data);
    treeRef.value.setCheckedKeys(currentCheckedKeys);
    checkedKeys.value = currentCheckedKeys;
    checkedNodes.value = currentCheckedNodes;
    coverData.value = currentCheckedNodes;
  }
  emit('update:coverData', coverData.value);
};
// 编辑按钮事件
const handleEdit = node => {
  if (node.data.level == 2) {
    layerParams.value.editFolder = true;
    layerParams.value.addAnEditingFolder = 2;
    if (props.activeName === '电子围栏') {
    if (props.activeName === '自定义识别区') {
      layerParams.value.fileType = 1;
    } else if (props.activeName === '自定义禁飞区') {
      layerParams.value.fileType = 2;
@@ -211,7 +237,7 @@
    coverData.value = [];
    layerParams.value.editNest = true;
    layerParams.value.decideWhetherToAddOrEdit = 2;
    if (props.activeName === '电子围栏') {
    if (props.activeName === '自定义识别区') {
      layerParams.value.fenceType = 1; // 电子围栏类型标识
    } else if (props.activeName === '自定义禁飞区') {
      layerParams.value.fenceType = 2; // 自定义禁飞区类型标识
@@ -234,7 +260,6 @@
        deleteFolderApi(id).then(res => {
          ElMessage.success('删除成功');
          gettreeDataApi();
          // 通知父组件删除该文件夹下所有围栏的地图实体
          EventBus.emit('deleteMapEntitiesByFolderId', id);
        });
      } else if (node.data.level === 3) {
@@ -242,7 +267,6 @@
        deleteFenceApi(id).then(res => {
          ElMessage.success('删除成功');
          gettreeDataApi();
          // 通知父组件删除对应的地图实体
          EventBus.emit('deleteMapEntityById', id);
        });
      }
@@ -255,7 +279,7 @@
const addFence = () => {
  layerParams.value.addNest = true;
  layerParams.value.decideWhetherToAddOrEdit = 1;
  if (props.activeName === '电子围栏') {
  if (props.activeName === '自定义识别区') {
    layerParams.value.fenceType = 1; // 电子围栏类型标识
  } else if (props.activeName === '自定义禁飞区') {
    layerParams.value.fenceType = 2; // 自定义禁飞区类型标识
@@ -266,13 +290,12 @@
const addFolder = () => {
  layerParams.value.addFolder = true;
  layerParams.value.addAnEditingFolder = 1;
  if (props.activeName === '电子围栏') {
  if (props.activeName === '自定义识别区') {
    layerParams.value.fileType = 1;
  } else if (props.activeName === '自定义禁飞区') {
    layerParams.value.fileType = 2;
  }
};
function uploadFlightFile(file, t) {
  const fileSuffix = file.name.substring(file.name.lastIndexOf('.') + 1);
  if (!['zip'].includes(fileSuffix)) {
@@ -360,7 +383,8 @@
  z-index: 99;
  width: 382px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  background: rgba(0,0,0,0.51);
  backdrop-filter: blur(4px);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 0  16px 12px;
  display: flex;
src/views/layerManagement/components/rightEdit.vue
@@ -1,6 +1,6 @@
<template>
  <div class="rightContainer">
    <div class="contenttitle">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}信息</div>
    <div class="contenttitle">{{ layerParams.fenceType === 1 ? '识别区' : '禁飞区' }}信息</div>
    <div class="centerBox">
      <div class="itemRow">
        <div class="label">文件夹名称</div>
@@ -22,7 +22,7 @@
        </div>
      </div>
      <div class="itemRow">
        <div class="label">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}名称</div>
        <div class="label">{{ layerParams.fenceType === 1 ? '识别区' : '禁飞区' }}名称</div>
        <div>
          <el-input
            :disabled="isDisabled"
@@ -49,7 +49,7 @@
        </div>
      </div>
      <div class="itemRow">
        <div class="label">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}备注</div>
        <div class="label">{{ layerParams.fenceType === 1 ? '识别区' : '禁飞区' }}备注</div>
        <div>
          <el-input
            :disabled="isDisabled"
@@ -64,7 +64,7 @@
    <div class="detailInfo">
      <div class="infpItem" v-if="layerParams.editNest && layerParams.fenceType === 1">
        <div class="itemData">
          <div class="title">围栏面积</div>
          <div class="title">识别区面积</div>
          <div class="num">
            <span class="areaStyle">{{ fenceArea }}</span> m²
          </div>
@@ -336,7 +336,8 @@
  width: 324px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  background: rgba(0,0,0,0.51);
  backdrop-filter: blur(4px);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
src/views/layerManagement/index.vue
@@ -9,12 +9,6 @@
      </div>
      <!-- 地图 -->
      <div class="mapContainer">
        <!-- <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="layerParams.addNest">点击地图生成测绘区域</div>
        <div id="layMap" class="ztzf-cesium"></div>
        <leftList
@@ -65,7 +59,7 @@
const areaCode = userInfo.value?.detail?.areaCode || '';
// 红色警告交叉面提示窗
const isShowWaringTip = ref(false);
const activeName = ref('电子围栏');
const activeName = ref('自定义识别区');
const activeType = ref('0');
let tbJwdList = [];
const selectDataList = ref([]);
@@ -73,7 +67,7 @@
let curPolygonPosition = [];
const tabData = ref([
  {
    name: '电子围栏',
    name: '自定义识别区',
    type: '0',
  },
  {
@@ -103,6 +97,7 @@
  editFolderData: null,
  editingIsProhibited: false, //禁止编辑
  isDetailShow: false,
  isSingleLocating:false
});
const handleClick = tab => {
@@ -121,7 +116,7 @@
};
const getdataFolderApi = () => {
  let id;
  if (activeName.value === '电子围栏') {
  if (activeName.value === '自定义识别区') {
    id = 1;
  } else if (activeName.value === '自定义禁飞区') {
    id = 2;
@@ -176,6 +171,7 @@
const handleCoverDataUpdate = data => {
  selectDataList.value = data;
  if (selectDataList.value.length > 0) {
    loadDataToMap(selectDataList.value);
  } else {
    viewer.entities.removeAll();
@@ -246,7 +242,11 @@
      zIndex: 99, 
    });
  });
 if (!layerParams.value.isSingleLocating) {
  focusOnAllFeatures();
  }
  viewInstance.value?.addLeftClickEvent(null, handleFenceClick);
};
const focusOnAllFeatures = () => {
@@ -259,7 +259,7 @@
  flyVisual({
    positionsData,
    viewer,
    multiple: 13, // 缩放倍数
      multiple:activeName.value == '国土空间规划' ? 13 :4, // 缩放倍数
    pitch: -90 
  });
};
@@ -267,10 +267,18 @@
const handleFenceClick = movement => {
  if (!viewer) return;
  layerParams.value.editDetailData = null;
  const pickedObject = viewer.scene.pick(movement.position);
  if (Cesium.defined(pickedObject) && pickedObject.id?.customType === 'fence_polygon') {
    const selectedEntity = pickedObject.id;
    const selectedData = selectedEntity.customInfo;
if(activeName.value !== '国土空间规划') {
    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;
    }
  }
    //   viewer.entities.values.forEach(entity => {
    // if (entity.customType === 'fence_polygon') {
    //   entity.polygon.material = entity === selectedEntity
@@ -278,11 +286,12 @@
    //     : Cesium.Color.YELLOW.withAlpha(0.5);
    // }
    // });
  if (selectedEntity) {
    const selectedData = selectedEntity.customInfo;
    if (activeName.value !== '国土空间规划') {
      layerParams.value.editNest = true; // 显示右侧编辑组件
      layerParams.value.decideWhetherToAddOrEdit = 2; // 标记为编辑模式
      // 根据围栏类型设置fenceType
      if (activeName.value === '电子围栏') {
      layerParams.value.editNest = true;
      layerParams.value.decideWhetherToAddOrEdit = 2;
      if (activeName.value === '自定义识别区') {
        layerParams.value.fenceType = 1;
      } else if (activeName.value === '自定义禁飞区') {
        layerParams.value.fenceType = 2;
@@ -292,6 +301,11 @@
      layerParams.value.editingIsProhibited = true;
    }
  }
}else {
}
};
let publicCesiumInstance = null;
let viewer = null;
@@ -303,11 +317,12 @@
    dom: 'layMap',
    flatMode: false,
    terrain: true,
    layerMode: 4,
    contour: false,
    layerMode: 4, //轮廓线
    dockOptions:{showDock:true}//机巢
  });
  viewer = publicCesiumInstance.getViewer();
  viewInstance.value = publicCesiumInstance;
  viewInstance.value?.flyToContour();
  const nanChangPosition = Cesium.Cartesian3.fromDegrees(
    115.892151, // 经度
    28.676493, // 纬度
@@ -394,13 +409,45 @@
  }
};
provide('layerParams', layerParams);
// onMounted(() => {
//   initMap();
//   cesiumContextMenu();
//   getdataFolderApi();
//   EventBus.on('deleteMapEntityById', deleteMapEntityById);
//   EventBus.on('deleteMapEntitiesByFolderId', deleteMapEntitiesByFolderId);
// });
onMounted(() => {
  initMap();
  cesiumContextMenu();
  getdataFolderApi();
  // 监听定位事件
  EventBus.on('focusOnNode', (nodeData) => {
    focusOnNode(nodeData);
  });
  EventBus.on('deleteMapEntityById', deleteMapEntityById);
  EventBus.on('deleteMapEntitiesByFolderId', deleteMapEntitiesByFolderId);
});
//定位到指定节点
const focusOnNode = (nodeData) => {
  if (!viewer || !nodeData?.geo_data) return;
  // 解析节点坐标
  const positions = parseGeoDataToPositions(nodeData.geo_data, nodeData.altitude);
if (positions.length < 3) {
    return;
  }
  flyVisual({
    positionsData: positions.map(pos => [pos.lng, pos.lat, pos.height || 0]),
    viewer,
      multiple:activeName.value == '国土空间规划' ? 13 :7, // 缩放倍数
    pitch: -90
  });
};
onBeforeUnmount(() => {
  destroyMap();
  cesiumContextMenu(false);
@@ -440,18 +487,6 @@
  position: relative;
  width: 100%;
  height: 80vh;
  .warning {
    position: absolute;
    top: 234px;
    color: #fff;
    background: rgba(140, 0, 0, 0.4);
    .icon {
      display: flex;
      align-items: center;
      color: #ff1f1f;
    }
  }
  #layMap {
    width: 100%;
    height: 100%;
src/views/system/user.vue
@@ -26,9 +26,9 @@
            <el-button type="danger" plain icon="el-icon-delete" v-if="permission.user_delete && !auditMode"
              @click="handleDelete">删 除
            </el-button>
            <el-button type="primary" plain icon="el-icon-operation"
              v-if="userInfo.role_name.includes('admin') && !auditMode" @click="handleAudit">审 核
            </el-button>
<!--            <el-button type="primary" plain icon="el-icon-operation"-->
<!--              v-if="userInfo.role_name.includes('admin') && !auditMode" @click="handleAudit">审 核-->
<!--            </el-button>-->
            <el-button type="success" plain icon="el-icon-check"
              v-if="userInfo.role_name.includes('admin') && auditMode" @click="handleAuditPass">通 过
            </el-button>
@@ -44,9 +44,9 @@
            <el-button type="info" plain v-if="permission.user_reset && !auditMode" icon="el-icon-refresh"
              @click="handleReset">密码重置
            </el-button>
            <el-button type="info" plain v-if="userInfo.role_name.includes('admin') && !auditMode"
              icon="el-icon-setting" @click="handlePlatform">平台配置
            </el-button>
<!--            <el-button type="info" plain v-if="userInfo.role_name.includes('admin') && !auditMode"-->
<!--              icon="el-icon-setting" @click="handlePlatform">平台配置-->
<!--            </el-button>-->
            <el-button type="info" plain v-if="userInfo.role_name.includes('admin') && !auditMode"
              icon="el-icon-coordinate" @click="handleLock">账号解封
            </el-button>
src/views/tickets/orderLog.vue
@@ -194,7 +194,7 @@
          </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="300"></el-input-number>
              <el-input-number v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
@@ -207,11 +207,16 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <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>
@@ -320,7 +325,7 @@
          <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="300"></el-input-number>
                               :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
@@ -333,11 +338,16 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <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"></el-checkbox>
            </el-form-item>
          </el-col>
        </el-row>
@@ -455,7 +465,7 @@
          </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="300"></el-input-number>
              <el-input-number :disabled="true" v-model="form.rth_altitude" :min="50" :max="500"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
@@ -469,11 +479,16 @@
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <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"></el-checkbox>
            </el-form-item>
          </el-col>
        </el-row>
@@ -555,7 +570,8 @@
  orderLogExport,
  jobStatusNum,
  userPublish,
  deleteOrderLog
  deleteOrderLog,
  getWaylineMaxTerrainHeight
} from '@/api/tickets/orderLog'
import { getTicketInfo } from '@/api/tickets/ticket'
import { getDictionaryByCode } from '@/api/system/dictbiz'
@@ -810,9 +826,13 @@
      return CircleClose
    },
    ...mapGetters(['userInfo', 'permission']),
    lastHeight () {
      const height = this.$store.state.common.lastHeight
      return Number(height) || 0 // 确保是数字类型
    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
@@ -992,15 +1012,22 @@
          const maxItem = checkedList.reduce((max, item) => {
            return item.drone_height > max.drone_height ? item : max;
          });
          console.log(maxItem.drone_height, '高度');
          console.log(this.lastHeight, '最后一点高度');
          let result = _.round((maxItem.drone_height + this.form.rth_altitude), 2);
          // 返航绝对高度
          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)
          let resultHeight = this.lastHeight - maxItem.drone_height
          let resultH = _.round(resultHeight, 2)
          if (this.lastHeight > result) {
            this.$confirm(`当前返航高度存在安全隐患,建议调整为${resultH}米以上后进行发布`, '提示', {
          if (valueHeight < 0) {
            this.$confirm(`当前返航高度存在安全隐患,建议调整为${resultHeight}米以上后进行发布`, '提示', {
              confirmButtonText: '确定',
              cancelButtonText: '取消',
              type: 'warning'
src/views/tickets/ticket.vue
@@ -134,7 +134,6 @@
                    collapse-tags-tooltip
                    clearable
                    @check="handleCheck"
                    @node-click="handleSFNodeClick"
                    @clear="handleClear"
                  />
                  </el-form-item>
@@ -207,6 +206,7 @@
                @click="openReviewDialog"
                >批量审核
              </el-button>
              <el-button
                v-if="permissionList.exportBtn"
                type="success"
@@ -234,6 +234,11 @@
                </el-button>
              </template>
              <template  v-if="row.status === 4 && activeTab === 'completed'">
                <el-button type="text" icon="el-icon-download"  @click="exportTheTick(row)"
                  >导出
                </el-button>
              </template>
              <template v-if="permission.tickets_repeat_review">
                <el-button
                  v-if="row.status === 4 && row.isReview !== 1"
@@ -1011,6 +1016,7 @@
  getStepInfo,
  getReviewById,
  getCreateEventJob,
  exportTheTicket
} from '@/api/tickets/ticket';
import { getSFDictionaryTree } from '@/api/job/task';
import { export_json_to_excel } from '@/utils/exportExcel';
@@ -1108,7 +1114,7 @@
          { label: '工单编号', prop: 'orderNumber', width: 170 },
          { label: '工单名称', prop: 'orderName', width: 150, overHidden: true, tooltip: true },
          { label: '所属部门', prop: 'department', overHidden: true, tooltip: true },
          { label: '发起时间', prop: 'startTime', width: 160, overHidden: true },
          { label: '发起任务时间', prop: 'startTime', width: 160, overHidden: true },
          { label: '关联算法', prop: 'aiType', overHidden: true, tooltip: true },
          {
            label: '工单类型',
@@ -1788,7 +1794,8 @@
      try {
        const currentTab = this.tabs.find(tab => tab.name === this.activeTab);
        const params = {
          word_order_types: this.filters.type || undefined,
          // word_order_types: this.filters.type || undefined,
          ai_types: this.filters.type || undefined, // 算法使用该字段筛选
          status:
            currentTab?.name === 'myTickets'
              ? undefined
@@ -1838,7 +1845,7 @@
            orderName: item.event_name,
            department:
              this.departments.find(d => d.value === item.dept_id)?.label || item.dept_name,
            startTime: item.create_time,
            startTime: item.job_create_time || '/',
            aiType: item.ai_types,
            content: item.content, // 将后端返回的 content 映射为 content
            type: item.work_order_type_dict_key,
@@ -1847,7 +1854,7 @@
                ? `${longitude.toFixed(6)}, ${latitude.toFixed(6)}`
                : '未知位置',
            address: item.address,
            creator: item.event_num?.slice(0, 2) === 'AI' ? 'AI 小飞' : item.create_user,
            creator: item.event_num?.slice(0, 2) === 'AI' ? '智飞agent' : item.create_user,
            handler: item.update_user || '未分配',
            status: Number(item.status || 0),
            // 保存原始字段
@@ -3188,6 +3195,23 @@
          });
      }
    },
    // 导出工单报表
    exportTheTick(row){
      const params = {
        num:row.orderNumber
      }
      exportTheTicket(params).then(res=>{
      const elink = document.createElement('a')
      elink.download = row.orderName + '.docx'
      elink.style.display = 'none'
      const blob = new Blob([res.data])
      elink.href = URL.createObjectURL(blob)
      document.body.appendChild(elink)
      elink.click()
      document.body.removeChild(elink)
      })
       this.$message.success('数据导出成功');
    }
  },
  activated() {
    this.handleReset();