无人机管理后台前端(已迁走)
linwei
2025-11-05 c48eafe8411adad734b57b424f5440284dbc85e3
Merge remote-tracking branch 'origin/test' into test
76 files modified
1 files renamed
20 files added
9362 ■■■■ changed files
.env.development 5 ●●●●● patch | view | raw | blame | history
.env.localhost 2 ●●● patch | view | raw | blame | history
.env.production 2 ●●● patch | view | raw | blame | history
.env.test 2 ●●● patch | view | raw | blame | history
src/App.vue 3 ●●●● patch | view | raw | blame | history
src/api/algorithmMange/algorithmMange.js 17 ●●●●● patch | view | raw | blame | history
src/api/layer/index.js 109 ●●●●● patch | view | raw | blame | history
src/api/logs.js 60 ●●●●● patch | view | raw | blame | history
src/api/resource/wayline.js 8 ●●●● patch | view | raw | blame | history
src/api/system/menu.js 6 ●●●●● patch | view | raw | blame | history
src/api/system/role.js 3 ●●●● patch | view | raw | blame | history
src/api/user.js 3 ●●●● patch | view | raw | blame | history
src/assets/images/layerManagement/cancelbtn.svg 24 ●●●●● patch | view | raw | blame | history
src/assets/images/layerManagement/newcon_box.png patch | view | raw | blame | history
src/assets/images/layerManagement/savebtn.svg 23 ●●●●● patch | view | raw | blame | history
src/assets/images/mh.svg 7 ●●●●● patch | view | raw | blame | history
src/assets/images/task/sign.svg 3 ●●●●● patch | view | raw | blame | history
src/assets/images/warn.svg 10 ●●●●● patch | view | raw | blame | history
src/components/CreateQRcode/CreateQRcode.vue 2 ●●● patch | view | raw | blame | history
src/components/commonDialog.vue 48 ●●●●● patch | view | raw | blame | history
src/hooks/common.js 52 ●●●●● patch | view | raw | blame | history
src/hooks/useRouteLine/useRouteLine.js 24 ●●●●● patch | view | raw | blame | history
src/option/job/jobinfo.js 7 ●●●● patch | view | raw | blame | history
src/option/job/jobserver.js 6 ●●●● patch | view | raw | blame | history
src/option/system/dict.js 10 ●●●● patch | view | raw | blame | history
src/option/system/dictbiz.js 10 ●●●● patch | view | raw | blame | history
src/page/index/GlobalWS.vue 11 ●●●●● patch | view | raw | blame | history
src/page/index/index.vue 47 ●●●● patch | view | raw | blame | history
src/page/index/tags.vue 2 ●●● patch | view | raw | blame | history
src/page/index/top/index.vue 4 ●●●● patch | view | raw | blame | history
src/page/index/useGlobalWS.js 45 ●●●● patch | view | raw | blame | history
src/store/modules/common.js 14 ●●●●● patch | view | raw | blame | history
src/store/modules/user.js 4 ●●●● patch | view | raw | blame | history
src/styles/common.scss 4 ●●●● patch | view | raw | blame | history
src/styles/element-ui.scss 762 ●●●● patch | view | raw | blame | history
src/styles/mapEdit.scss 59 ●●●●● patch | view | raw | blame | history
src/types/device-cmd.js 2 ●●● patch | view | raw | blame | history
src/utils/cesium/publicCesium.js 28 ●●●● patch | view | raw | blame | history
src/utils/geoParseUtil.js 94 ●●●●● patch | view | raw | blame | history
src/views/algorithmMange/algorithmMange.vue 304 ●●●●● patch | view | raw | blame | history
src/views/algorithmRepository/algorithmRepository.vue 314 ●●●● patch | view | raw | blame | history
src/views/authority/apiscope.vue 8 ●●●● patch | view | raw | blame | history
src/views/authority/datascope.vue 8 ●●●● patch | view | raw | blame | history
src/views/authority/role.vue 29 ●●●● patch | view | raw | blame | history
src/views/dataCenter/components/dataCenterMap.vue 3 ●●●● patch | view | raw | blame | history
src/views/dataCenter/components/searchData.vue 51 ●●●●● patch | view | raw | blame | history
src/views/dataCenter/dataCenter.vue 7 ●●●● patch | view | raw | blame | history
src/views/device/addDevice.vue 168 ●●●● patch | view | raw | blame | history
src/views/device/airport.vue 657 ●●●● patch | view | raw | blame | history
src/views/device/components/DeviceSettingBox.vue 8 ●●●● patch | view | raw | blame | history
src/views/device/components/DockControlPanel.vue 9 ●●●● patch | view | raw | blame | history
src/views/device/components/devicePerShare.vue 9 ●●●●● patch | view | raw | blame | history
src/views/device/components/firmwareManage.vue 37 ●●●●● patch | view | raw | blame | history
src/views/device/devicePerMenu.vue 12 ●●●●● patch | view | raw | blame | history
src/views/device/fly.vue 8 ●●●● patch | view | raw | blame | history
src/views/job/components/DeviceJobDetails.vue 34 ●●●● patch | view | raw | blame | history
src/views/job/components/JobRelatedEvents.vue 9 ●●●● patch | view | raw | blame | history
src/views/job/components/SearchBox.vue 6 ●●●● patch | view | raw | blame | history
src/views/job/components/TaskIntermediateContent/TaskIntermediateContent.vue 17 ●●●● patch | view | raw | blame | history
src/views/job/components/TaskTop/TaskEvent.vue 5 ●●●● patch | view | raw | blame | history
src/views/job/components/TaskTop/TaskIndustry.vue 22 ●●●● patch | view | raw | blame | history
src/views/job/components/TaskTop/TaskTime.vue 16 ●●●● patch | view | raw | blame | history
src/views/layerManagement/components/folderFile.vue 310 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/components/leftList.vue 463 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/components/nationalSpatialPlanning.vue 49 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/components/rightEdit.vue 420 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/components/utils.js 651 ●●●●● patch | view | raw | blame | history
src/views/layerManagement/index.vue 487 ●●●●● patch | view | raw | blame | history
src/views/license/license.vue 15 ●●●● patch | view | raw | blame | history
src/views/monitor/log/api.vue 16 ●●●● patch | view | raw | blame | history
src/views/monitor/log/error.vue 6 ●●●●● patch | view | raw | blame | history
src/views/monitor/log/flightLog.vue 529 ●●●●● patch | view | raw | blame | history
src/views/odm/task.vue 14 ●●●●● patch | view | raw | blame | history
src/views/resource/attach.vue 8 ●●●● patch | view | raw | blame | history
src/views/resource/components/DrawPolygon.vue 5 ●●●●● patch | view | raw | blame | history
src/views/resource/components/spotDetails.vue 304 ●●●● patch | view | raw | blame | history
src/views/resource/media.vue 9 ●●●●● patch | view | raw | blame | history
src/views/resource/oss.vue 20 ●●●●● patch | view | raw | blame | history
src/views/resource/patchManagement.vue 15 ●●●● patch | view | raw | blame | history
src/views/resource/patchTypeManagement.vue 24 ●●●● patch | view | raw | blame | history
src/views/resource/wayline.vue 12 ●●●●● patch | view | raw | blame | history
src/views/resource/waylineFile.vue 10 ●●●●● patch | view | raw | blame | history
src/views/system/client.vue 6 ●●●● patch | view | raw | blame | history
src/views/system/dept.vue 103 ●●●● patch | view | raw | blame | history
src/views/system/helpCenter.vue 88 ●●●●● patch | view | raw | blame | history
src/views/system/menu.vue 19 ●●●● patch | view | raw | blame | history
src/views/system/param.vue 45 ●●●●● patch | view | raw | blame | history
src/views/system/sysConfig.vue 13 ●●●●● patch | view | raw | blame | history
src/views/system/user.vue 10 ●●●●● patch | view | raw | blame | history
src/views/system/userinfo.vue 63 ●●●● patch | view | raw | blame | history
src/views/tickets/orderLog-copy1.vue 4 ●●●● patch | view | raw | blame | history
src/views/tickets/orderLog.vue 1505 ●●●●● patch | view | raw | blame | history
src/views/tickets/ticket.vue 570 ●●●●● patch | view | raw | blame | history
src/views/wel/components/backlog.vue 29 ●●●●● patch | view | raw | blame | history
src/views/wel/components/proportionStatic.vue 25 ●●●● patch | view | raw | blame | history
src/views/wel/components/statistics.vue 244 ●●●● patch | view | raw | blame | history
src/views/workspace/index.vue 8 ●●●● patch | view | raw | blame | history
.env.development
@@ -2,7 +2,7 @@
 # @Author       : yuan
 # @Date         : 2025-07-29 15:59:42
 # @LastEditors  : yuan
 # @LastEditTime : 2025-07-29 16:15:31
 # @LastEditTime : 2025-09-27 15:47:12
 # @FilePath     : \.env.development
 # @Description  :
 # Copyright 2025 OBKoro1, All Rights Reserved.
@@ -23,6 +23,7 @@
VITE_APP_URL = https://wrj.shuixiongit.com/api
#VITE_APP_URL= http://192.168.1.168
#VITE_APP_URL= http://192.168.1.33
#VITE_APP_URL= http://192.168.1.204
#新大屏地址
VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
@@ -36,7 +37,7 @@
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
# 图片存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/all_terrain
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 行政区划存放地址
VITE_APP_REGION_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_region
# 算法仓库图片地址
.env.localhost
@@ -26,7 +26,7 @@
VITE_APP_AIRLINE_URL = http://192.168.1.227:80/minio/cloud-bucket
# 图片存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/all_terrain
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 行政区划存放地址
VITE_APP_REGION_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_region
.env.production
@@ -28,7 +28,7 @@
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket
# 图片存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/all_terrain
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 算法仓库图片地址
VITE_APP_PICTURE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket
.env.test
@@ -26,7 +26,7 @@
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
# 图片存放地址
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/all_terrain
VITE_APP_TERRAIN_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket/ztzf_terrain/
# 算法仓库图片地址
VITE_APP_PICTURE_URL = https://wrj.shuixiongit.com/aiskyminio/cloud-bucket
src/App.vue
@@ -3,9 +3,8 @@
</template>
<script setup>
import { useDownloadWs } from '@/page/index/useDownloadWs';
useDownloadWs()
</script>
<style>
src/api/algorithmMange/algorithmMange.js
New file
@@ -0,0 +1,17 @@
import request from '@/axios';
// 算法列表
export const getAlgorithmManageList = (params) => {
  return request({
    url: `/drone-device-core/ai/page`,
    method: 'get',
    params: params,
  })
};
export const algorithmManageEdit = data => {
  return request({
    url: '/drone-device-core/ai/edit',
    method: 'put',
    data: data,
  });
};
src/api/layer/index.js
New file
@@ -0,0 +1,109 @@
import request from '@/axios';
export const treeDataApi = params => {
  return request({
    url: `/drone-device-core/layer/api/v1/tree`,
    method: 'get',
    params,
  });
};
export const spatialPlanningApi = data => {
  return request({
    url: `/drone-device-core/layer/api/v1/spatialPlanning`,
    method: 'post',
    data,
  });
};
//   电子围栏
//   编辑
export const deitFenceApi = data => {
  return request({
    url: `/drone-device-core/layer/api/v1/data`,
    method: 'put',
    data: data,
  });
};
// 删除
export const deleteFenceApi = id => {
  return request({
    url: `/drone-device-core/layer/api/v1/data/${id}`,
    method: 'delete',
  });
};
// 新增
export const addFenceApi = data => {
  return request({
    url: `/drone-device-core/layer/api/v1/data`,
    method: 'post',
    data,
  });
};
// 获取文件夹
export const dataFolderApi = id => {
  return request({
    url: `/drone-device-core/layer/api/v1/getFolderByCategoryId/${id}`,
    method: 'get',
  });
};
//  文件夹
//   编辑
export const deitFolderApi = data => {
  return request({
    url: `/drone-device-core/layer/api/v1/folder`,
    method: 'put',
    data: data,
  });
};
// 删除
export const deleteFolderApi = id => {
  return request({
    url: `/drone-device-core/layer/api/v1/folder/${id}`,
    method: 'delete',
  });
};
// 新增
export const addFolderApi = data => {
  return request({
    url: `/drone-device-core/layer/api/v1/folder`,
    method: 'post',
    data,
  });
};
// 同步
export const areasUpdateApi = data => {
  return request({
    url: `/drone-device-core/noFlyZoon/areasUpdate`,
    method: 'post',
    data,
  });
};
// 获取同步信息
export const failuresListApi = data => {
  return request({
    url: `/drone-device-core/noFlyZoon/failuresList`,
    method: 'get',
    data,
  });
};
// 取消同步
export const failuresDelApi = data => {
  return request({
    url: `/drone-device-core/noFlyZoon/failuresDel`,
    method: 'delete',
    data,
  });
};
src/api/logs.js
@@ -62,3 +62,63 @@
    },
  });
};
// 历史轨迹记录
export const getHistoryTrack = (params) => {
  return request({
    url: '/drone-device-core/log/droneFlightLog/page',
    method: 'get',
    params
  })
}
// 导出飞行日志
export const exportFlyLog = data => {
  return request({
    url: '/drone-device-core/log/droneFlightLogInfo/export',
    method: 'post',
    data
  })
}
// 收藏
export const addStar = data => {
  return request({
    url: '/drone-device-core/log/favorite/add',
    method: 'post',
    data
  })
}
// 收藏列表
export const starPage = (params) => {
  return request({
    url: '/drone-device-core/log/favorite/page',
    method: 'post',
    params
  })
}
export const updateDroneFlight = (data) => {
  return request({
    url: '/drone-device-core/log/droneFlightLog/updateDroneFlight',
    method: 'post',
    data
  })
}
// 删除日志
export const deleteDroneFlight = (data) => {
  return request({
    url: '/drone-device-core/log/droneFlightLog/deleteDroneFlight',
    method: 'post',
    data
  })
}
export const cancelStar = data => {
  return request({
    url: '/drone-device-core/log/favorite/cancel/'+data,
    method: 'get',
  })
}
export const droneFlightLogInfoPage = params => {
  return request({
    url: '/drone-device-core/log/droneFlightLogInfo/page',
    method: 'get',
    params
  })
}
src/api/resource/wayline.js
@@ -17,7 +17,13 @@
  });
};
export const newGetWorkspacesPage = params => {
  return request({
    url: `/drone-device-core/wayline/api/v1/wayline-groups/page`,
    method: 'get',
    params,
  })
}
export const getWaylineFileListByArea = (areaCode) => {
  return request({
src/api/system/menu.js
@@ -98,11 +98,9 @@
    method: 'get',
  });
export const getRoutes = topMenuId =>
export const getRoutes = params =>
  request({
    url: '/blade-system/menu/routes',
    method: 'get',
    params: {
      topMenuId,
    },
    params
  });
src/api/system/role.js
@@ -20,10 +20,11 @@
  });
};
export const grantTree = () => {
export const grantTree = (params) => {
  return request({
    url: '/blade-system/menu/grant-tree',
    method: 'get',
    params
  });
};
src/api/user.js
@@ -122,10 +122,11 @@
    },
  });
export const getButtons = () =>
export const getButtons = (params) =>
  request({
    url: '/blade-system/menu/buttons',
    method: 'get',
    params
  });
export const getCaptcha = () =>
src/assets/images/layerManagement/cancelbtn.svg
New file
@@ -0,0 +1,24 @@
<svg width="133" height="47" viewBox="0 0 133 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 1321316178">
<g id="Rectangle 34624972" filter="url(#filter0_d_2405_1766)">
<rect x="4" width="125" height="39" rx="4" fill="#FF9011"/>
</g>
<path id="&#229;&#143;&#150;&#230;&#182;&#136;" d="M65.038 22.806L66.606 22.582V21.196H65.038V22.806ZM66.606 15.414H65.038V16.842H66.606V15.414ZM65.038 18.256V19.782H66.606V18.256H65.038ZM69.392 23.576L68.23 23.786V26.418H66.606V24.08C65.234 24.318 63.932 24.542 62.84 24.71L62.518 23.142C62.784 23.114 63.106 23.072 63.442 23.03V15.414H62.672V13.93H69.042V15.414H68.23V22.344L69.308 22.19L69.392 23.576ZM73.48 16.394H71.086C71.366 17.962 71.744 19.432 72.276 20.734C72.822 19.46 73.228 18.004 73.48 16.394ZM74.18 14.77L75.216 15.036C74.908 18.116 74.236 20.58 73.2 22.554C73.858 23.604 74.67 24.458 75.678 25.074C75.314 25.354 74.81 25.942 74.544 26.362C73.648 25.732 72.906 24.962 72.262 24.024C71.548 25.018 70.694 25.816 69.728 26.474C69.49 26.068 69.028 25.508 68.692 25.228C69.728 24.598 70.624 23.66 71.38 22.484C70.554 20.762 70.022 18.69 69.63 16.394H69.14V14.826H73.9L74.18 14.77ZM87.746 13.692L89.37 14.266C88.922 15.246 88.39 16.24 87.956 16.898L86.514 16.366C86.934 15.652 87.466 14.532 87.746 13.692ZM78.282 26.376L76.812 25.312C77.512 24.248 78.464 22.484 79.22 20.846L80.452 21.854C79.808 23.352 79.038 24.962 78.282 26.376ZM79.934 18.774L78.94 20.076C78.422 19.544 77.316 18.816 76.434 18.354L77.372 17.178C78.24 17.584 79.374 18.256 79.934 18.774ZM77.078 14.658L78.044 13.496C78.912 13.888 80.004 14.56 80.55 15.092L79.514 16.38C79.024 15.848 77.932 15.12 77.078 14.658ZM83.588 16.31L82.076 16.996C81.908 16.31 81.362 15.218 80.816 14.406L82.216 13.79C82.776 14.56 83.378 15.582 83.588 16.31ZM82.888 22.288H87.13V21.238H82.888V22.288ZM87.13 18.816H82.888V19.838H87.13V18.816ZM88.754 17.262V24.626C88.754 25.438 88.614 25.872 88.082 26.124C87.564 26.39 86.808 26.404 85.758 26.404C85.702 25.956 85.478 25.27 85.254 24.836C85.898 24.878 86.626 24.878 86.85 24.878C87.06 24.864 87.13 24.794 87.13 24.612V23.688H82.888V26.418H81.25V17.262H84.218V13.37H85.898V17.262H88.754Z" fill="white"/>
<g id="Frame">
<path id="Vector" d="M53.4599 25.1179C53.2915 25.1179 53.1232 25.0536 52.9946 24.9251L43.0678 14.9981C42.8107 14.7412 42.8107 14.3244 43.0678 14.0675C43.3249 13.8105 43.7413 13.8105 43.9983 14.0675L53.9252 23.9946C54.1822 24.2514 54.1822 24.6683 53.9252 24.9251C53.7966 25.0536 53.6283 25.1179 53.4599 25.1179Z" fill="white"/>
<path id="Vector_2" d="M43.5331 25.1179C43.3647 25.1179 43.1963 25.0536 43.0678 24.9251C42.8107 24.6683 42.8107 24.2514 43.0678 23.9946L52.9946 14.0675C53.2517 13.8105 53.6681 13.8105 53.9252 14.0675C54.1822 14.3244 54.1822 14.7412 53.9252 14.9981L43.9983 24.9251C43.8698 25.0536 43.7014 25.1179 43.5331 25.1179Z" fill="white"/>
</g>
</g>
<defs>
<filter id="filter0_d_2405_1766" x="0" 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_2405_1766"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2405_1766" result="shape"/>
</filter>
</defs>
</svg>
src/assets/images/layerManagement/newcon_box.png
src/assets/images/layerManagement/savebtn.svg
New file
@@ -0,0 +1,23 @@
<svg width="133" height="47" viewBox="0 0 133 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Group 1321316177">
<g id="Rectangle 34624973" filter="url(#filter0_d_2405_1772)">
<rect x="4" width="125" height="39" rx="4" fill="#0059FF"/>
</g>
<path id="&#228;&#191;&#157;&#229;&#173;&#152;" d="M65.584 13.17L67.124 13.674C66.76 14.584 66.326 15.536 65.822 16.432V26.19H64.24V18.868C63.89 19.316 63.54 19.736 63.176 20.114C63.022 19.708 62.56 18.812 62.28 18.406C63.582 17.146 64.856 15.158 65.584 13.17ZM68.986 15.214V17.09H73.088V15.214H68.986ZM75.44 21.332H72.64C73.466 22.55 74.67 23.698 75.846 24.384C75.468 24.706 74.936 25.294 74.67 25.714C73.648 24.986 72.626 23.852 71.828 22.606V26.218H70.148V22.676C69.294 23.908 68.23 24.972 67.124 25.686C66.872 25.28 66.354 24.678 65.976 24.356C67.236 23.698 68.482 22.564 69.35 21.332H66.48V19.82H70.148V18.56H67.404V13.758H74.754V18.56H71.828V19.82H75.44V21.332ZM89.23 16.544H81.768C81.292 17.552 80.732 18.518 80.074 19.414V26.19H78.394V21.304C77.974 21.682 77.554 22.018 77.078 22.34C76.91 21.906 76.546 21.164 76.294 20.744C77.806 19.75 79.024 18.224 79.878 16.544H76.798V14.948H80.592C80.816 14.332 81.012 13.73 81.166 13.128L82.902 13.548C82.762 14.024 82.594 14.472 82.426 14.948H89.23V16.544ZM89.44 21.15V22.704H86.122V24.454C86.122 25.294 85.982 25.728 85.38 25.98C84.792 26.204 83.966 26.232 82.846 26.218C82.79 25.742 82.594 25.098 82.384 24.622C83.084 24.664 83.91 24.664 84.134 24.664C84.372 24.65 84.442 24.594 84.442 24.412V22.704H80.886V21.15H84.442V20.17C84.876 19.904 85.338 19.526 85.772 19.134H81.978V17.636H87.298L87.648 17.538L88.698 18.392C87.984 19.148 87.046 19.974 86.122 20.618V21.15H89.44Z" fill="white"/>
<g id="Frame">
<path id="Vector" d="M47.3835 24.4974C47.1566 24.4974 46.9383 24.4125 46.7737 24.2583L42.0767 19.8784C41.725 19.551 41.7129 19.0069 42.049 18.6639C42.3851 18.3208 42.9447 18.3104 43.2964 18.6379L47.3073 22.3785L54.5824 13.7694C54.8925 13.4038 55.4486 13.3501 55.8246 13.6516C56.2006 13.953 56.2543 14.4936 55.9459 14.8609L48.0662 24.1856C47.9102 24.3709 47.6816 24.4836 47.4373 24.4974H47.3835Z" fill="white"/>
</g>
</g>
<defs>
<filter id="filter0_d_2405_1772" x="0" 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_2405_1772"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2405_1772" result="shape"/>
</filter>
</defs>
</svg>
src/assets/images/mh.svg
New file
@@ -0,0 +1,7 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame">
<path id="Vector" d="M17.133 20.3135H6.83691C6.51348 20.3135 6.25098 20.051 6.25098 19.7275C6.25098 19.4041 6.51348 19.1416 6.83691 19.1416H17.133C17.4564 19.1416 17.7189 19.4041 17.7189 19.7275C17.7189 20.051 17.4564 20.3135 17.133 20.3135Z" fill="#707070"/>
<path id="Vector_2" d="M12.0234 19.9943C11.7 19.9943 11.4375 19.7318 11.4375 19.4084V16.1436C11.4375 15.8201 11.7 15.5576 12.0234 15.5576C12.3469 15.5576 12.6094 15.8201 12.6094 16.1436V19.4084C12.6094 19.7318 12.3469 19.9943 12.0234 19.9943Z" fill="#707070"/>
<path id="Vector_3" d="M19.1181 3.2793H5.16338C3.64932 3.2793 2.4165 4.51211 2.4165 6.02617V13.9809C2.4165 15.4949 3.64932 16.7277 5.16338 16.7277H19.1181C20.6321 16.7277 21.8649 15.4949 21.8649 13.9809V6.02852C21.8649 4.51211 20.6321 3.2793 19.1181 3.2793ZM7.5001 13.0316C7.5001 13.3551 7.2376 13.6176 6.91416 13.6176C6.59072 13.6176 6.32822 13.3551 6.32822 13.0316V10.5941C6.32822 10.2707 6.59072 10.0082 6.91416 10.0082C7.2376 10.0082 7.5001 10.2707 7.5001 10.5941V13.0316ZM10.922 13.0316C10.922 13.3551 10.6595 13.6176 10.336 13.6176C10.0126 13.6176 9.7501 13.3551 9.7501 13.0316V8.6957C9.7501 8.37227 10.0126 8.10977 10.336 8.10977C10.6595 8.10977 10.922 8.37227 10.922 8.6957V13.0316ZM14.4142 13.0316C14.4142 13.3551 14.1517 13.6176 13.8282 13.6176C13.5048 13.6176 13.2423 13.3551 13.2423 13.0316V9.89102C13.2423 9.56758 13.5048 9.30508 13.8282 9.30508C14.1517 9.30508 14.4142 9.56758 14.4142 9.89102V13.0316ZM17.7188 13.0316C17.7188 13.3551 17.4563 13.6176 17.1329 13.6176C16.8095 13.6176 16.547 13.3551 16.547 13.0316V6.98477C16.547 6.66133 16.8095 6.39883 17.1329 6.39883C17.4563 6.39883 17.7188 6.66133 17.7188 6.98477V13.0316Z" fill="#707070"/>
</g>
</svg>
src/assets/images/task/sign.svg
New file
@@ -0,0 +1,3 @@
<svg width="23" height="32" viewBox="0 0 23 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.8446 13.609C22.4288 14.8094 22.4288 17.1906 20.8446 18.3911L4.81187 30.5402C2.83607 32.0374 3.49992e-07 30.6282 4.58352e-07 28.1492L1.52047e-06 3.85082C1.62883e-06 1.37183 2.83608 -0.0374351 4.81187 1.45977L20.8446 13.609Z" fill="#1B94FF"/>
</svg>
src/assets/images/warn.svg
New file
@@ -0,0 +1,10 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame" clip-path="url(#clip0_2023_1729)">
<path id="Vector" d="M10.9609 1.95508C5.96561 1.95508 1.91602 6.00424 1.91602 11C1.91602 15.9958 5.96561 20.0449 10.9609 20.0449C15.9563 20.0449 20.0059 15.9949 20.0059 11C20.0059 6.0051 15.9563 1.95508 10.9609 1.95508ZM10.9609 15.843C10.7528 15.8426 10.5495 15.7805 10.3767 15.6645C10.2038 15.5486 10.0692 15.3841 9.98987 15.1917C9.91052 14.9993 9.88998 14.7877 9.93084 14.5836C9.9717 14.3795 10.0721 14.1921 10.2194 14.0451C10.3668 13.8981 10.5543 13.7981 10.7585 13.7576C10.9626 13.7172 11.1742 13.7381 11.3664 13.8179C11.5587 13.8976 11.723 14.0326 11.8386 14.2056C11.9541 14.3787 12.0158 14.5822 12.0158 14.7903C12.0158 14.9287 11.9885 15.0658 11.9355 15.1936C11.8824 15.3215 11.8047 15.4376 11.7067 15.5354C11.6087 15.6332 11.4924 15.7107 11.3645 15.7635C11.2365 15.8163 11.0994 15.8433 10.9609 15.843ZM12.0158 11.2836C12.0119 11.5607 11.899 11.8252 11.7016 12.0198C11.5042 12.2143 11.2382 12.3234 10.961 12.3234C10.6839 12.3234 10.4179 12.2143 10.2205 12.0198C10.0231 11.8252 9.91022 11.5607 9.90627 11.2836V6.37313C9.91022 6.09599 10.0231 5.83154 10.2205 5.63696C10.4179 5.44238 10.6839 5.3333 10.961 5.3333C11.2382 5.3333 11.5042 5.44238 11.7016 5.63696C11.899 5.83154 12.0119 6.09599 12.0158 6.37313V11.2836Z" fill="#FF0000"/>
</g>
<defs>
<clipPath id="clip0_2023_1729">
<rect width="22" height="22" fill="white"/>
</clipPath>
</defs>
</svg>
src/components/CreateQRcode/CreateQRcode.vue
@@ -21,7 +21,7 @@
    qrCodeInstance = new QRCode(myQrCode.value, {
        width: 130,
        height: 130,
        text: `https://uri.amap.com/marker?position=${transformed}&name=%E4%BA%8B%E4%BB%B6%E7%82%B9`,
        text: `https://uri.amap.com/marker?position=${transformed}&name=%E4%BA%8B%E4%BB%B6%E7%82%B9&from='ztzf'`,
    })
  if (myQrCode.value.hasAttribute('title')) {
    myQrCode.value.removeAttribute('title')
src/components/commonDialog.vue
New file
@@ -0,0 +1,48 @@
<template>
  <el-dialog class="ztzf-dialog-mange" append-to-body v-model="props.isShowView" :title="props.title"
             :width="props.width" :close-on-click-modal="false" :destroy-on-close="true" @close="cancel">
    <slot name="default" />
    <template #footer v-if="props.addEditView !== 'view'">
      <div class="dialog-footer">
        <el-button v-if="props.addEditView === 'add'" type="primary" @click="submit" :icon="CirclePlus">保存</el-button>
        <el-button v-else type="primary" @click="submit" :icon="CircleCheck">确认</el-button>
        <el-button @click="cancel"  :icon="CircleClose">取消</el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { Delete, Search, CircleCheck, CircleClose, CirclePlus } from '@element-plus/icons-vue'
import { defineEmits } from 'vue';
const emit = defineEmits(['cancel', 'confirm']);
const props = defineProps({
  width: {
    type: String,
    default: '800'
  },
  isShowView: {
    type: Boolean,
    default: false
  },
  addEditView: {
    type: String,
    default: 'add'
  },
  title:{
    type: String,
    default: ''
  }
})
function submit() {
  emit('submit')
}
function cancel() {
  emit('cancel')
}
</script>
<style scoped lang="scss">
</style>
src/hooks/common.js
@@ -26,14 +26,17 @@
    if (!placemark?.length) return []
    const isWGS84 = execute_height_mode === 'WGS84'
    const list = placemark.map(item => {
        const [longitude, latitude] = item.point.coordinates.trim().split(',')
        const [longitude, latitude] = item.point.coordinates.trim().split(',').map(i => _.round(i, 6))
        const [latitude1, longitude1] = (item?.waypoint_heading_param?.waypoint_poi_point?.trim()?.split(',') || [])
            .map(i => _.round(i, 6))
        let action_modes = item?.action_group?.action || []
        action_modes = Array.isArray(action_modes) ? action_modes : [action_modes]
        return {
            longitude: _.round(longitude, 8),
            latitude: _.round(latitude, 8),
            height: isWGS84 ? item.execute_height : item.execute_height + startPoint.height,
            longitude,
            latitude,
            height: _.round(item.execute_height,2),
            waypoint_speed: item.waypoint_speed,
            waypoint_poi_point: longitude1 ? { longitude: longitude1, latitude: latitude1 } : undefined,//兴趣点
            action_modes,
        }
    })
@@ -42,9 +45,6 @@
        flyRotateYaw: 0,
        pointType: 'start',
        waypoint_speed: auto_flight_speed,
        heading: 0, // 椎体 heading
        arrowHeading: 0, // 飞行器 heading
        pitch: 0, //椎体俯仰角
        fov: 19.5, // 这个是椎体的展示截面大小
    }
    list.forEach(item => {
@@ -56,7 +56,6 @@
                item1.action_actuator_func_param.focal_length = Number(focal_length) / 24
            }
        })
        item.arrowHeading = 0
    })
    return [pointStart, ...list]
}
@@ -69,6 +68,7 @@
    const waylinesXMLJSON = XMLToJSON(waylinesXML)?.['Document']
    const templateXMLObj = camelToSnake(removeTextKey(templateXMLJSON))
    const waylinesXMLObj = camelToSnake(removeTextKey(waylinesXMLJSON))
    const {
        mission_config: {
            take_off_ref_point,
@@ -77,12 +77,16 @@
        folder: {
            payload_param: { image_format } = {},
            placemark: {
                polygon
                polygon,
                margin: buffer_distance_meters = 0
            },
            wayline_coordinate_sys_param:{height_mode} = {},
            global_height, auto_flight_speed,
            template_type: templateType
        } = {},
    } = templateXMLObj
    const [latitude, longitude, height] = take_off_ref_point.split(',').map(item => _.round(item, 6))
    let startPoint = { latitude, longitude, height: Number.isNaN(height) ? 0 : height }
    const {
        mission_config: { take_off_security_height } = {},
@@ -98,14 +102,9 @@
        execute_height_mode = polygonPointFolder.execute_height_mode
        pointPlacemark = polygonPointFolder.placemark
    }
    const [latitude, longitude, height] = take_off_ref_point.split(',').map(item => _.round(item, 6))
    let startPoint = { latitude, longitude, height }
    // 取出点位
    const coordinates = polygon?.outer_boundary_is.linear_ring
        .coordinates?.split('\n') || []
    // 数组转换
    let polygonList = coordinates.map((coordinate) =>
        coordinate
@@ -113,9 +112,21 @@
            .split(',')
            .map((v) => Number(v)),
    )
    polygonList.pop()
    const templatePlacemark = templateXMLObj.folder.placemark
    // 点航线list
    let pointList = handlePointListForKmz({
        placemark: polygonPointFolder.placemark,
        startPoint,
        execute_height_mode,
        auto_flight_speed
    }).map((i,index)=> {
        // 注入是否使用全局高度参数
        return {
            ...i,
            use_global_height: index > 0 ? templatePlacemark[index-1]?.use_global_height : 0
        }
    })
    return {
        image_format,
        startPoint,
@@ -124,11 +135,14 @@
        take_off_ref_point,
        drone_enum_value,
        drone_sub_enum_value,
        take_off_security_height,
        execute_height_mode,
        take_off_security_height,//起飞安全高度
        execute_height_mode,//执行高度模式
        height_mode,//高度模式
        pointList,//航点航线 点list
        templateType,
        pointPlacemark,
        polygonList
        polygonList,
        buffer_distance_meters
    }
}
src/hooks/useRouteLine/useRouteLine.js
@@ -13,6 +13,8 @@
import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
import { flyVisual } from '@/utils/cesium/mapUtil'
import { useStore } from 'vuex'
let arrowLineMaterialProperty = new ArrowLineMaterialProperty({
    color: new Cesium.Color(128 / 255, 215 / 255, 255 / 255, 1),
    directionColor: new Cesium.Color(1, 1, 1, 1),
@@ -22,6 +24,7 @@
})
export function useRouteLine () {
    const store = useStore()
    let viewer = null
    let previewDataSource = null
@@ -162,15 +165,15 @@
            },
        })
            // 落点线
            ; (pointList || [])?.forEach((item, index) => {
                const topPosition = Cesium.Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude), Number(item.height))
                let setting = {
                    position: topPosition,
                    polyline: getPolyLine(viewer, { value: pointList }, index),
                }
                previewDataSource.entities.add(setting)
            })
        // 落点线
        ; (pointList || [])?.forEach((item, index) => {
            const topPosition = Cesium.Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude), Number(item.height))
            let setting = {
                position: topPosition,
                polyline: getPolyLine(viewer, { value: pointList }, index),
            }
            previewDataSource.entities.add(setting)
        })
        // 终点
        previewDataSource.entities.add({
@@ -191,6 +194,9 @@
                height: 50,
            },
        })
        // 获取最后一点高度
        console.log(pointList, '查看数组最后一点高度')
        store.commit('setLastHeight', Number(pointList[pointList.length - 1].height))
        flyVisual({
            positionsData:pointList.map(i => [Number(i.longitude), Number(i.latitude), Number(i.height)]),
            viewer,
src/option/job/jobinfo.js
@@ -118,7 +118,9 @@
export default {
  tip: false,
  searchShow: true,
  searchMenuSpan: 6,
  searchGutter: 30,
  searchMenuPosition: 'left',
  searchMenuSpan: 4,
  border: true,
  index: true,
  viewBtn: true,
@@ -144,6 +146,7 @@
        value: 'id',
      },
      search: true,
      searchSpan: 4,
      display: false,
      showOverflowTooltip: true,
      change: (value) => {
@@ -159,6 +162,7 @@
      prop: 'jobId',
      type: 'input',
      search: true,
      searchSpan: 4,
      width: 80,
      display: false,
      showOverflowTooltip: true,
@@ -168,6 +172,7 @@
      prop: 'jobName',
      type: 'input',
      search: true,
      searchSpan: 4,
      width: 200,
      display: false,
      showOverflowTooltip: true,
src/option/job/jobserver.js
@@ -11,7 +11,9 @@
export default {
  tip: false,
  searchShow: true,
  searchMenuSpan: 6,
  searchGutter: 30,
  searchMenuPosition: 'left',
  searchMenuSpan: 4,
  border: true,
  index: true,
  viewBtn: true,
@@ -29,6 +31,7 @@
      prop: 'jobServerName',
      type: 'input',
      span: 24,
      searchSpan: 4,
      search: true,
      rules: [
        {
@@ -56,6 +59,7 @@
      prop: 'jobAppName',
      type: 'input',
      span: 24,
      searchSpan: 4,
      search: true,
      rules: [
        {
src/option/system/dict.js
@@ -1,12 +1,14 @@
export const optionParent = {
  tip: false,
  searchShow: true,
  searchMenuSpan: 10,
  searchGutter: 30,
  searchMenuPosition: 'left',
  searchMenuSpan: 4,
  border: true,
  index: true,
  selection: true,
  viewBtn: true,
  menuWidth: 250,
  menuWidth: 280,
  dialogWidth: 880,
  dialogClickModal: false,
@@ -18,6 +20,7 @@
      label: '字典编号',
      prop: 'code',
      search: true,
      searchSpan: 4,
      slot: true,
      span: 24,
      rules: [
@@ -32,6 +35,7 @@
      label: '字典名称',
      prop: 'dictValue',
      search: true,
      searchSpan: 4,
      align: 'center',
      rules: [
        {
@@ -59,7 +63,7 @@
      label: '封存',
      prop: 'isSealed',
      type: 'switch',
      align: 'center',
      // align: 'center',
      width: 100,
      dicData: [
        {
src/option/system/dictbiz.js
@@ -1,12 +1,14 @@
export const optionParent = {
  tip: false,
  searchShow: true,
  searchMenuSpan: 10,
  searchGutter: 30,
  searchMenuPosition: 'left',
  searchMenuSpan: 4,
  border: true,
  index: true,
  selection: true,
  viewBtn: true,
  menuWidth: 250,
  menuWidth: 280,
  dialogWidth: 880,
  dialogClickModal: false,
@@ -18,6 +20,7 @@
      label: '字典编号',
      prop: 'code',
      search: true,
      searchSpan: 4,
      slot: true,
      span: 24,
      rules: [
@@ -32,6 +35,7 @@
      label: '字典名称',
      prop: 'dictValue',
      search: true,
      searchSpan: 4,
      align: 'center',
      rules: [
        {
@@ -109,6 +113,7 @@
      addDisabled: true,
      editDisabled: true,
      search: true,
      searchSpan: 4,
      span: 24,
      rules: [
        {
@@ -122,6 +127,7 @@
      label: '字典名称',
      prop: 'dictValue',
      search: true,
      searchSpan: 4,
      align: 'center',
      rules: [
        {
src/page/index/GlobalWS.vue
New file
@@ -0,0 +1,11 @@
<template>
</template>
<script setup>
import { useGlobalWS } from '@/page/index/useGlobalWS';
useGlobalWS()
</script>
<style scoped lang="scss">
</style>
src/page/index/index.vue
@@ -24,23 +24,26 @@
        </div>
      </div>
    </div>
    <GlobalWS />
    <!-- <wechat></wechat> -->
  </div>
</template>
<script>
import index from '@/mixins/index'
import wechat from './wechat.vue'
import index from '@/mixins/index';
import wechat from './wechat.vue';
//import { validatenull } from 'utils/validate';
import { mapGetters } from 'vuex'
import tags from './tags.vue'
import search from './search.vue'
import logo from './logo.vue'
import top from './top/index.vue'
import sidebar from './sidebar/index.vue'
import { mapGetters } from 'vuex';
import tags from './tags.vue';
import search from './search.vue';
import logo from './logo.vue';
import top from './top/index.vue';
import sidebar from './sidebar/index.vue';
import GlobalWS from '@/page/index/GlobalWS.vue';
export default {
  mixins: [index],
  components: {
    GlobalWS,
    top,
    logo,
    tags,
@@ -49,10 +52,10 @@
    wechat,
  },
  name: 'index',
  provide () {
  provide() {
    return {
      index: this,
    }
    };
  },
  computed: {
    ...mapGetters([
@@ -64,27 +67,27 @@
      'menu',
      'setting',
    ]),
    validSidebar () {
    validSidebar() {
      return !(
        (this.$route.meta || {}).menu === false || (this.$route.query || {}).menu === 'false'
      )
      );
    },
  },
  props: [],
  methods: {
    //打开菜单
    openMenu (item = {}) {
    openMenu(item = {}) {
      this.$store.dispatch('GetMenu', item.id).then(data => {
        if (data.length !== 0) {
          this.$router.$avueRouter.formatRoutes(data, true)
          this.$router.$avueRouter.formatRoutes(data, true);
          // 获取url里面的redirect
          const redirect = decodeURIComponent(this.$route.query.redirect || '')
          const redirect = decodeURIComponent(this.$route.query.redirect || '');
          if (redirect) {
            console.log('redirect', redirect)
            const [path, queryString] = redirect.split('?')
            const query = queryString ? Object.fromEntries(new URLSearchParams(queryString)) : {}
            this.$router.push({ path, query })
            console.log('redirect', redirect);
            const [path, queryString] = redirect.split('?');
            const query = queryString ? Object.fromEntries(new URLSearchParams(queryString)) : {};
            this.$router.push({ path, query });
          }
        }
        //当点击顶部菜单后默认打开第一个菜单
@@ -108,10 +111,10 @@
            }, itemActive.meta)
          });
        }*/
      })
      });
    },
  },
}
};
</script>
<style lang="scss" scoped>
@@ -130,6 +133,6 @@
  background: transparent;
  padding: 0 10px;
  --el-color-primary: rgba(20, 65, 255, 1);
    height: pxToVh(55);
  height: pxToVh(55);
}
</style>
src/page/index/tags.vue
@@ -23,7 +23,7 @@
      <el-dropdown class="avue-tags__menu">
        <el-button type="primary" style="background: #409eff;border-color: #409eff">
          {{ $t('tagsView.menu') }}
          <i class="el-icon-arrow-down el-icon--right"></i>
          <!-- <i class="el-icon-arrow-down el-icon--right"></i> -->
        </el-button>
        <template #dropdown>
          <el-dropdown-menu>
src/page/index/top/index.vue
@@ -15,7 +15,7 @@
        <top-lock v-if="setting.lock"/>
        <top-qna />
        <top-full v-if="setting.fullscreen" title="全屏"/>
        <img class="gateway" @click="jumpMH" src="@/assets/images/mh.png" alt="进入门户" title="进入门户" width="20" height="20">
        <img class="gateway" @click="jumpMH" src="@/assets/images/mh.svg" alt="进入门户" title="进入门户" width="20" height="20">
      </div>
      <div class="top-user">
        <img class="top-bar__img" :src="userInfo.avatar "  alt=""/>
@@ -96,7 +96,7 @@
      this.$store.commit('SET_COLLAPSE')
    },
    logout () {
      this.$confirm(this.$t('logoutTip'), this.$t('tip'), {
      this.$confirm(this.$t('logoutTip'), this.$t('提示'), {
        confirmButtonText: this.$t('submitText'),
        cancelButtonText: this.$t('cancelText'),
        type: 'warning',
src/page/index/useGlobalWS.js
File was renamed from src/page/index/useDownloadWs.js
@@ -1,20 +1,21 @@
import { computed, onBeforeUnmount, onMounted } from 'vue';
import { getWebsocketUrl } from '@/utils/websocket/config'
import { useConnectWebSocket } from '@/utils/websocket/connect-websocket'
import { useStore } from 'vuex'
import dayjs from 'dayjs'
import { aLinkDownloadUtil } from '@/utils/util'
import EventBus from '@/utils/eventBus'
import { computed, onBeforeUnmount, onMounted } from 'vue';
import { setStore,getStore } from '@/utils/store';
import { getDownloadStatusApi } from '@/api/dataCenter/dataCenter';
import { getStore, setStore } from '@/utils/store'
export const useDownloadWs = () => {
export const useGlobalWS = () => {
    const store = useStore()
    const loginUserInfo = computed(() => store.state.user.userInfo)
    const downloadProgress = computed(() => store.state.common.downloadProgress)
    let currentWebSocket = null
    function messageHandler(payload) {
        const {type,status,progress,download_url} = payload
    // 下载进度处理
    function downloadProcessing(payload) {
        const {type,status,progress,download_url} = payload?.data || {}
        let setProgress = 0
        if (status === 'PENDING'){
            setProgress = 1
@@ -29,7 +30,7 @@
            ...downloadProgress.value,
            [type]: setProgress,
        })
        if (status === 'COMPLETED' && ['htsjzx','htlsrwxq'].includes(type)) {
        if (status === 'COMPLETED' && ['dpsjzx','dplsrwxq'].includes(type)) {
            const name = `数据中心-${dayjs().format('YYYYMMDDHHmmss')}.zip`
            const currentUrl = getStore({name: 'downloadUrl'})
            if (currentUrl === download_url && download_url!== undefined) return
@@ -38,18 +39,42 @@
        }
    }
    const logOut = () => {
        store.commit('SET_THEME_NAME', '')
        store.dispatch('LogOut').then(() => {
            router.push({ path: '/login' })
            setTimeout(() => location.reload())
        })
    }
    function messageHandler(payload) {
        switch (payload.biz_code) {
            case 'JOB_ISREFRESH':
                store.commit('jobUpdateKeyAdd')
                break
            case 'DEVICE_ISREFRESH':
                store.commit('deviceUpdateKeyAdd')
                break
            case 'DOWNLOAD_PROGRESS':
                downloadProcessing(payload)
                break
            case 'LOGOUT_USER':
                logOut()
                break
        }
    }
    onMounted(() => {
        let webSocketUrl = getWebsocketUrl() + `&model_type=3&workspace-id=${loginUserInfo.value.user_id}`
        currentWebSocket = useConnectWebSocket((result)=>{
            const payload = JSON.parse(result)
            messageHandler(payload.data)
            messageHandler(payload)
        }, webSocketUrl)
        EventBus.on('useDownloadWs-messageHandler',messageHandler)
        EventBus.on('useGlobalWS-messageHandler',messageHandler)
    })
    onBeforeUnmount(() => {
        currentWebSocket?.close()
        EventBus.off('useDownloadWs-messageHandler',messageHandler)
        EventBus.off('useGlobalWS-messageHandler',messageHandler)
    })
    return {}
}
src/store/modules/common.js
@@ -15,15 +15,27 @@
    lockPasswd: getStore({ name: 'lockPasswd' }) || '',
    website: website,
    setting: website.setting,
    deviceUpdateKey: 0, //设备刷新key
    jobUpdateKey: 0, //任务刷新key
    downloadProgress:{
      htsjzx: 100, //数据中心
      htlsrwxq: 100, //历史任务详情
    }
    },
    lastHeight: 0, // 获取最后一点高度
  },
  mutations: {
    setLastHeight(state, data) {
      state.lastHeight = data
    },
    setDownloadProgress(state, data) {
      state.downloadProgress = data
    },
    deviceUpdateKeyAdd(state, data) {
      state.deviceUpdateKey = state.deviceUpdateKey + 1
    },
    jobUpdateKeyAdd(state, data) {
      state.jobUpdateKey = state.jobUpdateKey + 1
    },
    SET_LANGUAGE: (state, language) => {
      state.language = language;
      setStore({
src/store/modules/user.js
@@ -280,7 +280,7 @@
        })
      }
      return new Promise(resolve => {
        getRoutes(tenantId).then(res => {
        getRoutes({tenantId,sysType: 1}).then(res => {
          const data = res.data.data;
          let menu = deepClone(data);
          if (!res.data?.data?.length){
@@ -299,7 +299,7 @@
    },
    GetButtons({ commit }) {
      return new Promise(resolve => {
        getButtons().then(res => {
        getButtons({sysType: 1}).then(res => {
          const data = res.data.data;
          commit('SET_PERMISSION', data);
          resolve();
src/styles/common.scss
@@ -2,6 +2,10 @@
@import './variables.scss';
@import './base.scss';
@import './tool.scss';
@import './element-ui.scss';
// 地图编辑样式
@import './mapEdit.scss';
a {
  text-decoration: none;
  color: #333;
src/styles/element-ui.scss
@@ -1,141 +1,629 @@
.el-card.is-always-shadow {
  box-shadow: none;
  border: none !important;
}
.el-menu {
  border-right: none;
}
.el-message__icon,
.el-message__content {
  display: inline-block;
}
.el-date-editor .el-range-input,
.el-date-editor .el-range-separator {
  height: auto;
  overflow: hidden;
}
.el-dialog__wrapper {
  z-index: 2048;
}
.el-col {
  margin-bottom: 8px;
}
.el-main {
  padding: 0 !important;
}
.el-dropdown-menu__item--divided:before,
.el-menu,
.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus,
.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover,
.el-menu--horizontal>.el-sub-menu .el-sub-menu__title:hover {
  background-color: transparent;
}
.el-dropdown-menu__item--divided:before,
.el-menu,
.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus,
.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover,
.el-menu--horizontal>.el-sub-menu .el-sub-menu__title:hover {
  background-color: transparent !important;
}
.el-collapse-item__header {
  height: auto;
  overflow: hidden;
}
.el-button.is-text:not(.is-disabled):active {
  background-color: transparent;
}
.el-button.is-text:not(.is-disabled):focus,
.el-button.is-text:not(.is-disabled):hover {
  background-color: transparent;
}
.avue-icon i,
.avue-icon svg {
  line-height: 20px;
}
.avue--detail .el-form-item {
  background-color: #fafafa;
}
.showFullScreenDlg {
  display: flex;
  justify-content: space-between;
  .el-dialog {
    position: relative;
    padding: 0;
    overflow: hidden;
    .el-dialog__body {
      width: 100%;
      height: 100%;
      iframe {
        width: 100%;
        height: 100%;
      }
    }
    .el-dialog__header {
      height: 0;
      overflow: hidden;
      padding: 0;
      .el-dialog__headerbtn {
        position: absolute;
        left: 0;
        top: 14px;
        .el-dialog__close {
          color: #fff;
          font-size: 35px;
          z-index: 99;
        }
      }
    }
    box-shadow: none;
    border: none !important;
  }
}
.ztzf-drawer-body-basic-container {
  .el-drawer__body {
    display: flex;
    flex-direction: column;
  .el-menu {
    border-right: none;
  }
}
.ztzf-public-general-avue-crud {
  .el-table {
    tr {
      min-height: 30px !important;
      line-height: 30px !important;
      box-sizing: border-box !important;
    }
    tr th,
    tr td {
      padding: 6px 0 !important;
      min-height: 30px !important;
      line-height: 30px !important;
      box-sizing: border-box !important;
      div.cell {
        min-height: 30px !important;
        line-height: 30px !important;
      }
    }
  .el-message__icon,
  .el-message__content {
    display: inline-block;
  }
}
  .el-date-editor .el-range-input,
  .el-date-editor .el-range-separator {
    height: auto;
    overflow: hidden;
  }
  .el-dialog__wrapper {
    z-index: 2048;
  }
  .el-col {
    margin-bottom: 8px;
  }
  .el-main {
    padding: 0 !important;
  }
  .el-dropdown-menu__item--divided:before,
  .el-menu,
  .el-menu--horizontal>.el-menu-item:not(.is-disabled):focus,
  .el-menu--horizontal>.el-menu-item:not(.is-disabled):hover,
  .el-menu--horizontal>.el-sub-menu .el-sub-menu__title:hover {
    background-color: transparent;
  }
  .el-dropdown-menu__item--divided:before,
  .el-menu,
  .el-menu--horizontal>.el-menu-item:not(.is-disabled):focus,
  .el-menu--horizontal>.el-menu-item:not(.is-disabled):hover,
  .el-menu--horizontal>.el-sub-menu .el-sub-menu__title:hover {
    background-color: transparent !important;
  }
  .el-collapse-item__header {
    height: auto;
    overflow: hidden;
  }
  .el-button.is-text:not(.is-disabled):active {
    background-color: transparent;
  }
  .el-button.is-text:not(.is-disabled):focus,
  .el-button.is-text:not(.is-disabled):hover {
    background-color: transparent;
  }
  .avue-icon i,
  .avue-icon svg {
    line-height: 20px;
  }
  .avue--detail .el-form-item {
    background-color: #fafafa;
  }
  .showFullScreenDlg {
    display: flex;
    justify-content: space-between;
    .el-dialog {
      position: relative;
      padding: 0;
      overflow: hidden;
      .el-dialog__body {
        width: 100%;
        height: 100%;
        iframe {
          width: 100%;
          height: 100%;
        }
      }
      .el-dialog__header {
        height: 0;
        overflow: hidden;
        padding: 0;
        .el-dialog__headerbtn {
          position: absolute;
          left: 0;
          //top: 14px;
          .el-dialog__close {
            color: #fff;
            font-size: 35px;
            z-index: 99;
          }
        }
      }
    }
  }
  .ztzf-drawer-body-basic-container {
    .el-drawer__header {
      margin-bottom: 0;
    }
    .el-drawer__body {
      display: flex;
      flex-direction: column;
    }
  }
  .ztzf-el-card__body {
     .el-card__body {
      .avue-form{
      padding: 0;}}
  }
  .ztzf-public-general-avue-crud {
    .el-table {
      tr {
        min-height: 30px !important;
        line-height: 30px !important;
        box-sizing: border-box !important;
      }
      tr th,
      tr td {
        padding: 6px 0 !important;
        min-height: 30px !important;
        line-height: 30px !important;
        box-sizing: border-box !important;
        div.cell {
          min-height: 30px !important;
          line-height: 30px !important;
        }
      }
    }
  }
  .ztzf-layer-input {
      .el-input__wrapper {
          padding: 0 8px !important;
          // background: #2D2D2D;
      background-color: transparent !important;
          box-shadow: none !important;
          border: 1px solid #fff;
          .el-input__inner {
              color: #ffffff;
              &::placeholder {
          color: rgba(255,255,255,0.37);
              }
          }
      }
      &.is-disabled {
          .el-input__wrapper {
              background-color: #012a50;
          }
      }
  }
  .ztzf-button {
    background-color: transparent !important;
    color: #fff !important;
  }
  .ztzf-el-tree {
    background-color: transparent !important;
    --el-tree-node-hover-bg-color:transparent !important;
    color: #fff !important;
  }
  .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;
          }
      }
  }
  .ztzf-select-popper.el-select__popper {
      border:#2D2D2D !important;
      .el-select-dropdown {
          overflow-x: hidden !important;
          .el-scrollbar {
              .el-select-dropdown__wrap {
                  background: linear-gradient(180deg, rgba(13, 53, 86, 0.85) 0%, rgba(1, 35, 80, 0.85) 100%);
                  border-radius: 0px 0px 8px 8px;
                  border: 1px solid;
          border-image: linear-gradient(180deg, #fff, #fff) 1 1;
                  overflow-x: hidden !important;
                  margin-right: 0 !important;
                  .el-select-dropdown__list {
                      padding: 0 !important;
                      .el-select-dropdown__item {
                          background: #2D2D2D !important;
                          color: white;
                          &.hover {
                              background-color: #2D2D2D !important;
                              color: white;
                          }
                          &.selected {
                              color: #fff !important;
                              font-weight: 700;
                          }
                      }
                  }
              }
          }
          .el-select-dropdown__empty {
              background: #2D2D2D;
              border-radius: 0px 0px 8px 8px;
              border: 1px solid;
              border-image: linear-gradient(180deg, #fff, #fff) 1 1;
          }
      }
      .el-popper__arrow::before {
          background: #2D2D2D !important;
          border: 1px solid #fff !important;
      }
  }
  //时间日期选择器
  .ztzf-layer-date-picker {
      box-shadow: none !important;
      background: transparent;
      border: 1px solid #fff;
      border-radius: 5px;
      .el-range-editor.is-disabled input {
          background-color: transparent !important;
      }
      .el-range-editor.is-disabled {
          background-color: transparent !important;
      }
      .el-input__wrapper {
          box-shadow: none !important;
          background-color: transparent !important;
      width: 300px;
          &.is-focus {
              box-shadow: none !important;
          }
      }
      .el-range-input {
          color: #fff;
          &::placeholder {
              color: rgba(255, 255, 255, 0.37);
          }
      }
      .el-range-separator {
          color: #ffffff;
      }
  }
  // 日期下拉框
  .ztzf-layer-date-picker-popper {
      background: #012350 !important;
      border: 1px solid !important;
      border-radius: 0px 0px 8px 8px;
      border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
      .el-picker-panel .el-time-panel{
          background: #012350 !important;
      }
      .el-time-spinner__item {
      color: #fff !important;}
      .el-time-spinner__item:hover:not(.is-disabled):not(.is-active){
          background: transparent !important;
      }
      .el-picker-panel {
          .el-picker-panel__body-wrapper {
              .el-picker-panel__body {
                  background: #012350 !important;
                  color: #fff !important;
                  .el-date-picker__time-header {
                      border: none;
                      .el-date-picker__editor-wrap {
                          .el-time-panel {
                              background: #012350 !important;
                              border-radius: 0px 0px 8px 8px;
                              border: 1px solid !important;
                              border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
                              // 将 time-picker-popper 的样式移到这里
                              .el-time-panel__header {
                                  background: #012350 !important;
                                  color: #ffffff !important;
                                  border-bottom: 1px solid rgba(255, 255, 255, 0.37) !important;
                              }
                              .el-time-panel__content {
                                  .el-time-spinner__item {
                                      color: #e6e6e6 !important;
                                      &:hover {
                                          background: #012350 !important;
                                      }
                                      &.active {
                                          color: #479dff !important;
                                          font-weight: bold;
                                      }
                                  }
                              }
                              .el-time-panel__footer {
                                  background: #012350 !important;
                                  border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
                                  .el-button {
                                      color: #fff !important;
                                      &:hover {
                                          color: #479dff !important;
                                      }
                                  }
                                  .el-time-panel__btn.cancel {
                                      color: #fff !important;
                                      &:hover {
                                          color: #fff !important;
                                      }
                                  }
                              }
                          }
                      }
                  }
                  .el-picker-panel__content {
                      .el-date-range-picker__header {
                          .el-picker-panel__icon-btn {
                              color: #fff !important;
                              &:hover {
                                  color: var(--el-color-primary) !important;
                              }
                              .el-icon {
                                  &::before {
                                      color: inherit !important;
                                  }
                              }
                          }
                          & > div {
                              span {
                                  color: #fff !important;
                              }
                          }
                      }
                      .el-date-table {
                          th {
                              color: #fff !important;
                          }
                          td.disabled div {
                              background-color: #00193b !important;
                              color: #505050 !important;
                              cursor: not-allowed;
                          }
                          // td:hover,
                          // td.current:not(.disabled) {
                          //     background: rgba(60, 121, 202) !important;
                          //     color: white !important;
                          // }
                          td.start-date span,
                          td.start-date span,
                          td.end-date span {
                              background-color: #012350 !important;
                          }
                          td.in-range div,
                          td.in-range div:hover,
                          &.is-week-mode .el-date-table__row.current div,
                          &.is-week-mode .el-date-table__row:hover div {
                              background-color: rgba(60, 121, 202) !important;
                          }
                      }
                  }
              }
          }
      }
      .el-popper__arrow {
          &::before {
              background: #0d3556 !important;
              border: 1px solid #0d3556 !important;
              box-sizing: border-box;
          }
      }
      .el-picker-panel__sidebar {
          background: #012350;
          color: #fff;
          .el-picker-panel__shortcut {
              color: #ffffff;
          }
      }
      .el-picker-panel__body {
          .el-input.is-disabled .el-input__wrapper {
              background: #012350;
              box-shadow: 0 0 0 0.1rem#409eff;
              color: #fff;
              .el-input__inner {
                  color: #fff !important;
              }
          }
          .el-button.is-disabled,
          .el-button.is-disabled:hover {
              background: #012350;
          }
          .el-input__wrapper {
              background: #012350;
              box-shadow: 0 0 0 0.1rem#409eff;
              color: #fff;
              .el-input__inner {
                  color: #fff !important;
              }
          }
      }
      .el-picker-panel__footer {
          background: #012350;
          color: #fff;
          border-top: none;
          .el-button--small {
              box-shadow: 0 0 0 0.1rem#409eff;
              background-color: #012350;
              color: #fff;
              border: none;
              &:hover {
                  background-color: #012350;
                  color: #fff;
                  border-color: #409eff;
              }
          }
      }
  }
  .ztzf-layer-time-picker-popper {
    background: rgba(0,0,0,0.8) !important;
      border-radius: 0px 0px 8px 8px;
      border: 1px solid !important;
      border-image: linear-gradient(180deg, rgba(255, 255, 255, 0), rgba(115, 192, 255, 1)) 1 1 !important;
      .el-popper__arrow::before {
          background: rgba(0,0,0,0.8) !important;
          border: 1px solid #479dff !important;
      }
      .el-time-panel__header {
          background: rgba(0,0,0,0.8) !important;
          color: #ffffff !important;
          border-bottom: 1px solid rgba(71, 157, 255, 0.3) !important;
      }
      .el-time-panel__content {
          // background: #012350 !important;
          .el-time-spinner__item {
              color: #e6e6e6 !important;
              &:hover {
                  background: rgba(71, 157, 255, 0.3) !important;
              }
              &.active {
                  color: #479dff !important;
                  font-weight: bold;
              }
          }
      }
      .el-time-panel__footer {
          background: rgba(0,0,0,0.8) !important;
          border-top: 1px solid rgba(71, 157, 255, 0.3) !important;
          .el-button {
              color: #fff !important;
              &:hover {
                  color: #479dff !important;
              }
          }
          .el-time-panel__btn.cancel {
              color: #fff !important;
              &:hover {
                  color: #fff !important;
              }
          }
      }
  }
  //======以下针对表格功能 增删查改样式和弹窗=========
  .ztzf-dialog-mange {
    .el-dialog__body {
      padding: 2rem 2rem 4rem;
      flex: 1;
      overflow: scroll;
      border-top: 0.1rem solid #f0f0f0;
    }
    .el-dialog__footer {
      z-index: 2;
      display: block;
      padding: 1rem 1.6rem;
      box-sizing: border-box;
      border-top: 0.1rem solid #f0f0f0;
      width: 100%;
      position: absolute;
      left: 0;
      bottom: 0;
      background-color: #fff;
      text-align: right;
    }
  }
  .ztzf-search-mange {
    height: 40px;
    .search-contain {
      display: flex;justify-content: space-between;
    }
  }
  .ztzf-form-mange {
    .el-form-item {
      width: 300px;
      .el-form-item__content {
        width: 100%;
      }
      .el-form-item__label {
        width: 120px;
      }
    }
  }
  .ztzf-form-search {
    .el-form-item {
      width: 100%;
      .el-form-item__label {
        width: 80px;
      }
      .el-form-item__content {
        display: flex;
        flex: 1;
      }
    }
  }
  .ztzf-table-mange {
    th.el-table__cell  {
      color: rgba(0, 0, 0, .85);
      background-color: #fafafa;
    }
  }
  .table-view-mange {
    width: 100%;
    border-top: 0.1rem solid #ebeef5;
    border-left: 0.1rem solid #ebeef5;
    margin-left: 0;
    margin-right: 0;
    .el-col {
      padding-left: 0;
      padding-right: 0;
      margin-bottom: 0;
      height: 32px;
      line-height: 32px;
      border-right: 0.1rem solid #ebeef5;
      text-align: center;
      align-items: center;
      .el-input__wrapper {
        box-shadow: none !important;
        border-bottom: 0.1rem solid #ebeef5;
      }
    }
    .label {
      color: #909399;
      box-sizing: border-box;
      background: #fafafa;
    }
  }
src/styles/mapEdit.scss
New file
@@ -0,0 +1,59 @@
.create-wayline-box {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 130px;
    height: 70px;
    .create-wayline-menu {
        background: url('@/assets/images/layerManagement/newcon_box.png') no-repeat center / 100% 100%;
        margin: 15px;
        div {
            color: #fff;
            width: 90px;
            height: 30px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-weight: bold;
            font-size: 12px;
            &:hover {
                background-color: rgba(0, 0, 0, 0.3);
            }
        }
    }
}
.planar-polygon-edit-tooltip {
    position: absolute;
    top: 0px;
    left: 0px;
    .planar-polygon-edit-menu {
        padding: 4px 0px;
        transform: translate(24px, 10px);
        background: url('@/assets/images/layerManagement/newcon_box.png') no-repeat center / 100% 100%;
        border-radius: 4px;
        div {
            color: #fff;
            width: 90px;
            height: 26px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            font-weight: bold;
            font-size: 12px;
            &:hover {
                background-color: rgba(0, 0, 0, 0.3);
            }
        }
    }
}
src/types/device-cmd.js
@@ -146,7 +146,7 @@
    disabled: true,
  },
  {
    label: '4g 增强',
    label: '4G 增强',
    status: '--',
    operateText: '开启',
    cmdKey: DeviceCmd.SdrWorkModeSwitch,
src/utils/cesium/publicCesium.js
@@ -143,8 +143,28 @@
        this.viewer?.cesiumWidget.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK) // 禁用双击
        if (terrain) {
            // 正则:第一位非0,第二位任意数字,后10位都是0
            const re = /^[1-9]\d0{10}$/
            let result = null
            // 1️⃣先判断 areaCode
            if (re.test(store.state.user.userInfo.detail.areaCode)) {
                result = store.state.user.userInfo.detail.areaCode.slice(0, 6)
            } else {
                // 2️⃣如果不满足,从 ancestors 里找
                const matchedItems = store.state.user.userInfo.detail.ancestors
                    .split(',')
                    .filter(item => re.test(item)) // 过滤出符合条件的
                // 取第一个符合条件的前6位(可以改成 last one 看需求)
                if (matchedItems.length > 0) {
                    result = matchedItems[0].slice(0, 6)
                }
            }
            try {
                Cesium.CesiumTerrainProvider.fromUrl(`${import.meta.env.VITE_APP_TERRAIN_URL}`, {
                Cesium.CesiumTerrainProvider.fromUrl(`${import.meta.env.VITE_APP_TERRAIN_URL}${result}`, {
                    requestVertexNormals: true, // 启用地形法线增强立体感
                    requestWaterMask: true, // 启用水体遮罩效果
                }).then(terrainProvider => {
@@ -209,7 +229,7 @@
    async switchContour (open) {
        if (open) {
            await this.boundary?.openContour()
        } else {
            this.boundary?.closeContour()
        }
@@ -752,7 +772,7 @@
            Cesium.Cartesian3.subtract(a, this.viewer?.camera.position, a),
            Cesium.Cartesian3.normalize(r, r),
            Cesium.Cartesian3.normalize(a, a)) : (r = c.direction,
            a = h.direction)
                a = h.direction)
        let d = Cesium.Cartesian3.dot(r, a)
            , p = 0
        return d < 1 && (p = Math.acos(d)),
@@ -781,7 +801,7 @@
                , r = Cesium.Math.acosClamped(-e)
            a > 0 && a > r && (a = r - Cesium.Math.EPSILON4),
                r = Cesium.Math.acosClamped(e),
            a < 0 && -a > r && (a = -r + Cesium.Math.EPSILON4)
                a < 0 && -a > r && (a = -r + Cesium.Math.EPSILON4)
        }
        return a
    }
src/utils/geoParseUtil.js
New file
@@ -0,0 +1,94 @@
// @/utils/geoParseUtil.js
import _ from 'lodash'; // 若需要用到 lodash,需引入(当前解析逻辑暂未用到,可先注释)
/**
 * 单独的 WKT 解析函数(处理 POLYGON 和 LineString 类型)
 * @param {string} wktStr - WKT 格式的地理数据字符串
 * @param {number} altitude - 高度值
 * @returns {Array} 转换后的坐标数组,格式:[{lng, lat, height}, ...]
 */
export const parseWktToPositions = (wktStr, altitude) => {
  try {
    // 步骤1:移除 WKT 开头的类型标识(如 POLYGON(...) → (...))
    const content = wktStr.replace(/^(POLYGON|LineString)\(/i, '').replace(/\)$/, '');
    // 步骤2:处理 POLYGON 的双层括号(如 ((x1 y1, x2 y2)) → x1 y1, x2 y2)
    const coordStr = content.replace(/^\(\s*(.*?)\s*\)$/, '$1').trim();
    if (!coordStr) return [];
    // 步骤3:按逗号分割坐标对(如 "x1 y1, x2 y2" → ["x1 y1", "x2 y2"])
    const coordPairs = coordStr.split(',').map(pair => pair.trim());
    if (coordPairs.length < 2) {
      console.warn('WKT 坐标点数量不足');
      return [];
    }
    // 步骤4:转换为 {lng, lat, height} 格式(WKT 中坐标是 "lng lat" 顺序)
    return coordPairs.map(pair => {
      const [lng, lat] = pair.split(/\s+/).map(Number); // 按空格分割经纬度
      return {
        lng: lng,
        lat: lat,
        height: Number(altitude) || 0,
      };
    });
  } catch (error) {
    console.error('解析 WKT 格式失败:', error);
    return [];
  }
};
/**
 * 统一的地理数据解析函数(兼容 GeoJSON 和 WKT 格式)
 * @param {string} geoDataStr - 地理数据字符串(GeoJSON 或 WKT 格式)
 * @param {number} altitude - 高度值
 * @returns {Array} 转换后的坐标数组,格式:[{lng, lat, height}, ...]
 */
export const parseGeoDataToPositions = (geoDataStr, altitude) => {
  try {
    // 1. 先判断是否为 WKT 格式(以 POLYGON/LineString 开头,不含 { })
    if (
      (geoDataStr.startsWith('POLYGON') || geoDataStr.startsWith('LineString')) &&
      !geoDataStr.includes('{')
    ) {
      return parseWktToPositions(geoDataStr, altitude);
    }
    // 2. 新增:判断是否为纯坐标数组字符串(以 [ 开头,以 ] 结尾)
    else if (geoDataStr.startsWith('[') && geoDataStr.endsWith(']')) {
      const coordinates = JSON.parse(geoDataStr); // 直接解析为数组
      // 验证是否为二维数组([[lng, lat], ...])
      if (Array.isArray(coordinates) && coordinates.every(item => Array.isArray(item) && item.length >= 2)) {
        return coordinates.map(([lng, lat]) => ({
          lng: Number(lng),
          lat: Number(lat),
          height: Number(altitude) || 0,
        }));
      } else {
        console.warn('纯数组格式错误,需为 [[lng, lat], ...] 结构');
        return [];
      }
    }
    // 3. 最后按 GeoJSON 格式解析
    else {
      const geoData = JSON.parse(geoDataStr);
      let coordinates = [];
      if (geoData.type === 'Polygon') {
        coordinates = geoData.coordinates[0] || [];
      } else if (geoData.type === 'LineString') {
        coordinates = geoData.coordinates || [];
      } else {
        console.warn(`不支持的 GeoJSON 类型:${geoData.type}`);
        return [];
      }
      return coordinates.map(([lng, lat]) => ({
        lng: Number(lng),
        lat: Number(lat),
        height: Number(altitude) || 0,
      }));
    }
  } catch (error) {
    console.error('解析 geo_data 失败:', error);
    return [];
  }
};
src/views/algorithmMange/algorithmMange.vue
New file
@@ -0,0 +1,304 @@
<template>
  <div class="algorithmMange">
    <div class="ztzf-form-search">
      <el-form :model="params" inline>
        <el-row :gutter="24">
          <el-col :span="4">
          <el-form-item label="算法名称:">
            <el-input v-model="params.algName" placeholder="请输入算法名称" clearable />
          </el-form-item>
          </el-col>
          <el-col :span="4">
          <el-form-item label="算法类型:">
            <el-select class="ztzf-select" :teleported="false" v-model="params.algTypeCode" placeholder="请选择算法类型" clearable>
              <el-option v-for="item in sfTypes" :key="item.id" :label="item.dictValue" :value="item.dictKey" ></el-option>
            </el-select>
          </el-form-item>
          </el-col>
          <el-col :span="4">
            <el-button type="primary" @click="getList" :icon="Search">搜索</el-button>
            <el-button @click="cancelSearch" :icon="Delete">清空</el-button>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <div class="mange-table">
            <el-table border :data="tableList" class="ztzf-table-mange">
                <el-table-column label="序号" type="index" width="60"></el-table-column>
        <el-table-column prop="model_name" label="算法名称" align="center" show-overflow-tooltip></el-table-column>
        <el-table-column prop="alg_type" label="算法类型" align="center"></el-table-column>
                <el-table-column prop="qua_rate" label="最低准确率" align="center">
          <template #header>
            <el-tooltip placement="top" effect="dark" content="AI识别率高于最低准确率时会生成待审核状态的事件">
              <el-icon><QuestionFilled /></el-icon>
            </el-tooltip>
            <span>最低准确率</span>
          </template>
        </el-table-column>
                <el-table-column prop="pass_rate" label="最高准确率" align="center">
          <template #header>
            <el-tooltip placement="top" effect="dark" content="AI识别率高于最高准确率时会生成待处理状态的事件">
              <el-icon><QuestionFilled /></el-icon>
            </el-tooltip>
            <span>最高准确率</span>
          </template>
        </el-table-column>
        <el-table-column prop="event_type" label="事件生成类型" show-overflow-tooltip align="center">
          <template #default="scope">
            {{ scope.row.event_type == 0 ? '常规类' : '紧急类' }}
          </template>
        </el-table-column>
                <el-table-column prop="remark" label="算法描述" show-overflow-tooltip />
                <el-table-column label="操作" width="180" align="center">
                    <template #default="scope">
            <el-button icon="el-icon-view" type="text" @click="handleDetail(scope.row)">查看</el-button>
                        <el-button icon="el-icon-edit" type="text" @click="handleEdit(scope.row)">编辑</el-button>
                    </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]" :size="params.size" v-model:current-page="params.current"
                v-model:page-size="params.size" layout="total, sizes, prev, pager, next, jumper" :total="total"
                @size-change="handleSizeChange" @current-change="handleCurrentChange" />
        </div>
  </div>
  <commonDialog :isShowView="isShowViewTest" width="1000" title="查看" addEditView="view" @cancel="isShowViewTest = false">
    <template #default>
      <el-row :gutter="0" class="table-view-mange">
        <el-col :span="6" class="label">算法名称</el-col>
        <el-col :span="6">{{ rowView.model_name }}</el-col>
        <el-col :span="6" class="label">算法类型</el-col>
        <el-col :span="6">{{ rowView.alg_type }}</el-col>
      </el-row>
      <el-row :gutter="0" class="table-view-mange">
        <el-col :span="6" class="label">最低准确率</el-col>
        <el-col :span="6">{{ rowView.qua_rate }}</el-col>
        <el-col :span="6" class="label">最高准确率</el-col>
        <el-col :span="6">{{ rowView.pass_rate }}</el-col>
      </el-row>
      <el-row :gutter="0" class="table-view-mange">
        <el-col :span="6" class="label">事件生成类型</el-col>
        <el-col :span="6">{{ rowView.event_type == 0 ? '常规类' : '紧急类' }}</el-col>
        <el-col :span="6" class="label"></el-col>
        <el-col :span="6"></el-col>
      </el-row>
      <el-row :gutter="0" class="table-view-mange">
        <el-col :span="6" class="label">算法描述</el-col>
        <el-col :span="18">
          <el-input v-model="rowView.remark"></el-input>
        </el-col>
      </el-row>
    </template>
  </commonDialog>
  <commonDialog :isShowView="isShowEditView" title="编辑"  addEditView="edit"  @submit="submit(ruleFormRef)" @cancel="isShowEditView = false">
    <template #default>
      <el-form class="ztzf-form-mange" ref="ruleFormRef" :model="editParams" :rules="rules" inline>
        <el-form-item label="算法名称">
          <el-input v-model="editParams.model_name" disabled />
        </el-form-item>
        <el-form-item label="算法类型">
          <el-select class="ztzf-select" :teleported="false" v-model="editParams.alg_type" disabled>
            <el-option v-for="item in sfTypes" :key="item.id" :label="item.dictValue" :value="item.dictKey" ></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="最低准确率" prop="qua_rate">
          <el-input v-model="editParams.qua_rate" placeholder="请输入最低准确率" required clearable />
        </el-form-item>
        <el-form-item label="最高准确率" prop="pass_rate">
          <el-input v-model="editParams.pass_rate" placeholder="请输入最高准确率" clearable />
        </el-form-item>
        <el-form-item label="事件生成类型" prop="event_type">
          <el-select class="ztzf-select" v-model="editParams.event_type" placeholder="请选择" clearable>
            <el-option label="常规类" value="0"></el-option>
            <el-option label="紧急类" value="1"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="算法描述">
          <el-input v-model="editParams.remark" placeholder="请输入算法描述" clearable />
        </el-form-item>
      </el-form>
    </template>
  </commonDialog>
</template>
<script setup>
import { getAlgorithmManageList, algorithmManageEdit } from '@/api/algorithmMange/algorithmMange';
import { getDictionaryByCode } from '@/api/system/dictbiz';
import commonDialog from '@/components/commonDialog.vue'
import { ElMessage } from 'element-plus';
import { Delete, Search } from '@element-plus/icons-vue'
const total = ref(0)
const params = ref({
  current: 1,
  size: 10,
  algName: '',
  algTypeCode: '',
});
let tableList = ref([])
let isShowViewTest = ref(false)
let rowView = ref({})
let isShowEditView = ref(false)
let sfTypes = ref([])
const ruleFormRef = ref()
let editParams = ref({
  model_name: '',
  alg_type: '',
  qua_rate: '',
  pass_rate: '',
  event_type: '',
  remark: ''
})
const rules = reactive({
  qua_rate: [
    { required: true, message: '请输入最低准确率', trigger: 'blur' },
    {
      validator: (rule, value, callback) => {
        if (!/^\d+(\.\d+)?$/.test(value)) {
          callback(new Error('请输入有效的数字(整数或小数)'));
        } else if (parseFloat(value) < 0.1) {
          callback(new Error('最低准确率不能小于0.1'));
        } else if (parseFloat(value) >= parseFloat(editParams.value.pass_rate)) {
          callback(new Error('最低准确率必须小于最高准确率'));
        } else {
          callback();
        }
      },
      trigger: 'blur'
    }
  ],
  pass_rate: [
    { required: true, message: '请输入最低准确率', trigger: 'blur' },
    {
      validator: (rule, value, callback) => {
        if (!/^\d+(\.\d+)?$/.test(value)) {
          callback(new Error('请输入有效的数字(整数或小数)'));
        } else if (parseFloat(value) > 1) {
          callback(new Error('最高准确率不能大于1'));
        } else {
          callback();
        }
      },
      trigger: 'blur'
    }
  ],
  event_type: [
    {
      required: true,
      message: '请选择事件生成类型',
      trigger: 'change',
    },
  ],
})
function cancelSearch() {
  params.value = {
    algName: '',
    algTypeCode: '',
  }
  params.value.current = 1
  getList()
}
function getList() {
  getAlgorithmManageList(params.value).then(res => {
    tableList.value = res.data.data.records || []
    total.value = res.data.data.total || 0
  })
}
function getSFType() {
  getDictionaryByCode('WORK_ORDER_TYPE').then(res => {
    sfTypes.value = res.data.data.WORK_ORDER_TYPE || []
  });
}
function handleDetail(row) {
  isShowViewTest.value = true
  rowView.value = row
}
function handleEdit(row) {
  isShowEditView.value = true
  editParams.value = { ...row }
}
function handleSizeChange(val) {
  params.value.size = val
  getList()
}
function handleCurrentChange(val) {
  params.value.current = val
  getList()
}
async function submit(formValidate) {
  if (!formValidate) return
  await formValidate.validate((valid, fields) => {
    if (valid) {
      algorithmManageEdit(editParams.value).then(res => {
        ElMessage.success('保存成功')
        isShowEditView.value = false
        getList()
      })
    }
  })
}
onMounted(() => {
  getList()
  getSFType()
})
</script>
<style lang="scss" scoped>
  .algorithmMange {
    height: 0;
    flex: 1;
    margin: 0 10px 10px 10px;
    background-color: #ffffff;
    padding: 10px 20px;
    border-radius: 5px;
    display: flex;
    flex-direction: column;
    .search-box {
      height: 40px;
      .btns {
        display: flex;
        justify-content: end;
      }
    }
    :deep(.el-input) {
      .el-input__wrapper {
        width: 200px;
      }
    }
    // 表格
    .mange-table {
      height: 0;
      flex: 1;
      //margin-top: 18px;
      overflow: auto;
    }
    :deep(.el-pagination) {
      display: flex;
      justify-content: right;
    }
    :deep(.el-pagination button) {
      background: center center no-repeat none !important;
      color: #8eb8ea !important;
    }
    :deep(.ztzf-select){
      .el-select__selection {
        width: 200px;
      }
    }
  }
</style>
src/views/algorithmRepository/algorithmRepository.vue
@@ -2,19 +2,12 @@
  <basic-container>
    <div class="algorithContainer">
      <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)"
        >
        <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="" />
          <div class="item-title">{{ item.dictValue }}</div>
          <div
            :class="!statusSign && item.dictValue === nameSign ? 'stopStatus' : 'normalStatus'"
            @click.stop="changeStatus(item)"
          >
          <div :class="!statusSign && item.dictValue === nameSign ? 'stopStatus' : 'normalStatus'"
            @click.stop="changeStatus(item)">
            {{ !statusSign && item.dictValue === nameSign ? '停用状态' : '正常状态' }}
          </div>
        </div>
@@ -25,103 +18,70 @@
          <div class="searchBox">
            <div class="item">
              <div class="itemchild">模糊查询:</div>
              <el-input
                v-model="params.name"
                class="filter-item"
                placeholder="请输入事件名称"
                clearable
              ></el-input>
              <el-input v-model="params.name" class="filter-item" placeholder="请输入事件名称" clearable></el-input>
            </div>
            <div class="item">
              <el-date-picker
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                value-format="YYYY-MM-DD"
                v-model="taskData"
                placeholder="请选择日期"
                @change="changeselect"
                  clearable
              />
                <!-- @clear="handleDateClear" -->
              <el-date-picker type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"
                value-format="YYYY-MM-DD" v-model="taskData" placeholder="请选择日期" @change="changeselect" clearable />
              <!-- @clear="handleDateClear" -->
            </div>
            <div class="item">
              <div class="itemchild">机巢查询:</div>
              <el-select v-model="params.device_name" placeholder="请选择" class="filter-item" @change="handleSearch">
              <el-select v-model="params.device_name" placeholder="请选择" class="filter-item" @change="handleSearch"  clearable>
                <el-option v-for="item in jcoptions" :key="item" :label="item" :value="item" />
              </el-select>
            </div>
          </div>
          <div class="search-btn">
            <el-button type="primary" icon="el-icon-back" @click="goback">返回</el-button>
            <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
            <el-button icon="el-icon-delete" @click="handleReset">清空</el-button>
            <el-button type="primary" icon="el-icon-back" @click="goback">返回</el-button>
          </div>
        </div>
       <div  v-if="detailData.length > 0" class="pictureBox">
         <div
          class="pictureitem"
          v-loading="loading"
          element-loading-text="加载中"
        >
          <div class="imgitem" v-for="(item, index) in detailData" :key="index">
            <el-image
              ref="imageRef"
              :src="getSmallImg(item.url)"
              show-progress
              :preview-src-list="[getShowImg(item.url)]"
              fit="cover"
            />
        <div v-if="detailData.length > 0" class="pictureBox">
          <div class="pictureitem" v-loading="loading" element-loading-text="加载中">
            <div class="imgitem" v-for="(item, index) in detailData" :key="index">
              <el-image ref="imageRef" :src="getSmallImg(item.url)" show-progress
                :preview-src-list="[getShowImg(item.url)]" fit="cover" />
            <div class="info">
              <div class="name">{{ item.name }}</div>
              <div class="time">{{ item.create_time.slice(5, 16).replace('-', '/', 1) }}</div>
              <div class="info">
                <div class="name">{{ item.name }}</div>
                <div class="time">{{ item.create_time.slice(5, 16).replace('-', '/', 1) }}</div>
              </div>
            </div>
          </div>
        </div>
       </div>
        <el-empty class="custom-empty" v-else>
          <template #description>
            <span class="custom-text">暂无数据</span>
          </template>
        </el-empty>
        <!-- 分页 -->
        <el-pagination
          class="pageStyle"
          background
          :page-sizes="[15, 20, 30, 50]"
          v-model:current-page="params.current"
          v-model:page-size="params.size"
          layout="total, prev, pager, next,sizes, jumper"
          :total="total"
          @size-change="handleSizeChange"
          @current-change="handleCurrentChange"
        />
        <el-pagination class="pageStyle" background :page-sizes="[15, 20, 30, 50]" v-model:current-page="params.current"
          v-model:page-size="params.size" layout="total, prev, pager, next,sizes, jumper" :total="total"
          @size-change="handleSizeChange" @current-change="handleCurrentChange" />
      </div>
    </div>
  </basic-container>
</template>
<script setup>
import { useStore } from 'vuex';
import { getDictionaryByCode } from '@/api/system/dictbiz';
import { getalgorithmList, selectDeviceList } from '@/api/algorithm';
import { getSmallImg,getShowImg } from '@/utils/util';
import { useRouter } from 'vue-router';
const store = useStore();
const router = useRouter();
const baseUrl = import.meta.env.VITE_APP_PICTURE_URL;
const showDetail = ref(false);
const taskData = ref('');
const jcvalue = ref('');
const jcoptions = ref([]);
const total = ref(0);
const loading = ref(true);
const userAreaCode = computed(() => store.getters.userInfo.detail.areaCode);
import { useStore } from 'vuex'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getalgorithmList, selectDeviceList } from '@/api/algorithm'
import { getSmallImg, getShowImg } from '@/utils/util'
import { useRouter } from 'vue-router'
const store = useStore()
const router = useRouter()
const baseUrl = import.meta.env.VITE_APP_PICTURE_URL
const showDetail = ref(false)
const taskData = ref('')
const jcvalue = ref('')
const jcoptions = ref([])
const total = ref(0)
const loading = ref(true)
const userAreaCode = computed(() => store.getters.userInfo.detail.areaCode)
const params = ref({
  ai_type_key: '',
  start_date: null,
@@ -130,128 +90,132 @@
  name: '',
  current: 1,
  size: 15,
});
})
// 请求字典字段
let AlgorithmData = ref([]);
const detailData = ref([]);
let AlgorithmData = ref([])
const detailData = ref([])
const requestDictionary = () => {
  getDictionaryByCode('SF').then(res => {
    if (res.code !== 0) {
      // 处理数据
      AlgorithmData.value = res.data.data['SF'];
      AlgorithmData.value = res.data.data['SF']
    }
  });
};
const activeItem = ref(null);
  })
}
const activeItem = ref(null)
const jumpDatail = val => {
  showDetail.value = true;
  activeItem.value = val.dictValue;
  params.value.ai_type_key = val.dictKey;
  getList();
};
const statusSign = ref(true);
const nameSign = ref('');
  showDetail.value = true
  activeItem.value = val.dictValue
  params.value.ai_type_key = val.dictKey
  getList()
}
const statusSign = ref(true)
const nameSign = ref('')
const changeStatus = val => {
  nameSign.value = val.dictValue;
  statusSign.value = !statusSign.value;
};
  nameSign.value = val.dictValue
  statusSign.value = !statusSign.value
}
// 详情
const getList = () => {
const detailParams={
   ai_type_key: params.value.ai_type_key,
  start_date:params.value.start_date,
  end_date: params.value.end_date,
  device_name: params.value.device_name,
  name:params.value.name,
}
  getalgorithmList(detailParams,{  current: params.value.current,
    size: params.value.size,}).then(res => {
    loading.value = true;
    detailData.value = res.data.data.records;
    total.value = res.data.data.total;
  const detailParams = {
    ai_type_key: params.value.ai_type_key,
    start_date: params.value.start_date,
    end_date: params.value.end_date,
    device_name: params.value.device_name,
    name: params.value.name,
  }
  getalgorithmList(detailParams, {
    current: params.value.current,
    size: params.value.size,
  }).then(res => {
    loading.value = true
    detailData.value = res.data.data.records
    total.value = res.data.data.total
    setTimeout(() => {
      loading.value = false;
    }, 1000);
  });
};
      loading.value = false
    }, 1000)
  })
}
// 机巢查询
const getDeviceList = () => {
console.log('userAreaCode',userAreaCode.value);
// { areaCode: userAreaCode.value }
  console.log('userAreaCode', userAreaCode.value)
  // { areaCode: userAreaCode.value }
  selectDeviceList().then(res => {
    jcoptions.value = res.data.data;
    console.log('机巢数',jcoptions.value );
  });
};
    jcoptions.value = res.data.data
  })
}
// 日期选择
const changeselect = () => {
  params.value.start_date = taskData.value?.length ? `${taskData.value[0]} 00:00:00` : null;
  params.value.end_date = taskData.value?.length ? `${taskData.value[1]} 23:59:59` : null;
  params.value.start_date = taskData.value?.length ? `${taskData.value[0]} 00:00:00` : null
  params.value.end_date = taskData.value?.length ? `${taskData.value[1]} 23:59:59` : null
  handleSearch()
};
}
// 处理日期清空
const handleDateClear = () => {
  taskData.value = ''; // 清空绑定的日期数据
  params.value.start_date = null; // 重置开始日期参数
  params.value.end_date = null; // 重置结束日期参数
  getList();
};
const handleSearch = () => {
// console.log('taskData',taskData.value);
if(taskData.value === null){
    params.value.start_date = null; // 重置开始日期参数
  params.value.end_date = null; // 重置结束日期参数
  taskData.value = '' // 清空绑定的日期数据
  params.value.start_date = null // 重置开始日期参数
  params.value.end_date = null // 重置结束日期参数
  getList()
}
const handleSearch = () => {
  getList();
};
  if (taskData.value === null) {
    params.value.start_date = null // 重置开始日期参数
    params.value.end_date = null // 重置结束日期参数
  }
  getList()
}
const handleReset = () => {
taskData.value =''
  params.value.start_date = null;
  params.value.end_date = null;
  params.value.device_name = '';
  params.value.name = '';
  getList();
};
  taskData.value = ''
  params.value.start_date = null
  params.value.end_date = null
  params.value.device_name = ''
  params.value.name = ''
  getList()
}
const goback = () => {
  showDetail.value = false;
  activeItem.value = null;
  params.value.current =1
  params.value.size=15
  params.value.name=''
};
  showDetail.value = false
  activeItem.value = null
  params.value.current = 1
  params.value.size = 15
  params.value.name = ''
}
// 分页大小改变
const handleSizeChange = val => {
  params.size = val;
  getList();
};
  params.size = val
  getList()
}
// 页码改变
const handleCurrentChange = val => {
  params.current = val;
  getList();
};
  params.current = val
  getList()
}
onMounted(() => {
  requestDictionary();
  getDeviceList();
});
  requestDictionary()
  getDeviceList()
})
</script>
<style scoped lang="scss">
.algorithContainer {
height: 100%;
overflow: auto;
  height: calc(100vh - 130px);
}
.algorithItem {
  // padding: 20px;
  height: 100%;
  overflow-y: auto;
  overflow-x: hidden;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  // gap: 13px;
  text-align: center;
  vertical-align: middle;
  .item {
    height: 267px;
    display: flex;
@@ -260,21 +224,25 @@
    align-items: center;
    background: url('/src/assets/images/ht-sfbg.png') no-repeat center;
    background-size: 100% 100%;
    .item-title {
      font-weight: bold;
      font-size: 18px;
      color: #656565;
    }
    .imgicon {
      width: 100px;
      height: 100px;
      margin-bottom: 17px;
    }
    &:hover {
      background: url('/src/assets/images/ht-sfbg-hover.png') no-repeat center;
      background-size: 100% 100%;
      cursor: pointer;
    }
    &.active-bg {
      background: url('/src/assets/images/ht-sfbg-click.png') no-repeat center;
      background-size: 100% 100%;
@@ -293,6 +261,7 @@
      margin-top: 23px;
      cursor: pointer;
    }
    .stopStatus {
      width: 116px;
      height: 43px;
@@ -306,19 +275,22 @@
      margin-top: 23px;
      cursor: pointer;
    }
    .normalStatus:hover {
      background: rgba(6, 217, 87, 0.2);
    }
  }
}
.algorithItemDetail {
  padding: 20px;
    .pictureBox {
  .pictureBox {
    // height: 625px;
       height: pxToVh(760);
    height: pxToVh(760);
    overflow: auto;
    }
  }
  .pictureitem {
    display: grid;
@@ -328,6 +300,7 @@
    .imgitem {
      border-radius: 12px 12px 0 0;
      overflow: hidden;
      .el-image {
        width: 100%;
        height: 170px;
@@ -335,6 +308,7 @@
        margin: 0;
        padding: 0;
      }
      .info {
        display: flex;
        justify-content: space-between;
@@ -352,6 +326,7 @@
          font-size: 16px;
          color: #363636;
        }
        .time {
          margin-right: 12px;
          font-weight: 500;
@@ -361,14 +336,17 @@
      }
    }
  }
  .search {
    display: flex;
    justify-content: space-between;
    //justify-content: space-between;
  }
  .searchBox {
    display: flex;
    align-items: center;
    margin-bottom: 27px;
    .itemchild {
      white-space: nowrap;
      margin-right: 5px;
@@ -376,19 +354,23 @@
      font-size: 14px;
      color: #363636;
    }
    .item {
      display: flex;
      align-items: center;
      margin-right: 49px;
      margin-right: 40px;
    }
  }
  .search-btn {
    display: flex;
  }
  .filter-item {
    width: 218px;
  }
}
.pageStyle {
  margin-top: 32px;
  display: flex;
src/views/authority/apiscope.vue
@@ -77,7 +77,9 @@
        tip: false,
        simplePage: true,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        dialogWidth: '60%',
        tree: true,
        border: true,
@@ -99,6 +101,7 @@
            prop: 'name',
            width: 300,
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -154,6 +157,7 @@
            label: '菜单编号',
            prop: 'code',
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -286,6 +290,7 @@
            label: '权限名称',
            prop: 'scopeName',
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -298,6 +303,7 @@
            label: '权限编号',
            prop: 'resourceCode',
            search: true,
            searchSpan: 4,
            width: 180,
            rules: [
              {
src/views/authority/datascope.vue
@@ -80,7 +80,9 @@
        tip: false,
        simplePage: true,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        dialogWidth: '60%',
        tree: true,
        border: true,
@@ -102,6 +104,7 @@
            prop: 'name',
            width: 300,
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -157,6 +160,7 @@
            label: '菜单编号',
            prop: 'code',
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -289,6 +293,7 @@
            label: '权限名称',
            prop: 'scopeName',
            search: true,
            searchSpan: 4,
            value: '',
            rules: [
              {
@@ -302,6 +307,7 @@
            label: '权限编号',
            prop: 'resourceCode',
            search: true,
            searchSpan: 4,
            width: 100,
            rules: [
              {
src/views/authority/role.vue
@@ -14,9 +14,14 @@
    </avue-crud>
    <el-dialog title="角色权限配置" append-to-body v-model="box" width="345px">
      <el-tabs type="border-card">
        <el-tab-pane label="菜单权限">
        <el-tab-pane label="后台管理">
          <el-tree :data="menuGrantList" show-checkbox node-key="id" ref="treeMenu" :default-checked-keys="menuTreeObj"
            :props="props">
          </el-tree>
        </el-tab-pane>
        <el-tab-pane label="移动app">
          <el-tree :data="menuGrantListApp" show-checkbox node-key="id" ref="treeMenuApp" :default-checked-keys="menuTreeObjApp"
                   :props="props">
          </el-tree>
        </el-tab-pane>
        <!-- <el-tab-pane label="数据权限">
@@ -76,10 +81,12 @@
        value: 'key',
      },
      menuGrantList: [],
      menuGrantListApp: [],
      dataScopeGrantList: [],
      apiScopeGrantList: [],
      apiGrantList: [],
      menuTreeObj: [],
      menuTreeObjApp: [],
      dataScopeTreeObj: [],
      apiScopeTreeObj: [],
      selectionList: [],
@@ -94,7 +101,9 @@
        tip: false,
        simplePage: true,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        tree: true,
        border: true,
        index: true,
@@ -111,6 +120,7 @@
            label: '角色名称',
            prop: 'roleName',
            search: true,
            searchSpan: 4,
            span: 24,
            rules: [
              {
@@ -147,6 +157,7 @@
            label: '角色别名',
            prop: 'roleAlias',
            search: true,
            searchSpan: 4,
            span: 24,
            rules: [
              {
@@ -226,11 +237,12 @@
    },
    submit () {
      const menuList = this.$refs.treeMenu.getCheckedKeys()
      const menuListApp = this.$refs.treeMenuApp.getCheckedKeys()
      // const dataScopeList = this.$refs.treeDataScope.getCheckedKeys();
      const dataScopeList = []
      // const apiScopeList = this.$refs.treeApiScope.getCheckedKeys();
      const apiScopeList = []
      grant(this.idsArray, menuList, dataScopeList, apiScopeList).then(() => {
      grant(this.idsArray, [...menuList,...menuListApp], dataScopeList, apiScopeList).then(() => {
        this.box = false
        this.$message({
          type: 'success',
@@ -320,7 +332,7 @@
      this.menuTreeObj = []
      this.dataScopeTreeObj = []
      this.apiScopeTreeObj = []
      grantTree().then(res => {
      grantTree({sysType: 1}).then(res => {
        this.menuGrantList = res.data.data.menu
        this.dataScopeGrantList = res.data.data.dataScope
        this.apiScopeGrantList = res.data.data.apiScope
@@ -328,6 +340,13 @@
          this.menuTreeObj = res.data.data.menu
          this.dataScopeTreeObj = res.data.data.dataScope
          this.apiScopeTreeObj = res.data.data.apiScope
          this.box = true
        })
      })
      grantTree({sysType: 3}).then(res => {
        this.menuGrantListApp = res.data.data.menu
        getRole(this.ids).then(res => {
          this.menuTreeObjApp = res.data.data.menu
          this.box = true
        })
      })
@@ -374,4 +393,4 @@
  },
}
</script>
<style scoped lang="scss"></style>
<style scoped lang="scss"></style>
src/views/dataCenter/components/dataCenterMap.vue
@@ -1,5 +1,5 @@
<template>
  <el-dialog modal-class="mapDialog"  v-model="isShow" width="80%">
  <el-dialog modal-class="mapDialog"  v-model="isShow" width="80%" class="ztzf-dialog-mange">
     <template #header="{ titleId, titleClass }">
          <div class="my-header">
            <h4 :id="titleId" :class="titleClass">定位</h4>
@@ -312,6 +312,7 @@
<style scoped lang="scss">
.mapBox {
  margin-top: 10px;
  z-index: 2;
  height: 650px;
  width: 98%;
src/views/dataCenter/components/searchData.vue
@@ -1,9 +1,10 @@
<template>
  <div class="search-box-test">
    <el-form :model="searchForm" inline>
      <div class="search-first">
      <el-row :gutter="24">
        <el-col :span="4">
        <el-form-item label="行政区划:" >
          <el-tree-select
          <el-tree-select
          :disabled="viewDetailsDisabled"
            popper-class="custom-tree-select"
            v-model="searchForm.areaCode"
@@ -15,6 +16,8 @@
            @node-click="handleNodeClick"
          />
        </el-form-item>
        </el-col>
          <el-col :span="4">
        <el-form-item label="所属机巢:">
          <el-select
          :disabled="viewDetailsDisabled"
@@ -32,10 +35,14 @@
            />
          </el-select>
        </el-form-item>
          </el-col>
        <el-col :span="4">
        <el-form-item label="任务名称:">
          <el-input  :disabled="viewDetailsDisabled" v-model="searchForm.jobName" placeholder="请输入" clearable />
        </el-form-item>
        <el-form-item>
        </el-col>
        <el-col :span="8">
        <el-form-item label="选择日期:">
          <el-date-picker
           :disabled="viewDetailsDisabled"
            popper-class="custom-date-picker"
@@ -60,6 +67,8 @@
            }"
          />
        </el-form-item>
        </el-col>
        <el-col :span="4">
        <el-form-item label="文件格式:">
          <el-select
          :disabled="foldersDisabled || viewDetailsDisabled"
@@ -78,9 +87,11 @@
            />
          </el-select>
        </el-form-item>
        </el-col>
        <el-col :span="4">
        <el-form-item label="文件类别:">
          <el-select
            :disabled="disabled || foldersDisabled || viewDetailsDisabled"
            :teleported="false"
            v-model="searchForm.photoType"
@@ -97,16 +108,17 @@
            />
          </el-select>
        </el-form-item>
      </div>
      <div class="search-first">
        </el-col>
        <el-col :span="4">
        <el-form-item label="文件名称:">
          <el-input :disabled="foldersDisabled || viewDetailsDisabled" v-model="searchForm.name" placeholder="请输入" clearable />
        </el-form-item>
        <div class="search-btn">
        </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>
        </div>
      </div>
        </el-col>
      </el-row>
      <div class="search-first">
        <div class="search-btn">
<!--          <el-button type="primary" icon="el-icon-download" @click="allDownloadFun"-->
@@ -283,7 +295,7 @@
  machineData.value = '';
  const droneList = await getDeviceRegion({ areaCode: data.id });
  machineData.value = droneList?.data?.data;
  // 默认选中值
  if (signDevice_sn.value) {
    searchForm.deviceSn = signDevice_sn.value;
@@ -292,7 +304,7 @@
  deptData.value = [];
  getDeptsByAreaCode();
  handleSearch();
};
// 所属部门信息
const getDeptsByAreaCode = () => {
@@ -347,21 +359,23 @@
function cancelDownload() {
  cancelDownloadApi({ type:'htsjzx' }).then(res =>{
    ElMessage.success('取消成功')
  }).catch(e =>{
    EventBus.emit('useGlobalWS-messageHandler', {biz_code: 'DOWNLOAD_PROGRESS',data:{status: 'CANCELLED',type:'htsjzx'}})
  })
}
// 切换文件夹
const handleswitchFolders =()=>{
  emit('handleswitchFolders');
  emit('handleswitchFolders');
}
// 返回按钮
const handleBack = ()=>{
  emit('handleBack');
  emit('handleBack');
}
onMounted(() => {
  requestDockInfo();
  getDownloadStatusApi({type: 'htsjzx'}).then(res =>{
    if (!['CANCELLED','COMPLETED'].includes(res.data.data?.status || 'COMPLETED')){
      EventBus.emit('useDownloadWs-messageHandler',res.data.data)
      EventBus.emit('useGlobalWS-messageHandler',res.data.data)
    }
  })
});
@@ -467,12 +481,17 @@
    }
    .el-form-item {
      margin-bottom: 0;
      width: 233px;
      //margin-bottom: 0;
      width: 100%;
      .el-form-item__label {
        color: #363636;
        line-height: 32px;
        width: 80px;
      }
      .el-form-item__content {
        display: flex;
        flex: 1;
      }
    }
  }
src/views/dataCenter/dataCenter.vue
@@ -18,7 +18,7 @@
      <!-- 文件夹 -->
      <div v-if="switchFolders" class="dataTable">
            <el-table
            border
         class="custom-header custom-body"
          v-loading="loadings"
          element-loading-text=""
@@ -35,7 +35,7 @@
              }}
            </template>
          </el-table-column>
          <el-table-column property="recordName" label="任务名称" show-overflow-tooltip />
          <el-table-column property="name" label="任务名称" show-overflow-tooltip />
           <el-table-column property="regionName" label="所属区域">
             <template #default="scope">
              <span>{{scope.row.city_name ? scope.row.city_name :'--'}},{{scope.row.area_name ? scope.row.area_name :'--'}}</span>
@@ -184,6 +184,7 @@
      <!-- 查看弹框 -->
      <el-dialog
        v-model="dialogVisible"
        class="ztzf-dialog-mange"
        width="60%"
        append-to-body
        @close="dialogClose"
@@ -292,6 +293,7 @@
      <!-- 视频预览 -->
      <el-dialog
        :title="currentVideoTitle"
        class="ztzf-dialog-mange"
        modal-class="videoDialog"
        append-to-body
        width="54%"
@@ -498,6 +500,7 @@
        .then(res => {
    folderList.value = res.data.data.records.map(i => ({ ...i, checked: false }))
    total.value = res.data.data.total
        })
        .finally(() => {
          loadings.value = false;
src/views/device/addDevice.vue
@@ -72,10 +72,10 @@
                dialogWidth: 1050,
                searchGutter: 30,
                searchMenuPosition: 'left',
                searchMenuSpan: 4,
                tip: false,
                searchShow: true,
                searchMenuSpan: 6,
                searchGutter: 30,
                border: true,
                index: true,
                indexLabel: '序号',
@@ -98,8 +98,7 @@
                        editDisplay: false,
                        labelWidth: 130,
                        search: true,
                        // searchSpan: 4,
                        labelWidth: 130,
                        searchSpan: 4,
                        rules: [
                            {
                                required: true,
@@ -126,7 +125,7 @@
                        ],
                    },
                    {
                        label: '所属单位',
                        label: '所属部门',
                        prop: 'dept_name',
                        addDisplay: false,
                        editDisplay: false,
@@ -136,13 +135,13 @@
                        rules: [
                        {
                            required: true,
                            message: '请输入所属单位',
                            message: '请输入所属部门',
                            trigger: 'blur',
                        },
                        ],
                    },
                    {
                        label: '所属单位',
                        label: '所属部门',
                        prop: 'dept_id',
                        hide: true,
                        type: 'tree',
@@ -158,7 +157,7 @@
                        rules: [
                        {
                            required: true,
                            message: '请输入所属单位',
                            message: '请输入所属部门',
                            trigger: 'blur',
                        },
                        ],
@@ -214,66 +213,44 @@
                        width: 200,
                    },
                    {
                        label: '设备位置',
                        prop: 'area_code',
                        type: 'cascader',
                        labelWidth: 130,
                        searchSpan: 4,
                        hide: true,
                        search: true,
                        // overHidden: true,
                        editDisplay: true,
                        viewDisplay: true,
                        checkStrictly: true,
                        props: {
                        label: 'title',
                        value: 'value',
                        emitPath: false,
                        multiple: false,
                        expandTrigger: 'hover',
                        },
                        dataType: 'string',
                        rules: [
                      label: '设备位置',
                      prop: 'area_code',
                      // type: 'cascader',
                      type: 'tree',
                      labelWidth: 130,
                      searchSpan: 4,
                      hide: true,
                      search: true,
                      // overHidden: true,
                      editDisplay: true,
                      viewDisplay: true,
                      checkStrictly: true,
                      defaultExpandAll: true,
                      dicData: [],
                      props: {
                        label: 'name',
                        value: 'id',
                        children: 'childrens',
                      },
                      rules: [
                        {
                            required: true,
                            message: '请选择设备位置',
                            trigger: 'change',
                          required: true,
                          message: '请选择设备位置',
                          trigger: 'change',
                        },
                        ],
                        lazy: true,
                        lazyLoad (node, resolve) {
                        let level = node.level
                        let list = []
                        let callback = () => {
                            resolve(
                            (list || []).map(ele => ({
                                ...ele,
                                value: ele.value,
                                leaf: level >= 2,
                            }))
                            )
                        }
                      ],
                    },
                    {
                        label: '创建时间',
                        prop: 'create_time',
                        addDisplay: false,
                        editDisplay: false,
                        overHidden: true,
                        type: 'date',
                        labelWidth: 130,
                        width: 160,
                        format: 'YYYY-MM-DD HH:mm:ss',
                        if (level === 0) {
                            getLazyTree('000000000000').then(res => {
                            list = res.data.data
                            callback()
                            })
                        } else if (level === 1) {
                            getLazyTree(node.value).then(res => {
                            list = res.data.data
                            callback()
                            })
                        } else if (level === 2) {
                            getLazyTree(node.value).then(res => {
                            list = res.data.data
                            callback()
                            })
                        } else {
                            callback()
                        }
                        },
                    },
                    {
                        label: '设备描述', // '工作空间描述',
@@ -312,18 +289,7 @@
                        ],
                    },
                    
                    {
                        label: '创建时间',
                        prop: 'create_time',
                        addDisplay: false,
                        editDisplay: false,
                        overHidden: true,
                        type: 'date',
                        labelWidth: 130,
                        width: 160,
                        format: 'YYYY-MM-DD HH:mm:ss',
                    },
                    
                ],
@@ -369,6 +335,12 @@
        await this.getRegionTreeList();
        await this.getSystemNamesList();
    },
    mounted() {
      getRegionTreeAll({ parentCode: this.userInfo.detail.areaCode }).then(res => {
        const column = this.findObject(this.option.column, 'area_code');
        column.dicData = res.data.data ? [res.data.data] : [];
      });
    },
    methods: {
        async getRegionTreeList(code) {
            try {
@@ -399,20 +371,20 @@
                this.systemNamesList = [];
            }
        },
        beforeOpen (type, row, index) {
            if (type === 'add') {
                this.form = {
                    workspace_name: '',
                    platform_name: '',
                    dept_name: '',
                    bind_code: '',
                    type: 0,
                    workspace_desc: '',
                    area_name: '',
                    area_code: '',
                }
            }
        },
        // beforeOpen (type, row, index) {
        //     if (type === 'add') {
        //         this.form = {
        //             workspace_name: '',
        //             platform_name: '',
        //             dept_name: '',
        //             bind_code: '',
        //             type: 0,
        //             workspace_desc: '',
        //             area_name: '',
        //             area_code: '',
        //         }
        //     }
        // },
        async getXZQHList(area_code) {
            try {
                const res = await getLazyTree(area_code);
@@ -605,12 +577,24 @@
            }
        },
        beforeOpen (done, type) {
            if (type === 'add') {
              this.form = {
                workspace_name: '',
                platform_name: '',
                dept_name: '',
                bind_code: '',
                type: 0,
                workspace_desc: '',
                area_name: '',
                area_code: '',
              }
            }
            if (['edit', 'view'].includes(type)) {
                getDetail(this.form.id).then(async res => {
                    const data = res.data.data
                     this.form = {
                        ...data,
                        area_code: await this.getFullAreaCode(data.area_code),
                        // area_code: await this.getFullAreaCode(data.area_code),
                     }
                })
            }
src/views/device/airport.vue
@@ -10,7 +10,8 @@
        </el-button>
      </template>
      <template #dept_name="{ row }">
        <el-tag>{{ row.dept_name }}</el-tag>
        <el-tag v-if="row.dept_name">{{ row.dept_name }}</el-tag>
        <span v-else>/</span>
      </template>
      <template #status="{ row }">
        <div v-if="row.status == true" class="onlineStatus">
@@ -51,52 +52,57 @@
        </span>
      </template>
      <template #mode_code="{ row }">
        <span class="text" v-if="row.domain == 3 || row.domain == 0" :style="row.mode_code != '-1' ? 'color: #00ee8b' : 'color: #de5e5e'">
        <span class="text" v-if="row.domain == 3 || row.domain == 0"
          :style="row.mode_code != '-1' ? 'color: #00ee8b' : 'color: #de5e5e'">
          {{ getModelText(row.mode_code) }}
        </span>
      </template>
      <template #menu="scope">
        <el-dropdown>
          <el-button type="primary" text v-if="permission.oss_set"><el-icon><MoreFilled /></el-icon>更多</el-button>
              <template #dropdown v-if="scope.row.domain == 3">
                <el-dropdown-menu teleported>
                  <el-dropdown-item command="a">
                    <el-button type="primary" text icon="el-icon-paperclip" v-if="permission.oss_set" @click.stop="handleOpenOssSet(scope.row, scope.index)">存储配置</el-button>
                  </el-dropdown-item>
                  <el-dropdown-item command="b">
                    <el-button type="primary" text icon="el-icon-share" v-if="permission.per_share && scope.row.domain == 3"
                      @click.stop="handleOpenDevicePerShare(scope.row, scope.index)">机场授权</el-button>
                  </el-dropdown-item>
                  <el-dropdown-item command="c"> <el-button type="primary" text icon="el-icon-position" :disabled="!scope.row.status"
                    v-if="permission.rang_con && scope.row.domain == 3"
                    @click.stop="handleOpenRemoteDebugging(scope.row, scope.index)">远程调试</el-button>
                  </el-dropdown-item>
                  <el-dropdown-item command="d"><el-button type="primary" text icon="el-icon-collection" v-if="permission.fly_device_offline"
                        @click.stop="rollFirmware(scope.row)">固件版本管理</el-button>
                  </el-dropdown-item>
                 <el-dropdown-item command="d" v-if="!scope.row.status">
                    <el-button type="primary" text icon="el-icon-collection" @click.stop="dockNotLine(scope.row)">
                      {{ scope.row.hidden_flag === 1 ? '设备上线' : '设备下线'}}
                    </el-button>
                  </el-dropdown-item>
                  <el-dropdown-item command="e"><el-button type="primary" text icon="el-icon-document-delete" v-if="permission.fly_device_offline"
                    @click.stop="handleDeviceOffline(scope.row)">注销</el-button>
                  </el-dropdown-item>
                </el-dropdown-menu>
              </template>
              <template #dropdown v-else>
                <el-dropdown-menu teleported>
                  <!-- <el-dropdown-item command="a">
          <el-button type="primary" text v-if="permission.oss_set"><el-icon>
              <MoreFilled />
            </el-icon>更多</el-button>
          <template #dropdown v-if="scope.row.domain == 3">
            <el-dropdown-menu teleported>
              <el-dropdown-item command="a">
                <el-button type="primary" text icon="el-icon-paperclip" v-if="permission.oss_set"
                  @click.stop="handleOpenOssSet(scope.row, scope.index)">存储配置</el-button>
              </el-dropdown-item>
              <el-dropdown-item command="b">
                <el-button type="primary" text icon="el-icon-share" v-if="permission.per_share && scope.row.domain == 3"
                  @click.stop="handleOpenDevicePerShare(scope.row, scope.index)">机场授权</el-button>
              </el-dropdown-item>
              <el-dropdown-item command="c"> <el-button type="primary" text icon="el-icon-position"
                  :disabled="!scope.row.status" v-if="permission.rang_con && scope.row.domain == 3"
                  @click.stop="handleOpenRemoteDebugging(scope.row, scope.index)">远程调试</el-button>
              </el-dropdown-item>
              <el-dropdown-item command="d"><el-button type="primary" text icon="el-icon-collection"
                  v-if="permission.fly_device_offline" @click.stop="rollFirmware(scope.row)">固件版本管理</el-button>
              </el-dropdown-item>
              <el-dropdown-item command="d" v-if="!scope.row.status">
                <el-button type="primary" text icon="el-icon-collection" @click.stop="dockNotLine(scope.row)">
                  {{ scope.row.hidden_flag === 1 ? '设备上线' : '设备下线' }}
                </el-button>
              </el-dropdown-item>
              <el-dropdown-item command="e"><el-button type="primary" text icon="el-icon-document-delete"
                  v-if="permission.fly_device_offline" @click.stop="handleDeviceOffline(scope.row)">注销</el-button>
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
          <template #dropdown v-else>
            <el-dropdown-menu teleported>
              <!-- <el-dropdown-item command="a">
                    <el-button type="primary" text icon="el-icon-circle-close" @click.stop="rowDel(scope.row, scope.index)">删除</el-button>
                  </el-dropdown-item> -->
                  <!-- <el-dropdown-item command="a">
              <!-- <el-dropdown-item command="a">
                    <el-button type="primary" text icon="el-icon-key" v-if="permission.operate_password_set" @click.stop="handleOperatePassword(scope.row, scope.index)">操控密码设置</el-button>
                  </el-dropdown-item> -->
                  <el-dropdown-item command="d"><el-button type="primary" text icon="el-icon-collection"  @click.stop="rollFirmware(scope.row)">固件版本管理</el-button>
                  </el-dropdown-item>
                </el-dropdown-menu>
              </template>
            </el-dropdown>
              <el-dropdown-item command="d"><el-button type="primary" text icon="el-icon-collection"
                  @click.stop="rollFirmware(scope.row)">固件版本管理</el-button>
              </el-dropdown-item>
            </el-dropdown-menu>
          </template>
        </el-dropdown>
      </template>
      <!-- 添加行政区划显示模板 -->
@@ -126,14 +132,16 @@
      <el-input v-model="operate_password" disabled></el-input>
    </el-dialog>
    <el-dialog title="注销" class="zx-cancel" append-to-body v-model="cancenOperate" width="460px">
      <div style="display: flex;justify-content: center;margin-bottom: 10px;">注销
        <span style="color:cornflowerblue;font-weight: bolder;margin: 0px 4px;">
    <el-dialog title="注销" class="ztzf-dialog-mange zx-cancel" append-to-body v-model="cancenOperate" width="560px">
      <div class="txt-contain">
        <img :src="warnSvg" />注销
        <span class="name">
          {{ cancelSNName }}
        </span> 设备可能会影响相关数据,确认注销吗?</div>
        <div style="display: flex;justify-content: center;color:red;font-size: 12px;">*删除相关图片、视频、事件</div>
        </span> 设备可能会影响相关数据,确认注销吗?
      </div>
      <div class="delete-txt">*删除相关图片、视频、事件</div>
      <template #footer>
        <span class="dialog-footer" style="display: flex;justify-content: center;">
        <span class="dialog-footer">
          <el-button @click="cancenOperate = false">取 消</el-button>
          <el-button type="primary" @click="cancenOperateDo">确 定</el-button>
        </span>
@@ -186,7 +194,7 @@
      </template>
    </el-dialog>
    <el-drawer class="ztzf-drawer-body-basic-container" title="机场授权管理" size="60%" append-to-body
    <el-drawer class="ztzf-drawer-body-basic-container ztzf-el-card__body" title="机场授权管理" size="60%" append-to-body
      v-model="devicePerShareVisible" :direction="'rtl'">
      <DevicePerShare ref="devicePerShare" />
    </el-drawer>
@@ -200,6 +208,7 @@
<script>
import { ElMessage, ElMessageBox } from 'element-plus'
import { getRegionTreeAll } from '@/api/job/task'
import { pxToRem } from '@/utils/rem'
import {
  getList,
@@ -223,8 +232,10 @@
import FirmwareManage from './components/firmwareManage.vue'
import DevicePerShare from './components/devicePerShare.vue'
import DockControlPanel from './components/DockControlPanel.vue'
import { getWebsocketUrl } from '@/utils/websocket/config';
import ConnectWebSocket from '@/utils/websocket';
import { getWebsocketUrl } from '@/utils/websocket/config'
import ConnectWebSocket from '@/utils/websocket'
import warnSvg from '@/assets/images/warn.svg'
export default {
  components: {
    FirmwareManage,
@@ -232,6 +243,8 @@
    DockControlPanel,
  },
  data () {
    // const self = this
    const validatePass = (rule, value, callback) => {
      if (value === '') {
        callback(new Error('请输入密码'))
@@ -254,6 +267,7 @@
        password: [{ required: true, validator: validatePass, trigger: 'blur' }],
        password2: [{ required: true, validator: validatePass2, trigger: 'blur' }],
      },
      warnSvg: warnSvg,
      cancelSNName: '',
      cancenOperate: false,
      cancenOperateRow: {},
@@ -306,7 +320,10 @@
        searchGutter: 30,
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        // searchMenuSpan: 16,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        indexLabel: '序号',
@@ -320,7 +337,7 @@
        grid: false,
        height: 'auto',
        calcHeight: 180,
        calcHeight: 20,
        column: [
          {
@@ -363,116 +380,8 @@
            searchSpan: 4,
            labelWidth: 145,
            width: 120,
            // rules: [
            //   {
            //     required: true,
            //     message: '请输入设备型号',
            //     trigger: 'blur',
            //   },
            // ],
          },
          {
            label: '设备SN',
            prop: 'device_sn',
            labelWidth: 145,
            width: 120,
            overHidden: true,
            searchSpan: 4,
            // editDisabled: true,
            editDisplay: false, //编辑显示
            // rules: [
            //   {
            //     required: true,
            //     message: '请输入设备SN',
            //     trigger: 'blur',
            //   },
            // ],
          },
          // {
          //   label: '机巢编号',
          //   prop: 'machine_nest_sn',
          //   labelWidth: 145,
          //   editDisabled: true,
          //   rules: [
          //     {
          //       required: false,
          //       message: '请输入机巢编号',
          //       trigger: 'blur',
          //     },
          //   ],
          // },
          {
            label: '设备位置',// '行政区划',
            prop: 'area_name',
            // hide: true,
            overHidden: true,
            editDisplay: false, //编辑显示
            viewDisplay: false, //查看显示
            labelWidth: 145,
            width: 200,
          },
          {
            label: '设备位置',
            prop: 'area_code',
            type: 'cascader',
            labelWidth: 145,
            searchSpan: 4,
            hide: true,
            search: true,
            // overHidden: true,
            editDisplay: true,
            viewDisplay: true,
            checkStrictly: true,
            props: {
              label: 'title',
              value: 'value',
              emitPath: false,
              multiple: false,
              expandTrigger: 'hover',
            },
            dataType: 'string',
            rules: [
              {
                required: true,
                message: '请选择设备位置',
                trigger: 'change',
              },
            ],
            lazy: true,
            lazyLoad (node, resolve) {
              let level = node.level
              let list = []
              let callback = () => {
                resolve(
                  (list || []).map(ele => ({
                    ...ele,
                    value: ele.value,
                    leaf: level >= 2,
                  }))
                )
              }
              if (level === 0) {
                getLazyTree('000000000000').then(res => {
                  list = res.data.data
                  callback()
                })
              } else if (level === 1) {
                getLazyTree(node.value).then(res => {
                  list = res.data.data
                  callback()
                })
              } else if (level === 2) {
                getLazyTree(node.value).then(res => {
                  list = res.data.data
                  callback()
                })
              } else {
                callback()
              }
            },
          },
          {
            label: '设备名称',
            prop: 'nickname',
@@ -491,20 +400,23 @@
            ],
          },
          // {
          //   label: '设备位置',
          //   prop: 'address',
          //   labelWidth: 145,
          //   width: 100,
          //   overHidden: true,
          //   rules: [
          //     {
          //       required: true,
          //       message: '请输入设备位置',
          //       trigger: 'blur',
          //     },
          //   ],
          // },
          {
            label: '4G增强',
            prop: 'link_workmode',
            labelWidth: 145,
            width: 160,
            overHidden: true,
            editDisplay: false, //编辑显示
            viewDisplay: false,
            align: 'center',
            formatter: (row, column, cellValue) => {
              const statusMap = {
                0: '关',
                1: '开',
              }
              return statusMap[cellValue] || '/'
            }
          },
          {
            label: '负载设备',
@@ -513,15 +425,154 @@
            width: 160,
            overHidden: true,
            editDisplay: false, //编辑显示
          },
          {
            label: '流量剩余(GB)',
            prop: 'traffic_remaining',
            labelWidth: 145,
            width: 120,
            // editDisabled: true,
            editDisplay: false, //编辑显示
            // rules: [
            //   {
            //     required: true,
            //     message: '请输入负载设备',
            //     message: '请输入流量剩余',
            //     trigger: 'blur',
            //   },
            // ],
          },
          {
            label: '流量到期时间',
            prop: 'traffic_expire_time',
            labelWidth: 145,
            width: 120,
            type: 'date',
            format: 'YYYY-MM-DD',
            editDisplay: false, //编辑显示
          },
          {
            label: '设备状态',
            prop: 'cnstatus',
            hide: true,
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            labelWidth: 145,
          },
          {
            label: '设备状态',
            prop: 'status',
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            labelWidth: 100,
            hide: true,
            // searchSpan: 4,
            // search: true,
            slot: true,
            width: 100,
            rules: [
              {
                required: true,
                message: '请输入在线状态',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '设备状态',
            prop: 'cnmode_code',
            hide: true,
            addDisplay: false,
            editDisplay: false,
            viewDisplay: true,
            labelWidth: 145,
            width: 110,
          },
          {
            label: '设备状态',
            prop: 'mode_code',
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            labelWidth: 145,
            searchSpan: 4,
            search: true,
            type: 'select',
            dicData: [
              { label: '在线', value: 0 },
              { label: '离线', value: -1 },
              { label: '远程调试', value: 2 },
              { label: '现场调试', value: 1 },
              { label: '固件升级中', value: 3 }
            ],
            slot: true,
            width: 110,
            rules: [
              {
                required: true,
                message: '请输入机场状态',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '设备SN',
            prop: 'device_sn',
            labelWidth: 145,
            width: 120,
            overHidden: true,
            searchSpan: 4,
            // editDisabled: true,
            editDisplay: false, //编辑显示
          },
          {
            label: '设备位置',// '行政区划',
            prop: 'area_name',
            // hide: true,
            overHidden: true,
            editDisplay: false, //编辑显示
            viewDisplay: false, //查看显示
            labelWidth: 145,
            width: 200,
          },
          {
            label: '设备位置',
            prop: 'area_code',
            // type: 'cascader',
            type: 'tree',
            labelWidth: 145,
            searchSpan: 4,
            hide: true,
            search: true,
            // overHidden: true,
            editDisplay: true,
            viewDisplay: true,
            checkStrictly: true,
            defaultExpandAll: true,
            dicData: [],
            props: {
              label: 'name',
              value: 'id',
              children: 'childrens',
            },
            rules: [
              {
                required: true,
                message: '请选择设备位置',
                trigger: 'change',
              },
            ],
          },
          {
            // hide: true,
            label: '保险有效期',
@@ -529,10 +580,11 @@
            labelWidth: 145,
            width: 200,
            type: 'daterange',
            format: 'YYYY-MM-DD',
            valueFormat: 'YYYY-MM-DD HH:mm:ss',
            format: 'YYYY-MM-DD ',
            valueFormat: 'YYYY-MM-DD',
            startPlaceholder: '保险开始日期',
            endPlaceholder: '保险结束日期',
            align: 'center',
            rules: [
              {
                required: false,
@@ -540,6 +592,9 @@
                trigger: 'blur',
              },
            ],
            formatter: (row, value) => {
              return value[0] ? `${this.$dayjs(value[0]).format('YYYY-MM-DD')}~${this.$dayjs(value[1]).format('YYYY-MM-DD')}` : '/'
            },
          },
          // {
          //   label: '保险有效期',
@@ -554,52 +609,14 @@
          //     },
          //   ],
          // },
          {
            label: '流量剩余',
            prop: 'traffic_remaining',
            labelWidth: 145,
            width: 100,
            // editDisabled: true,
            editDisplay: false, //编辑显示
            // rules: [
            //   {
            //     required: true,
            //     message: '请输入流量剩余',
            //     trigger: 'blur',
            //   },
            // ],
          },
          {
            label: '流量到期时间',
            prop: 'traffic_expire_time',
            labelWidth: 145,
            width: 120,
            type: 'date',
            format: 'YYYY-MM-DD',
            editDisplay: false, //编辑显示
            // editDisabled: true,
            // rules: [
            //   {
            //     required: true,
            //     message: '请输入流量到期时间',
            //     trigger: 'blur',
            //   },
            // ],
          },
          {
            label: '固件版本',
            prop: 'firmware_version',
            labelWidth: 145,
            width: 110,
            editDisplay: false, //编辑显示
            // editDisabled: true,
            // rules: [
            //   {
            //     required: true,
            //     message: '请输入固件版本',
            //     trigger: 'blur',
            //   },
            // ],
          },
          {
            label: '固件升级',
@@ -610,43 +627,36 @@
            viewDisabled: true,
            addDisabled: true,
            editDisplay: false, //编辑显示
            // editDisabled: true,
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            // rules: [
            //   {
            //     required: true,
            //     message: '请输入固件升级',
            //     trigger: 'blur',
            //   },
            // ],
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'dept_name',
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            align: 'center',
            labelWidth: 145,
            width: 180,
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'dept_id',
            hide: true,
            type: 'tree',
            defaultExpandAll: true,
            search: true,
            searchSpan: 4,
            labelWidth: 145,
            defaultExpandAll: true,
            dicUrl: '/blade-system/dept/getTree',
            props: {
              label: 'name',
@@ -655,7 +665,7 @@
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],
@@ -749,74 +759,6 @@
            width: 160,
            format: 'YYYY-MM-DD HH:mm:ss',
          },
          {
            label: '设备状态',
            prop: 'cnstatus',
            hide: true,
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            labelWidth: 145,
          },
          {
            label: '设备状态',
            prop: 'status',
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            labelWidth: 100,
            hide: true,
            // searchSpan: 4,
            // search: true,
            slot: true,
            width: 100,
            rules: [
              {
                required: true,
                message: '请输入在线状态',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '设备状态',
            prop: 'cnmode_code',
            hide: true,
            addDisplay: false,
            editDisplay: false,
            viewDisplay: true,
            labelWidth: 145,
            width: 110,
          },
          {
            label: '设备状态',
            prop: 'mode_code',
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            labelWidth: 145,
            searchSpan: 4,
            search: true,
            type: 'select',
            dicData: [
              { label: '在线', value: 0 },
              { label: '离线', value: -1 },
              { label: '远程调试', value: 2 },
              { label: '现场调试', value: 1 },
              { label: '固件升级中', value: 3 }
            ],
            slot: true,
            width: 110,
            rules: [
              {
                required: true,
                message: '请输入机场状态',
                trigger: 'blur',
              },
            ],
          },
        ],
      },
      data: [],
@@ -834,8 +776,9 @@
      },
    }
  },
  computed: {
    ...mapGetters(['permission']),
    ...mapGetters(['permission', 'userInfo']),
    permissionList () {
      return {
        addBtn: this.validData(this.permission.air_add, true),
@@ -862,8 +805,11 @@
      }
    })
  },
  mounted() {
  mounted () {
    getRegionTreeAll({ parentCode: this.userInfo.detail.areaCode }).then(res => {
      const column = this.findObject(this.option.column, 'area_code')
      column.dicData = res.data.data ? [res.data.data] : []
    })
  },
  methods: {
    getDockModeText (value) {
@@ -873,11 +819,11 @@
      let txt = '离线'
      if (value === 0 || value === 4) {
        txt = '在线'
      } else if(value === 1) {
      } else if (value === 1) {
        txt = '现场调试'
      } else if(value === 2) {
      } else if (value === 2) {
        txt = '远程调试'
      } else if(value === 3) {
      } else if (value === 3) {
        txt = '固件升级中'
      }
      return txt
@@ -963,7 +909,7 @@
      this.cancenOperate = true
      this.cancelSNName = row.nickname
      this.cancenOperateRow = row
    },
    cancenOperateDo () {
@@ -971,7 +917,7 @@
      this.deleteFlag = false
      deviceOffline(this.cancenOperateRow.device_sn, true).then(res => {
        ElMessage.success('注销成功')
        this.init()
      }).catch(error => {
        ElMessage.error('注销失败')
@@ -1003,16 +949,16 @@
      })
    },
    // 设备下线
    dockNotLine(row) {
      let txt = row.hidden_flag === 1?'上线':'下线'
    dockNotLine (row) {
      let txt = row.hidden_flag === 1 ? '上线' : '下线'
      ElMessageBox.confirm(`确定${txt}该设备吗?`, '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => {
          let hidden_flag = row.hidden_flag === 1?0:1
          devicesUpAndDown({id:row.id, hiddenFlag:hidden_flag})
          let hidden_flag = row.hidden_flag === 1 ? 0 : 1
          devicesUpAndDown({ id: row.id, hidden_flag: hidden_flag })
            .then(res => {
              ElMessage.success(`${txt}成功`)
              this.init()
@@ -1298,8 +1244,8 @@
      const submitData = {
        ...row,
        area_code: row.area_code.split(',').pop(),
        insure_start_time: row.duration_of_insurance[0] || null,
        insure_expired_time: row.duration_of_insurance[1] || null,
        insure_start_time: row.duration_of_insurance[0] + ' 00:00:00' || null,
        insure_expired_time: row.duration_of_insurance[1] + ' 23:59:59' || null,
        deptId: row.dept_id,
        areaCode: row.area_code.split(',').pop(),
      }
@@ -1321,7 +1267,7 @@
              getList(1, 10, params).then(res => {
                const data = res.data.data.records
                data.forEach(e => {
                  e.duration_of_insurance = [e.insure_start_time || '', e.insure_expired_time || '']
                  e.duration_of_insurance = [e?.insure_start_time?.slice(0, 10) || '', e?.insure_expired_time?.slice(0, 10) || '']
                })
                resolve(data)
              })
@@ -1398,6 +1344,7 @@
        })
    },
    async getFullAreaCode (areaCode) {
      console.log('getFullAreaCode', areaCode)
      if (!areaCode) return ''
      const code = areaCode.toString()
@@ -1419,16 +1366,14 @@
      if (['edit', 'view'].includes(type)) {
        getDetail(this.form.id).then(async res => {
          const data = res.data.data
          this.form = {
            ...data,
            area_code: await this.getFullAreaCode(data.area_code),
            // area_code: await this.getFullAreaCode(data.area_code),
            area_name: this.form.area_name,
            cnmode_code: this.getDockModeText(this.form.mode_code),
            // domain: data.domain === 0 ? '无人机' : data.domain === 3 ? '机巢' : '未知',
            domainTxt: data.domain === 0 ? '无人机' : data.domain === 3 ? '机巢' : '未知',
            cnstatus: this.form.status === false ? '离线' : '在线',
            duration_of_insurance: [data?.insure_start_time || '', data?.insure_expired_time || ''],
            duration_of_insurance: [data?.insure_start_time?.slice(0, 10) || '', data?.insure_expired_time?.slice(0, 10) || ''],
          }
          done()
@@ -1461,7 +1406,7 @@
      }
      values['type'] = 1
      if (values.area_code) {
          values['area_code'] = values.area_code.split(',').pop()
        values['area_code'] = values.area_code.split(',').pop()
      }
      this.loading = true
      getList(page.currentPage, page.pageSize, values).then(res => {
@@ -1472,7 +1417,7 @@
          if (e.firmware_status == 4) {
            this.connectWebSocket(e)
          }
          e.duration_of_insurance = [e.insure_start_time || '', e.insure_expired_time || '']
          e.duration_of_insurance = [e?.insure_start_time?.slice(0, 10) || '', e?.insure_expired_time?.slice(0, 10) || '']
        })
        this.data = data.records
        this.loading = false
@@ -1488,28 +1433,28 @@
      getList(1, 10, params).then(res => {
        const data = res.data.data.records
        data.forEach(e => {
          e.duration_of_insurance = [e.insure_start_time || '', e.insure_expired_time || '']
          e.duration_of_insurance = [e?.insure_start_time?.slice(0, 10) || '', e?.insure_expired_time?.slice(0, 10) || '']
        })
        resolve(data)
      })
    },
    rowDel (row) {
      this.$confirm('确定将选择数据删除?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
      })
        .then(() => {
          return remove(row.id)
        })
        .then(() => {
          this.onLoad(this.page)
          this.$message({
            type: 'success',
            message: '操作成功!',
          })
        })
    },
    // rowDel (row) {
    //   this.$confirm('确定将选择数据删除?', {
    //     confirmButtonText: '确定',
    //     cancelButtonText: '取消',
    //     type: 'warning',
    //   })
    //     .then(() => {
    //       return remove(row.id)
    //     })
    //     .then(() => {
    //       this.onLoad(this.page)
    //       this.$message({
    //         type: 'success',
    //         message: '操作成功!',
    //       })
    //     })
    // },
    // 设置无人机操作密码
    setOperatePasswordConfirm () {
      var that = this
@@ -1567,8 +1512,10 @@
  display: flex;
  flex-direction: column;
  align-items: center;
  .el-tag:first-child{
  margin-bottom: 5px;}
  .el-tag:first-child {
    margin-bottom: 5px;
  }
}
.onlineStatus {
@@ -1609,12 +1556,15 @@
    border-color: #409eff;
  }
}
:deep(.avue-crud__menu) {
  display: flex;
}
.more-container {
  position: relative;
}
.show-more-do {
  position: absolute;
  background-color: #ffffff;
@@ -1634,5 +1584,38 @@
  right: 30px;
}
.force-center-dialog .el-dialog {}
.zx-cancel {
  .txt-contain {
    display: flex;
    justify-content: center;
    margin-bottom: 20px;
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 14px;
    img {
      width: 20px;
      height: 20px;
    }
    .name {
      color: #0E8BFF;
      margin-left: 6px;
      margin-right: 6px;
    }
  }
  .delete-txt {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    display: flex;
    justify-content: center;
    color: #FF0000;
    font-size: 12px;
    margin-bottom: 20px;
  }
}
</style>
src/views/device/components/DeviceSettingBox.vue
@@ -375,7 +375,7 @@
  .device-setting-header {
    font-size: 14px;
    font-weight: 600;
    //font-weight: 600;
    padding: 10px 10px 0px;
  }
@@ -398,6 +398,7 @@
    flex-wrap: wrap;
    justify-content: space-between;
    padding: 4px 10px;
    font-size: 14px;
    .control-setting-item {
      width: 220px;
@@ -414,7 +415,10 @@
        flex-direction: column;
        .item-label {
          font-weight: 700;
          //font-weight: 700;
        }
        .item-status {
          color: #8d8b8b;
        }
      }
      .control-setting-item-right {
src/views/device/components/DockControlPanel.vue
@@ -233,6 +233,7 @@
watch(
  () => Store.getters.deviceState,
  value => {
    console.log(value, '测试')
    // console.log('设备状态变化', value.deviceInfo[props.deviceInfo.child_sn])
    if (props.sn && (value.currentSn === props.deviceInfo.child_sn || value.currentSn === props.deviceInfo.device_sn)) {
      debugStatus.value = value.dockInfo[props.deviceInfo.device_sn]?.basic_osd?.mode_code === EDockModeCode.Remote_Debugging
@@ -479,9 +480,10 @@
  border-radius: 2px;
  .control-cmd-wrapper {
    font-size: 14px;
    .control-cmd-header {
      font-size: 14px;
      font-weight: 600;
      //font-weight: 600;
      padding: 10px 10px 0px;
      .debug-btn {
@@ -512,7 +514,10 @@
          flex-direction: column;
          .item-label {
            font-weight: 700;
            //font-weight: 700;
          }
          .item-status {
            color: #8d8b8b;
          }
        }
      }
src/views/device/components/devicePerShare.vue
@@ -39,6 +39,7 @@
        index: true,
        viewBtn: true,
        addBtn: true,
        editBtn: false,
        selection: true,
        excelBtn: false,
        dialogClickModal: false,
@@ -51,7 +52,7 @@
          {
            label: '授权单位',
            prop: 'deptId',
            labelWidth: 130,
            // labelWidth: 100,
            type: 'tree',
            dicData: [],
            props: {
@@ -74,7 +75,7 @@
          {
            label: '授权单位',
            prop: 'deptName',
            labelWidth: 130,
            // labelWidth: 100,
            addDisplay: false,
            editDisabled: true,
            search: true,
@@ -119,7 +120,7 @@
            editDisplay: false,
            span: 24,
            row: true,
            width: 160,
            width: 200,
            format: 'YYYY-MM-DD HH:mm:ss',
          },
        ],
@@ -134,7 +135,7 @@
        addBtn: this.validData(this.permission.airport_add, true),
        viewBtn: this.validData(this.permission.airport_view, true),
        delBtn: this.validData(this.permission.airport_delete, true),
        editBtn: this.validData(this.permission.airport_edit, true),
        editBtn: this.validData(this.permission.airport_edit, false),
      }
    },
    ids () {
src/views/device/components/firmwareManage.vue
@@ -25,12 +25,12 @@
        </el-button>
      </template>
    </avue-crud>
    <el-dialog title="固件上传" append-to-body v-model="firmwareUploadBox" width="40%" height="60%">
    <el-dialog title="固件上传" class="ztzf-dialog-mange" append-to-body v-model="firmwareUploadBox" width="40%" height="60%">
      <el-form
        ref="form"
        class="ztzf-form-search"
        :model="importForm"
        v-loading="loadingForm"
        label-width="80px"
        style="width: 80%; margin-left: 10%"
        :rules="rules"
      >
@@ -39,7 +39,7 @@
        </el-form-item>
        <!-- 上传 -->
        <el-form-item label="固件文件" label-width="80px" prop="file">
        <el-form-item label="固件文件" prop="file">
          <el-upload
            class="upload-demo"
            action="#"
@@ -50,7 +50,7 @@
            :before-upload="beforeUploadFile"
          >
            <i class="el-icon-upload"></i>
            <div class="el-upload__text" style="height: 50px"><em>点击上传</em></div>
            <div class="el-upload__text" style="height: 50px"> <el-button>点击上传</el-button></div>
          </el-upload>
        </el-form-item>
        <el-form-item label="是否可用" prop="status">
@@ -59,14 +59,16 @@
            <el-option label="否" value="0"></el-option>
          </el-select>
        </el-form-item>
        <el-form-item label="说明" label-width="80px" prop="release_note">
        <el-form-item label="说明" prop="release_note">
          <el-input type="textarea" v-model="importForm.release_note" clearable></el-input>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="onConfirm">确定</el-button>
          <el-button @click="onCancel">取消</el-button>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
        <el-button type="primary" @click="onConfirm" icon="el-icon-circle-check">确定</el-button>
        <el-button @click="onCancel" icon="el-icon-circle-close">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </basic-container>
</template>
@@ -104,7 +106,9 @@
        dialogWidth: 950,
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
@@ -120,7 +124,6 @@
          {
            label: '设备名称',
            prop: 'device_name',
            editDisabled: true,
            // search: true,
            // searchSpan: 5,
            labelWidth: 130,
@@ -135,12 +138,12 @@
            ],
          },
          {
            label: '固件文件名称',
            label: '文件名称',
            prop: 'file_name',
            labelWidth: 130,
            search: true,
            searchSpan: 8,
            searchLabelWidth: 120,
            searchSpan: 4,
            // searchLabelWidth: 120,
            editDisabled: true,
            rules: [
              {
@@ -432,4 +435,8 @@
  /* color: aqua; */
}
</style>
<style scoped lang="scss"></style>
<style scoped lang="scss">
.upload-demo {
  height: 30px;
}
</style>
src/views/device/devicePerMenu.vue
@@ -28,7 +28,9 @@
        dialogWidth: 750,
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
@@ -43,10 +45,10 @@
        column: [
          {
            label: '名称',
            label: '菜单名称',
            prop: 'name',
            search: true,
            searchSpan: 5,
            searchSpan: 4,
            span: 24,
            row: true,
            rules: [
@@ -61,7 +63,7 @@
            label: '绑定菜单',
            prop: 'menu_name',
            search: true,
            searchSpan: 5,
            searchSpan: 4,
            span: 24,
            row: true,
            display: false,
@@ -77,6 +79,8 @@
          },
          {
            label: '绑定菜单',
            searchSpan: 5,
            span: 24,
            prop: 'menu_id',
            type: 'tree',
            dicUrl: '/blade-system/menu/deviceButtons',
src/views/device/fly.vue
@@ -201,7 +201,7 @@
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'dept_name',
            addDisplay: false,
            editDisplay: false,
@@ -210,13 +210,13 @@
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'dept_id',
            hide: true,
            type: 'tree',
@@ -230,7 +230,7 @@
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],
src/views/job/components/DeviceJobDetails.vue
@@ -4,7 +4,7 @@
        :width="pxToRem(1800)" :close-on-click-modal="false" :destroy-on-close="true">
        <div class="content">
            <div class="contentLeft">
                <div class="machineTableDetailsTitle"><span>详情</span></div>
                <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">
@@ -29,11 +29,14 @@
                </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" />
@@ -159,6 +162,7 @@
import { droneEventList } from '../const/drc'
import { ElMessage,ElLoading } from 'element-plus'
import { useStore } from 'vuex';
import EventBus from '@/utils/eventBus';
const store = useStore();
const htlsrwxq = computed(() => store.state.common.downloadProgress?.htlsrwxq || 100)
const isShow = defineModel('show')
@@ -181,7 +185,7 @@
    { name: '任务类型', value: '', field: 'industry_type_str' },
    { name: '飞行事件', value: '', field: 'event_number' },
    { name: '所属机巢', value: '', field: 'device_names' },
    { name: '所属单位', value: '', field: 'dept_name' },
    { name: '所属部门', value: '', field: 'dept_name' },
    { name: '任务时间', value: '', field: 'cycle_time_value' },
    { name: '关联算法', value: '', field: 'ai_type_str' },
    { name: '任务描述', value: '', field: 'remark' },
@@ -347,7 +351,9 @@
function cancelDownload() {
    cancelDownloadApi({ type:'htlsrwxq' }).then(res =>{
        ElMessage.success('取消成功')
    })
    }).catch(e =>{
    EventBus.emit('useGlobalWS-messageHandler', {biz_code: 'DOWNLOAD_PROGRESS',data:{status: 'CANCELLED',type:'htlsrwxq'}})
  })
}
onMounted(() => {
    getDetails()
@@ -384,14 +390,18 @@
        .machineTableDetailsTitle {
            margin-bottom: 10px;
            background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
            // 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: 30px;
                margin-left: 10px;
                font-size: 16px;
                color: #0282ff;
                // color: #0282ff;
                color: #303133;
                line-height: 20px;
                text-align: left;
                margin-bottom: 8px;
@@ -400,12 +410,13 @@
        .devicetitle {
            margin-bottom: 16px;
            background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
            // 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{
            .rightBox{
                display: flex;
                align-items: center;
                padding-right: 10px;
@@ -451,9 +462,12 @@
                }
            }
            img{
            width: 15px;
            height: 15px;}
            p {
                display: inline-block;
                margin-left: 30px;
                margin-left: 10px;
                font-size: 16px;
                color: #363636;
                line-height: 20px;
src/views/job/components/JobRelatedEvents.vue
@@ -1,6 +1,7 @@
<template>
    <div class="JobRelatedEvents">
        <div class="machineTableDetailsTitle">
            <img src="/src/assets/images/task/sign.svg" alt="">
        <p>
            关联事件
            <span>{{ total }}</span>
@@ -194,11 +195,15 @@
.JobRelatedEvents {
    .machineTableDetailsTitle {
    margin-bottom: 16px;
    background: url('/src/assets/images/task/detailtitle.png') no-repeat center;
    // 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;}
    p {
        display: inline-block;
        margin-left: 30px;
        margin-left: 10px;
        font-size: 16px;
        color: #363636;
        line-height: 20px;
src/views/job/components/SearchBox.vue
@@ -14,7 +14,8 @@
            check-strictly
            node-key="id"
            :props="treeProps"
            @node-click="handleNodeClick"
            clearable
            @change="handleNodeClick"
          />
        </el-form-item>
        <el-form-item label="所属机巢:">
@@ -61,7 +62,7 @@
        <div class="more" v-if="isExpand" @click="toggleExpand">收起</div>
        <div class="more" v-else @click="toggleExpand">更多</div>
        <div class="search-btn" :style="{ bottom: isExpand ? '5px' : '-3px' }">
          <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
          <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
        </div>
@@ -89,7 +90,6 @@
            collapse-tags
            collapse-tags-tooltip
            clearable
            @node-click="handleNodeClick"
            @check="handleCheck"
            @clear="handleClear"
          />
src/views/job/components/TaskIntermediateContent/TaskIntermediateContent.vue
@@ -17,7 +17,7 @@
            </el-tooltip-copy>
          </template>
        </el-table-column>
        <el-table-column prop="name" label="任务编号" width="160">
        <el-table-column prop="name" label="任务名称" width="160">
          <template #default="scope">
            <el-tooltip-copy :content="scope.row.name" :showCopyText="true">
              {{scope.row.name}}
@@ -264,14 +264,13 @@
const changeKey = inject('changeKey')
// 轮询查询是否刷新
const polling = setInterval(async () => {
  const res = await statusChangedApi()
  if (res.data.data.DEVICE_REFRESH || res.data.data.JOB_REFRESH) {
    getJobList()
    changeKey.value++
  }
}, 4000)
const jobUpdateKey = computed(() => store.state.common.jobUpdateKey)
const deviceUpdateKey = computed(() => store.state.common.deviceUpdateKey)
watch([jobUpdateKey,deviceUpdateKey],()=>{
  getJobList()
  changeKey.value++
})
onBeforeUnmount(() => {
  clearInterval(polling)
src/views/job/components/TaskTop/TaskEvent.vue
@@ -52,10 +52,7 @@
        type: 'category',
        // data: ['1月', '2月', '3月', '4月', '5月', '6月'],
        axisLine: {
            show: false, // 隐藏轴线
        },
        axisLine: {
            show: false, // 隐藏轴线
            show: true, // 隐藏轴线
        },
        axisLabel: {
            color: '#5B5B5D',
src/views/job/components/TaskTop/TaskIndustry.vue
@@ -22,15 +22,15 @@
        formatter: '{b}: {c} ({d}%)',
    },
    legend: {
        orient: 'horizontal',
        left: 'center',
        bottom: '2%',
    orient: 'vertical',
    top: 'center',
    right: '20',
        textStyle: {
            color: '#363636',
            fontSize: 12,
        },
        itemWidth: 2, // 减小图例标记的宽度
        itemHeight: 3, // 减小图例标记的高度
        itemWidth: 8, // 减小图例标记的宽度
        itemHeight: 8, // 减小图例标记的高度
        itemGap: 8, // 减小图例项之间的间距
        formatter: '{name}', // 简化图例文本
    },
@@ -39,20 +39,18 @@
            name: '机巢事件统计',
            type: 'pie',
            // roseType: 'radius',
            radius: ['36%', '60%'],
            // center: ['50%', '45%'],
            // radius: '70%', // 设置饼图的半径
            center: ['50%', '36%'], // 调整饼图位置
      radius: ['20%', '70%'],
      center: ['30%', '50%'],
            data: [],
            label: {
                show: true,
                show: false,
                position: 'outside',
                formatter: '{c}',
                fontSize: 12,
                color: '#000',
            },
            labelLine: {
                show: true,
                show: false,
                length: 10,
                length2: 10,
                lineStyle: {
@@ -94,7 +92,7 @@
<style lang="scss" scoped>
.task-industry {
    width: 276px;
    width: 300px;
    height: 100%;
    background: #FFFFFF;
    border-radius: 8px 8px 8px 8px;
src/views/job/components/TaskTop/TaskTime.vue
@@ -63,15 +63,15 @@
        type: 'category',
        data: ['1'],
        axisLine: {
            show: false, // 隐藏轴线
            show: true, // 隐藏轴线
        },
        axisTick: {
            show: true,
            length: 2,
            lineStyle: {
                color: '#363636',
            },
        },
        // axisTick: {
        //     show: true,
        //     length: 2,
        //     lineStyle: {
        //         color: 'red',
        //     },
        // },
        axisLabel: {
            color: '#363636',
            margin: 8,
src/views/layerManagement/components/folderFile.vue
New file
@@ -0,0 +1,310 @@
<template>
  <div class="rightContainer">
    <div class="contenttitle">{{itSLandPlanning ? '文件' : '文件夹'}}信息</div>
    <div class="centerBox">
      <div class="itemRow">
        <div class="label">{{itSLandPlanning ? '文件' : '文件夹'}}名称</div>
        <div>
          <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="label">关联算法</div>
        <div>
          <el-select
            class="ztzf-layer-search filter-item"
            @change="handleTypeChange"
            v-model="formData.event_type"
            placeholder="请选择"
            clearable
          >
            <el-option
              v-for="item in types"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
          <el-select
            v-model="formData.algorithm_name"
            class="ztzf-layer-search"
            placeholder="请选择"
            clearable
            :disabled="!formData.event_type"
          >
            <el-option
              v-for="item in algorithms"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </div>
      </div>
    </div>
    <div class="detailInfo" v-if="layerParams.editFolder">
      <div class="infpItem" v-if="!itSLandPlanning">
        <div class="itemData">
          <div class="title">围栏总数量</div>
          <div class="num">
            <span>{{ formData.total_count }}</span> 个
          </div>
        </div>
        <div class="itemData">
          <div class="title">围栏总面积</div>
          <div class="num">
            <span class="areaStyle">{{ formData.total_area }}</span> m²
          </div>
        </div>
      </div>
      <div class="infpItem">
        <div class="itemData">
          <div class="title">创建时间</div>
          <div class="num">
            <span>{{ formData?.create_time }}</span>
          </div>
        </div>
        <div class="itemData">
          <div class="title">创建人</div>
          <div class="num">
            <span>{{ formData?.create_name }}</span>
          </div>
        </div>
      </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="" />
    </div>
  </div>
</template>
<script setup>
const emit = defineEmits(['refreshMethod']);
import EventBus from '@/utils/eventBus';
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
import { deleteFolderApi, deitFolderApi, addFolderApi } from '@/api/layer/index';
import { getTicketInfo } from '@/api/tickets/ticket';
import { computed } from 'vue';
const layerParams = inject('layerParams');
const types = ref([]); //工单类型
const allAlgorithms = ref([]);
const algorithms = ref([]);
const formData = ref({
  category_id: '',
  name: '',
  event_type: '',
  algorithm_name: '',
  total_count: 0,
  total_area: 0,
});
const detailData = ref(null);
detailData.value = layerParams.value.editFolderData;
const props = defineProps({
  activeName: String,
});
const itSLandPlanning = computed(() => props.activeName === '国土空间规划')
const getTicketInfoData = async () => {
  const response = await getTicketInfo();
  const { dept_data, event_type, ai_type, info } = response.data.data;
  allAlgorithms.value = info;
  types.value = Object.entries(event_type).map(([key, value]) => ({
    label: value,
    value: key,
  }));
};
const handleTypeChange = typeValue => {
  const matchedCategory = allAlgorithms.value.find(category => category.dict_key === typeValue);
  if (!matchedCategory || !matchedCategory.algorithms || matchedCategory.algorithms.length === 0) {
    // 无匹配的算法时清空
    algorithms.value = [];
    return;
  }
  algorithms.value = matchedCategory.algorithms.map(algo => ({
    label: algo.dict_value,
    value: algo.dict_key,
    dict_key: algo.dict_key,
    dict_value: algo.dict_value,
  }));
};
const clearAllData = () => {
  formData.value = {
    category_id: '',
    name: '',
    event_type: '',
    algorithm_name: '',
    total_count: 0,
    total_area: 0,
    create_time: '',
    create_name: '',
  };
  algorithms.value = [];
  detailData.value = {};
  layerParams.value.decideWhetherToAddOrEdit = 1;
  layerParams.value.addFolder = false;
  layerParams.value.editFolder = false;
  layerParams.value.editDetailData = null;
  layerParams.value.polygonPosition = null;
};
const cancelHandel = () => {
  clearAllData();
};
const submitHandle = () => {
  if (!formData.value.name) {
    ElMessage.error('请输入文件夹名称');
    return;
  }
  if(!itSLandPlanning.value){
    if (!formData.value.event_type && layerParams.value.fileType === 1) {
      ElMessage.warning('请选择类型');
      return;
    }
    if (!formData.value.algorithm_name && layerParams.value.fileType === 1) {
      ElMessage.warning('请选择算法');
      return;
    }
  }
  const params = {
    category_id: layerParams.value.fileType === 1 ? 1 : 2,
    name: formData.value.name,
    event_type: formData.value.event_type,
    algorithm_name: formData.value.algorithm_name,
  };
  // 如果是编辑模式,才添加 id
  if (layerParams.value.addAnEditingFolder === 2) {
    params.id = detailData.value.id;
  }
  if (layerParams.value.addAnEditingFolder === 1) {
    // 文件夹新增
    addFolderApi(params).then(res => {
      ElMessage.success('新增成功');
        EventBus.emit('gettreeDataApi');
          emit('refreshMethod');
      clearAllData();
    });
  } else {
    //文件夹编辑
    deitFolderApi(params).then(res => {
      ElMessage.success('编辑成功');
        EventBus.emit('gettreeDataApi');
      clearAllData();
    });
  }
};
onMounted(async () => {
  await getTicketInfoData();
  const addOrEditStatus = layerParams.value.addAnEditingFolder || 1;
  //  判断是否有编辑数据,有则回显
  if (detailData.value && addOrEditStatus === 2) {
    const editData = detailData.value;
    formData.value = {
      ...formData.value,
      category_id: editData.category_id,
      name: editData.name,
      event_type: editData.event_type, // 回显“关联算法类型”值
      algorithm_name: editData.algorithm_name, // 回显“算法”值
      total_count: editData.total_count,
      total_area: editData.total_area,
      create_time: editData.create_time ? dayjs(editData.create_time).format('YYYY年MM月DD日') : '',
      create_name: editData.create_name,
    };
    if (formData.value.event_type) {
      handleTypeChange(formData.value.event_type);
      setTimeout(() => {
        formData.value.algorithm_name = editData.algorithm_name;
      }, 100);
    }
  }
});
</script>
<style scoped lang="scss">
.rightContainer {
  position: absolute;
  top: 0;
  right: 0;
  height: 95%;
  z-index: 99;
  width: 324px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
  :deep(.el-textarea__inner) {
    background: transparent !important;
    color: #ffffff !important;
  }
  .contenttitle {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: bold;
    font-size: 16px;
    color: #ffffff;
    margin-bottom: 20px;
  }
  .centerBox {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 14px;
    color: #ffffff;
    .itemRow {
      margin-bottom: 35px;
    }
    .label {
      margin-bottom: 5px;
    }
  }
  .detailInfo {
    .infpItem {
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 400;
      font-size: 14px;
      color: #DFDFDF;
      margin-bottom: 23px;
      .num {
        span {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: bold;
          font-size: 14px;
          color: #ffffff;
        }
         .areaStyle {
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: bold;
        font-size: 20px;
        color: #FFFFFF;
                }
      }
      .itemData {
        width: 50%;
      }
    }
  }
  .btnGroups {
    display: flex;
    justify-content: center;
    position: absolute;
    bottom: 59px;
    left: 0;
    right: 0;
    img {
      width: 125px;
      height: 39px;
      cursor: pointer;
    }
  }
}
.filter-item {
  margin-bottom: 12px;
}
</style>
src/views/layerManagement/components/leftList.vue
New file
@@ -0,0 +1,463 @@
<template>
  <div class="table-overlay">
    <div class="searchBox">
      <el-input
        class="ztzf-layer-input inputName"
        v-model="formData.dkbh"
        placeholder="输入名称模糊搜索"
      ></el-input>
      <div class="searchAndReset">
        <el-button type="primary" icon="el-icon-search" @click="search">搜索</el-button>
        <el-button class="ztzf-button" icon="el-icon-refresh" @click="reset">清空</el-button>
      </div>
    </div>
    <div class="btnGroups">
      <el-button v-if="activeName !== '国土空间规划'" class="ztzf-button" @click="addFence">{{
        props.activeName === '电子围栏' ? '新增围栏' : '新增禁飞区'
      }}</el-button>
      <el-button v-if="activeName !== '国土空间规划'" class="ztzf-button" @click="addFolder"
        >新增文件夹</el-button
      >
      <el-upload
        v-if="activeName === '国土空间规划'"
        action="#"
        :show-file-list="false"
        :before-upload="e => uploadFlightFile(e, '1')"
        accept=".zip"
      >
        <el-button class="ztzf-button"> 上传 </el-button>
      </el-upload>
      <template v-if="activeName === '自定义禁飞区'">
        <template v-if="failuresInfo.length">
          <el-button class="ztzf-button" @click="synchronize"> 继续同步 </el-button>
          <el-button class="ztzf-button" @click="failuresDel"> 取消同步 </el-button>
        </template>
        <el-button v-else class="ztzf-button" @click="synchronize"> 同步 </el-button>
      </template>
      <el-button class="ztzf-button" @click="selectAll">全选</el-button>
    </div>
    <div
      class="tableListBox"
      v-loading="loading"
      element-loading-background="rgba(0, 0, 0, 0.5)"
      element-loading-text="加载中..."
    >
      <el-tree
        class="ztzf-el-tree"
        :class="{ isTheTerritory: activeName === '国土空间规划' }"
        ref="treeRef"
        v-model="checkedKeys"
        :data="treeData"
        show-checkbox
        node-key="id"
        default-expand-all
        :expand-on-click-node="false"
        :filter-node-method="filterNode"
        @check="handleCheck"
        :props="{
          label: 'name',
          children: 'children',
        }"
      >
        <!-- 自定义节点内容 -->
        <template #default="{ node }">
          <div class="tree-node">
            <!-- 节点名称 -->
            <span>{{ node.label }}</span>
            <!-- 操作按钮组 -->
            <div class="tree-node-actions">
              <el-button
                link
                @click.stop="handleLocation(node)"
              >定位</el-button>
              <el-button link @click.stop="handleEdit(node)">编辑</el-button>
              <el-button link @click.stop="handleDelete(node)">删除</el-button>
            </div>
          </div>
        </template>
      </el-tree>
    </div>
  </div>
</template>
<script setup>
import { ElMessage, ElMessageBox } from 'element-plus';
import {
  treeDataApi,
  deleteFenceApi,
  deleteFolderApi,
  spatialPlanningApi,
  failuresListApi,
  failuresDelApi,
  areasUpdateApi,
} from '@/api/layer/index';
import { inject, nextTick, onMounted, watch } from 'vue';
const emit = defineEmits(['update:coverData', 'update:deitData', 'newFencesMethods']);
import EventBus from '@/utils/eventBus';
const coverData = ref();
const layerParams = inject('layerParams');
const checkedKeys = ref([]); // 存储勾选的节点key
const treeRef = ref(null); // 获取tree实例
const checkedNodes = ref([]); // 存储勾选的节点数据
const props = defineProps(['activeName']);
const formData = ref({
  isPush: undefined,
  page: 1,
  isGenerated: undefined,
  pageSize: 17,
  dkbh: '',
  patchesName: '',
});
const loading = ref(false);
const treeData = ref([]);
const treeAllData = ref([]);
// 获取数据
const gettreeDataApi = async () => {
  try {
    loading.value = true;
    const res = await treeDataApi();
    treeAllData.value = res.data.data;
    setupWatch();
  } catch (error) {
    console.error('获取数据失败:', error);
  } finally {
    loading.value = false;
  }
};
const setupWatch = () => {
  watch(
    () => props.activeName,
    newVal => {
      const matchedCategory = treeAllData.value.find(item => item.name === newVal);
      treeData.value = matchedCategory.children;
      nextTick(search);
      if (treeData.value && treeData.value.length > 0) {
        const folderNode = treeData.value[0];
        // 赋值给 layerParams
        layerParams.value.total_count = folderNode.total_count ? folderNode.total_count : 0;
        layerParams.value.total_area = folderNode.total_area ? folderNode.total_area : 0;
      }
    },
    { immediate: true }
  );
};
const handleCheck = (checkedNodesData, checkedKeysData) => {
  const filteredNodes = checkedKeysData.checkedNodes.filter(node => node.level === 3);
  checkedNodes.value = checkedNodesData.checkedNodes;
  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 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; // 同步更新 checkedKeys
      checkedNodes.value = allFenceNodes; // 存储 level 3 数据
      coverData.value = allFenceNodes; // 同步 coverData
    }
    emit('update:coverData', coverData.value);
  }
};
// 定位按钮事件
const handleLocation = node => {
  checkedKeys.value = [];
  checkedNodes.value = [];
  coverData.value = [];
  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 handleEdit = node => {
  if (node.data.level == 2) {
    layerParams.value.editFolder = true;
    layerParams.value.addAnEditingFolder = 2;
    if (props.activeName === '电子围栏') {
      layerParams.value.fileType = 1;
    } else if (props.activeName === '自定义禁飞区') {
      layerParams.value.fileType = 2;
    }
    emit('update:editFolder', node.data);
  } else if (node.data.level === 3) {
    checkedKeys.value = [];
    checkedNodes.value = [];
    coverData.value = [];
    layerParams.value.editNest = true;
    layerParams.value.decideWhetherToAddOrEdit = 2;
    if (props.activeName === '电子围栏') {
      layerParams.value.fenceType = 1; // 电子围栏类型标识
    } else if (props.activeName === '自定义禁飞区') {
      layerParams.value.fenceType = 2; // 自定义禁飞区类型标识
    }
    emit('update:deitData', node.data);
  }
};
// 删除按钮事件
const handleDelete = node => {
  let id = node.data.id;
  ElMessageBox.confirm('确定要删除该内容吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => {
      if (node.data.level === 2) {
        // 删除文件夹
        deleteFolderApi(id).then(res => {
          ElMessage.success('删除成功');
          gettreeDataApi();
          // 通知父组件删除该文件夹下所有围栏的地图实体
          EventBus.emit('deleteMapEntitiesByFolderId', id);
        });
      } else if (node.data.level === 3) {
        // 删除单个围栏/禁飞区
        deleteFenceApi(id).then(res => {
          ElMessage.success('删除成功');
          gettreeDataApi();
          // 通知父组件删除对应的地图实体
          EventBus.emit('deleteMapEntityById', id);
        });
      }
    })
    .catch(() => {
      ElMessage.warning('已取消删除');
    });
};
// 新增围栏
const addFence = () => {
  layerParams.value.addNest = true;
  layerParams.value.decideWhetherToAddOrEdit = 1;
  if (props.activeName === '电子围栏') {
    layerParams.value.fenceType = 1; // 电子围栏类型标识
  } else if (props.activeName === '自定义禁飞区') {
    layerParams.value.fenceType = 2; // 自定义禁飞区类型标识
  }
  emit('newFencesMethods');
};
// 新增文件夹
const addFolder = () => {
  layerParams.value.addFolder = true;
  layerParams.value.addAnEditingFolder = 1;
  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)) {
    return ElMessage.error('请上传zip格式的文件');
  }
  let data = new FormData();
  const params = {
    file: file,
    fileName: file.name,
  };
  Object.keys(params).forEach(key => {
    data.append(key, params[key]);
  });
  spatialPlanningApi(data).then(res => {
    ElMessage.success('上传成功');
    gettreeDataApi();
  });
}
function search() {
  treeRef.value?.filter(formData.value.dkbh);
}
function reset() {
  formData.value.dkbh = '';
  treeRef.value?.filter('');
}
function filterNode(value, data) {
  if (props.activeName === '国土空间规划') {
    if (!value) return data.level === 2 || data.level === 1;
    return (data.level === 2 || data.level === 1) && data.name.includes(value);
  }
  if (!value) return true;
  return data.name.includes(value);
}
// 同步信息
const failuresInfo = ref([]);
function failuresList() {
  failuresListApi().then(res => {
    failuresInfo.value = res.data.data || [];
  });
}
// 同步
function synchronize() {
  const data = failuresInfo.value.map(item => item.device_sn);
  areasUpdateApi(data).then(res => {
    if (res.data.data.length) {
      let stringVal = res.data.data.reduce((acc, cur) => {
        return acc + cur.nickname + '(' + cur.reason + ')' + ',';
      }, '');
      stringVal = stringVal.slice(0, -1);
      ElMessageBox.alert(stringVal, '同步失败', {
        confirmButtonText: '确定',
      });
    } else {
      ElMessage.success('同步成功');
    }
  });
}
// 取消同步
function failuresDel() {
  failuresDelApi().then(res => {
    ElMessage.success('取消同步成功');
  });
}
onMounted(() => {
  failuresList();
  gettreeDataApi();
  EventBus.on('gettreeDataApi', gettreeDataApi);
});
onBeforeUnmount(() => {
  EventBus.off('gettreeDataApi', gettreeDataApi);
});
</script>
<style scoped lang="scss">
.table-overlay {
  position: absolute;
  top: 0;
  left: 0;
  height: 95%;
  z-index: 99;
  width: 392px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
}
.searchBox {
  display: flex;
  margin-top: 10px;
  .inputName {
    width: 184px;
    height: 32px;
  }
  .searchAndReset {
    width: 182px;
    display: flex;
    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 {
  margin: 12px 0 14px 0;
  display: flex;
  gap: 0 10px;
  .el-button {
    margin: 0 !important;
  }
}
.tableListBox {
  :deep(.el-tree-node__content) {
  height: 38px !important;
  line-height: 38px !important;
  }
  overflow-y: scroll;
  height: 0;
  flex-grow: 1;
  .isTheTerritory {
    :deep() {
      .el-tree-node__expand-icon {
        visibility: hidden;
      }
    }
  }
  :deep() {
    .el-checkbox {
      --el-checkbox-bg-color: transparent !important;
    }
  }
  .tree-node {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
  }
  .tree-node-actions {
    display: flex;
    // gap: 8px; // 按钮之间的间距
    button {
      padding: 0; // 清除默认内边距
      // color: #fff; // 按钮图标颜色
       color: #409eff;
      &:hover {
        color: #409eff; //  hover 时的颜色
      }
    }
  }
}
</style>
src/views/layerManagement/components/nationalSpatialPlanning.vue
New file
@@ -0,0 +1,49 @@
<template>
  <div class="spLeft">
    <div class="input-search">
      <el-input placeholder="请输入关键字" clearable></el-input>
      <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
      <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
    </div>
  </div>
  <div class="spRight"></div>
</template>
<script setup>
function handleSearch() {}
function handleReset() {}
</script>
<style scoped lang="scss">
.spLeft {
  position: absolute;
  top: 0;
  left: 0;
  height: 95%;
  z-index: 99;
  width: 392px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
  .input-search {
    display: flex;
    .el-input {
      margin-right: 10px;
    }
  }
}
.spRight {
  position: absolute;
  top: 0;
  right: 0;
  height: 95%;
  z-index: 99;
  width: 324px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
}
</style>
src/views/layerManagement/components/rightEdit.vue
New file
@@ -0,0 +1,420 @@
<template>
  <div class="rightContainer">
    <div class="contenttitle">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}信息</div>
    <div class="centerBox">
      <div class="itemRow">
        <div class="label">文件夹名称</div>
        <div>
          <el-select
            :disabled="isDisabled"
            class="ztzf-layer-search"
            clearable
            v-model="formData.folder_id"
            placeholder="请选择"
          >
            <el-option
              v-for="item in options"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </div>
      </div>
      <div class="itemRow">
        <div class="label">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}名称</div>
        <div>
          <el-input
            :disabled="isDisabled"
            class="ztzf-layer-input inputName"
            v-model="formData.name"
            placeholder="请输入"
          />
        </div>
      </div>
      <div class="itemRow" v-if="layerParams.fenceType === 2">
        <div class="label">禁飞区管控时间</div>
        <div>
          <!-- popper-class="ztzf-layer-date-picker-popper  " -->
          <el-date-picker
            :disabled="isDisabled"
            class="ztzf-layer-date-picker"
            v-model="timeValue"
            type="datetimerange"
            format="YYYY-MM-DD HH:mm"
            range-separator="至"
            start-placeholder="开始日期"
            end-placeholder="结束日期"
          />
        </div>
      </div>
      <div class="itemRow">
        <div class="label">{{ layerParams.fenceType === 1 ? '围栏' : '禁飞区' }}备注</div>
        <div>
          <el-input
            :disabled="isDisabled"
            v-model="formData.description"
            :rows="5"
            type="textarea"
            placeholder="请输入"
          />
        </div>
      </div>
    </div>
    <div class="detailInfo">
      <div class="infpItem" v-if="layerParams.editNest && layerParams.fenceType === 1">
        <div class="itemData">
          <div class="title">围栏面积</div>
          <div class="num">
            <span class="areaStyle">{{ fenceArea }}</span> m²
          </div>
        </div>
        <div class="itemData">
          <div class="title">创建人</div>
          <div class="num">
            <span>{{ formData?.create_name }}</span>
          </div>
        </div>
      </div>
      <div class="infpItem" v-if="layerParams.fenceType === 2">
        <div class="itemData" v-if="layerParams.editNest && layerParams.fenceType === 2">
          <div class="title">禁飞区面积</div>
          <div class="num">
            <span class="areaStyle">{{ fenceArea }}</span> m²
          </div>
        </div>
        <div class="itemData">
          <div class="title">禁飞区高度</div>
          <div class="num noFly">
            <span
              ><el-input
                :disabled="isDisabled"
                class="ztzf-layer-input inputName"
                v-model="formData.altitude"
                placeholder="请输入"
            /></span>
            米
          </div>
        </div>
      </div>
      <div class="infpItem" v-if="layerParams.editNest && layerParams.fenceType === 2">
        <div class="itemData">
          <div class="title">创建时间</div>
          <div class="num">
            <span>{{ formData?.create_time }}</span>
          </div>
        </div>
        <div class="itemData">
          <div class="title">创建人</div>
          <div class="num">
            <span>{{ formData?.create_name }}</span>
          </div>
        </div>
      </div>
      <div class="infpItem" v-if="layerParams.editNest && layerParams.fenceType === 1">
        <div class="itemData">
          <div class="title">创建时间</div>
          <div class="num">
            <span>{{ formData?.create_time }}</span>
          </div>
        </div>
      </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="" />
    </div>
  </div>
</template>
<script setup>
import { ElMessage } from 'element-plus';
import dayjs from 'dayjs';
import { deitFenceApi, addFenceApi } from '@/api/layer/index';
import EventBus from '@/utils/eventBus';
const emit = defineEmits(['callParentMethod']);
const layerParams = inject('layerParams');
const options = ref([]);
const timeValue = ref('');
options.value = layerParams.value.folderOption;
const formData = ref({
  folder_id: '',
  name: '',
  description: '',
  is_enabled: false,
  area: '',
  altitude: '',
  control_start_time: '',
  control_end_time: '',
  geo_data: '',
});
const detailData = ref(null);
const fenceArea = ref(0);
detailData.value = layerParams.value.editDetailData;
fenceArea.value = layerParams.value.fenceArea;
const isDisabled = computed(() => {
  return layerParams.value.editingIsProhibited;
});
watch(
  () => layerParams.value.fenceArea,
  newArea => {
    if (newArea !== undefined && newArea !== null) {
      fenceArea.value = newArea;
      formData.value.area = newArea;
    }
  },
  { immediate: true }
);
// 监听editDetailData的变化
watch(
  () => layerParams.value.editDetailData,
  newVal => {
    if (newVal && layerParams.value.editingIsProhibited) {
      // 当editDetailData有值时,填充表单
      formData.value = {
        ...formData.value,
        folder_id: newVal.folder_id || '',
        name: newVal.name || '',
        description: newVal.description || '',
        is_enabled: newVal.is_enabled || false,
        area: newVal.area || '',
        altitude: newVal.altitude || '',
        control_start_time: newVal.control_start_time || '',
        control_end_time: newVal.control_end_time || '',
        geo_data: newVal.geo_data || '',
        create_name: newVal.create_name || '',
        create_time: newVal.create_time ? dayjs(newVal.create_time).format('YYYY年MM月DD日') : '',
      };
      // 处理文件夹选择
      if (newVal.folder_id && options.value.length > 0) {
        const matchedFolder = options.value.find(item => item.value === String(newVal.folder_id));
        if (matchedFolder) {
          formData.value.folder_id = matchedFolder.value;
        }
      }
      // 处理时间选择
      if (newVal.control_start_time && newVal.control_end_time) {
        timeValue.value = [
          dayjs(newVal.control_start_time).toDate(),
          dayjs(newVal.control_end_time).toDate(),
        ];
      }
    }
  },
  { immediate: true, deep: true }
);
watch(
  () => timeValue.value,
  newTimeArr => {
    if (Array.isArray(newTimeArr) && newTimeArr.length === 2) {
      const startTime = newTimeArr[0];
      const endTime = newTimeArr[1];
      formData.value.control_start_time = dayjs(startTime).format('YYYY-MM-DD HH:mm:ss');
      formData.value.control_end_time = dayjs(endTime).format('YYYY-MM-DD HH:mm:ss');
    } else {
      formData.value.control_start_time = '';
      formData.value.control_end_time = '';
    }
  },
  { immediate: true }
);
const clearAllData = () => {
  detailData.value = {};
  layerParams.value.decideWhetherToAddOrEdit = 1;
  // 重置面板状态和缓存数据
  layerParams.value.addNest = false;
  layerParams.value.editNest = false;
  layerParams.value.editDetailData = null;
  layerParams.value.polygonPosition = null;
  layerParams.value.editingIsProhibited = false;
};
const cancelHandel = () => {
  clearAllData();
  emit('callParentMethod');
  EventBus.emit('gettreeDataApi');
};
const submitHandle = () => {
  if (!formData.value.folder_id) {
    ElMessage.error('请选择文件夹');
    return;
  }
  if (!formData.value.name) {
    ElMessage.error('请输入名称');
    return;
  }
  if (!formData.value.altitude && layerParams.value.fenceType === 2) {
    ElMessage.error('请输入高度');
    return;
  }
  if (fenceArea.value === 0) {
    ElMessage.error('请绘制区域');
    return;
  }
  const params = {
    folder_id: formData.value.folder_id,
    name: formData.value.name,
    description: formData.value.description,
    is_enabled: true,
    area: formData.value.area,
    altitude: formData.value.altitude,
    geo_data: layerParams.value.polygonPosition
      ? layerParams.value.polygonPosition
      : formData.value.geo_data,
    control_start_time: formData.value.control_start_time,
    control_end_time: formData.value.control_end_time,
  };
  // 如果是编辑模式,才添加 id
  if (layerParams.value.decideWhetherToAddOrEdit === 2) {
    params.id = detailData.value.id;
  }
  if (layerParams.value.decideWhetherToAddOrEdit === 1) {
    //围栏新增
    addFenceApi(params).then(res => {
      ElMessage.success('新增成功');
      EventBus.emit('gettreeDataApi');
      clearAllData();
      emit('callParentMethod');
    });
  } else if (layerParams.value.decideWhetherToAddOrEdit === 2) {
    //围栏编辑
    deitFenceApi(params).then(res => {
      ElMessage.success('编辑成功');
      EventBus.emit('gettreeDataApi');
      clearAllData();
      emit('callParentMethod');
    });
  }
};
onMounted(() => {
  // 获取判断状态(默认设为 1)
  const addOrEditStatus = layerParams.value.decideWhetherToAddOrEdit || 1;
  if (addOrEditStatus === 2) {
    // 编辑状态:回显数据
    const editData = layerParams.value.editDetailData || {};
    formData.value = {
      folder_id: editData.folder_id || '',
      name: editData.name || '',
      description: editData.description || '',
      is_enabled: editData.is_enabled || false,
      area: editData.area || '',
      altitude: editData.altitude || '',
      control_start_time: editData.control_start_time || '',
      control_end_time: editData.control_end_time || '',
      geo_data: editData.geo_data || '',
      create_name: editData.create_name,
      create_time: editData.create_time ? dayjs(editData.create_time).format('YYYY年MM月DD日') : '',
    };
    if (editData.folder_id && options.value.length > 0) {
      const matchedFolder = options.value.find(item => item.value === String(editData.folder_id));
      if (matchedFolder) {
        formData.value.folder_id = matchedFolder.value;
      }
    }
    if (editData.control_start_time && editData.control_end_time) {
      timeValue.value = [
        dayjs(editData.control_start_time).toDate(),
        dayjs(editData.control_end_time).toDate(),
      ];
    }
    fenceArea.value = editData.area || 0;
  }
});
</script>
<style scoped lang="scss">
.rightContainer {
  position: absolute;
  top: 0;
  right: 0;
  height: 95%;
  z-index: 99;
  width: 324px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 8px 8px 8px 8px;
  padding: 16px 12px;
  :deep(.el-textarea__inner) {
    background: transparent !important;
    color: #ffffff !important;
  }
  .contenttitle {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: bold;
    font-size: 16px;
    color: #ffffff;
    margin-bottom: 20px;
  }
  .centerBox {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 14px;
    color: #ffffff;
    .itemRow {
      margin-bottom: 35px;
    }
    .label {
      margin-bottom: 5px;
    }
    :deep(.el-date-editor.el-input__wrapper) {
      width: auto !important;
      background: transparent !important;
    }
    :deep(.el-range-editor.is-disabled input) {
      background-color: transparent !important;
    }
  }
  .detailInfo {
    .infpItem {
      display: flex;
      justify-content: space-between;
      align-items: center;
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 400;
      font-size: 14px;
      color: #dfdfdf;
      margin-bottom: 23px;
      .num {
        span {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: bold;
          font-size: 14px;
          color: #ffffff;
        }
        .areaStyle {
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: bold;
          font-size: 20px;
          color: #ffffff;
        }
      }
      .noFly {
        display: flex;
        align-items: center;
      }
      .itemData {
        width: 50%;
      }
    }
  }
  .btnGroups {
    display: flex;
    justify-content: center;
    position: absolute;
    bottom: 29px;
    left: 0;
    right: 0;
    img {
      width: 125px;
      height: 39px;
      cursor: pointer;
    }
  }
}
</style>
src/views/layerManagement/components/utils.js
New file
@@ -0,0 +1,651 @@
import * as Cesium from 'cesium'
import _, { cloneDeep, throttle } from 'lodash'
import * as turf from '@turf/turf'
import { boxTransformScale } from '@/utils/turfFunc'
/**
 * 多边形绘制与编辑工具类
 * 功能:
 *  - 绘制多边形
 *  - 拖动编辑端点
 *  - 删除端点、删除整个多边形
 *  - 多边形自交检查(避免非法几何)
 *  - 外部订阅/通知机制
 */
export class DrawPolygon {
    constructor() {
        // 是否绘制
        this.whetherToDraw =false
        // 图斑预览模式标记
        this.isPureSpotPreview = false
        // 是否删除测区
        this.isDeleteTheArea = true
        //是否可以编辑图斑
        this.isPreviewMode = true
        // Cesium 视图对象
        this.viewer = null
        // 当前绘制的多边形
        this.curPolygon = null
        // 绘制模式标识
        this.drawingMode = false
        // 编辑模式标识
        this.editingMode = false
        // 是否正在拖拽端点
        this.isDragging = false
        // 当前拖拽的点实体
        this.draggedEntity = null
        // 多边形实体
        this.polygonEntity = null
        // 存储端点的 DataSource
        this.editPolygonDataSource = null
        this.editPolygonPointDataSource = null
        // 鼠标事件处理器
        this.handler = null
        // 右键菜单 DOM
        this.menuPopup = null
        // 被右键选中的点
        this.delPolygonPoint = null
        // 是否显示警告提示(自交)
        this.isShowWaringTip = false
        // 当前拖拽点是否合法
        this.currentDragPointIsValid = false
        // 当前拖拽点的坐标
        this.currentDragPointPosition = null
        // 事件回调函数绑定 this
        this.handleLeftDown = this.handleLeftDown.bind(this)
        this.handleLeftUp = this.handleLeftUp.bind(this)
        this.handleMouseMove = this.handleMouseMove.bind(this)
        this.handleLeftClick = this.handleLeftClick.bind(this)
        this.handleRightClick = this.handleRightClick.bind(this)
        this.delPolygon = this.delPolygon.bind(this)
        this.delPoint = this.delPoint.bind(this)
        // 外部订阅者
        this.listeners = []
    }
    // 实体命名常量
    static ENTITY_NAMES = {
        POLYGON: '区域-面',
        POINT: '区域-端点',
        POLYGON_ID: 'planar-route-polygon',
        POINT_ID_PREFIX: 'planar-route-point',
    }
    // 颜色常量
    static COLORS = {
        DEFAULT_POLYGON: Cesium.Color.fromBytes(45, 140, 240, 99), // 默认面颜色
        DEFAULT_LINE: Cesium.Color.fromBytes(45, 140, 240, 255), // 默认边界线颜色
        ERROR_POLYGON: Cesium.Color.fromCssColorString('rgba(255, 0, 0, .3)'), // 错误(自交)面颜色
        ERROR_LINE: Cesium.Color.fromCssColorString('rgba(255, 0, 0, 1)'), // 错误(自交)线颜色
    }
    // ============ 发布订阅机制 ============
    // 外部订阅数据变化
    subscribe(key, listener) {
        this.listeners.push({ key, listener })
    }
    // 通知订阅者
    notify(key, data) {
        this.listeners.filter(subscriber => subscriber.key === key).forEach(subscriber => subscriber.listener(data))
    }
    // ============ 绘制相关 ============
    // 编辑图斑
    editThePatch(data) {
        this.isPreviewMode = data
    }
    // 绘制
    drawTheArea(data){
        this.whetherToDraw = data
    }
    // 删除测区
    deleteTheArea(data) {
        this.isDeleteTheArea = data
    }
    // 开始绘制
    startDrawing() {
        if (!this.whetherToDraw) {
            return;
          }
        this.drawingMode = true
        this.curPolygon = new Cesium.PolygonHierarchy()
        // 如果还没有 DataSource,就新建一个
        if (!this.editPolygonDataSource) {
            this.editPolygonDataSource = new Cesium.CustomDataSource('editPolygonDataSource')
            this.viewer?.dataSources.add(this.editPolygonDataSource)
        }
        if (!this.editPolygonPointDataSource) {
            this.editPolygonPointDataSource = new Cesium.CustomDataSource('editPolygonPointDataSource')
            this.viewer?.dataSources.add(this.editPolygonPointDataSource)
        }
        // 清空之前的点
        this.editPolygonDataSource?.entities.removeAll()
        this.editPolygonPointDataSource?.entities.removeAll()
    }
    // 创建多边形实体(含边界线)
    createPolygonEntity() {
        this.polygonEntity = this.editPolygonDataSource.entities.add({
            name: DrawPolygon.ENTITY_NAMES.POLYGON,
            id: DrawPolygon.ENTITY_NAMES.POLYGON_ID,
            polygon: {
                hierarchy: new Cesium.CallbackProperty(() => this.curPolygon, false),
                material: DrawPolygon.COLORS.DEFAULT_POLYGON,
                outline: false,
                outlineWidth: 2,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
            },
            polyline: {
                width: 2,
                material: DrawPolygon.COLORS.DEFAULT_LINE,
                clampToGround: true,
                positions: new Cesium.CallbackProperty(
                    () => [...this.curPolygon.positions, this.curPolygon.positions[0]], // 闭合线
                    false
                ),
            },
        })
    }
    // 创建端点实体
    createPointEntity(position, isAdd) {
        const pointIndex = isAdd ? this.curPolygon.positions.length - 2 : this.curPolygon.positions.length - 1
        this.editPolygonPointDataSource.entities.add({
            name: DrawPolygon.ENTITY_NAMES.POINT,
            id: `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}${pointIndex}`,
            position: position.clone(),
            point: {
                pixelSize: 14,
                color: Cesium.Color.WHITE,
                heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
                disableDepthTestDistance: Number.POSITIVE_INFINITY,
            },
            customData: {
                ind: pointIndex, // 索引记录
            },
        })
    }
    // 清除不在范围内的点
    removeLastInvalidPoint() {
        const posLen = this.curPolygon.positions.length
        if (posLen === 0) return
        let targetPointId = ''
        let removeCount = 0
        if (posLen === 2) {
            removeCount = 2
            targetPointId = `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}0`
        } else if (posLen > 2) {
            removeCount = 1
            const pointIndex = posLen - 2
            targetPointId = `${DrawPolygon.ENTITY_NAMES.POINT_ID_PREFIX}${pointIndex}`
        }
        this.curPolygon.positions.splice(posLen - removeCount, removeCount)
        const invalidPoint = this.editPolygonPointDataSource.entities.getById(targetPointId)
        if (invalidPoint) {
            this.editPolygonPointDataSource.entities.remove(invalidPoint)
        }
        if (this.curPolygon.positions.length === 0 && this.polygonEntity) {
            this.editPolygonDataSource.entities.remove(this.polygonEntity)
            this.polygonEntity = null
        }
    }
    // 添加一个点
    addPosition(position, isAdd = true) {
        // 第一个点要重复压入一次,形成动态绘制效果
        if (this.curPolygon.positions.length === 0 && isAdd) {
            this.curPolygon.positions.push(position.clone())
        }
        this.curPolygon.positions.push(position.clone())
        // 如果没有实体则创建
        if (!this.polygonEntity) {
            this.createPolygonEntity()
        }
        // 创建端点实体
        if (this.isPreviewMode) {
            this.createPointEntity(position, isAdd)
        }
        this.notify('getPoints', position)
    }
    // ============ 鼠标事件 ============
    // 鼠标左键按下(选中端点拖动)
    handleLeftDown(movement) {
        if (!this.editingMode) return
        const pickedEntity = this.viewer.scene.pick(movement.position)?.id
        const isPoint = pickedEntity?.name === DrawPolygon.ENTITY_NAMES.POINT
        if (pickedEntity && isPoint) {
            this.isDragging = true
            this.draggedEntity = pickedEntity
            this.currentDragPointPosition = this.curPolygon.positions[this.draggedEntity?.customData.ind]
            this.disableMapControl() // 禁止地图交互
        }
    }
    // 鼠标左键抬起(拖拽结束)
    handleLeftUp() {
        if (!(this.editingMode && this.curPolygon?.positions && this.draggedEntity)) return
        if (this.currentDragPointIsValid && this.isDragging) {
            // 更新点位置
            this.draggedEntity.position = this.currentDragPointPosition
            this.curPolygon.positions[this.draggedEntity?.customData.ind] = this.currentDragPointPosition
            // 校验多边形是否自交
            if (!this.curDragPointIsValid(this.curPolygon.positions)) {
                this.isShowWaringTip = true
                this.currentDragPointIsValid = true
                this.updatePolygonAppearance(DrawPolygon.COLORS.ERROR_POLYGON, DrawPolygon.COLORS.ERROR_LINE)
            } else {
                this.isShowWaringTip = false
                this.currentDragPointIsValid = false
                this.updatePolygonAppearance(DrawPolygon.COLORS.DEFAULT_POLYGON, DrawPolygon.COLORS.DEFAULT_LINE)
            }
            this.notify('getShowWaringTip', this.isShowWaringTip)
        }
        // 拖拽结束,恢复交互
        if (this.isDragging) {
            this.notify('getPolygonPositions', this.curPolygon.positions)
            this.isDragging = false
            this.draggedEntity = null
            this.enableMapControl()
        }
    }
    // 鼠标移动
    handleMouseMove(movement) {
        if (!this.drawingMode && !this.editingMode) return
        const cartesian = this.viewer.scene.pickPosition(movement.endPosition)
        if (!cartesian) return
        // 编辑模式下,拖拽点实时更新
        if (this.editingMode && this.draggedEntity?.customData) {
            this.draggedEntity.position = cartesian
            this.curPolygon.positions[this.draggedEntity?.customData.ind] = cartesian
        }
        // 绘制模式下,实时更新最后一个点
        if (this.drawingMode && this.polygonEntity && this.curPolygon.positions.length >= 1) {
            this.curPolygon.positions.pop()
            this.curPolygon.positions.push(cartesian)
        }
        // 实时检查是否自交
        if (
            (this.editingMode && this.draggedEntity?.customData) ||
            (this.drawingMode && this.polygonEntity && this.curPolygon.positions.length >= 3)
        ) {
            if (!this.curDragPointIsValid(this.curPolygon.positions)) {
                this.isShowWaringTip = true
                this.currentDragPointIsValid = true
                this.updatePolygonAppearance(DrawPolygon.COLORS.ERROR_POLYGON, DrawPolygon.COLORS.ERROR_LINE)
            } else {
                this.isShowWaringTip = false
                this.currentDragPointIsValid = false
                this.updatePolygonAppearance(DrawPolygon.COLORS.DEFAULT_POLYGON, DrawPolygon.COLORS.DEFAULT_LINE)
            }
            this.notify('getShowWaringTip', this.isShowWaringTip)
        }
    }
    // 鼠标左键点击
    handleLeftClick(click) {
        this.removeMenuPopup()
        const pickedAllEntity = this.viewer.scene.drillPick(click.position).filter(i => i.id)
        const isPolygonPoint = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POINT)
        const isPolygon = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POLYGON)
        if (this.drawingMode && !this.whetherToDraw) {
            return; // 阻断后续绘制逻辑
          }
        // 如果不是绘制模式
        if (!this.drawingMode) {
            if (!isPolygon) {
                this.editingMode = false
                this.editPolygonPointDataSource.entities.show = false
                return
            }
            if (isPolygon) {
                this.editingMode = true
                this.editPolygonPointDataSource.entities.show = true
            }
            return
        }
        // 点击闭合多边形
        if (this.curPolygon.positions.length < 4 && isPolygonPoint) {
            return
        }
        if (this.curPolygon.positions.length >= 4 && isPolygonPoint) {
            if (!this.drawingMode) return
            this.finishDrawing()
            return
        }
        // 添加新的点
        const cartesian = this.viewer.scene.pickPosition(click.position)
        if (!cartesian) return
        this.addPosition(cartesian)
    }
    // 鼠标右键点击(弹出菜单)
    handleRightClick(click) {
        const that = this
        if (that.drawingMode) return
        that.removeMenuPopup()
        const pickedAllEntity = that.viewer.scene.drillPick(click.position).filter(i => i.id)
        const isPolygon = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POLYGON)
        const isEditPoint = pickedAllEntity.find(i => i.id.name === DrawPolygon.ENTITY_NAMES.POINT)
        const {
            position: { x, y },
        } = click
        let pickedEntity, tooltipEvent, menuType
        if (isPolygon) {
            pickedEntity = isPolygon
            tooltipEvent = that.delPolygon
            menuType = 'polygon'
        } else if (isEditPoint) {
            pickedEntity = isEditPoint
            tooltipEvent = that.delPoint
            menuType = 'edit-point'
        }
        if (pickedEntity && this.isDeleteTheArea) {
            that.delPolygonPoint = pickedEntity.id
            that.menuPopup = that.createMenuPopup(menuType)
            that.menuPopup.style.transform = `translate3d(${x - 10}px, ${y - 10}px, 0)`
            that.viewer.container.appendChild(that.menuPopup)
            that.menuPopup.addEventListener('click', tooltipEvent)
        }
    }
    // ============ 删除相关 ============
    // 删除所有实体
    removeEntities() {
        if (this.editPolygonDataSource) {
            this.editPolygonDataSource.entities.removeAll()
            this.editPolygonDataSource = null
        }
        if (this.editPolygonPointDataSource) {
            this.editPolygonPointDataSource.entities.removeAll()
            this.editPolygonPointDataSource = null
        }
        this.editingMode = false
        this.polygonEntity = null
        this.curPolygon = null
    }
    // 完成绘制
    finishDrawing() {
        this.curPolygon.positions.pop()
        if (this.curPolygon.positions.length >= 3) {
            this.drawingMode = false
            this.editingMode = true
            this.editPolygonPointDataSource.entities.show = true
            if (!this.curDragPointIsValid(this.curPolygon.positions)) {
                this.isShowWaringTip = true
                this.currentDragPointIsValid = true
                this.updatePolygonAppearance(DrawPolygon.COLORS.ERROR_POLYGON, DrawPolygon.COLORS.ERROR_LINE)
            } else {
                this.isShowWaringTip = false
                this.currentDragPointIsValid = false
                this.updatePolygonAppearance(DrawPolygon.COLORS.DEFAULT_POLYGON, DrawPolygon.COLORS.DEFAULT_LINE)
            }
            this.notify('getShowWaringTip', this.isShowWaringTip)
            this.notify('getPolygonPositions', this.curPolygon.positions)
        }
    }
    // 删除多边形
    delPolygon() {
        this.removeEntities()
        this.removeMenuPopup()
        this.notify('getPolygonPositions', [])
        this.startDrawing()
    }
    // 删除图斑
    delSpot() {
        this.removeEntities()
        this.isPreviewMode = true
        this.startDrawing()
    }
    // 删除端点
    delPoint() {
        if (this.curPolygon.positions.length <= 3) {
            this.removeMenuPopup()
            return ElMessage.warning('端点不可少于3个')
        }
        if (!this.delPolygonPoint) return
        this.curPolygon.positions.splice(this.delPolygonPoint.customData.ind, 1)
        this.editPolygonPointDataSource.entities.remove(this.delPolygonPoint)
        this.removeMenuPopup()
        // 更新剩余点索引
        this.editPolygonPointDataSource.entities.values.forEach((item, index) => {
            item.customData.ind = index
        })
        this.notify('getPolygonPositions', this.curPolygon.positions)
    }
    // ============ 工具方法 ============
    // 创建右键菜单
    createMenuPopup(type = 'polygon') {
        const menuPopupVBox = document.createElement('div')
        menuPopupVBox.id = 'planarPolygonEdit'
        menuPopupVBox.className = 'planar-polygon-edit-tooltip'
        const menuPopup = document.createElement('div')
        menuPopup.id = 'planarPolygonEditMenu'
        menuPopup.className = 'planar-polygon-edit-menu'
        const menuItems =
            type === 'polygon'
                ? [{ title: '删除测区', class: 'del-planar-polygon' }]
                : [{ title: '删除端点', class: 'del-planar-point' }]
        menuItems.forEach(item => {
            const titleDiv = document.createElement('div')
            titleDiv.innerText = item.title
            titleDiv.className = item.class
            menuPopup.appendChild(titleDiv)
        })
        this.isPreviewMode = true
        menuPopupVBox.appendChild(menuPopup)
        return menuPopupVBox
    }
    // 移除菜单
    removeMenuPopup() {
        const that = this
        if (that.menuPopup) {
            that.menuPopup.removeEventListener('click', that.delPolygon)
            that.menuPopup.removeEventListener('click', that.delPoint)
            that.viewer.container.removeChild(that.menuPopup)
            that.menuPopup = null
        }
        that.delPolygonPoint = null
    }
    // 更新多边形样式(正常/错误)
    updatePolygonAppearance(polygonColor, lineColor) {
        this.polygonEntity.polygon.material = polygonColor
        this.polygonEntity.polyline.material = lineColor
    }
    // 禁用地图交互
    disableMapControl() {
        const controller = this.viewer.scene.screenSpaceCameraController
        controller.enableRotate = false
        controller.enableTranslate = false
        controller.enableZoom = false
    }
    // 启用地图交互
    enableMapControl() {
        const controller = this.viewer.scene.screenSpaceCameraController
        controller.enableRotate = true
        controller.enableTranslate = true
        controller.enableZoom = true
    }
    // 检查多边形是否自交
    curDragPointIsValid(positions) {
        if (positions.length < 3) return true
        const cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray(positions)
        const latLngPoints = cartographics.map(cartographic => [
            Cesium.Math.toDegrees(cartographic.longitude),
            Cesium.Math.toDegrees(cartographic.latitude),
        ])
        // 用 turf.js 检查自交
        const poly = turf.polygon([[...latLngPoints, latLngPoints[0]]])
        const intersections = turf.kinks(poly)
        return intersections.features.length === 0
    }
    // 初始化已有多边形
    initPolygon(viewer, positions, isPurePreview = false) {
        this.initHandler(viewer)
        if (this.whetherToDraw) {
            this.startDrawing();
          } else {
            // 新增:不允许绘制时,确保绘制模式为 false,避免误触发
            this.drawingMode = false;
          }
        let newPosition = positions.map(item => {
            return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item.height))
        })
        newPosition.forEach(item => {
            this.addPosition(item, false)
        })
        // 视角飞入区域
        const newBox = boxTransformScale(
            positions.map(item => [item.lng, item.lat]),
            5
        )
        viewer.camera.flyTo({
            destination: Cesium.Rectangle.fromDegrees(...newBox),
            offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
            duration: 0.5,
        })
        this.drawingMode = false
        this.editingMode = true
    }
    // 初始化事件处理器
    initHandler(viewer) {
        this.viewer = viewer
        if(this.whetherToDraw){
            this.startDrawing()
        }
        if (!this.handler) {
            this.handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
            // 注册鼠标事件
            const events = [
                [Cesium.ScreenSpaceEventType.LEFT_DOWN, this.handleLeftDown],
                [Cesium.ScreenSpaceEventType.LEFT_UP, this.handleLeftUp],
                [Cesium.ScreenSpaceEventType.MOUSE_MOVE, this.handleMouseMove],
                [Cesium.ScreenSpaceEventType.LEFT_CLICK, this.handleLeftClick],
                [Cesium.ScreenSpaceEventType.RIGHT_CLICK, this.handleRightClick],
            ]
            events.forEach(([type, handler]) => {
                this.handler.setInputAction(handler, type)
            })
        }
    }
    // 移除事件处理器
    removeHandler() {
        if (this.handler) {
            const eventTypes = [
                Cesium.ScreenSpaceEventType.LEFT_DOWN,
                Cesium.ScreenSpaceEventType.LEFT_UP,
                Cesium.ScreenSpaceEventType.MOUSE_MOVE,
                Cesium.ScreenSpaceEventType.LEFT_CLICK,
                Cesium.ScreenSpaceEventType.RIGHT_CLICK,
            ]
            eventTypes.forEach(type => {
                this.handler.removeInputAction(type)
            })
            this.handler = null
        }
    }
    /**
     * 销毁实例,释放资源
     */
    destroy() {
        if (!this.viewer) return
        this.removeMenuPopup()
        this.removeEntities()
        this.removeHandler()
        this.enableMapControl()
    }
}
src/views/layerManagement/index.vue
New file
@@ -0,0 +1,487 @@
<template>
  <basic-container>
    <div class="layerContainer">
      <div>
        <el-tabs v-model="activeType" @tab-click="handleClick">
          <el-tab-pane v-for="tab in tabData" :key="tab.type" :label="tab.name" :name="tab.type">
          </el-tab-pane>
        </el-tabs>
      </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
          v-if="
            !layerParams.addNest &&
            !layerParams.editNest &&
            !layerParams.addFolder &&
            !layerParams.editFolder
          "
          @update:coverData="handleCoverDataUpdate"
          @update:deitData="handleEdit"
          @update:editFolder="handleFolder"
          :activeName="activeName"
          @newFencesMethods="newFencesMethods"
        ></leftList>
        <rightEdit
          v-if="layerParams.addNest || layerParams.editNest"
          @callParentMethod="parentMethod"
        ></rightEdit>
        <folderFile
          @refreshMethod="refreshMethod"
          :activeName="activeName"
          v-if="layerParams.addFolder || layerParams.editFolder"
        ></folderFile>
      </div>
    </div>
  </basic-container>
</template>
<script setup>
import EventBus from '@/utils/eventBus';
import * as turf from '@turf/turf';
import { dataFolderApi } from '@/api/layer/index';
import folderFile from '@/views/layerManagement/components/folderFile.vue';
import { parseGeoDataToPositions } from '@/utils/geoParseUtil';
import { flyVisual } from '@/utils/cesium/mapUtil';
import rightEdit from '@/views/layerManagement/components/rightEdit.vue';
import leftList from '@/views/layerManagement/components/leftList.vue';
import nationalSpatialPlanning from '@/views/layerManagement/components/nationalSpatialPlanning.vue';
import { DrawPolygon } from '@/views/layerManagement/components/utils';
import * as Cesium from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import _, { cloneDeep, throttle } from 'lodash';
import { ElButton } from 'element-plus';
import { provide } from 'vue';
import { useStore } from 'vuex';
const store = useStore();
const userInfo = computed(() => store.getters.userInfo);
const areaCode = userInfo.value?.detail?.areaCode || '';
// console.log('用户信息',areaCode);
// 红色警告交叉面提示窗
const isShowWaringTip = ref(false);
const activeName = ref('电子围栏');
const activeType = ref('0');
let tbJwdList = [];
const selectDataList = ref([]);
// 当前面位置信息
let curPolygonPosition = [];
const tabData = ref([
  {
    name: '电子围栏',
    type: '0',
  },
  {
    name: '自定义禁飞区',
    type: '1',
  },
  {
    name: '国土空间规划',
    type: '2',
  },
]);
const layerParams = ref({
  addNest: false,
  editNest: false,
  editDetailData: null,
  total_count: 0,
  total_area: 0,
  polygonPosition: null,
  decideWhetherToAddOrEdit: null, //新增or编辑围栏
  addAnEditingFolder: null, //新增or编辑文件夹
  addFolder: false,
  editFolder: false,
  folderOption: [], //文件夹选项
  fenceType: 1,
  fenceArea: 0, //围栏面积
  fileType: 1, //文件夹类型
  editFolderData: null,
  editingIsProhibited: false, //禁止编辑
  isDetailShow: false,
});
const handleClick = tab => {
  const clickedTab = tabData.value.find(item => item.type === tab.paneName);
  activeType.value = clickedTab.type;
  activeName.value = clickedTab.name;
  getdataFolderApi();
  parentMethod();
  layerParams.value.addNest = false
    layerParams.value.editNest = false
     layerParams.value.addFolder = false
    layerParams.value.editFolder = false
};
const refreshMethod = () => {
  getdataFolderApi();
};
const getdataFolderApi = () => {
  let id;
  if (activeName.value === '电子围栏') {
    id = 1;
  } else if (activeName.value === '自定义禁飞区') {
    id = 2;
  } else {
    id = 3;
  }
  dataFolderApi(id).then(res => {
    const originalFolderList = res.data.data || [];
    const formattedFolderOption = originalFolderList.map(folder => ({
      label: folder.name,
      value: folder.id.toString(),
    }));
    layerParams.value.folderOption = formattedFolderOption;
  });
};
// 编辑围栏区域
// const handleEdit = val => {
// console.log('编辑数据',val.geo_data);
//   viewer.entities.removeAll();
//   layerParams.value.editDetailData = val;
//   drawPolygonExample.editThePatch(true);
//   drawPolygonExample.drawTheArea(true);
//   const positions = parseGeoDataToPositions(val.geo_data, val.altitude);
//   if (positions.length < 3) return; // 少于3个点无法构成多边形
//   drawPolygonExample.initPolygon(viewer, positions, true);
// };
// 编辑围栏区域
const handleEdit = val => {
  viewer.entities.removeAll();
  layerParams.value.editDetailData = val;
  drawPolygonExample.editThePatch(true);
  drawPolygonExample.drawTheArea(true);
  // 移除最后一个点
  let geoDataArr = JSON.parse(val.geo_data);
  if (geoDataArr.length > 0) {
    geoDataArr.pop();
  }
  const processedGeoData = JSON.stringify(geoDataArr);
  // 使用处理后的 geo_data 解析坐标
  const positions = parseGeoDataToPositions(processedGeoData, val.altitude);
  if (positions.length < 3) return; // 少于3个点无法构成多边形
  drawPolygonExample.initPolygon(viewer, positions, true);
};
// 编辑文件夹
const handleFolder = val => {
  layerParams.value.editFolderData = val;
};
// 新增开启绘制
const newFencesMethods = () => {
  viewer.entities.removeAll();
  drawPolygonExample.initHandler(viewer);
  drawPolygonExample.drawTheArea(true);
  drawPolygonExample.startDrawing();
};
// 清除地图数据
const parentMethod = () => {
  drawPolygonExample.delPolygon();
  drawPolygonExample.drawTheArea(false);
  viewer.entities.removeAll();
};
// 点击显示区域
const handleCoverDataUpdate = data => {
  selectDataList.value = data;
  if (selectDataList.value.length > 0) {
    loadDataToMap(selectDataList.value);
  } else {
    viewer.entities.removeAll();
  }
};
const loadDataToMap = dataList => {
  if (!viewer) return;
  viewer.entities.removeAll();
  // 存储所有有效坐标,用于后续定位
  tbJwdList = [];
  dataList.forEach(item => {
    let positions = parseGeoDataToPositions(item.geo_data, item.altitude);
    // positions.push(positions[0]);
    if (positions.length < 3) {
      console.warn(`数据 ${item.name} 坐标点不足,无法绘制`);
      return;
    }
    tbJwdList.push(...positions);
    //  转换为 Cesium 所需的 [lng, lat, lng, lat, ...] 扁平数组格式
    let degreesArray = [];
    positions.forEach(pos => {
      degreesArray.push(pos.lng, pos.lat, pos.height);
    });
    viewer.entities.add({
      id: `polygon_${item.id}`,
      customType: 'fence_polygon',
      customInfo: item,
      polygon: {
        hierarchy: new Cesium.PolygonHierarchy(
          Cesium.Cartesian3.fromDegreesArrayHeights(degreesArray)
        ),
        material: Cesium.Color.YELLOW.withAlpha(0.5), // 填充色
        outline: true, // 显示边框
        outlineColor: Cesium.Color.YELLOW, // 边框色
        outlineWidth: 2, // 边框宽度
        clampToGround: true, // 贴地显示
      },
      polyline: {
        positions: Cesium.Cartesian3.fromDegreesArrayHeights(degreesArray),
        width: 2,
        material: Cesium.Color.YELLOW,
        clampToGround: true,
      },
      zIndex: 99, // 层级,确保在其他图层上方显示
    });
  });
  focusOnAllFeatures();
  viewInstance.value?.addLeftClickEvent(null, handleFenceClick);
};
//计算所有图斑的包围球并定位
const focusOnAllFeatures = () => {
  if (tbJwdList.length === 0 || !viewer) return;
  // 转换所有经纬度坐标为Cartesian3
  const allPositions = tbJwdList.flatMap(pos =>
    Cesium.Cartesian3.fromDegrees(pos.lng, pos.lat, pos.height || 0)
  );
  if (allPositions.length === 0) return;
  const boundingSphere = Cesium.BoundingSphere.fromPoints(allPositions);
  viewer.camera.flyToBoundingSphere(boundingSphere, {
    duration: 0, // 飞行时间(秒)
    offset: new Cesium.HeadingPitchRange(
      Cesium.Math.toRadians(0), // 方向角
      Cesium.Math.toRadians(-90) // 俯仰角
    ),
  });
};
// 区域点击显示详细信息
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;
    //   viewer.entities.values.forEach(entity => {
    // if (entity.customType === 'fence_polygon') {
    //   entity.polygon.material = entity === selectedEntity
    //     ? Cesium.Color.RED.withAlpha(0.5)
    //     : Cesium.Color.YELLOW.withAlpha(0.5);
    // }
    // });
    if (activeName.value !== '国土空间规划') {
      layerParams.value.editNest = true; // 显示右侧编辑组件
      layerParams.value.decideWhetherToAddOrEdit = 2; // 标记为编辑模式
      // 根据围栏类型设置fenceType
      if (activeName.value === '电子围栏') {
        layerParams.value.fenceType = 1;
      } else if (activeName.value === '自定义禁飞区') {
        layerParams.value.fenceType = 2;
      }
      layerParams.value.editDetailData = selectedData;
      layerParams.value.fenceArea = selectedData.area;
      layerParams.value.editingIsProhibited = true;
    }
  }
};
let publicCesiumInstance = null;
let viewer = null;
const viewInstance = shallowRef(null);
const homeViewer = shallowRef(null);
// 初始化绘制工具实例
const drawPolygonExample = new DrawPolygon();
// 地图初始化
const initMap = () => {
  publicCesiumInstance = new PublicCesium({
    dom: 'layMap',
    flatMode: false,
    terrain: true,
    layerMode: 4,
    contour: false,
  });
  viewer = publicCesiumInstance.getViewer();
  viewInstance.value = publicCesiumInstance;
  const nanChangPosition = Cesium.Cartesian3.fromDegrees(
    115.892151, // 经度
    28.676493, // 纬度
    15000 // 相机高度
  );
  // 执行相机飞行
  viewer.camera.flyTo({
    destination: nanChangPosition,
    duration: 0,
    orientation: {
      heading: Cesium.Math.toRadians(0),
      pitch: Cesium.Math.toRadians(-90),
      roll: 0,
    },
  });
};
// 编辑/绘制
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); //高度
      return {
        lng: _.round(lng, 6),
        lat: _.round(lat, 6),
        height: _.round(height, 6),
      };
    });
  }
  let polygon = curPolygonPosition.map(item => [item?.lng, item?.lat]);
 if(polygon.length > 0){
      polygon.push([curPolygonPosition[0]?.lng, curPolygonPosition[0]?.lat])
 }
  let polygonString = JSON.stringify(polygon);
  layerParams.value.polygonPosition = polygon.length > 0 ? polygonString : null;
  if (polygon.length >= 3) {
    //  确保多边形闭合:最后一个点与第一个点一致(避免计算偏差)
    const firstPoint = polygon[0];
    const lastPoint = polygon[polygon.length - 1];
    const isClosed =
      _.round(firstPoint[0], 6) === _.round(lastPoint[0], 6) &&
      _.round(firstPoint[1], 6) === _.round(lastPoint[1], 6);
    // 若未闭合,手动添加闭合点
    const closedPolygon = isClosed ? polygon : [...polygon, firstPoint];
    //  转换为 turf 支持的 GeoJSON 格式(Polygon 类型需外层嵌套数组)
    const turfPolygon = turf.polygon([closedPolygon]);
    //  计算面积(turf.area() 返回平方米,保留2位小数)
    const area = _.round(turf.area(turfPolygon), 2);
    //  赋值给 layerParams,
    layerParams.value.fenceArea = area;
  } else {
    // 坐标点不足3个时,清空面积
    layerParams.value.fenceArea = 0;
  }
};
drawPolygonExample.subscribe('getShowWaringTip', data => {
  isShowWaringTip.value = data;
});
const throttleLoadPlanarRoute = throttle(loadPlanarRoute, 200);
drawPolygonExample.subscribe('getPolygonPositions', data => {
  throttleLoadPlanarRoute(data);
});
// 地图销毁
const destroyMap = () => {
  drawPolygonExample.destroy();
  if (viewer) {
    viewer.destroy();
    viewer = null;
  }
  publicCesiumInstance = null;
};
// 阻止浏览器默认
const preventDefault = event => {
  event.preventDefault();
  return;
};
const cesiumContextMenu = (isAdd = true) => {
  let cesium = document.getElementById('layMap');
  if (!cesium) return;
  if (isAdd) {
    cesium.addEventListener('contextmenu', preventDefault);
  } else {
    cesium.removeEventListener('contextmenu', preventDefault);
  }
};
provide('layerParams', layerParams);
onMounted(() => {
  initMap();
  cesiumContextMenu();
  getdataFolderApi();
  // 监听单个实体删除事件
  EventBus.on('deleteMapEntityById', deleteMapEntityById);
  // 监听文件夹下所有实体删除事件
  EventBus.on('deleteMapEntitiesByFolderId', deleteMapEntitiesByFolderId);
});
onBeforeUnmount(() => {
  destroyMap();
  cesiumContextMenu(false);
  // 移除事件监听
  EventBus.off('deleteMapEntityById', deleteMapEntityById);
  EventBus.off('deleteMapEntitiesByFolderId', deleteMapEntitiesByFolderId);
});
// 删除单个地图实体
const deleteMapEntityById = entityId => {
  if (!viewer) return;
  const entity = viewer.entities.getById(`polygon_${entityId}`);
  if (entity) {
    viewer.entities.remove(entity);
  }
  // 更新选中数据列表
  selectDataList.value = selectDataList.value.filter(item => item.id !== entityId);
};
// 删除文件夹下所有地图实体
const deleteMapEntitiesByFolderId = folderId => {
  if (!viewer) return;
  // 假设围栏数据中包含 folder_id 字段,根据文件夹ID过滤
  const entitiesToDelete = viewer.entities.values.filter(entity => {
    return entity.customInfo?.folder_id === folderId;
  });
  entitiesToDelete.forEach(entity => {
    viewer.entities.remove(entity);
  });
  // 更新选中数据列表
  selectDataList.value = selectDataList.value.filter(item => item.folder_id !== folderId);
};
</script>
<style scoped lang="scss">
.layerContainer {
  width: 100%;
  height: 90vh;
}
.mapContainer {
  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%;
  }
  .tool-tip {
    display: flex;
    justify-content: center;
    align-items: center;
    position: absolute;
    width: 800px;
    height: 64px;
    right: 430px;
    top: 60px;
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-size: 18px;
    color: #ffffff;
    background: rgba(11, 31, 58, 0.7);
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    user-select: none;
  }
}
</style>
src/views/license/license.vue
@@ -41,7 +41,7 @@
        </avue-crud>
        <!-- 新增弹窗 -->
        <el-dialog title="新增 License" v-model="addDialogVisible" width="70%" :close-on-click-modal="false"
        <el-dialog class="ztzf-dialog-mange" title="新增 License" v-model="addDialogVisible" width="70%" :close-on-click-modal="false"
            @close="resetForm">
            <el-form :model="newLicense" ref="licenseForm" label-width="120px">
                <el-form-item label="机构ID" prop="licenseId">
@@ -50,7 +50,7 @@
                <el-form-item label="机构名称" prop="licenseName" required>
                    <el-input v-model="newLicense.licenseName" />
                </el-form-item>
                <el-form-item label="是否验证过期时间类型" prop="licenseType" required>
                <el-form-item label="过期时间类型" prop="licenseType" required>
                    <el-select v-model="newLicense.licenseType" placeholder="请选择">
                        <el-option label="不验证过期时间" :value="1" />
                        <el-option label="验证过期时间" :value="2" />
@@ -79,8 +79,8 @@
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button @click="addDialogVisible = false">取消</el-button>
                    <el-button type="primary" @click="createLicense">保存</el-button>
                  <el-button type="primary" @click="createLicense" :icon="CircleCheck">保存</el-button>
                  <el-button @click="addDialogVisible = false"  :icon="CircleClose">取消</el-button>
                </div>
            </template>
@@ -91,6 +91,7 @@
<script>
import { getList, add } from '@/api/license/license'
import { mapGetters } from 'vuex'
import { CircleCheck, CircleClose } from '@element-plus/icons-vue';
export default {
    data () {
@@ -269,6 +270,12 @@
        }
    },
    computed: {
      CircleClose() {
        return CircleClose
      },
      CircleCheck() {
        return CircleCheck
      },
        ...mapGetters(['userInfo', 'permission']),
        permissionList () {
            return {
src/views/monitor/log/api.vue
@@ -1,5 +1,5 @@
<template>
  <basic-container>
  <basic-container >
    <avue-crud :option="option" :table-loading="loading" :data="data" ref="crud" v-model="form"
      :permission="permissionList" :before-open="beforeOpen" v-model:page="page" @search-change="searchChange"
      @search-reset="searchReset" @current-change="currentChange" @size-change="sizeChange"
@@ -29,7 +29,9 @@
        // calcHeight: 32,
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
@@ -114,7 +116,7 @@
            type: 'daterange',
            search: true,
            searchRange: true,
            searchSpan: 6,
            searchSpan: 8,
            format: 'YYYY-MM-DD',
            valueFormat: 'YYYY-MM-DD',
            startPlaceholder: '开始时间',
@@ -205,4 +207,10 @@
}
</script>
<style scoped lang="scss"></style>
<style lang="scss">
.avue-crud__dialog__header .el-dialog__title {
  font-size: 18px !important;
}
</style>
src/views/monitor/log/error.vue
@@ -27,7 +27,9 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
@@ -72,7 +74,7 @@
            label: '请求接口',
            prop: 'requestUri',
            search: true,
            searchSpan: 6,
            searchSpan: 4,
          },
          {
            label: '操作人',
src/views/monitor/log/flightLog.vue
New file
@@ -0,0 +1,529 @@
<!-- 飞行日志 -->
<template>
  <div class="flight">
    <div class="ztzf-form-search">
      <el-form :model="params" inline>
        <el-row :gutter="24">
          <el-col :span="4">
          <el-form-item label="任务名称:">
            <el-input v-model="params.jobName" placeholder="请输入任务名称" clearable />
          </el-form-item>
          </el-col>
          <el-col :span="4">
          <el-form-item label="任务编号:">
            <el-input v-model="params.jobInfoNum" placeholder="请输入任务编号" clearable />
          </el-form-item>
          </el-col>
          <el-col :span="8">
          <el-form-item label="选择日期:">
            <el-date-picker
              v-model="rangTime"
              type="daterange"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              clearable
              @change="changeselect"
            />
          </el-form-item>
          </el-col>
          <el-col :span="4">
          <el-button type="primary" @click="getList" :icon="Search">搜索</el-button>
          <el-button @click="cancelSearch" :icon="Delete">清空</el-button>
          </el-col>
        </el-row>
      </el-form>
    </div>
    <div class="mange-table">
      <!--      <el-tabs v-model="tabType" class="demo-tabs" @tab-click="handleTabClick">-->
      <!--        <el-tab-pane label="全部" name="全部"></el-tab-pane>-->
      <!--        <el-tab-pane label="我的收藏" name="收藏"></el-tab-pane>-->
      <!--        -->
      <!--      </el-tabs>-->
      <el-table border :data="tableList" class="custom-header">
        <el-table-column label="序号" type="index" width="60"></el-table-column>
        <el-table-column prop="job_name" label="任务名称" align="center" show-overflow-tooltip></el-table-column>
        <el-table-column prop="job_info_num" label="任务编号" align="center" show-overflow-tooltip></el-table-column>
<!--        <el-table-column prop="title" label="飞行类型" align="center" show-overflow-tooltip></el-table-column>-->
        <el-table-column prop="nickname" label="机巢名称" align="center"></el-table-column>
        <el-table-column prop="start_time" label="开始时间" align="center">
          <template #default="scope">
            {{ timeFormatConvert(scope.row.start_time) }}
          </template>
        </el-table-column>
        <el-table-column prop="end_time" label="结束时间" align="center">
          <template #default="scope">
            {{ timeFormatConvert(scope.row.end_time) }}
          </template>
        </el-table-column>
        <el-table-column prop="create_name" label="创建人" align="center"></el-table-column>
        <!--        <el-table-column prop="end_time" label="标签" align="center">-->
        <!--          <template #default="scope">-->
        <!--            <el-select v-model="scope.row.label_id" @change="handleUpdateLabel(scope.row)" clearable>-->
        <!--              <el-option-->
        <!--                v-for="item in tagList"-->
        <!--                :key="item.id"-->
        <!--                :label="item.label_name"-->
        <!--                :value="item.id"-->
        <!--              ></el-option>-->
        <!--            </el-select>-->
        <!--          </template>-->
        <!--        </el-table-column>-->
        <el-table-column label="操作" width="300" align="center">
          <template #default="scope">
            <el-button icon="el-icon-view" type="text" @click="handleDetail(scope.row)">查看</el-button>
            <el-button icon="el-icon-view" type="text" @click="handleLineTrajectory(scope.row)">轨迹</el-button>
            <el-button icon="el-icon-delete" type="text" @click="handleDelete(scope.row)">删除</el-button>
            <!--            <el-button type="text" @click="handleStar(scope.row)">-->
            <!--              <el-icon><Star /></el-icon>-->
            <!--              {{ scope.row.is_favorite ? '取消收藏':'收藏' }}</el-button>-->
            <el-button type="text" @click="handleExport(scope.row)"><el-icon><Download /></el-icon>导出</el-button>
          </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]" :size="size" v-model:current-page="params.page"
                     v-model:page-size="params.page_size" layout="total, sizes, prev, pager, next, jumper" :total="total"
                     @size-change="handleSizeChange" @current-change="handleCurrentChange" />
    </div>
  </div>
  <el-dialog class="ztzf-dialog-mange" append-to-body v-model="isShowView" title="查看" :width="pxToRem(1200)" :close-on-click-modal="false" :destroy-on-close="true">
    <el-table border :data="tableDataDetails" height="466">
      <el-table-column label="序号" type="index" width="60"></el-table-column>
      <el-table-column prop="flight_type" label="飞行类型" align="center" show-overflow-tooltip></el-table-column>
      <el-table-column prop="create_time" label="飞行时间" align="center" show-overflow-tooltip></el-table-column>
      <el-table-column prop="longitude" label="经度" align="center"></el-table-column>
      <el-table-column prop="latitude" label="纬度" align="center"></el-table-column>
      <el-table-column prop="height" label="绝对高度" align="center"></el-table-column>
      <el-table-column prop="elevation" label="实时真高" align="center"></el-table-column>
    </el-table>
    <template #footer>
      <div class="pagination dialog-footer" style="display: flex;justify-content: end;margin-top: 10px;">
        <el-pagination class="ztzf-pagination" popper-class="custom-pagination-dropdown" background
                       :page-sizes="[10, 20, 30, 40, 50, 100]" :size="size" v-model:current-page="detailParams.current"
                       v-model:page-size="detailParams.size" layout="total, sizes, prev, pager, next, jumper" :total="detailTotal"
                       @size-change="handleSizeChangeDetails" @current-change="handleCurrentChangeDetails" />
      </div>
    </template>
  </el-dialog>
  <el-dialog class="ztzf-dialog-mange" append-to-body v-model="isShowFlyMapView" title="查看" :width="pxToRem(1000)" :close-on-click-modal="false" :destroy-on-close="true">
    <div id="flyMap" class="ztzf-cesium"></div>
  </el-dialog>
</template>
<script setup>
import { getHistoryTrack, cancelStar, addStar, updateDroneFlight, deleteDroneFlight, droneFlightLogInfoPage } from '@/api/logs';
// import { flightLogPage } from '@/api/airspace/airspace';
import { downloadXls } from '@/utils/util';
import { exportBlob } from '@/api/common'
import * as Cesium from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
import { Delete, Search } from '@element-plus/icons-vue';
import { ArrowLineMaterialProperty } from '@/utils/cesium/Material'
import { flyVisual } from '@/utils/cesium/mapUtil'
import rwqfdImg from '@/assets/images/signMachineNest/rwqfd.png'
import endPointImg from '@/assets/images/EndPointicon.png'
import { nextTick } from 'vue';
let publicCesiumInstance = null;
let viewer = null;
const viewInstance = shallowRef(null);
const homeViewer = shallowRef(null);
let previewDataSource = null
const total = ref(0)
let rangTime = ref([])
const params = ref({
  isFavorite: false,
  page: 1,
  page_size: 10,
  startTime: '',
  endTime: '',
  jobName: '',
  jobInfoNum: '',
});
let tableList = ref([])
let isShowView = ref(false)
let isShowFlyMapView = ref(false)
let tabType = ref('全部')
function timeFormatConvert(time) {
  if (!time) return '/'
  const date = new Date(time);
  return date.toLocaleString();
}
function cancelSearch() {
  rangTime.value = []
  params.value = {
    startTime: '',
    endTime: '',
    isFavorite: tabType.value === '收藏' ? true : false,
    page: 1,
    page_size: 10,
    jobName: '',
    jobInfoNum: '',
  }
  getList()
}
function getList() {
  params.value.startTime = rangTime.value?.length ? rangTime.value[0].getTime() : ''
  params.value.endTime = rangTime.value?.length? new Date(
    rangTime.value[1].getFullYear(),
    rangTime.value[1].getMonth(),
    rangTime.value[1].getDate(),
    23, 59, 59
  ).getTime() : ''
  getHistoryTrack(params.value).then(res => {
    tableList.value = res.data.data.list || []
    total.value = res.data.data.pagination.total || 0
  })
}
function handleAll() {
  params.value.isFavorite = false
  params.value.page = 1
  tabType.value = '全部'
  getList()
}
// 查看
let tableDataDetails = ref([])
let detailTotal = ref(0)
let detailParams = ref({
  current: 1,
  size: 10,
  flightId: ''
})
function handleDetail(row) {
  isShowView.value = true
  detailParams.value.current = 1
  detailParams.value.size = 10
  detailParams.value.flightId = row.id
  detailTotal.value = 0
  droneFlightLogInfoPage(detailParams.value).then(res => {
    tableDataDetails.value = res.data.data.records
    detailTotal.value = res.data.data.total
  })
}
// 地图
const initMap = () => {
  if (!document.getElementById('flyMap')) {
    return;
  }
  publicCesiumInstance = new PublicCesium({
    dom: 'flyMap',
    flatMode: false,
    terrain: true,
    layerMode: 4,
    contour: false,
  });
  homeViewer.value = publicCesiumInstance.getViewer();
  viewer = publicCesiumInstance.getViewer();
  viewInstance.value = publicCesiumInstance;
  viewer.scene.globe.depthTestAgainstTerrain = true;
  viewInstance.value.switchContour(true);
};
// 预览轨迹
let arrowLineMaterialProperty = new ArrowLineMaterialProperty({
  color: new Cesium.Color(128 / 255, 215 / 255, 255 / 255, 1),
  directionColor: new Cesium.Color(1, 1, 1, 1),
  outlineColor: new Cesium.Color(1, 1, 1, 1),
  outlineWidth: 0,
  speed: 5,
})
async function handleLineTrajectory(row) {
  isShowFlyMapView.value = true;
  // 确保地图容器已渲染
  await nextTick();
  // 确保地图初始化完成
  // if (!viewer) {
  //   initMap();
  // }
  initMap();
  detailParams.value.size = 9999
  detailParams.value.current = 1
  detailParams.value.flightId = row.id;
  const res = await droneFlightLogInfoPage(detailParams.value);
  tableDataDetails.value = res.data.data.records;
  // 清除现有数据源(如果有)
  if (previewDataSource) {
    viewer.dataSources.remove(previewDataSource);
  }
  // 创建新数据源
  previewDataSource = new Cesium.CustomDataSource('previewDataSource');
  await viewer.dataSources.add(previewDataSource);
  // 确保数据有效
  const validPositions = tableDataDetails.value
    .filter(i => i.longitude && i.latitude && i.height)
    .map(i => Cesium.Cartesian3.fromDegrees(
      Number(i.longitude),
      Number(i.latitude),
      Number(i.height)
    ));
  if (validPositions.length > 0) {
    previewDataSource.entities.add({
      polyline: {
        width: 4,
        positions: validPositions,
        material: arrowLineMaterialProperty,
        clampToGround: false,
      },
    });
    // 起点
    // previewDataSource.entities.add({
    //   position: validPositions[0],
    //   billboard: {
    //     image: rwqfdImg,
    //     width: 50,
    //     height: 50,
    //   },
    // })
    // 终点
    // previewDataSource.entities.add({
    //   position: validPositions[validPositions.length - 1],
    //   billboard: {
    //     image: new Cesium.ConstantProperty(endPointImg),
    //     width: 30,
    //     height: 30,
    //     verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐
    //   },
    // })
    flyVisual({
      positionsData: tableDataDetails.value.map(i => [
        Number(i.longitude),
        Number(i.latitude),
        Number(i.height)
      ]),
      viewer,
      multiple: 4.6,
      pitch: -60
    });
  } else {
    console.warn('No valid positions data');
  }
}
// 分页
function handleSizeChangeDetails(val) {
  detailParams.value.size = val
  detailParams.value.current = 1
  droneFlightLogInfoPage(detailParams.value).then(res => {
    tableDataDetails.value = res.data.data.records
    detailTotal.value = res.data.data.total
  })
}
function handleCurrentChangeDetails(val) {
  detailParams.value.current = val
  droneFlightLogInfoPage(detailParams.value).then(res => {
    tableDataDetails.value = res.data.data.records
    detailTotal.value = res.data.data.total
  })
}
function handleStartList() {
  params.value.isFavorite = true
  tabType.value = '收藏'
  params.value.page = 1
  getList()
}
// function handleTabClick() {
//   if (tabType.value === '全部') {
//     handleAll()
//   } else if (tabType.value === '收藏') {
//     handleStartList()
//   }
// }
// 收藏
function handleStar(row) {
  if (row.is_favorite) {
    cancelStar(row.id).then(res => {
      ElMessage.success('取消收藏成功')
      getList()
    })
  } else {
    addStar({ flight_log_id: row.id }).then(res => {
      ElMessage.success('收藏成功')
      getList()
    })
  }
}
// 打标签
function handleUpdateLabel(row) {
  updateDroneFlight({ id: row.id, label_id: row.label_id }).then(res => {
    // getList()
    ElMessage.success('设置标签成功')
  })
}
// 删除
function handleDelete(row) {
  ElMessageBox.confirm('确定删除该条数据?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  }).then(() => {
    deleteDroneFlight({ id: row.id }).then(res => {
      ElMessage.success('删除成功')
      getList()
    })
  }).catch(() => {
    ElMessage({
      type: 'info',
      message: '已取消删除'
    })
  })
}
// 导出
function handleExport(row) {
  exportBlob(
    `/drone-device-core/log/droneFlightLogInfo/export/${row.id}`
  ).then(res => {
    downloadXls(res.data, `${row.job_name}.xlsx`)
  })
}
// 日期选择
// const changeselect = () => {
//   params.value.startTime = rangTime.value[0] ? rangTime.value[0].getTime() : ''
//   params.value.endTime = rangTime.value[1]? rangTime.value[1].getTime() : ''
//   getList()
// };
function handleSizeChange(val) {
  params.value.page_size = val
  getList()
}
function handleCurrentChange(val) {
  params.value.page = val
  getList()
}
let tagList = ref([])
function tagManageList() {
  flightLogPage({ current:1, size: 99 }).then(res => {
    tagList.value = res.data.data.records || []
  })
}
onMounted(() => {
  // tagManageList()
  getList()
})
</script>
<style lang="scss" scoped>
.flight {
  height: 0;
  flex: 1;
  margin: 0 10px 10px 10px;
  background-color: #ffffff;
  padding: 10px 20px;
  border-radius: 5px;
  display: flex;
  flex-direction: column;
  //.search-box {
  //  height: 30px;
  //}
  :deep(.el-input) {
    .el-input__wrapper {
      width: 200px;
    }
  }
  // 表格
  .mange-table {
    height: 0;
    flex: 1;
    //margin-top: 18px;
    overflow: auto;
  }
  :deep(.el-pagination) {
    display: flex;
    justify-content: right;
  }
  :deep(.el-pagination button) {
    background: center center no-repeat none !important;
    color: #8eb8ea !important;
  }
  :deep(.ztzf-select){
    .el-select__selection {
      width: 200px;
    }
  }
}
.content {
  padding: 20px;
  .view-table {
    width: 100%;
    border-collapse: collapse;
    border: 1px solid #EBEEF5;
    tr {
      &:not(:last-child) {
        border-bottom: 1px solid #EBEEF5;
      }
      td {
        padding: 12px 10px;
        &.label {
          width: 140px;
          text-align: right;
          // color: #909399;
          // background-color: #F5F7FA;
          border-right: 1px solid #EBEEF5;
        }
        &.value {
          width: 180px;
          // color: #303133;
        }
      }
    }
  }
}
.content-edit {
  .el-form {
    .el-form-item {
      width: 300px;
      :deep(.el-form-item__label) {
        width: 120px;
      }
    }
    .btns {
      display: flex;
      justify-content: center
    }
  }
}
#flyMap {
  height: 500px;
  width: 100%;
}
</style>
src/views/odm/task.vue
@@ -80,7 +80,9 @@
        dialogWidth: 950,
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
@@ -102,7 +104,7 @@
            search: true,
            labelWidth: 130,
            searchSpan: 4,
            searchLabelWidth: 100,
            // searchLabelWidth: 100,
            //labelWidth: 130,
            searchPlaceholder: '任务名称',
            rules: [
@@ -154,7 +156,7 @@
            type: 'select',
            search: true,
            searchSpan: 4,
            searchLabelWidth: 100,
            // searchLabelWidth: 100,
            //width: 100,
            labelWidth: 130,
            searchPlaceholder: '任务类型',
@@ -176,7 +178,7 @@
            label: '机场名称',
            prop: 'airportName',
            searchSpan: 4,
            searchLabelWidth: 100,
            // searchLabelWidth: 100,
            labelWidth: 130,
            //search: true,
          },
@@ -204,7 +206,7 @@
            type: 'select',
            search: true,
            searchSpan: 4,
            searchLabelWidth: 100,
            // searchLabelWidth: 100,
            searchPlaceholder: '任务状态',
            labelWidth: 130,
            width: 90,
@@ -242,7 +244,7 @@
            labelWidth: 130,
            search: true,
            searchSpan: 4,
            searchLabelWidth: 100,
            // searchLabelWidth: 100,
            //valueFormat: 'YYYY-MM-DD',
            width: 160,
            change: value => {
src/views/resource/attach.vue
@@ -47,13 +47,17 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
        // grid: true,
        selection: true,
        dialogClickModal: false,
        addBtn: false,
        height: 'auto',
        calcHeight: 20,
@@ -142,7 +146,7 @@
            type: 'daterange',
            search: true,
            searchRange: true,
            searchSpan: 6,
            searchSpan: 8,
            format: 'YYYY-MM-DD',
            valueFormat: 'YYYY-MM-DD',
            startPlaceholder: '开始时间',
src/views/resource/components/DrawPolygon.vue
@@ -214,7 +214,7 @@
  viewInstance.value.removeMouseHandler('drawCustomPolygon');
}
const emit = defineEmits(['upDateDrawState']);
const emit = defineEmits(['upDateDrawState',]);
function updatePolygon() {
  let polygon = savePolygonPosition.reduce((pre, cur) => {
@@ -222,7 +222,7 @@
    return pre;
  }, []);
  polygon.push(polygon[0]);
  console.log('绘制', `POLYGON((${polygon.join(',')}))`);
  // 接口
  sdfwUpdate({
      ids: selectionIds.value,
@@ -244,6 +244,7 @@
      })
}
function cancel() {
  remove();
  removeMenuPanel();
src/views/resource/components/spotDetails.vue
@@ -1,10 +1,11 @@
<template>
  <el-dialog
    class="spotDialog"
    class="spotDialog ztzf-dialog-mange"
    :title="props.title"
    v-model="uploadPatchDialog"
    width="78%"
    align-center
  >
    <div class="container">
      <!-- 信息展示区 -->
@@ -65,14 +66,12 @@
              :row-class-name="tableRowClassName"
              :data="tableData"
              @row-click="handleLocationPolygon"
              @current-change="handleTableRowChange"
            >
              <el-table-column type="index" align="center" :width="pxToRemNum(30)" label="序号">
                <template #default="{ $index }">
                  {{
                    ($index + 1 + (params.page - 1) * params.pageSize).toString().padStart(2, '0')
                  }}
                </template>
               <template #default="{ $index }">
                {{ ($index + 1).toString().padStart(2, '0') }}
              </template>
              </el-table-column>
              <el-table-column prop="dkbh" align="center"  label="图斑名称" show-overflow-tooltip />
              <el-table-column prop="is_exception" :width="pxToRemNum(50)" align="center" label="图斑状态">
@@ -93,20 +92,7 @@
                </template>
              </el-table-column>
            </el-table>
            <div class="pagination-container">
              <el-pagination
                :current-page="params.page"
                :page-size="params.pageSize"
                background
                layout="prev, pager, next, ..."
                :pager-count="3"
                prev-text="上一页"
                next-text="下一页"
                :total="total"
                @size-change="handleSizeChange"
                @current-change="handleCurrentChange"
              />
            </div>
          </div>
        </div>
        <!--绘制按钮-->
@@ -115,6 +101,7 @@
          ref="drawPolygonRef"
          v-if="isEditing"
          @upDateDrawState="handleUpDateDrawState"
        />
        <!-- 完成/取消 -->
        <div class="btnGroups" v-if="props.title === '图斑编辑'">
@@ -129,6 +116,7 @@
</template>
<script setup>
import { flyVisual } from '@/utils/cesium/mapUtil'
import { pxToRem, pxToRemNum } from '@/utils/rem'
import DrawPolygon from '@/views/resource/components/DrawPolygon.vue';
import { ElMessage, ElMessageBox } from 'element-plus';
@@ -145,6 +133,8 @@
import * as Cesium from 'cesium';
import { PublicCesium } from '@/utils/cesium/publicCesium';
import { ref, watch, onBeforeUnmount, onMounted } from 'vue';
// 标记地图是否初始化完成
const isMapReady = ref(false);
const uploadPatchDialog = defineModel('show');
const props = defineProps(['title', 'detailid', 'detailList', 'spotTypeOption', 'regionalData']);
const polygonTableEle = ref(null);
@@ -251,65 +241,35 @@
  page: 1,
  pageSize: 10,
});
const isInit = ref(true);
// 图斑管理表格
const getTableList = () => {
  const requestParams = {
    patchesInfoId: props.detailid,
    page: params.value.page,
    pageSize: params.value.pageSize,
  };
  tableMapListApi(requestParams).then(res => {
    tableData.value = res.data.data.records?.map(item => ({
  AlltableMapListApi(requestParams).then(res => {
    tableData.value = res.data.data?.map(item => ({
      ...item,
      dkfw: item.sdfw && item.is_exception == 1 ? item.sdfw : item.dkfw,
    }));
    total.value = res.data.data.total || res.data.data.length;
    tbJwdList = [];
    viewer?.entities.removeAll();
    if (!AlltableData.value) {
    if (!tableData.value) {
      viewInstance.value?.flyToContour();
    } else {
      entitiesAddSpot();
    }
  });
};
// 所有图斑数据
const getAlltableMapListApi = () => {
  loading.value = true;
  const requestParams = {
    patchesInfoId: props.detailid,
  };
  AlltableMapListApi(requestParams).then(res => {
    AlltableData.value = res.data.data?.map(item => ({
      ...item,
      dkfw: item.sdfw && item.is_exception == 1 ? item.sdfw : item.dkfw,
    }));
    tbJwdList = [];
    viewer?.entities.removeAll();
    if (AlltableData.value.length < 1) {
      viewInstance.value?.flyToContour();
    } else {
      entitiesAddSpot();
    }
    loading.value = false;
  });
};
const handleSizeChange = val => {
  params.value.pageSize = val;
  params.value.page = 1;
  getTableList();
};
const handleCurrentChange = val => {
  params.value.page = val;
  getTableList();
};
// 地图
const initMap = () => {
  if (!document.getElementById('spotMap')) {
    return;
  }
  if (!document.getElementById('spotMap') || isMapReady.value) return;
  publicCesiumInstance = new PublicCesium({
    dom: 'spotMap',
    flatMode: false,
@@ -317,41 +277,80 @@
    layerMode: 4,
    contour: false,
  });
  homeViewer.value = publicCesiumInstance.getViewer();
  viewer = publicCesiumInstance.getViewer();
  viewInstance.value = publicCesiumInstance;
  viewInstance.value = publicCesiumInstance;
  viewer.scene.globe.depthTestAgainstTerrain = true;
  viewInstance.value.switchContour(true);
  // 确保 readyPromise 存在
  if (viewer.readyPromise) {
    viewer.readyPromise.then(() => {
      isMapReady.value = true;
      getTableList();
    }).catch((error) => {
      console.error('地图加载失败:', error);
      ElMessage.error('地图加载失败,请刷新重试');
    });
  } else {
    // 如果没有 readyPromise
    setTimeout(() => {
      isMapReady.value = true;
      getTableList();
    }, 1000);
  }
};
// 仅弹框初始化时执行的定位逻辑
const initMapLocation = () => {
  if (tbJwdList.length === 0 || !homeViewer.value) return;
  // 计算所有图斑的包围球(用于初始化定位)
  const allPositions = tbJwdList.flatMap(item =>
    Cesium.Cartesian3.fromDegreesArray(item.grouped)
  );
  const boundingSphere = Cesium.BoundingSphere.fromPoints(allPositions);
  // 初始化定位(仅执行一次)
  homeViewer.value.camera.flyToBoundingSphere(boundingSphere, {
    duration: 0,
    offset: new Cesium.HeadingPitchRange(
      Cesium.Math.toRadians(0),
      Cesium.Math.toRadians(-90)
    ),
  });
};
// 初始化所有图斑
const entitiesAddSpot = () => {
if (!isMapReady.value || !viewer) return;
  viewer?.entities.removeAll();
  AlltableData.value.forEach(item => {
    // 取出当中经纬度
  tbJwdList = []; // 重置经纬度列表
  tableData.value.forEach(item => {
    const numbersWithCommas = item.dkfw.match(/\d+(\.\d+)?/g);
    if (!numbersWithCommas) return;
    // 绘制图斑
    const grouped = numbersWithCommas.map(item => Number(item));
    // 为框选存储对比值
    tbJwdList.push({
      ...item,
      grouped: grouped,
    });
    viewInstance.value.removeById('polygon_dk' + item.id);
    // 根据 is_exception 设置颜色
    const color =
      item.is_exception == 2 ? Cesium.Color.RED.withAlpha(0.5) : Cesium.Color.YELLOW.withAlpha(0.5);
    const outlineColor = item.is_exception == 2 ? Cesium.Color.RED : Cesium.Color.YELLOW;
    const grouped = numbersWithCommas.map(Number);
    tbJwdList.push({ ...item, grouped }); // 存储经纬度用于后续操作
    // 绘制图斑(保留原有逻辑,仅移除定位相关代码)
    const fillColor = item.is_exception === 2
      ? Cesium.Color.RED.withAlpha(0.5)
      : Cesium.Color.YELLOW.withAlpha(0.5);
    const outlineColor = item.is_exception === 2
      ? Cesium.Color.RED
      : Cesium.Color.YELLOW;
    homeViewer.value?.entities?.add({
      id: 'polygon_dk' + item.id,
      id: `polygon_dk${item.id}`,
      customType: 'pattern_spot_polygon',
      customInfo: {
        ...item,
      },
      customInfo: item,
      polygon: {
        hierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArray(grouped)),
        material: color,
        material: fillColor,
        outline: true,
        outlineColor: outlineColor,
        outlineWidth: 2,
@@ -361,23 +360,22 @@
      polyline: {
        positions: Cesium.Cartesian3.fromDegreesArray(grouped),
        width: 2,
        material: color,
        material: outlineColor,
        clampToGround: true,
      },
      zIndex: 99,
    });
    if (tbJwdList.length > 0) {
      const positions = tbJwdList.flatMap(item => Cesium.Cartesian3.fromDegreesArray(item.grouped));
      const boundingSphere = Cesium.BoundingSphere.fromPoints(positions);
      homeViewer.value?.camera.flyToBoundingSphere(boundingSphere, {
        duration: 0,
        offset: new Cesium.HeadingPitchRange(Cesium.Math.toRadians(0), Cesium.Math.toRadians(-90)),
      });
    }
    viewInstance.value?.removeLeftClickEvent('spotHighlighting');
    viewInstance.value?.addLeftClickEvent(null, spotHighlighting, 'spotHighlighting');
  });
  // 仅在初始化阶段执行定位(关键:通过isInit控制)
  if (isInit.value) {
    initMapLocation();
    isInit.value = false; // 初始化完成,后续不再执行定位
  }
  // 保留图斑点击高亮事件(原有逻辑不变)
  viewInstance.value?.removeLeftClickEvent('spotHighlighting');
  viewInstance.value?.addLeftClickEvent(null, spotHighlighting, 'spotHighlighting');
};
// 高亮当前选中图斑,并定位
@@ -440,16 +438,12 @@
  updateMapSpotInfo(nowEntity);
}
// 更新上一个和这一个信息
function updateMapSpotInfo(nowEntity) {
  const nowData = nowEntity.customInfo;
  if (!nowEntity) return;
  if (nowEntity.customInfo.id === lastEntity?.customInfo.id) return;
  // 设置新的
  nowEntity.polygon.material = Cesium.Color.RED.withAlpha(0);
  nowEntity.polyline.material = Cesium.Color.RED;
  // 重置上一个
  if (lastEntity) {
    const lastData = lastEntity.customInfo;
    const originalFillColor =
@@ -458,7 +452,6 @@
        : Cesium.Color.YELLOW.withAlpha(0.5);
    const originalOutlineColor =
      lastData.is_exception == 2 ? Cesium.Color.RED : Cesium.Color.YELLOW;
    lastEntity.polygon.material = originalFillColor;
    lastEntity.polygon.outlineColor = originalOutlineColor;
    lastEntity.polyline.material = originalOutlineColor;
@@ -466,23 +459,20 @@
  lastEntity = nowEntity;
  const numbersWithCommas = nowData.dkfw.match(/\d+(\.\d+)?/g);
  if (numbersWithCommas) {
    // 解析经纬度数组
    const groupedArr = numbersWithCommas.reduce((acc, val, index, src) => {
      if (index % 2 === 0) acc.push(src.slice(index, index + 2));
      return acc;
    }, []);
    const polygonCenter = getCenterPoint(groupedArr);
    // 相机定位
    homeViewer.value.scene.camera.setView({
      destination: Cesium.Cartesian3.fromDegrees(
        Number(polygonCenter.lng),
        Number(polygonCenter.lat),
        1500
      ),
      zIndex: 100,
    const positionsData = [];
    for (let i = 0; i < numbersWithCommas.length; i += 2) {
      const lon = Number(numbersWithCommas[i]);
      const lat = Number(numbersWithCommas[i + 1]);
      positionsData.push([lon, lat, 10]);
    }
    flyVisual({
      positionsData: positionsData,
      viewer: homeViewer.value,
      multiple: 15,
    });
  }
  // flyMapSpot(nowEntity?.customInfo)
}
// 清空选中的数据
const clearSelect = () => {
@@ -491,26 +481,31 @@
  viewer?.entities.removeAll();
};
// 编辑
const handleSelectionChange = row => {
  let curRowIsSelect = row;
  curRowIsSelect && handleLocationPolygon(row);
  // 如果是同一个图斑且正在编辑,则取消编辑
  if (selectionIds.value === row.id && isEditing.value) {
    isEditing.value = false;
    handleUpDateDrawState(false);
    selectionIds.value = null;
    selectionList.value = [];
    return;
  }
  // 定位到选中的图斑
  handleLocationPolygon(row);
  // 仅异常图斑可编辑
  if (row.is_exception !== 2) {
    ElMessage.warning('仅异常图斑支持编辑绘制');
    return;
  }
  // 设置编辑状态
  selectionIds.value = row.id;
  selectionList.value = row;
  if (isEditing.value) {
    //当前已编辑 → 取消编辑(关闭绘制、重置状态)
    isEditing.value = false;
    handleUpDateDrawState(false); // 通知DrawPolygon取消绘制
  } else {
    // 当前未编辑 → 开启编辑(仅异常图斑可编辑)
    if (row.is_exception === 2 && curRowIsSelect) {
      isEditing.value = true;
      handleUpDateDrawState(true); // 通知DrawPolygon开启绘制
    } else {
      ElMessage.warning('仅异常图斑支持编辑绘制');
      isEditing.value = false;
    }
  }
  isEditing.value = true;
  handleUpDateDrawState(true);
};
const handleUpDateDrawState = show => {
  isEditing.value = show; // 同步编辑状态
@@ -521,7 +516,7 @@
  }
};
// 删除
const handleDelete = row => {
const handleDelete = (row) => {
  ElMessageBox.confirm('确认删除当前行图斑?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
@@ -530,9 +525,27 @@
    deletePatches({ ids: row.id }).then(res => {
      if (res.data.code !== 0) return ElMessage.warning('删除失败');
      ElMessage.success('删除成功');
      getTableList();
      getspotManagementTableApi();
      getAlltableMapListApi();
      tableData.value = tableData.value.filter(item => item.id !== row.id);
      //从地图上移除对应的图斑实体
      const entityId = `polygon_dk${row.id}`;
      viewer?.entities.removeById(entityId);
      const exceptionCountItem = infoList.value.find(item => item.name === '异常图斑数量');
      if (exceptionCountItem && row.is_exception === 2) {
        exceptionCountItem.value = Math.max(0, parseInt(exceptionCountItem.value) - 1);
      }
      const totalCountItem = infoList.value.find(item => item.name === '图斑数量');
      if (totalCountItem) {
        totalCountItem.value = Math.max(0, parseInt(totalCountItem.value) - 1);
      }
      // 清除选中状态
      if (selectionIds.value === row.id) {
        selectionIds.value = null;
        selectionList.value = [];
        isEditing.value = false;
        handleUpDateDrawState(false);
      }
    });
  });
};
@@ -601,18 +614,24 @@
  publicCesiumInstance = null;
};
watch(uploadPatchDialog, newVal => {
  if (newVal) {
    isInit.value = true;
    isMapReady.value = false;
    setTimeout(() => {
      initMap();
      getTableList();
      getAlltableMapListApi();
      getspotManagementTableApi();
      initMap();
      getspotManagementTableApi();
    }, 0);
  } else {
    tableData.value = [];
    isEditing.value = false;
    handleUpDateDrawState(false);
    destroyMap();
    isInit.value = false;
    clearSelect();
    isMapReady.value = false;
  }
  refreshonload();
});
@@ -633,6 +652,10 @@
  font-size: 16px !important;
  color: #363636 !important;
}
:global(.spotDialog .el-dialog__body) {
    padding: 2rem 2rem 0 !important;
  }
.container {
  display: flex;
  flex-direction: column;
@@ -723,8 +746,8 @@
  z-index: 99;
  width: 277px;
  overflow: hidden;
  background: rgba(0, 0, 0, 0.6);
  // backdrop-filter: blur(5px);
  background: rgba(0, 0, 0, 0.8);
  border-radius: 8px 8px 8px 8px;
  padding: 6px 10px 0 10px;
  display: flex;
@@ -793,7 +816,7 @@
    // 表格主体
    .el-table__body {
      tr:hover > td {
        background-color: rgba(0, 0, 0, 0.6) !important;
        background-color: rgba(64, 158, 255,0.3) !important;
      }
      td {
@@ -803,7 +826,8 @@
    // 选中行
    .current-row td {
      background-color: rgba(0, 0, 0, 0.6) !important;
      background-color: rgba(64, 158, 255,0.3) !important;
    }
  }
  :deep(.el-table__body) {
src/views/resource/media.vue
@@ -59,21 +59,24 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
        selection: true,
        dialogClickModal: false,
        delBtn: false,
        addBtn: false,
        height: 'auto',
        calcHeight: 20,
        column: [
          { label: '机场选择', prop: 'airportNickname', type: 'select', search: true, searchSpan: 5, dicData: [], props: { label: 'nickname', value: 'nickname' }, hide: true, change: (value) => {
          { label: '机场选择', prop: 'airportNickname', type: 'select', search: true, searchSpan: 4, dicData: [], props: { label: 'nickname', value: 'nickname' }, hide: true, change: (value) => {
               this.searchChange({ ...this.query,airportNickname:value.value}, () => {});
          } },
          { label: '创建日期', prop: 'daterange', type: 'daterange', search: true, searchRange: true, searchSpan: 6, format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD', startPlaceholder: '开始时间', endPlaceholder: '结束时间', viewDisplay: false, hide: true, searchValue: [defaultStartDate, defaultEndDate]
          { label: '创建日期', prop: 'daterange', type: 'daterange', search: true, searchRange: true, searchSpan: 8, format: 'YYYY-MM-DD', valueFormat: 'YYYY-MM-DD', startPlaceholder: '开始时间', endPlaceholder: '结束时间', viewDisplay: false, hide: true, searchValue: [defaultStartDate, defaultEndDate]
           , change: (value) => {
               this.searchChange({ ...this.query,daterange:value.value}, () => {});
          }
src/views/resource/oss.vue
@@ -53,7 +53,9 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
@@ -68,13 +70,13 @@
        calcHeight: 20,
        column: [
          {
            label: '分类',
            label: '存储分类',
            type: 'radio',
            value: 1,
            span: 24,
            width: 120,
            searchLabelWidth: 50,
            searchSpan: 3,
            // searchLabelWidth: 50,
            searchSpan: 4,
            row: true,
            dicUrl: '/blade-system/dict/dictionary?code=oss',
            props: {
@@ -97,11 +99,11 @@
            }
          },
          {
            label: '名称',
            label: '存储名称',
            prop: 'name',
            span: 24,
            search: true,
            searchSpan: 5,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -115,7 +117,7 @@
            prop: 'ossCode',
            span: 24,
            width: 120,
            searchLabelWidth: 100,
            // searchLabelWidth: 100,
            search: true,
            searchSpan: 4,
            rules: [
@@ -163,8 +165,8 @@
            prop: 'accessKey',
            span: 24,
            search: true,
            searchSpan: 5,
            searchLabelWidth: 100,
            searchSpan: 4,
            // searchLabelWidth: 100,
            overHidden: true,
            rules: [
              {
src/views/resource/patchManagement.vue
@@ -35,13 +35,13 @@
        <el-button type="primary" text icon="el-icon-edit" @click="uploadPatch(scope.row, 'edit')"
          >编辑
        </el-button>
        <el-button type="primary" text icon="el-icon-delete" @click="rowDel(scope.row)"
        <el-button :disabled="scope.row.patches_type_desc==='综合类'" type="primary" text icon="el-icon-delete" @click="rowDel(scope.row)"
          >删除
        </el-button>
      </template>
    </avue-crud>
    <el-dialog title="上传图斑" append-to-body align-center v-model="box" width="550px">
    <el-dialog title="上传图斑" class="ztzf-dialog-mange" append-to-body align-center v-model="box" width="550px">
      <el-form
        ref="ruleFormRef"
        style="max-width: 600px"
@@ -156,8 +156,9 @@
  addBtn: false,
  tip: false,
  searchShow: true,
  searchMenuSpan: 8,
  searchMenuPosition: 'right',
  searchGutter: 30,
  searchMenuPosition: 'left',
  searchMenuSpan: 4,
  border: true,
  index: true,
  indexLabel: '序号',
@@ -192,7 +193,7 @@
      label: '图斑类型',
      prop: 'patches_type_desc',
      span: 24,
      searchLabelWidth: 100,
      // searchLabelWidth: 100,
      search: true,
      searchSpan: 4,
      type: 'select',
@@ -220,7 +221,7 @@
      prop: 'areaName',
      span: 24,
      width: 180,
      searchLabelWidth: 100,
      // searchLabelWidth: 100,
      search: true,
      searchSpan: 4,
      type: 'tree',
@@ -248,7 +249,7 @@
      label: '创建人',
      prop: 'user_name',
      span: 24,
      searchLabelWidth: 100,
      // searchLabelWidth: 100,
      search: true,
      searchSpan: 4,
      type: 'select',
src/views/resource/patchTypeManagement.vue
@@ -21,13 +21,24 @@
      @on-load="onLoad"
    >
      <template #search-menu="{ row, size }">
        <el-button icon="el-icon-back" @click="goPatchManagement"> 返回 </el-button>
        <el-button icon="el-icon-back" @click="goPatchManagement" type="primary"> 返回 </el-button>
      </template>
      <template #status="{ row }">
        <el-tag>{{ row.statusName }}</el-tag>
      </template>
      <template #category="{ row }">
        <el-tag>{{ row.categoryName }}</el-tag>
      </template>
        <template #menu="{ row }">
        <el-button
          :disabled="row.patches_type === '综合类'"
          type="primary"
          text
          icon="el-icon-delete"
          @click="rowDel(row)"
        >
          删除
        </el-button>
      </template>
    </avue-crud>
  </basic-container>
@@ -67,8 +78,9 @@
  addBtnText: '新增类型',
  tip: false,
  searchShow: true,
  searchMenuSpan: 16,
  searchMenuPosition: 'right',
  searchGutter: 30,
  searchMenuPosition: 'left',
  searchMenuSpan: 4,
  border: true,
  index: true,
  indexLabel: '序号',
@@ -76,8 +88,8 @@
  selection: true,
  grid: false,
  menuWidth: 180,
  labelWidth: 100,
  dialogWidth: 880,
  labelWidth: 90,
  dialogWidth: 600,
  dialogClickModal: false,
  height: 'auto',
  calcHeight: 20,
@@ -85,12 +97,14 @@
  gridBtn: false,
  searchShowBtn: false,
  columnBtn: false,
  delBtn: false,
  column: [
    {
      label: '类型名称',
      prop: 'patches_type',
      search: true,
      searchSpan: 4,
      span: 24,
      rules: [{ required: true, message: '请输入类型名称', trigger: 'blur' }],
    },
    {
src/views/resource/wayline.vue
@@ -42,10 +42,12 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        selection: true,
        // selection: true,
        dialogClickModal: false,
        menu: false, // 禁用默认操作列
@@ -57,7 +59,7 @@
            prop: 'dock_name',
            type: 'select',
            search: true,
            searchSpan: 5,
            searchSpan: 4,
            dicData: [],
            props: {
              label: 'nickname',
@@ -72,7 +74,7 @@
            label: '任务名称',
            prop: 'job_name',
            search: true,
            searchSpan: 6,
            searchSpan: 4,
          },
          {
            label: '时间范围',
@@ -80,7 +82,7 @@
            type: 'daterange',
            search: true,
            searchRange: true,
            searchSpan: 6,
            searchSpan: 8,
            format: 'YYYY-MM-DD', // 显示格式
            valueFormat: 'YYYY-MM-DD', // 值格式
            startPlaceholder: '开始时间',
src/views/resource/waylineFile.vue
@@ -25,7 +25,9 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        menu: false,
@@ -39,7 +41,7 @@
            prop: 'dock_name',
            type: 'select',
            search: true,
            searchSpan: 5,
            searchSpan: 4,
            dicData: [],
            props: { label: 'nickname', value: 'nickname' },
            hide: true,
@@ -51,7 +53,7 @@
            label: '航线名称',
            prop: 'name',
            search: true,
            searchSpan: 6,
            searchSpan: 4,
          },
          {
            label: '时间范围',
@@ -59,7 +61,7 @@
            type: 'daterange',
            search: true,
            searchRange: true,
            searchSpan: 6,
            searchSpan: 8,
            format: 'YYYY-MM-DD',
            valueFormat: 'YYYY-MM-DD',
            startPlaceholder: '开始时间',
src/views/system/client.vue
@@ -32,7 +32,9 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        viewBtn: true,
@@ -48,6 +50,7 @@
            label: '应用id',
            prop: 'clientId',
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -60,6 +63,7 @@
            label: '应用密钥',
            prop: 'clientSecret',
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
src/views/system/dept.vue
@@ -40,10 +40,12 @@
import { getLazyTree } from '@/api/base/region'
import { mapGetters } from 'vuex'
import website from '@/config/website'
import { getList } from '@/api/device/device';
export default {
  data () {
    return {
      treeResolveMap: new Map(),
      form: {},
      selectionList: [],
      query: {},
@@ -59,7 +61,9 @@
        tip: false,
        simplePage: true,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        tree: true,
        border: true,
        index: true,
@@ -73,33 +77,35 @@
        column: [
          {
            label: '机构名称',
            label: '部门名称',
            prop: 'deptName',
            search: true,
            searchSpan: 4,
            labelWidth: 120,
            rules: [
              {
                required: true,
                message: '请输入机构名称',
                message: '请输入部门名称',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '机构全称',
            label: '部门全称',
            prop: 'fullName',
            search: true,
            searchSpan: 4,
            labelWidth: 120,
            rules: [
              {
                required: true,
                message: '请输入机构全称',
                message: '请输入部门全称',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '上级机构',
            label: '上级部门',
            prop: 'parentId',
            dicData: [],
            type: 'tree',
@@ -113,13 +119,13 @@
            rules: [
              {
                required: false,
                message: '请选择上级机构',
                message: '请选择上级部门',
                trigger: 'click',
              },
            ],
          },
          {
            label: '机构类型',
            label: '部门类型',
            type: 'select',
            dicUrl: '/blade-system/dict/dictionary?code=org_category',
            props: {
@@ -134,13 +140,13 @@
            rules: [
              {
                required: true,
                message: '请输入机构类型',
                message: '请输入部门类型',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '机构性质',
            label: '部门性质',
            type: 'select',
            dicUrl: '/blade-system/dict/dictionary?code=org_nature',
            props: {
@@ -155,7 +161,7 @@
            rules: [
              {
                required: true,
                message: '请输入机构性质',
                message: '请输入部门性质',
                trigger: 'blur',
              },
            ],
@@ -183,6 +189,7 @@
            labelWidth: 120,
            addDisplay: false,
            editDisplay: false,
            viewDisplay: false,
            width: 80,
            rules: [
              {
@@ -264,8 +271,34 @@
            },
          },
          {
            label: '部署模式',
            type: 'radio',
            prop: 'deploymentMode',
            labelWidth: 120,
            value: 0,
            dicData: [
              {
                label: '统一部署',
                value: 0,
              },
              {
                label: '独立部署',
                value: 1,
              },
            ],
            rules: [
              {
                required: true,
                message: '请选择部署模式',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '备注',
            prop: 'remark',
            type: 'textarea',
            span: 24,
            labelWidth: 120,
            rules: [
              {
@@ -352,6 +385,14 @@
            type: 'success',
            message: '操作成功!',
          })
          this.parentId = ''
          const resolve = this.treeResolveMap.get(row.parentId)
          if (resolve) {
            // 重新加载子节点数据
            getChildLazyTree({ parentId: row.parentId }).then(res => {
              resolve(res.data.data)
            })
          }
          this.onLoad(this.page)
          done(row)
        },
@@ -367,7 +408,7 @@
        loading()
        return
      }
      console.log('--- Start rowUpdate ---')
      // console.log('--- Start rowUpdate ---')
      // 提交时只使用县级代码
      const submitData = {
@@ -375,27 +416,33 @@
        areaCode: row.areaCode.split(',').pop(), // 取最后一个代码(县级)
      }
      console.log('Row data before submit:', row)
      console.log('编辑', row)
      update(submitData)
        .then(
          res => {
            console.log('Update response:', res.data)
            const resolve = this.treeResolveMap.get(row.parentId)
            if (resolve) {
              // 重新加载子节点数据
              getChildLazyTree({ parentId: row.parentId }).then(res => {
                resolve(res.data.data)
              })
            }
            this.onLoad(this.page)
            this.$message({
              type: 'success',
              message: '操作成功!',
            })
            this.onLoad(this.page)
            done(row)
          },
          error => {
            console.log('Update error:', error)
            // console.log('Update error:', error)
            this.loading = false
            loading()
          }
        )
        .catch(error => {
          console.log('Update promise rejected:', error)
          // console.log('Update promise rejected:', error)
          this.loading = false
        })
    },
@@ -409,11 +456,18 @@
          return remove(row.id)
        })
        .then(() => {
          const resolve = this.treeResolveMap.get(row.parentId)
          if (resolve) {
            // 重新加载子节点数据
            getChildLazyTree({ parentId: row.parentId }).then(res => {
              resolve(res.data.data)
            })
          }
          this.onLoad(this.page)
          this.$message({
            type: 'success',
            message: '操作成功!',
          })
          this.onLoad(this.page)
          done(row)
        })
    },
@@ -515,11 +569,11 @@
    },
    onLoad (page, params = {}) {
      this.loading = true
      console.log('onLoad triggered with page:', page, 'params:', params)
      console.log('Current parentId:', this.parentId, 'query:', this.query)
      // console.log('onLoad triggered with page:', page, 'params:', params)
      // console.log('Current parentId:', this.parentId, 'query:', this.query)
      getLazyList(this.parentId, Object.assign(params, this.query))
        .then(res => {
          console.log('onLoad response:', res.data)
          // console.log('onLoad response:', res.data)
          // 处理 areaCode,确保是单一字符串
          const data = (res.data.data || []).map(item => {
            const fullAreaCode = this.getFullAreaCode(item.areaCode)
@@ -534,16 +588,17 @@
          this.data = data
          this.loading = false
          this.selectionClear()
          console.log('Table data after onLoad:', this.data)
          // console.log('Table data after onLoad:', this.data)
        })
        .catch(error => {
          console.log('onLoad error:', error)
          // console.log('onLoad error:', error)
          this.loading = false
          this.data = this.data // 保留当前数据
          console.log('Table data after onLoad error:', this.data)
          // console.log('Table data after onLoad error:', this.data)
        })
    },
    treeLoad (tree, treeNode, resolve) {
      this.treeResolveMap.set(tree.id, resolve)
      getChildLazyTree({ parentId: tree.id }).then(res => {
        resolve(res.data.data)
      })
src/views/system/helpCenter.vue
@@ -2,7 +2,8 @@
  <basic-container>
    <div class="helpContainer">
      <el-table
        class="custom-table"
        border
        class="custom-header"
        :data="helpData"
        style="width: 100%"
        :row-class-name="tableRowClassName"
@@ -16,22 +17,37 @@
        <el-table-column prop="levelTwoType" label="二级类别" />
        <el-table-column prop="uploadUser" label="上传者" />
        <el-table-column prop="createTime" label="上传时间" />
        <el-table-column label="操作" width="150" align="center">
          <template #default="scope">
            <span class="look" @click="clickDetail(scope.row)" v-if="permission.helpCenterLook"
              >查看</span
            >
            <el-upload
              style="display: inline-block"
              :show-file-list="false"
              :before-upload="file => onUploadFileBefore(file, scope.row)"
              :limit="1"
              accept=".pdf"
            >
              <span class="uploads" v-if="permission.helpCenterUpload">上传</span>
            </el-upload>
          </template>
        </el-table-column>
     <el-table-column label="操作" width="200" align="center">
  <template #default="scope">
    <div class="actionBar" >
      <el-button
        class="look"
        icon="el-icon-view"
        type="text"
        @click="clickDetail(scope.row)"
        v-if="permission.helpCenterLook"
      >
        查看
      </el-button>
      <el-upload
        style="display: inline-block"
        :show-file-list="false"
        :before-upload="file => onUploadFileBefore(file, scope.row)"
        :limit="1"
        accept=".pdf"
      >
        <el-button
          class="uploads"
          icon="el-icon-upload"
          type="text"
          v-if="permission.helpCenterUpload"
        >
          上传
        </el-button>
      </el-upload>
    </div>
  </template>
</el-table-column>
      </el-table>
    </div>
  </basic-container>
@@ -110,12 +126,12 @@
</script>
<style lang="scss">
.helpContainer {
  .el-table .success-row {
    --el-table-tr-bg-color: #fff;
  }
  .el-table .warning-row {
    --el-table-tr-bg-color: #f3f6ff;
  }
  // .el-table .success-row {
  //   --el-table-tr-bg-color: #fff;
  // }
  // .el-table .warning-row {
  //   --el-table-tr-bg-color: #f3f6ff;
  // }
}
</style>
<style scoped lang="scss">
@@ -145,18 +161,30 @@
.helpContainer {
  height: 100%;
  .look {
    font-family: Source Han Sans CN, Source Han Sans CN;
    color: #1c5cff;
    // font-family: Source Han Sans CN, Source Han Sans CN;
    // color: #1c5cff;
    cursor: pointer;
    margin-right: 15px;
    font-size: 14px;
    // font-size: 14px;
  }
  .uploads {
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 400;
    font-size: 14px;
    color: #19876d;
    // font-family: Source Han Sans CN, Source Han Sans CN;
    // font-weight: 400;
    // font-size: 14px;
    // color: #19876d;
    cursor: pointer;
  }
  .actionBar {
  display: flex;
  justify-content: center;
   align-items: center;
    gap: 8px;
  }
  :deep(.custom-header th.el-table__cell) {
  color: rgba(0, 0, 0, 0.85);
  background: #fafafa;
}
}
</style>
src/views/system/menu.vue
@@ -38,7 +38,8 @@
  data () {
    return {
      form: {},
      query: {},
      query: {
      },
      loading: true,
      selectionList: [],
      parentId: 0,
@@ -52,7 +53,9 @@
        tip: false,
        simplePage: true,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        dialogWidth: '60%',
        tree: true,
        border: true,
@@ -71,6 +74,7 @@
            prop: 'name',
            width: 300,
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -114,6 +118,7 @@
            type: 'icon',
            slot: true,
            iconList: iconList,
            value: 'el-icon-menu',
            rules: [
              {
                required: true,
@@ -126,6 +131,7 @@
            label: '菜单编号',
            prop: 'code',
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -138,6 +144,7 @@
            label: '菜单别名',
            prop: 'alias',
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -174,6 +181,8 @@
            prop: 'sysType',
            type: 'radio',
            value: 1,
            search: true,
            searchSpan: 4,
            dicData: [
              {
                label: '后台管理',
@@ -182,6 +191,10 @@
              {
                label: '大屏',
                value: 2,
              },
              {
                label: '移动app',
                value: 3,
              },
            ],
            hide: true,
@@ -462,4 +475,4 @@
.namei {
  margin-right: 5px;
}
</style>
</style>
src/views/system/param.vue
@@ -1,21 +1,33 @@
<template>
  <basic-container>
    <div class="avue-crud">
      <el-row :hidden="!search" class="rowStyle" >
      <el-row :hidden="!search">
        <!-- 查询模块 -->
        <el-form :inline="true" :model="query">
          <el-form-item label="参数名:">
            <el-input v-model="query.paramName" placeholder="请输入参数名"></el-input>
          </el-form-item>
          <el-form-item label="参数键:">
            <el-input v-model="query.paramKey" placeholder="请输入参数键"></el-input>
          </el-form-item>
          <!-- 查询按钮 -->
          <el-form-item>
            <el-button type="primary" icon="el-icon-search" @click="searchChange">搜 索</el-button>
            <el-button icon="el-icon-delete" @click="searchReset()">清 空</el-button>
          </el-form-item>
        </el-form>
        <div class="ztzf-form-search">
          <el-form inline :model="query">
            <el-row :gutter="24">
              <el-col :span="8">
                <el-form-item label="参数名:">
                  <el-input v-model="query.paramName" placeholder="请输入参数名"></el-input>
                </el-form-item>
              </el-col>
              <el-col :span="8">
                <el-form-item label="参数键:">
                  <el-input v-model="query.paramKey" placeholder="请输入参数键"></el-input>
                </el-form-item>
              </el-col>
              <!-- 查询按钮 -->
              <el-col :span="8">
                <el-form-item>
                  <el-button type="primary" icon="el-icon-search" @click="searchChange"
                    >搜 索</el-button
                  >
                  <el-button icon="el-icon-delete" @click="searchReset()">清 空</el-button>
                </el-form-item>
              </el-col>
            </el-row>
          </el-form>
        </div>
      </el-row>
      <el-row>
        <div class="avue-crud__header">
@@ -125,6 +137,7 @@
      </el-row>
      <!-- 表单模块 -->
      <el-dialog
        class="ztzf-dialog-mange"
        :title="title"
        v-model="box"
        width="50%"
@@ -372,8 +385,6 @@
<style scoped lang="scss">
.rowStyle {
padding: 6px 18px
  padding: 6px 18px;
}
</style>
src/views/system/sysConfig.vue
@@ -40,7 +40,9 @@
        dialogWidth: 950,
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        // 禁用默认按钮
@@ -63,6 +65,7 @@
            span: 24,
            row: true,
            search: true,
            searchSpan: 4,
            rules: [
              {
                required: true,
@@ -72,7 +75,7 @@
            ],
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'deptName',
            addDisplay: false,
            editDisplay: false,
@@ -80,13 +83,13 @@
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'deptId',
            hide: true,
            type: 'tree',
@@ -99,7 +102,7 @@
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],
src/views/system/user.vue
@@ -227,7 +227,9 @@
      option: {
        tip: false,
        searchShow: true,
        searchMenuSpan: 6,
        searchGutter: 30,
        searchMenuPosition: 'left',
        searchMenuSpan: 4,
        border: true,
        index: true,
        selection: true,
@@ -243,6 +245,7 @@
            label: '登录账号',
            prop: 'account',
            search: true,
            searchSpan: 5,
            display: false,
          },
          // {
@@ -255,6 +258,7 @@
            label: '用户姓名',
            prop: 'realName',
            search: true,
            searchSpan: 5,
            display: false,
          },
          {
@@ -515,6 +519,7 @@
            column: [
              {
                label: '到期时间',
                labelWidth: 110,
                prop: 'expireTime',
                type: 'datetime',
                format: 'YYYY-MM-DD HH:mm:ss',
@@ -522,12 +527,14 @@
              },
              {
                label: '次数限制',
                labelWidth: 110,
                prop: 'maxLoginNum',
                type: 'input',
              },
              {
                label: '飞行开始时间',
                labelWidth: 110,
                prop: 'flightStartTime',
                type: 'time',
                format: 'HH:mm',
@@ -542,6 +549,7 @@
              },
              {
                label: '飞行结束时间',
                labelWidth: 110,
                prop: 'flightEndTime',
                type: 'time',
                format: 'HH:mm',
src/views/system/userinfo.vue
@@ -44,7 +44,7 @@
            <el-form-item label="姓名:">
              <el-input
                class="ztzf-input"
                :style="{ width: pxToRem(230) }"
                :style="{ width: pxToRem(350) }"
                v-model="userInfo.realName"
                disabled
              />
@@ -52,7 +52,7 @@
            <!--                        <el-form-item label="昵称:">
                                                    <el-input class="ztzf-input" v-model="userInfo.name" />
                                                </el-form-item>-->
            <el-form-item label="所属单位:">
            <el-form-item label="所属部门:">
              <el-input class="ztzf-input" v-model="userInfo.deptName" disabled />
            </el-form-item>
            <el-form-item label="手机号:">
@@ -165,6 +165,29 @@
    }
    return true
}
// 校验密码不能包含中文
const validatePasswordNoChinese = (password) => {
  const chineseRegex = /[\u4e00-\u9fa5]/;
  if (chineseRegex.test(password)) {
    ElMessage.error('密码不能包含中文字符');
    return false;
  }
  return true;
};
// 校验密码复杂度(长度≥6位,包含字母和数字)
const validatePasswordStrength = (password) => {
  if (password.length < 6) {
    ElMessage.error('密码长度不能少于6位');
    return false;
  }
  // const hasLetter = /[a-zA-Z]/.test(password);
  // const hasNumber = /\d/.test(password);
  // if (!hasLetter || !hasNumber) {
  //   ElMessage.error('密码需同时包含字母和数字');
  //   return false;
  // }
  return true;
};
const passwordForm = ref({
  oldPassword: '',
  newPassword: '',
@@ -245,6 +268,26 @@
      }
    });
  } else if (checked.value === 1) {
   // 原始密码校验
    if (!passwordForm.value.oldPassword) {
      ElMessage.error('请输入原始密码');
      return;
    }
     // 中文校验
    if (!validatePasswordNoChinese(passwordForm.value.newPassword)) return;
    // 新密码校验
    if (!passwordForm.value.newPassword) {
      ElMessage.error('请输入新密码');
      return;
    }
    // 确认密码校验
    if (passwordForm.value.newPassword !== passwordForm.value.newPassword1) {
      ElMessage.error('两次输入的新密码不一致');
      return;
    }
    // 复杂度校验
    if (!validatePasswordStrength(passwordForm.value.newPassword)) return;
    updatePassword(
      md5(passwordForm.value.oldPassword),
      md5(passwordForm.value.newPassword),
@@ -300,6 +343,7 @@
  margin: 16px;
  padding: 10px;
  background-color: #ffffff;
  height: 100%;
}
.titleBoxs {
  margin: 0 24px;
@@ -319,26 +363,25 @@
:deep(.el-form-item__label) {
  color: #000 !important;
}
:deep(.el-button) {
  width: 150px !important;
}
// :deep(.el-button) {
//   width: 150px !important;
// }
.info {
  display: flex;
  justify-content: space-between;
  justify-content: center;
  align-items: center;
  padding: 0 80px 0 50px;
  .section:first-child{
  margin-right: 50px;}
}
.password {
  padding: 0 120px 0 50px;
}
.butn {
  // position: absolute;
  // bottom: 20px;
  // left: 50%;
  // transform: translate(-50%);
  display: flex;
  justify-content: center;
  cursor: pointer;
  img {
    width: 96px;
    height: 32px;
src/views/tickets/orderLog-copy1.vue
@@ -23,7 +23,7 @@
              </div>
              <div class="search-bar-box-item">
                <el-select placeholder="请选择所属单位" v-model="filters.create_dept" clearable>
                <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>
@@ -587,7 +587,7 @@
            showOverflowTooltip: true,
          },
          {
            label: '所属单位', prop: 'dept_name', width: 100, ellipsis: true,
            label: '所属部门', prop: 'dept_name', width: 100, ellipsis: true,
            showOverflowTooltip: true,
          },
          {
src/views/tickets/orderLog.vue
@@ -1,165 +1,102 @@
<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"
      >
      <el-tab-pane v-for="tab in filteredTabs" :key="tab.name" :label="`${tab.label} (${tab.count})`" :name="tab.name">
        <basic-main-content>
          <!-- 查询条件筛选栏 -->
          <div class="filter-bar">
            <div class="search-bar-box">
              <div class="search-bar-box-item">
                <el-input
                  v-model="filters.key_word"
                  placeholder="输入工单编号/名称/内容/姓名"
                  clearable
                  @keyup.enter="handleSearch"
                />
              </div>
          <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>-->
                  <!--              <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>-->
              <div class="search-bar-box-item">
                <el-date-picker
                  @change="handleSearch"
                  v-model="filters.dateRange"
                  type="daterange"
                  range-separator="至"
                  start-placeholder="开始日期"
                  end-placeholder="结束日期"
                  :default-value="datePickerDefaultVal"
                />
              </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
                  @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>
              </div>
                  <!-- <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> -->
              <div class="search-bar-box-item">
                <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>
              </div>
              <!-- <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> -->
              <div class="search-bar-box-item">
                <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>
              </div>
            </div>
            <div class="search-bar-box">
              <div class="search-bar-box-item flex-2">
                <el-date-picker
                  @change="handleSearch"
                  v-model="filters.cycleDateRange"
                  type="daterange"
                  range-separator="至"
                  start-placeholder="工单周期开始日期"
                  end-placeholder="工单周期结束日期"
                  :default-value="datePickerDefaultVal"
                />
              </div>
              <div class="search-bar-box-item">
                <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>
              </div>
              <div class="search-bar-box-item">
                <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',
                  }"
                />
              </div>
              <div class="search-bar-box-item"></div>
              <div class="search-bar-box-item"></div>
              <div class="search-bar-box-item search-btn">
                <el-button type="primary" icon="el-icon-search" @click="handleSearch"
                  >搜索</el-button
                >
                <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
              </div>
            </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"
          >
          <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 }}
@@ -173,28 +110,19 @@
            </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
              >
              <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
                  >
                  <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()) &&
@@ -202,48 +130,34 @@
                  " -->
                <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-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
                  >
                  <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
                  >
                  <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
                  >
                  <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>
@@ -261,70 +175,42 @@
    </el-tabs>
    <!-- 新建工单对话框 -->
    <el-dialog
      v-model="dialogVisible"
      title="新建工单"
      width="70%"
      :close-on-click-modal="false"
      @close="resetForm"
    >
    <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-input v-model="form.name" placeholder="请输入工单名称" maxlength="100" show-word-limit></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <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 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="300"></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 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-option v-for="item in ai_types" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
              </el-select>
            </el-form-item>
          </el-col>
@@ -333,27 +219,15 @@
        <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-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-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">
@@ -364,15 +238,10 @@
                </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"
                <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>
@@ -384,47 +253,39 @@
          </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-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
      align-center
      v-model="detailVisible"
      :title="detailTitle"
      width="70%"
      :close-on-click-modal="false"
      @close="resetForm"
    >
    <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"
            >
            <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-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">
@@ -444,64 +305,38 @@
        <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-input v-model="form.name" placeholder="请输入工单名称" :disabled="detailTitle === '工单详情'"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <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 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="300"></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 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 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>
@@ -516,54 +351,30 @@
          <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-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-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
              style="width: 100px"
              v-model="form.deal_time"
              prop="deal_time"
              value-format="HH:mm"
              :picker-options="{
            <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 === '工单详情'"
            />
              }" :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-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>
@@ -573,72 +384,45 @@
            <map-container v-if="detailVisible" ref="MapContainer"></map-container>
          </el-col>
        </el-row>
        <el-row>
          <div class="add-box-btns">
            <el-button
              type="danger"
              v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
              @click="submitForm(1)"
              >发布</el-button
            >
            <el-button
              type="primary"
              v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
              @click="submitForm(0)"
              >保存</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>
        </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
      align-center
      v-model="detailVisibleCopy"
      title="工单详情"
      width="70%"
      :close-on-click-modal="false"
      @close="resetForm"
    >
    <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"
            >
            <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-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">
@@ -658,23 +442,20 @@
        <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-input v-model="form.name" placeholder="请输入工单名称" :disabled="true"></el-input>
            </el-form-item>
          </el-col>
          <el-col :span="12">
          <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-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="300"></el-input-number>
            </el-form-item>
          </el-col>
        </el-row>
@@ -682,35 +463,16 @@
        <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 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 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>
@@ -725,14 +487,8 @@
          <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-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">
@@ -742,32 +498,18 @@
          </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="{
            <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-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>
@@ -778,39 +520,31 @@
          </el-col>
        </el-row>
        <el-row>
          <div class="add-box-btns">
            <el-button
              type="danger"
              v-if="form.status == 0 || (form.status == 2 && userInfo.user_id == form.create_user)"
              @click="submitForm(1)"
              >发布</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
            >
            <el-button @click="detailVisibleCopy = false">取消</el-button>
          </div>
        </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 { calculateDefaultRange } from '@/utils/util';
import { pxToRem, pxToRemNum } from '@/utils/rem'
import { calculateDefaultRange } from '@/utils/util'
import {
  getList,
  saveUpdateOrderLog,
@@ -821,39 +555,44 @@
  orderLogExport,
  jobStatusNum,
  userPublish,
  deleteOrderLog,
} from '@/api/tickets/orderLog';
import { getTicketInfo } from '@/api/tickets/ticket';
import { getDictionaryByCode } from '@/api/system/dictbiz';
import { getWaylineFileListByArea } 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';
  deleteOrderLog
} 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')
dayjs.extend(weekday);
dayjs.locale('zh-cn');
// const lastHeight = computed(() => store.state.common.lastHeight)
export default {
  components: { elTooltipCopy },
  name: 'TicketPage',
  data() {
  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 },
        { label: '草稿', name: 'DRAFT', value: 0, count: 0 }
      ],
      filters: {
        key_word: '',
@@ -863,7 +602,7 @@
        file_id: '',
        ai_types: [],
        status: '',
        dateRange: [],
        dateRange: []
      },
      departments: [],
      types: [],
@@ -877,7 +616,7 @@
        { label: '草稿', value: '0' },
        { label: '待审核', value: '1' },
        { label: '已驳回', value: '2' },
        { label: '已通过', value: '3' },
        { label: '已通过', value: '3' }
      ],
      //周期
      cycles: ['每天', '周一', '周二', '周三', '周四', '周五', '周六', '周末', '周天', '工作日'],
@@ -889,7 +628,7 @@
        indexLabel: '序号',
        indexFixed: true,
        align: 'center',
        align: 'left',
        border: true,
        stripe: false,
        searchShow: true,
@@ -914,7 +653,7 @@
          {
            label: '工单编号',
            prop: 'job_info_num',
            width: 150,
            width: 150
          },
          {
            label: '工单名称',
@@ -922,47 +661,47 @@
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '工单状态',
            prop: 'status',
            width: 88,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '所属单位',
            prop: 'dept_name',
            ellipsis: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '创建时间',
            prop: 'create_time',
            width: 144,
            ellipsis: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '已执行次数',
            prop: 'job_num',
            width: 70,
            ellipsis: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '工单内容',
            prop: 'content',
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '关联航线',
            prop: 'wayline_name',
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '关联算法',
@@ -970,7 +709,7 @@
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
            label: '关联机巢',
@@ -978,7 +717,7 @@
            width: 100,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          {
@@ -987,7 +726,7 @@
            width: 96,
            ellipsis: true,
            overHidden: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
          },
          // {
          //   label: '关联机巢', prop: 'device_names', width: 112, ellipsis: true, overHidden: true,
@@ -1000,16 +739,16 @@
            formatter: row => this.formatCycleTime(row),
            html: true,
            ellipsis: true,
            showOverflowTooltip: true,
            showOverflowTooltip: true
            // overHidden: true
          },
        ],
          }
        ]
      },
      page: {
        pageSize: 10,
        currentPage: 1,
        total: 0,
        total: 0
      },
      dialogVisible: false,
      detailVisible: false,
@@ -1018,171 +757,178 @@
      rules: {
        name: [
          { required: true, message: '请输入工单名称', trigger: 'blur' },
          { max: 100, message: '工单名称不能超过100个字', 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' },
          { 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' }],
        deal_time: [{ required: true, message: '请选择执行时间', trigger: 'blur' }]
      },
      loading: false,
      globalCounts: {},
      mapLoaded: false,
      // 配置时间选择器默认配置
      datePickerDefaultVal: calculateDefaultRange(),
    };
      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'];
  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.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,
    }));
      value: item.id
    }))
  },
  mounted() {
    this.fetchTableData();
    const id = this.$route.query.id;
    console.log('idddddd', 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 = {});
      this.handleViewDetail({ id })
      const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/orderLog')
      find && (find.query = {})
    } else {
      console.error('工单ID不存在!');
      console.error('工单ID不存在!')
    }
  },
  computed: {
    CircleClose () {
      return CircleClose
    },
    ...mapGetters(['userInfo', 'permission']),
    filteredTabs() {
    lastHeight () {
      const height = this.$store.state.common.lastHeight
      return Number(height) || 0 // 确保是数字类型
    },
    filteredTabs () {
      // rejection_and_draft 权限控制“已驳回”和“草稿”tab
      // console.log(this.permission, '权限信息')
      // console.log(this.userInfo, '权限信息22')
      const canShowRejectAndDraft = this.permission?.rejection_and_draft === true;
      const canShowRejectAndDraft = this.permission?.rejection_and_draft === true
      return this.tabs
        .map(tab => {
          if (tab.name === 'DRAFT') {
            return { ...tab, isShow: canShowRejectAndDraft };
            return { ...tab, isShow: canShowRejectAndDraft }
          }
          if (tab.name === 'REJECTED') {
            return { ...tab, isShow: canShowRejectAndDraft };
            return { ...tab, isShow: canShowRejectAndDraft }
          }
          return { ...tab, isShow: true };
          return { ...tab, isShow: true }
        })
        .filter(tab => tab.isShow);
    },
        .filter(tab => tab.isShow)
    }
  },
  methods: {
    handleCellClick(row, column) {
    handleCellClick (row, column) {
      if (column.no === 1) {
        navigator.clipboard.writeText(row.job_info_num).then(() => {
          this.$message.success('复制工单编号成功');
        });
          this.$message.success('复制工单编号成功')
        })
      } else if (column.no === 2) {
        navigator.clipboard.writeText(row.name).then(() => {
          this.$message.success('复制工单名称成功');
        });
          this.$message.success('复制工单名称成功')
        })
      } else if (column.no === 4) {
        navigator.clipboard.writeText(row.dept_name).then(() => {
          this.$message.success('复制所属单位成功');
        });
          this.$message.success('复制所属单位成功')
        })
      } else if (column.no === 8) {
        navigator.clipboard.writeText(row.wayline_name).then(() => {
          this.$message.success('复制关联航线成功');
        });
          this.$message.success('复制关联航线成功')
        })
      } else if (column.no === 9) {
        navigator.clipboard.writeText(row.ai_type_str).then(() => {
          this.$message.success('复制关联算法成功');
        });
          this.$message.success('复制关联算法成功')
        })
      }
    },
    disabledDate(time) {
      return time.getTime() < Date.now() - 8.64e7; // 86400000 = 24 * 60 * 60 * 1000
    disabledDate (time) {
      return time.getTime() < Date.now() - 8.64e7 // 86400000 = 24 * 60 * 60 * 1000
    },
    searchChange(params, done) {
    searchChange (params, done) {
      // console.log('searchChange')
      this.query = params;
      this.parentId = '';
      this.page.currentPage = 1;
      this.onLoad(this.page, params);
      done();
      this.query = params
      this.parentId = ''
      this.page.currentPage = 1
      this.onLoad(this.page, params)
      done()
    },
    async onLoad(page, params = {}) {
      this.loading = true;
    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();
      });
        this.tableData = res.data.data
        this.loading = false
        this.selectionClear()
      })
    },
    selectionClear() {
      this.selectionList = [];
      this.$refs.crud.toggleSelection();
    selectionClear () {
      this.selectionList = []
      this.$refs.crud.toggleSelection()
    },
    async loadAMapScripts() {
    async loadAMapScripts () {
      try {
        // await loadAMap();
        // await loadAMapUI();
        this.mapLoaded = true;
        this.mapLoaded = true
      } catch (error) {
        console.error('Failed to load AMap scripts:', error);
        this.$message.error('地图加载失败,请检查网络或API Key配置');
        console.error('Failed to load AMap scripts:', error)
        this.$message.error('地图加载失败,请检查网络或API Key配置')
      }
    },
    formatCycleTime(row) {
      return `${row.cycle_time_value}`;
    formatCycleTime (row) {
      return `${row.cycle_time_value}`
    },
    async fetchTableData() {
      this.loading = true;
    async fetchTableData () {
      this.loading = true
      try {
        let params = this.getQueryParam();
        let params = this.getQueryParam()
        // console.log('发送的参数:', params)
        const response = await getList(params, this.page.currentPage, this.page.pageSize);
        const response = await getList(params, this.page.currentPage, this.page.pageSize)
        if (!response?.data?.data?.records) {
          throw new Error('接口返回数据格式不正确');
          throw new Error('接口返回数据格式不正确')
        }
        const { total, records } = response.data.data;
        const { total, records } = response.data.data
        this.tableData = records.map(item => {
          return item;
        });
          return item
        })
        // console.log('权限检查:', this.permission)
        this.page.total = total || 0;
        this.updateGlobalCounts();
        this.page.total = total || 0
        this.updateGlobalCounts()
      } catch (error) {
        // console.error('获取数据失败:', error)
        this.$message.error(error.message || '获取数据失败');
        this.tableData = [];
        this.page.total = 0;
        this.$message.error(error.message || '获取数据失败')
        this.tableData = []
        this.page.total = 0
      } finally {
        this.loading = false;
        this.loading = false
      }
    },
    getQueryParam() {
      const currentTab = this.tabs.find(tab => tab.name === this.activeTab);
    getQueryParam () {
      const currentTab = this.tabs.find(tab => tab.name === this.activeTab)
      if (this.filters.dateRange) {
        // console.log(
        //   'this.formatDate(this.filters.dateRange[0])',
@@ -1216,187 +962,203 @@
        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;
        size: this.page.pageSize
      }
      return params
    },
    sizeChange(pageSize) {
      this.page.pageSize = pageSize;
    sizeChange (pageSize) {
      this.page.pageSize = pageSize
    },
    async submitForm(status) {
    async submitForm (status) {
      this.$refs.testform.validate(async valid => {
        if (valid) {
          let dateRange = this.form.date_range;
          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]);
          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();
            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('任务时间不能小于当前时间');
              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;
          });
          console.log(maxItem.drone_height, '高度');
          console.log(this.lastHeight, '最后一点高度');
          let result = _.round((maxItem.drone_height + this.form.rth_altitude), 2);
          const submitData = {
            ...this.form,
            status: status,
            ai_types: this.form.ai_types?.length ? this.form.ai_types : [],
          };
          // 判断该值this.form.date_range[0] 周几 和rep_fre_type 是不是相等
          // if (this.form.deal_time) {
          //    const weekday = dayjs(this.form.date_range[0]).format('dddd');
          //    console.log('444', weekday)
          //   //  form.rep_fre_type
          //   const now = new Date()
          //   const today = now.toDateString()
          //   const selectedDate = new Date(this.form.date_range[0]).toDateString()
          let resultHeight = this.lastHeight - maxItem.drone_height
          let resultH = _.round(resultHeight, 2)
          //   if (today === selectedDate) {
          //     const [hours, minutes] = this.form.deal_time.split(':')
          //     const selectedTime = new Date()
          //     selectedTime.setHours(parseInt(hours), parseInt(minutes))
          if (this.lastHeight > result) {
            this.$confirm(`当前返航高度存在安全隐患,建议调整为${resultH}米以上后进行发布`, '提示', {
              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(() => {
          //     if (selectedTime < now) {
          //       return this.$message.warning('任务时间不能小于当前时间')
          //     }
          //   }
          // }
          await saveUpdateOrderLog(submitData);
          let id = this.form.id;
          if (id) {
            this.$message.success('工单发布成功');
            })
          } else {
            this.$message.success('工单创建成功');
            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()
          }
          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;
    async rejectDetail (id) {
      const response = await orderLogDetails(id)
      let data = response.data.data
      this.$confirm(data.remark, '驳回原因', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        type: 'warning'
      }).then(() => {
        this.form = {
          ...response.data.data,
        };
          ...response.data.data
        }
        // this.detailVisible = true
        this.handleViewDetail(data);
      });
        this.handleViewDetail(data)
      })
    },
    formatDate(date) {
      if (!date) return undefined;
      const d = new Date(date);
    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`;
      ).padStart(2, '0')} 00:00:00`
    },
    mapStatus(status) {
    mapStatus (status) {
      const statusTextMap = {
        0: '草稿',
        1: '待审核',
        2: '已驳回',
        3: '已通过',
      };
      return statusTextMap[status] || '未知状态';
        3: '已通过'
      }
      return statusTextMap[status] || '未知状态'
    },
    getStatusTagType(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';
        5: 'danger'
      }
      this.page.currentPage = 1;
      this.fetchTableData();
      return statusMap[status] || 'info'
    },
    handleSearch() {
      this.page.currentPage = 1;
      this.fetchTableData();
    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()
    },
    handleReset() {
    handleSearch () {
      this.page.currentPage = 1
      this.fetchTableData()
    },
    handleReset () {
      this.filters = {
        keyword: '',
        department: '',
        type: '',
        dateRange: [],
        status: '',
      };
      this.page.currentPage = 1;
      this.fetchTableData();
        status: ''
      }
      this.page.currentPage = 1
      this.fetchTableData()
    },
    currentChange(currentPage) {
      this.page.currentPage = currentPage;
    currentChange (currentPage) {
      this.page.currentPage = currentPage
    },
    async updateGlobalCounts() {
    async updateGlobalCounts () {
      const counts = {
        all: 0,
        DRAFT: 0,
        WAIT_AUDIT: 0,
        REJECTED: 0,
        PASS: 0,
      };
      var reponse = await jobStatusNum();
        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);
        const tab = this.tabs.find(t => t.name === item.dict_key)
        if (tab) {
          tab.count = item.num;
          tab.count = item.num
        }
      });
      })
    },
    handleAdd() {
      this.form = {};
      this.dialogVisible = true;
    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.$refs.MapContainer.initAddEntity('initPosition')
        }
      });
      })
      //航线列表
      this.asyncgetWaylineFileListByArea();
      this.asyncgetWaylineFileListByArea()
    },
    resetForm() {
    resetForm () {
      this.form = {
        name: '',
        type: '',
@@ -1405,257 +1167,274 @@
        location: '',
        address: '',
        content: '',
        photos: [],
      };
        photos: []
      }
      if (this.$refs.testform) {
        this.$refs.testform.resetFields();
        this.$refs.testform.resetFields()
      }
    },
    formatLocation(location) {
    formatLocation (location) {
      if (!Array.isArray(location)) {
        return '未知位置';
        return '未知位置'
      }
      return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`;
      return `${location[0].toFixed(6)}, ${location[1].toFixed(6)}`
    },
    async handleViewDetail(row) {
      const response = await orderLogDetails(row.id);
      const data = response.data.data;
    async handleViewDetail (row) {
      const response = await orderLogDetails(row.id)
      const data = response.data.data
      this.form = {
        ...data,
      };
        ...data
      }
      // 更新机巢列表
      this.device_sns = data.device_list;
      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.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.detailTitle = '编辑工单')
      this.detailVisible = true;
      this.detailVisible = true
      // 更新航线列表,追加 wayline_file_region_vo 数据
      if (data.wayline_file_region_vo) {
        const newWayline = 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.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 = {});
      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;
    async handleCheckDetail (row) {
      const response = await orderLogDetails(row.id)
      const data = response.data.data
      this.form = {
        ...data,
      };
        ...data
      }
      // 更新航线列表,追加 wayline_file_region_vo 数据
      // 更新航线列表,追加 wayline_file_region_vo 数据
      if (data.wayline_file_region_vo) {
        const newWayline = 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.wayLineList.push(newWayline)
        }
      }
      // 更新机巢列表
      this.device_sns = data.device_list;
      this.detailVisibleCopy = true;
      this.initMapLine(data.device_map_infos);
      this.device_sns = data.device_list
      this.detailVisibleCopy = true
      this.initMapLine(data.device_map_infos)
    },
    //导出
    async exportData() {
    async exportData () {
      this.$confirm('是否智飞工单数据?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        type: 'warning'
      }).then(() => {
        NProgress.start();
        let params = this.getQueryParam();
        NProgress.start()
        let params = this.getQueryParam()
        orderLogExport(params).then(res => {
          downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`);
          NProgress.done();
        });
      });
          downloadXls(res.data, `智飞工单${this.$dayjs().format('YYYY-MM-DD')}.xlsx`)
          NProgress.done()
        })
      })
    },
    hasAddBtnPermission() {
    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;
      return this.permission && this.permission.order_log_add === true
    },
    hasPaddingBtnPermission() {
    hasPaddingBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.order_log_review === true;
      return this.permission && this.permission.order_log_review === true
    },
    hasRecallPaddingBtnPermission() {
    hasRecallPaddingBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      // 智飞工单撤回
      return this.permission && this.permission.order_log_recall === true;
      return this.permission && this.permission.order_log_recall === true
    },
    //驳回按钮权限
    hasRejectionBtnPermission() {
    hasRejectionBtnPermission () {
      // undefined 或 false 都返回 false,只有 true 返回 true
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.rejection_btn === true;
      return this.permission && this.permission.rejection_btn === true
    },
    //自己点发布
    userPublishPush(id) {
    userPublishPush (id) {
      this.$confirm('确定发布吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        type: 'warning'
      }).then(() => {
        let response = userPublish(id);
        this.$message.success('发布成功');
        this.fetchTableData();
      });
        let response = userPublish(id)
        this.$message.success('发布成功')
        this.fetchTableData()
      })
    },
    //删除
    deleteOrderLog(id) {
    deleteOrderLog (id) {
      this.$confirm('确定删除吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        type: 'warning'
      }).then(() => {
        let response = deleteOrderLog(id);
        this.$message.success('删除');
        this.fetchTableData();
      });
        let response = deleteOrderLog(id)
        this.$message.success('删除')
        this.fetchTableData()
      })
    },
    refreshChange() {
      this.fetchTableData();
    refreshChange () {
      this.fetchTableData()
    },
    //获取航线列表
    async asyncgetWaylineFileListByArea(name) {
      var wayLineListResponse = await getWaylineFileListByArea(this.userInfo.detail.areaCode);
      this.wayLineList = wayLineListResponse.data.data;
    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();
      this.initMapLine()
    },
    initMapLine(infos = {}, cb = () => {}) {
      let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id);
    initMapLine (infos = {}, cb = () => {
    }) {
      let currentLine = this.wayLineList.find(item => item.wayline_id == this.form.file_id)
      if (!currentLine) return;
      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()}`,
            url: `${import.meta.env.VITE_APP_AIRLINE_URL + currentLine.object_key
              }?_t=${new Date().getTime()}`,
            type: currentLine.wayline_type,
            cb,
            infos,
          });
            infos
          })
        }
      });
      })
    },
    //可飞行机巢列表
    async getFlyingNestBy(waylineId) {
      const that = this;
    async getFlyingNestBy (waylineId) {
      const that = this
      // 先清空
      that.form.device_sns = [];
      that.device_sns = [];
      that.form.device_sns = []
      that.device_sns = []
      this.initMapLine({}, async polygon => {
        const currentLine = that.wayLineList.find(item => item.wayline_id === waylineId);
        const currentLine = that.wayLineList.find(item => item.wayline_id === waylineId)
        //按照航线来
        const params = {
          type: [2, 4, 5].includes(currentLine.wayline_type) ? 2 : 0,
          type: ['2', '4', '5'].includes(currentLine.wayline_type) ? 2 : 0,
          wayline_id: waylineId,
          polygon,
        };
        var wayLineListResponse = await getFlyingNestBy(params);
          polygon
        }
        var wayLineListResponse = await getFlyingNestBy(params)
        // 再赋值
        that.device_sns = wayLineListResponse.data.data;
      });
        that.device_sns = wayLineListResponse.data.data
      })
    },
    //撤回
    async orderLogRecall(id) {
    async orderLogRecall (id) {
      this.$confirm('确定撤回则到草稿箱。', '是否撤回?', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning',
        type: 'warning'
      }).then(async () => {
        let reposne = await orderLogRecall(id);
        this.handleSearch();
      });
        let reposne = await orderLogRecall(id)
        this.handleSearch()
      })
    },
    onLoad() {
      this.fetchTableData();
    onLoad () {
      this.fetchTableData()
    },
    /**
     * 通过
     */
    async orderLogPass(id) {
      let response = await orderLogPass(id);
      let data = response.data.data;
      this.$message.success('审核通过');
      this.detailVisibleCopy = false;
    async orderLogPass (id) {
      let response = await orderLogPass(id)
      let data = response.data.data
      this.$message.success('审核通过')
      this.detailVisibleCopy = false
      this.detailVisible = false;
      this.detailVisible = false
      this.page.currentPage = 1;
      this.onLoad(this.page, this.query);
      this.page.currentPage = 1
      this.onLoad(this.page, this.query)
      // this.handleViewDetail()
    },
    /**
     * 驳回
     */
    async orderLogReject(id) {
    async orderLogReject (id) {
      this.$prompt('', '驳回原因', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        cancelButtonText: '取消'
      }).then(async ({ value }) => {
        let response = await orderLogReject(id, value);
        let data = response.data.data;
        this.$message.success('驳回成功');
        this.detailVisibleCopy = false;
        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);
      });
    },
        this.page.currentPage = 1
        this.onLoad(this.page, this.query)
      })
    }
  },
  watch: {
    // lastHeight(newVal, oldVal) {
    //   console.log('lastHeight', newVal, oldVal);
    //   this.getlastHeight = newVal;
    // },
    tableData: {
      handler() {
      handler () {
        // this.updateTabCounts()
      },
      deep: true,
    },
  },
};
      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;
@@ -1696,7 +1475,7 @@
    &-item {
      flex: 1;
      & > ::v-deep(.el-date-editor) {
      &> ::v-deep(.el-date-editor) {
        width: 100%;
        box-sizing: border-box;
      }
@@ -1839,7 +1618,7 @@
    justify-content: center;
    flex-wrap: wrap;
    & > ::v-deep(.el-button) {
    &> ::v-deep(.el-button) {
      flex: 1;
      margin-left: 0;
      margin-right: 10px;
@@ -1853,6 +1632,16 @@
  justify-content: center;
}
.safe-height {
  :deep(.el-form-item__label) {
    width: 120px !important;
  }
  :deep(.el-input-number) {
    width: 180px !important;
  }
}
.flex {
  display: flex;
}
@@ -1860,7 +1649,7 @@
.flex-1 {
  flex: 1;
  & > ::v-deep(div) {
  &> ::v-deep(div) {
    width: 100% !important;
  }
}
src/views/tickets/ticket.vue
@@ -9,133 +9,163 @@
      >
        <basic-main-content>
          <!-- 查询条件筛选栏 -->
          <div class="filter-bar">
            <el-input
              v-model="filters.keyword"
              placeholder="请输入关键字"
              class="filter-item"
              clearable
              @clear="handleKeyWords"
              @keyup.enter="handleSearch"
            />
            <!-- <el-select
              v-model="filters.department"
              placeholder="请选择所属单位"
              class="filter-item"
              clearable
            >
              <el-option
                v-for="item in departments"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select> -->
            <!-- <el-select
              v-model="filters.type"
              placeholder="请选择工单类型"
              class="filter-item"
              clearable
              @change="handleSearch"
            >
              <el-option
                v-for="item in types"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select> -->
            <el-date-picker
              v-model="filters.dateRange"
              type="daterange"
              class="filter-item"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              :default-value="datePickerDefaultVal"
            >
            </el-date-picker>
            <el-select
              v-model="filters.status"
              placeholder="请选择状态"
              class="filter-item"
              clearable
              @change="handleSearch"
            >
              <el-option
                v-for="item in statuses"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
            <!-- <el-select
              v-model="filters.algorithm"
              placeholder="请选择关联算法"
              class="filter-item"
              clearable
              @change="handleSearch"
            >
              <el-option
                v-for="item in algorithms"
                :key="item.dict_key"
                :label="item.dict_value"
                :value="item.dict_key"
              />
            </el-select> -->
            <!-- <el-tree-select
              popper-class="custom-tree-select"
              :style="{ width: pxToRem(186) }"
              placeholder="请选择关联算法"
              v-model="dictKey"
              :data="dataList"
              :default-expanded-keys="[dictKey]"
              check-strictly
              node-key="id"
              :props="treePropsSF"
              @node-click="handleSFNodeClick"
              clearable
              @clear="handleClear"
            /> -->
            <el-tree-select
              style="z-index: 1000"
              :teleported="false"
              class="custom-tree-select"
              :style="{ width: pxToRem(186) }"
              v-model="dictKey"
              :data="dataList"
              :default-expanded-keys="[dictKey]"
              :props="treePropsSF"
              :render-after-expand="false"
              :default-checked-keys="checkedKeys"
              node-key="id"
              multiple
              show-checkbox
              collapse-tags
              collapse-tags-tooltip
              clearable
              @check="handleCheck"
              @node-click="handleSFNodeClick"
              @clear="handleClear"
            />
            <el-select
              v-model="filters.isReview"
              placeholder="请选择复核状态"
              class="filter-item"
              clearable
              @change="handleSearch"
            >
              <el-option
                v-for="item in reviewStatuses"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
            <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
            <el-button icon="el-icon-refresh" @click="handleReset">清空</el-button>
          </div>
          <!-- 查询条件筛选栏 -->
          <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.keyword"
                      placeholder="请输入关键字"
                      class="filter-item"
                      clearable
                      @clear="handleKeyWords"
                      @keyup.enter="handleSearch"
                    />
                  </el-form-item>
                </el-col>
                <!-- <el-select
                  v-model="filters.department"
                  placeholder="请选择所属部门"
                  class="filter-item"
                  clearable
                >
                  <el-option
                    v-for="item in departments"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select> -->
                <!-- <el-select
                  v-model="filters.type"
                  placeholder="请选择工单类型"
                  class="filter-item"
                  clearable
                  @change="handleSearch"
                >
                  <el-option
                    v-for="item in types"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select> -->
                <el-col :span="5">
                  <el-form-item label="选择日期:">
                  <el-date-picker
                    v-model="filters.dateRange"
                    type="daterange"
                    class="filter-item"
                    range-separator="至"
                    start-placeholder="开始日期"
                    end-placeholder="结束日期"
                    :default-value="datePickerDefaultVal"
                  >
                  </el-date-picker>
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="选择状态:">
                  <el-select
                    v-model="filters.status"
                    placeholder="请选择状态"
                    class="filter-item"
                    clearable
                    @change="handleSearch"
                  >
                    <el-option
                      v-for="item in statuses"
                      :key="item.value"
                      :label="item.label"
                      :value="item.value"
                    />
                  </el-select>
                  </el-form-item>
                </el-col>
                <!-- <el-select
                  v-model="filters.algorithm"
                  placeholder="请选择关联算法"
                  class="filter-item"
                  clearable
                  @change="handleSearch"
                >
                  <el-option
                    v-for="item in algorithms"
                    :key="item.dict_key"
                    :label="item.dict_value"
                    :value="item.dict_key"
                  />
                </el-select> -->
                <!-- <el-tree-select
                  popper-class="custom-tree-select"
                  :style="{ width: pxToRem(186) }"
                  placeholder="请选择关联算法"
                  v-model="dictKey"
                  :data="dataList"
                  :default-expanded-keys="[dictKey]"
                  check-strictly
                  node-key="id"
                  :props="treePropsSF"
                  @node-click="handleSFNodeClick"
                  clearable
                  @clear="handleClear"
                /> -->
                <el-col :span="4">
                  <el-form-item label="选择算法:">
                  <el-tree-select
                    style="z-index: 1000"
                    :teleported="false"
                    class="custom-tree-select"
                    :style="{ width: pxToRem(186) }"
                    v-model="dictKey"
                    :data="dataList"
                    :default-expanded-keys="[dictKey]"
                    :props="treePropsSF"
                    :render-after-expand="false"
                    :default-checked-keys="checkedKeys"
                    node-key="id"
                    multiple
                    show-checkbox
                    collapse-tags
                    collapse-tags-tooltip
                    clearable
                    @check="handleCheck"
                    @node-click="handleSFNodeClick"
                    @clear="handleClear"
                  />
                  </el-form-item>
                </el-col>
                <el-col :span="4">
                  <el-form-item label="复核状态:">
                  <el-select
                    v-model="filters.isReview"
                    placeholder="请选择复核状态"
                    class="filter-item"
                    clearable
                    @change="handleSearch"
                  >
                    <el-option
                      v-for="item in reviewStatuses"
                      :key="item.value"
                      :label="item.label"
                      :value="item.value"
                    />
                  </el-select>
                  </el-form-item>
                </el-col>
                <el-col :span="3">
                  <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"
@@ -168,23 +198,23 @@
                type="primary"
                icon="el-icon-plus"
                @click="handleAdd"
                >新建工单</el-button
              >
                >新建工单
              </el-button>
              <el-button
                v-if="activeTab === 'pending' && permissionList.reviewBtn"
                type="success"
                icon="el-icon-check"
                @click="openReviewDialog"
                >批量审核</el-button
              >
                >批量审核
              </el-button>
              <el-button
                v-if="permissionList.exportBtn"
                type="success"
                plain
                icon="el-icon-download"
                @click="exportData"
                >导出</el-button
              >
                >导出
              </el-button>
            </template>
            <template #menu="{ row }">
              <template v-if="row.status === -1">
@@ -194,14 +224,14 @@
                  icon="el-icon-delete"
                  class="danger-button"
                  @click="handleDelete(row)"
                  >删除</el-button
                >
                  >删除
                </el-button>
              </template>
              <template v-else>
                <el-button type="text" icon="el-icon-view" @click="handleViewDetail(row)"
                  >详情</el-button
                >
                  >详情
                </el-button>
              </template>
              <template v-if="permission.tickets_repeat_review">
@@ -210,8 +240,8 @@
                  type="text"
                  icon="el-icon-check"
                  @click="reCheck(row)"
                  >复核</el-button
                >
                  >复核
                </el-button>
              </template>
            </template>
            <template #status="{ row }">
@@ -277,10 +307,10 @@
          <el-row :gutter="16">
            <el-col :span="12">
              <el-form-item label="所属部门" prop="department">
              <el-form-item label="处理部门" prop="department">
                <el-select
                  v-model="form.department"
                  placeholder="请选择所属部门"
                  placeholder="请选择处理部门"
                  @change="handleDepartmentChange"
                  class="full-width"
                >
@@ -297,7 +327,7 @@
              <el-form-item label="处理人员" prop="handler">
                <el-select
                  v-model="form.handler"
                  placeholder="请先选择所属部门"
                  placeholder="请先选择处理人员"
                  :disabled="!form.department"
                  class="full-width"
                >
@@ -332,7 +362,7 @@
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="选择位置" prop="location">
              <el-form-item label="事件位置" prop="location">
                <div class="location-wrapper">
                  <avue-input-map
                    v-model="form.location"
@@ -365,7 +395,7 @@
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="附件图片" prop="photos" required class="upload-wrapper">
              <el-form-item label="事件图片" prop="photos" required class="upload-wrapper">
                <el-upload
                  v-if="createoredit === 1"
                  ref="upload"
@@ -414,13 +444,24 @@
        </div>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="danger" :loading="submitLoading" @click="submitForm">发布</el-button>
          <el-button type="infoprimary" plain :loading="draftLoading" @click="saveDraft"
            >存草稿</el-button
          >
        <div class="dialog-footer-new">
          <el-button
            type="danger"
            :loading="submitLoading"
            @click="submitForm"
            icon="el-icon-position"
            >发布
          </el-button>
          <el-button
            type="infoprimary"
            plain
            :loading="draftLoading"
            @click="saveDraft"
            icon="el-icon-document-add"
            >存草稿
          </el-button>
          <el-button @click="handleCancel">取 消</el-button>
          <el-button @click="handleCancel" icon="el-icon-circle-close">取 消</el-button>
        </div>
      </template>
    </el-dialog>
@@ -577,7 +618,7 @@
                  <span class="required-star">*</span>事件处理详情
                </span>
              </template>
              <template v-else> 事件处理详情 </template>
              <template v-else> 事件处理详情</template>
            </div>
            <!-- 处理中状态显示输入框 -->
            <template v-if="currentDetail.status === 3 && hasProcessedAndOverBtnPermission()">
@@ -604,7 +645,7 @@
              <template v-if="currentDetail.status === 3">
                <span class="required-label"> <span class="required-star">*</span>上传图片 </span>
              </template>
              <template v-else> 上传图片 </template>
              <template v-else> 上传图片</template>
            </div>
            <el-upload
              v-if="hasProcessedAndOverBtnPermission()"
@@ -644,7 +685,7 @@
                      :src="getThumbUrl(currentDetail.mediaUrl)"
                      :preview-src-list="[getPreviewUrl(currentDetail.mediaUrl)]"
                      fit="contain"
                      style="width: 700px; height: 520px; cursor: pointer"
                      style=" cursor: pointer"
                    >
                      <template #placeholder>
                        <div class="image-placeholder">
@@ -695,21 +736,30 @@
                  <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 && detailVisible"
                     placement="top"
                     title=""
                     trigger="click"
                      <el-popover
                        v-if="currentDetail.status === 3 || currentDetail.status === 0"
                        popper-class="custom-qrcode-popover"
                        :width="120"
                        :visible="currentDetail.showQR && detailVisible"
                        placement="top"
                        title=""
                        trigger="click"
                      >
                        <template #reference>
                          <img @click.stop="handleQRCode(currentDetail)"  class="QRCodeImg" src="@/assets/images/dataCenter/qrCode.svg" alt="" title="事件导航" />
                          <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 && detailVisible" :latAndLon="currentDetail.location"></CreateQRcode>
                          <div class="close-btn" @click.stop="currentDetail.showQR = false">×</div>
                          <CreateQRcode
                            v-if="currentDetail.showQR && detailVisible"
                            :latAndLon="currentDetail.location"
                          ></CreateQRcode>
                        </div>
                      </el-popover>
                    </div>
@@ -724,7 +774,7 @@
        </div>
        <!-- 操作按钮 -->
        <div class="dialog-footer1">
        <div class="dialog-footer1-new">
          <div class="leftBtn" :class="currentIndex === 0 ? 'disableds' : ''" @click="leftClick">
            上一页
          </div>
@@ -736,16 +786,18 @@
                type="primary"
                :loading="approveLoading"
                @click="approveTicket"
                >通过</el-button
              >
                icon="el-icon-check"
                >通过
              </el-button>
              <el-button
                v-if="hasReviewBtnPermission()"
                type="danger"
                :loading="rejectLoading"
                @click="rejectTicket"
                >不通过</el-button
              >
              <el-button @click="detailVisible = false">取消</el-button>
                icon="el-icon-close"
                >不通过
              </el-button>
              <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
            </template>
            <template v-else-if="currentDetail.status === 0">
              <el-button
@@ -753,16 +805,18 @@
                type="primary"
                :loading="dispatchLoading"
                @click="approveAndDispatch"
                >受理</el-button
              >
                icon="el-icon-check"
                >受理
              </el-button>
              <el-button
                v-if="hasProcessingBtnPermission()"
                type="danger"
                :loading="rejectLoading"
                @click="rejectTicket"
                >不受理</el-button
              >
              <el-button @click="detailVisible = false">取消</el-button>
                icon="el-icon-close"
                >不受理
              </el-button>
              <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
            </template>
            <template v-if="currentDetail.status === 3">
              <!-- 处理中 -->
@@ -771,15 +825,16 @@
                type="primary"
                :loading="completeLoading"
                @click="completeTicket"
                >完成工单</el-button
              >
              <el-button @click="detailVisible = false">取消</el-button>
                icon="el-icon-circle-check"
                >完成工单
              </el-button>
              <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
            </template>
            <template v-else-if="currentDetail.status === 4">
              <!-- 已完成 -->
              <!-- <el-button v-if="hasProcessedAndOverBtnPermission()" type="primary" :loading="finalizeLoading"
              @click="finalizeTicket">完结工单</el-button> -->
              <el-button @click="detailVisible = false">取消</el-button>
              <el-button @click="detailVisible = false" icon="el-icon-circle-close">取消</el-button>
            </template>
          </div>
          <div
@@ -832,10 +887,12 @@
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="dispatchDialogVisible = false">取消</el-button>
        <el-button @click="dispatchDialogVisible = false" icon="el-icon-circle-close"
          >取消
        </el-button>
        <el-button type="primary" :loading="dispatchLoading" @click="submitDispatch"
          >确认派发</el-button
        >
          >确认派发
        </el-button>
      </template>
    </el-dialog>
@@ -845,19 +902,20 @@
      title="批量审核"
      width="1100"
      append-to-body
      class="ztzf-dialog-mange"
      custom-class="review-dialog"
      @close="cancleBatchReject"
    >
      <div class="review-container">
        <div class="review-image-wrapper">
          <!-- 修改左右箭头的显示条件 -->
          <div
          <el-button
            v-if="selections.length > 1 && currentReviewImage"
            class="arrow-button left"
            @click="handlePrevImage"
            icon="el-icon-arrow-left"
          >
            <i class="el-icon-arrow-left"></i>
          </div>
          </el-button>
          <div class="review-image-container">
            <!-- <el-image v-if="currentReviewImage" :src="getThumbUrl(currentReviewImage)" fit="fill"
@@ -882,13 +940,13 @@
          </div>
          <!-- 修改右箭头的显示条件 -->
          <div
          <el-button
            v-if="selections.length > 1 && currentReviewImage"
            class="arrow-button right"
            @click="handleNextImage"
            icon="el-icon-arrow-right"
          >
            <i class="el-icon-arrow-right"></i>
          </div>
          </el-button>
        </div>
        <!-- 修改分页器的显示条件 -->
@@ -908,9 +966,13 @@
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleBatchApprove">通过</el-button>
          <el-button type="danger" @click="handleBatchReject">不通过</el-button>
          <el-button @click="cancleBatchReject">取消</el-button>
          <el-button type="primary" @click="handleBatchApprove" icon="el-icon-check"
            >通过
          </el-button>
          <el-button type="danger" @click="handleBatchReject" icon="el-icon-close"
            >不通过
          </el-button>
          <el-button @click="cancleBatchReject" icon="el-icon-circle-close">取消</el-button>
        </div>
      </template>
    </el-dialog>
@@ -922,7 +984,7 @@
      title="工单复核"
      width="30%"
      append-to-body
      custom-class="re-check-dialog"
      class="ztzf-dialog-mange"
      @close="reCheckDialog = false"
    >
      <div class="dialog-footer">
@@ -934,6 +996,7 @@
</template>
<script>
import { pxToRem, pxToRemNum } from '@/utils/rem';
import { getSmallImg, getShowImg } from '@/utils/util';
import { ElMessageBox, ElLoading } from 'element-plus';
import { calculateDefaultRange } from '@/utils/util';
@@ -957,7 +1020,10 @@
import elTooltipCopy from '@/components/ElTooltipCopy.vue';
import getBaseConfig from '@/buildConfig/config';
import CreateQRcode from '@/components/CreateQRcode/CreateQRcode.vue';
import { CircleClose } from '@element-plus/icons-vue';
const { envName } = getBaseConfig();
function regExp(label, name) {
  var reg = new RegExp(label + '=([^&]*)(&|$)', 'g');
  return name.match(reg)[0].split('=')[1];
@@ -1041,8 +1107,8 @@
          // { label: "序号", prop: "id", width: 70 },
          { 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 },
          { label: '所属部门', prop: 'department', overHidden: true, tooltip: true },
          { label: '发起时间', prop: 'startTime', width: 160, overHidden: true },
          { label: '关联算法', prop: 'aiType', overHidden: true, tooltip: true },
          {
            label: '工单类型',
@@ -1060,8 +1126,8 @@
            width: 152,
            overHidden: true,
          },
          { label: '创建人', prop: 'creator', width: 70 },
          { label: '处理人', prop: 'handler', width: 70 },
          { label: '创建人', prop: 'creator', width: 120, overHidden: true },
          { label: '处理人', prop: 'handler', width: 120, overHidden: true },
          {
            slot: true,
            hide: envName === 'jiangwu' ? true : false,
@@ -1177,8 +1243,6 @@
    this.loadAMapScripts();
    this.fetchDropdownData();
    // console.log('permission.tickets_processing_btn', this.permission.tickets_processing_btn);
    // console.log('permission', this.permission.tickets_tab_pending);
  },
  mounted() {
@@ -1191,7 +1255,7 @@
    }
    let curQueryParams = {};
    let isTabProcessed = false;
    if (href.indexOf('?') != -1 && href.split('?').length > 0) {
      curQueryParams = href
        .split('?')[1]
@@ -1204,7 +1268,7 @@
          return pre;
        }, {});
      const { orderNumber = undefined, day = undefined } = curQueryParams;
      const { orderNumber = undefined, day = undefined, tab = undefined } = curQueryParams;
      // 日历传值
      if (day) {
@@ -1233,12 +1297,25 @@
          find && (find.query = {});
        });
      }
      if (tab) {
        const isTabValid = this.filteredTabs.some(t => t.name === tab);
        if (isTabValid) {
          this.activeTab = tab;
          this.handleTabChangeAfterJump();
          isTabProcessed = true;
          const find = this.$store.state.tags.bsTagList.find(i => i.path === '/tickets/ticket');
          find && (find.query = {});
        }
      }
    }
    this.fetchTabCounts(); // 新增:初始化时获取 tab 数据
    this.fetchTableData();
  },
  computed: {
    CircleClose() {
      return CircleClose;
    },
    firstRowData() {
      return this.tableData.length > 0 ? this.tableData[0] : null;
    },
@@ -1264,7 +1341,7 @@
          editable: false,
        },
        {
          label: '任务发起人',
          label: '工单创建人',
          value: this.currentDetail.creator,
          editable: false,
        },
@@ -1317,7 +1394,7 @@
          type: 'input',
        },
        { label: '关键任务', value: this.currentDetail.keyData, editable: false },
        { label: '任务发起人', value: this.currentDetail.creator, editable: false },
        { label: '工单创建人', value: this.currentDetail.creator, editable: false },
        { label: '当前状态', value: this.mapStatus(this.currentDetail.status), editable: false },
        { label: '事件地址', value: this.currentDetail.address, editable: false },
        {
@@ -1346,19 +1423,17 @@
          // 修改这里:使用 types 数组查找对应的 label
          value:
            this.types.find(t => t.value === this.currentDetail.type)?.label ||
            this.currentDetail.type ||
            '/',
            this.currentDetail.type,
        },
        { label: '关联任务', value: this.currentDetail.job_name || '/' },
        { label: '任务发起人', value: this.currentDetail.creator },
        { label: '工单创建人', value: this.currentDetail.creator },
        { label: '当前状态', value: this.mapStatus(this.currentDetail.status) },
        { label: '事件地址', value: this.currentDetail.address || this.currentDetail.latAndLon }, // 包含经纬度信息
        {
          label: '关联算法',
          value:
            this.algorithms.find(t => t.value === this.currentDetail.aiType)?.label ||
            this.currentDetail.aiType ||
            '/',
            this.currentDetail.aiType,
        },
        { label: '发起单位', value: this.currentDetail.department },
        { label: '发起任务时间', value: this.currentDetail.startTime },
@@ -1480,7 +1555,6 @@
    },
    handleSFNodeClick(data) {
      console.log(data.dictKey, '666666666');
      this.filters.type = '';
      this.filters.algorithm = '';
      this.filters.type = data.dictKey;
@@ -1505,7 +1579,7 @@
        });
      } else if (column.no === 4) {
        navigator.clipboard.writeText(row.department).then(() => {
          this.$message.success('复制所属单位成功');
          this.$message.success('复制所属部门成功');
        });
      }
    },
@@ -1737,7 +1811,8 @@
          user_id: currentTab?.name === 'myTickets' ? this.userInfo.user_id : undefined,
          is_review: this.filters.isReview === '' ? undefined : this.filters.isReview, // 添加复核状态查询参数
        };
       source:1  ,//数据来源
       };
        const response = await getList(params);
        if (!response?.data?.data?.records) {
@@ -1785,6 +1860,8 @@
            job_name: item.job_name || '',
            job_create_time: item.job_create_time || '',
            isReview: item.is_review, // 添加复核状态字段映射
          };
        });
@@ -2033,7 +2110,6 @@
    handleTabChange(tab) {
      this.activeTab = tab.props?.name || tab.name;
      const isReview = this.findObject(this.option.column, 'isReview');
      // 根据条件切换显隐
      isReview.hide = !['all', 'completed', 'myTickets'].includes(this.activeTab);
@@ -2043,7 +2119,15 @@
      this.fetchTableData();
      this.fetchTabCounts(); // 切换 tab 时重新获取数据
    },
    //新增 跳转后触发的Tab切换
    handleTabChangeAfterJump() {
      const isReview = this.findObject(this.option.column, 'isReview');
      isReview.hide = !['all', 'completed', 'myTickets'].includes(this.activeTab);
      this.handleReset();
      this.page.currentPage = 1;
      this.fetchTableData();
      this.fetchTabCounts();
    },
    handleSearch() {
      this.page.currentPage = 1;
      this.fetchTableData();
@@ -2187,9 +2271,10 @@
      };
      console.log('this.currentDetail', this.currentDetail);
      this.detailVisible = true;
      this.handleTypeChange(this.currentDetail.type);
      console.log('this.currentDetail.location', this.currentDetail.location);
      this.$nextTick(() => {
        if (this.$refs.MapContainer && this.$refs.MapContainer.initAddEntity) {
@@ -2265,7 +2350,7 @@
        const headers = [
          '工单编号',
          '工单名称',
          '所属单位',
          '所属部门',
          '发起时间',
          '关联算法',
          '工单内容',
@@ -2293,7 +2378,7 @@
      return {
        工单编号: item.orderNumber || item.event_num || '',
        工单名称: item.orderName || item.event_name || '',
        所属单位: item.department || item.dept_name || '',
        所属部门: item.department || item.dept_name || '',
        发起时间: item.startTime || item.create_time || '',
        关联算法: item.aiType || item.ai_types || '',
        工单内容: item.address || item.content || '',
@@ -2511,6 +2596,7 @@
      // console.log('权限检查:', this.permission)
      return this.permission && this.permission.tickets_review_btn === true;
    },
    async submitDispatch() {
      if (this.dispatchLoading) return;
      if (!this.currentDetail.orderName || !this.currentDetail.orderName.trim()) {
@@ -2535,7 +2621,22 @@
      }
      this.dispatchLoading = true;
      console.log('派发成功', this.currentDetail);
      const isValue = this.algorithms.some(item => item.value === this.currentDetail.aiType);
      let resultValue;
      if (isValue) {
        // 如果已经是value,直接使用
        resultValue = this.currentDetail.aiType;
      } else {
        // 否则查找对应的value
        const matched = this.algorithms.find(
          item => item.dict_value === this.currentDetail.aiType ||
                  item.label === this.currentDetail.aiType
        );
        resultValue = matched ? matched.value : null;
      }
// console.log('resultValue',resultValue);
      this.$refs.dispatchForm.validate(async valid => {
        if (valid) {
          try {
@@ -2549,7 +2650,7 @@
              content: this.currentDetail.content, // 使用 content 替代原来的 remark
              createDept: this.dispatchForm.department, // 派发部门 ID
              updateUser: this.dispatchForm.handler, // 处理人 ID
              aiType: this.currentDetail.aiType,
              aiType:resultValue,
            };
            console.log('派发', data);
@@ -3096,13 +3197,20 @@
<style lang="scss">
.custom-dialog {
  max-height: 96vh;
  .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;
  .qrcode-content {
    margin-top: 8px;
    .close-btn {
      position: absolute;
      top: 2px;
@@ -3115,8 +3223,6 @@
      color: #999;
      font-size: 16px;
      z-index: 10;
    }
  }
}
@@ -3325,8 +3431,12 @@
  }
}
.dialog-footer {
  text-align: center;
:deep(.el-dialog__body) {
  border-top: 0.1rem solid #f0f0f0;
}
.dialog-footer-new {
  text-align: right;
  padding-top: 16px;
  border-top: 1px solid #ebeef5;
@@ -3342,7 +3452,8 @@
    }
  }
}
.dialog-footer1 {
.dialog-footer1-new {
  position: sticky;
  bottom: 28px;
  left: 0;
@@ -3354,12 +3465,15 @@
  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;
@@ -3369,6 +3483,7 @@
    }
  }
}
.map-container {
  width: 100%;
  height: 400px;
@@ -3547,11 +3662,13 @@
  pointer-events: none;
  opacity: 0.3 !important;
}
.PopUpTableScrolls {
  height: 600px;
  max-height: 600px;
  overflow-y: scroll;
  overflow-x: hidden;
}
.media-box {
  width: 100%;
  border: 1px solid #dcdfe6;
@@ -3565,6 +3682,7 @@
    margin-bottom: 5px;
    display: flex;
    align-items: center;
    .QRCodeImg {
      width: 18px;
      height: 18px;
@@ -3644,6 +3762,7 @@
  display: block;
  text-align: left;
  margin-bottom: 5px;
  &:first-child {
    font-weight: bold;
    margin-bottom: 4px;
@@ -3739,6 +3858,7 @@
  :deep(.el-dialog__body) {
    padding: 0;
    background-color: #f5f7fa;
    border-top: 0.1rem solid #f0f0f0;
  }
}
@@ -3746,6 +3866,7 @@
  :deep(.el-dialog__body) {
    padding: 0;
    background-color: #f5f7fa;
    border-top: 0.1rem solid #f0f0f0;
  }
}
@@ -3791,6 +3912,7 @@
    height: 44px;
    background: rgba(0, 0, 0, 0.3);
    border-radius: 50%;
    border: none;
    display: flex;
    align-items: center;
    justify-content: center;
@@ -3809,10 +3931,12 @@
    &.left {
      left: 20px;
      color: #ffffff;
    }
    &.right {
      right: 20px;
      color: #ffffff;
    }
  }
src/views/wel/components/backlog.vue
@@ -30,9 +30,10 @@
            </div>
            <div class="action-area">
              <!-- <img :src="st7" alt="" /> -->
              <!-- <span class="todo-date">{{ item.date?.slice(0, 10).replace(/-/g, '.') }}</span> -->
          <span class="todo-date">{{ item.name }}</span>
          <!-- <span class="todo-date">{{ item.name }}</span> -->
          <el-tooltip effect="dark" :content="item.name" placement="top">
            <span class="todo-date">{{item.name}}</span>
          </el-tooltip>
            </div>
          </div>
        </div>
@@ -59,7 +60,9 @@
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
const userInfo = computed(() => store.getters.userInfo)
const todos = ref([])
const loading = ref(false)
const router = useRouter()
const store = useStore()
const permission = computed(() => store.getters.permission)
@@ -82,12 +85,11 @@
])
const titleClick = async (item, index) => {
todos.value =[]
  checked.value = item.name
  await getListMatter()
}
const userInfo = computed(() => store.getters.userInfo)
const todos = ref([])
const loading = ref(false)
// * 事件工单中2=待审核,0=待处理,3=处理中
// * 智飞工单中1: 待审核,2=已驳回,3=已通过
const statusMap = {
@@ -397,7 +399,7 @@
      .action-area {
        display: flex;
        align-items: center;
        margin-right: 42px;
        margin-right: 22px;
        img {
          width: 15px;
@@ -406,9 +408,14 @@
        .todo-date {
          font-weight: 400;
        font-size: 14px;
color: #343434;
          margin-left: 2px;
      font-size: 14px;
      color: #343434;
      margin-left: 2px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      max-width: 200px;
      display: inline-block;
        }
      }
    }
src/views/wel/components/proportionStatic.vue
@@ -13,7 +13,7 @@
          :key="index"
          @click="timeClick(item, index)"
        >
          {{ timeListStr[index] }}
          {{ timeListStr[index] }}
        </div>
      </div>
    </div>
@@ -21,7 +21,7 @@
      <div class="card-group">
        <div class="main-card">
          <div class="status-grid">
            <div class="status-item" v-for="(item, index) in eventTypeList" :key="index">
            <div class="status-item" v-for="(item, index) in eventTypeList" :key="index" @click="jumpEventDetails(item,index)">
              <div class="statusCon">
                <div class="status-label">{{ item.name }}</div>
                <div class="status-value">{{ item.value }}<span>个</span></div>
@@ -51,6 +51,8 @@
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'
const router = useRouter()
let checked = ref('CURRENT_YEAR');
let timeListStr = ['本周', '本月', '本年'];
let timeListEnum = ['CURRENT_WEEK', 'CURRENT_MONTH', 'CURRENT_YEAR'];
@@ -59,13 +61,14 @@
  device_sn: '',
  end_date: undefined,
  start_date: undefined,
   source:1,//数据来源
});
const dateSelect = ref('CURRENT_YEAR');
const eventTypeList = ref([
  { name: '待审核', value: 0, img: overviewImg2, color: '#FF472F', status: '2', rate: 0 },
  { name: '待处理', value: 0, img: overviewImg3, color: '#FF7411', status: '0', rate: 0 },
  { name: '处理中', value: 0, img: overviewImg4, color: '#FFC300', status: '3', rate: 0 },
  { name: '已完成', value: 0, img: overviewImg5, color: '#0291A1', status: '4', rate: 0 },
  { name: '待审核', value: 0, img: overviewImg2, color: '#FF472F', status: '2', rate: 0 ,tagging:'pending' },
  { name: '待处理', value: 0, img: overviewImg3, color: '#FF7411', status: '0', rate: 0 ,tagging:'processing' },
  { name: '处理中', value: 0, img: overviewImg4, color: '#FFC300', status: '3', rate: 0 ,tagging:'inProgress'},
  { name: '已完成', value: 0, img: overviewImg5, color: '#0291A1', status: '4', rate: 0 ,tagging:'completed'},
]);
//  工单统计
const getTypeData = () => {
@@ -95,6 +98,14 @@
  dateSelect.value = item;
  getTypeData();
};
// 跳转事件工单
const jumpEventDetails = (item,index)=>{
// console.log('tiaozhuan',item);
  router.replace({
    path: `/tickets/ticket`,
    query: { tab: item.tagging }
  });
}
// 图表
const echartsRef = ref(null);
let { chart } = useEchartsResize(echartsRef);
@@ -250,7 +261,7 @@
          width: 144px;
          background: #f6f8fe;
          border-radius: 8px 8px 8px 8px;
          cursor: pointer;
          img {
            width: 26px;
            height: 26px;
src/views/wel/components/statistics.vue
@@ -22,20 +22,13 @@
        </div>
        <div class="status-list">
          <template v-if="test[item]?.name == '机巢' || test[item]?.name == '无人机'">
            <div
              v-for="statusKey in [4, 0, -1]"
              :key="statusKey"
              class="status-item"
            <div v-for="statusKey in [4, 0, -1]" :key="statusKey" class="status-item"
              :class="getStatusStyle(test[item]?.name, statusKey)"
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }"
            >
              <span
                class="indicator"
                :style="{
                  backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                  color: getStatusColor(test[item]?.name, statusKey),
                }"
              ></span>
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }">
              <span class="indicator" :style="{
                backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                color: getStatusColor(test[item]?.name, statusKey),
              }"></span>
              <span class="label">{{ getStatusLabel(test[item]?.name, statusKey) }}</span>
              <span class="count">
                {{ newtitleData[item].status_map[statusKey] }}
@@ -44,20 +37,13 @@
            </div>
          </template>
          <template v-else-if="test[item]?.name == '监控设备'">
            <div
              v-for="statusKey in [1, 0]"
              :key="statusKey"
              class="status-item"
            <div v-for="statusKey in [1, 0]" :key="statusKey" class="status-item"
              :class="getStatusStyle(test[item]?.name, statusKey)"
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }"
            >
              <span
                class="indicator"
                :style="{
                  backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                  color: getStatusColor(test[item]?.name, statusKey),
                }"
              ></span>
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }">
              <span class="indicator" :style="{
                backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                color: getStatusColor(test[item]?.name, statusKey),
              }"></span>
              <span class="label">{{ getStatusLabel(test[item]?.name, statusKey) }}</span>
              <span class="count">
                {{ newtitleData[item].status_map[statusKey] }}
@@ -65,21 +51,14 @@
              </span>
            </div>
          </template>
          <template v-else-if="test[item]?.name == '机巢保险'">
            <div
              v-for="statusKey in [1, 0,2]"
              :key="statusKey"
              class="status-item"
          <template v-else-if="test[item]?.name == '无人机保险'">
            <div v-for="statusKey in [1, 0, 2]" :key="statusKey" class="status-item"
              :class="getStatusStyle(test[item]?.name, statusKey)"
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }"
            >
              <span
                class="indicator"
                :style="{
                  backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                  color: getStatusColor(test[item]?.name, statusKey),
                }"
              ></span>
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }">
              <span class="indicator" :style="{
                backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                color: getStatusColor(test[item]?.name, statusKey),
              }"></span>
              <span class="label">{{ getStatusLabel(test[item]?.name, statusKey) }}</span>
              <span class="count">
                {{ newtitleData[item].status_map[statusKey] }}
@@ -87,21 +66,14 @@
              </span>
            </div>
          </template>
            <template v-else-if="test[item]?.name == '无人机流量'">
            <div
              v-for="statusKey in [ 0,2,1]"
              :key="statusKey"
              class="status-item"
          <template v-else-if="test[item]?.name == '无人机流量'">
            <div v-for="statusKey in [0, 2, 1]" :key="statusKey" class="status-item"
              :class="getStatusStyle(test[item]?.name, statusKey)"
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }"
            >
              <span
                class="indicator"
                :style="{
                  backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                  color: getStatusColor(test[item]?.name, statusKey),
                }"
              ></span>
              :style="{ color: getStatusColor(test[item]?.name, statusKey) }">
              <span class="indicator" :style="{
                backgroundColor: getStatusBackground(test[item]?.name, statusKey),
                color: getStatusColor(test[item]?.name, statusKey),
              }"></span>
              <span class="label">{{ getStatusLabel(test[item]?.name, statusKey) }}</span>
              <span class="count">
                {{ newtitleData[item].status_map[statusKey] }}
@@ -110,20 +82,13 @@
            </div>
          </template>
          <template v-else>
            <div
              v-for="(status, statusIndex) in newtitleData[item].status_map"
              :key="statusIndex"
              class="status-item"
            <div v-for="(status, statusIndex) in newtitleData[item].status_map" :key="statusIndex" class="status-item"
              :class="getStatusStyle(test[item]?.name, statusIndex)"
              :style="{ color: getStatusColor(test[item]?.name, statusIndex) }"
            >
              <span
                class="indicator"
                :style="{
                  backgroundColor: getStatusBackground(test[item]?.name, statusIndex),
                  color: getStatusColor(test[item]?.name, statusIndex),
                }"
              ></span>
              :style="{ color: getStatusColor(test[item]?.name, statusIndex) }">
              <span class="indicator" :style="{
                backgroundColor: getStatusBackground(test[item]?.name, statusIndex),
                color: getStatusColor(test[item]?.name, statusIndex),
              }"></span>
              <span class="label">{{ getStatusLabel(test[item]?.name, statusIndex) }}</span>
              <span class="count">{{ status }} {{ unitMap[test[item]?.name] }}</span>
            </div>
@@ -142,30 +107,30 @@
</template>
<script setup>
import { useStore } from 'vuex';
import { computed } from 'vue';
import titleImg1 from '@/assets/images/workbench/st3.svg';
import titleImg2 from '@/assets/images/workbench/st4.svg';
import titleImg3 from '@/assets/images/workbench/st8.svg';
import titleImg4 from '@/assets/images/workbench/st5.svg';
import titleImg5 from '@/assets/images/workbench/st6.svg';
import titleImg6 from '@/assets/images/workbench/st9.svg';
import { getStatics } from '@/api/home/index';
import { useRouter } from 'vue-router';
const router = useRouter();
const store = useStore();
import { useStore } from 'vuex'
import { computed } from 'vue'
import titleImg1 from '@/assets/images/workbench/st3.svg'
import titleImg2 from '@/assets/images/workbench/st4.svg'
import titleImg3 from '@/assets/images/workbench/st8.svg'
import titleImg4 from '@/assets/images/workbench/st5.svg'
import titleImg5 from '@/assets/images/workbench/st6.svg'
import titleImg6 from '@/assets/images/workbench/st9.svg'
import { getStatics } from '@/api/home/index'
import { useRouter } from 'vue-router'
const router = useRouter()
const store = useStore()
const refresh = () => {
  getStaticsList();
};
  getStaticsList()
}
const jumppage = () => {
  router.push({
    path: '/device/index',
  });
};
const userInfo = computed(() => store.getters.userInfo);
const permission = computed(() => store.getters.permission);
  })
}
const userInfo = computed(() => store.getters.userInfo)
const permission = computed(() => store.getters.permission)
const newtitleData = ref({});
const newtitleData = ref({})
const test = {
  no_move_list: {
@@ -190,40 +155,40 @@
  },
  insure_list: {
    name: '机巢保险',
    name: '无人机保险',
    img: titleImg6,
  },
};
}
const statusSelect = {
  4: '作业中',
  0: '空闲中',
  '-1': '离线中',
};
}
const flowStatus = {
  0: '无忧',
  1: '到期',
  1: '临期',
  2: '不足',
};
}
const monitorStatus = {
  0: '离线中',
  1: '在线中',
};
}
const insureStatus = {
  0: '临近到期',
  1: '正常期限',
  2:'保险过期'
};
  2: '保险过期'
}
const moveListStatus = {
  '-1': '离线中',
  4: '在线中',
};
}
// 样式配置对象
const statusStyles = {
  机巢保险: {
  无人机保险: {
    0: { class: 'expired', color: '#FFA600', background: '#FFA600' },
    1: { class: 'normal', color: 'rgba(0, 180, 69, 1)', background: 'rgba(0, 180, 69, 1)' },
     2: { class: 'flying',color: 'rgba(255, 36, 36, 1)', background: 'rgba(255, 36, 36, 1)' },
    2: { class: 'flying', color: 'rgba(255, 36, 36, 1)', background: 'rgba(255, 36, 36, 1)' },
  },
  无人机流量: {
@@ -245,64 +210,64 @@
    4: { class: 'success', color: 'rgba(255, 106, 0, 1)', background: 'rgba(255, 106, 0, 1)' },
    '-1': { class: 'success', color: 'rgba(186, 186, 186, 1)', background: 'rgba(186, 186, 186, 1)' },
  },
};
}
const getStatusStyle = (name, statusIndex) => {
  const styleConfig = statusStyles[name] || statusStyles.default;
  return styleConfig[statusIndex]?.class || '';
};
  const styleConfig = statusStyles[name] || statusStyles.default
  return styleConfig[statusIndex]?.class || ''
}
const getStatusColor = (name, statusIndex) => {
  const styleConfig = statusStyles[name] || statusStyles.default;
  return styleConfig[statusIndex]?.color || '#333';
};
  const styleConfig = statusStyles[name] || statusStyles.default
  return styleConfig[statusIndex]?.color || '#333'
}
//背景颜色获取方法
const getStatusBackground = (name, statusIndex) => {
  const styleConfig = statusStyles[name] || statusStyles.default;
  return styleConfig[statusIndex]?.background || styleConfig[statusIndex]?.color || '#F0F0F0';
};
  const styleConfig = statusStyles[name] || statusStyles.default
  return styleConfig[statusIndex]?.background || styleConfig[statusIndex]?.color || '#F0F0F0'
}
const getStatusLabel = (itemName, statusCode) => {
  switch (itemName) {
    case '无人机流量':
      return flowStatus[statusCode] || statusSelect[statusCode];
      return flowStatus[statusCode] || statusSelect[statusCode]
    case '监控设备':
      return monitorStatus[statusCode] || statusSelect[statusCode];
    case '机巢保险':
      return insureStatus[statusCode] || statusSelect[statusCode];
      return monitorStatus[statusCode] || statusSelect[statusCode]
    case '无人机保险':
      return insureStatus[statusCode] || statusSelect[statusCode]
    case '移动机巢':
      return moveListStatus[statusCode] || statusSelect[statusCode];
      return moveListStatus[statusCode] || statusSelect[statusCode]
    default:
      return statusSelect[statusCode] || `未知状态(${statusCode})`;
      return statusSelect[statusCode] || `未知状态(${statusCode})`
  }
};
}
const getStaticsList = () => {
  getStatics(userInfo.value.detail.areaCode).then(res => {
//  console.log('permission.value', permission.value);
    //  console.log('permission.value', permission.value);
    // console.log('设备', res.data.data);
    if (permission.value?.device_statistics_six) {
      const { move_list, monitor_list, ...filteredData } = res.data.data;
      newtitleData.value = filteredData;
      return;
      const { move_list, monitor_list, ...filteredData } = res.data.data
      newtitleData.value = filteredData
      return
    }
    for (let key in res.data.data) {
      if (permission.value?.device_statistics_four) {
        const { flow_type_list, insure_list, ...filteredData } = res.data.data;
        newtitleData.value = filteredData;
        return;
        const { flow_type_list, insure_list, ...filteredData } = res.data.data
        newtitleData.value = filteredData
        return
      }
    }
  });
};
  })
}
const unitMap = {
  无人机流量: '架',
  无人机: '架',
  机巢: '个',
  机巢保险: '个',
  无人机保险: '个',
  监控设备: '个',
  移动机巢: '个',
};
}
watch(
  () => [
    userInfo.value.detail?.areaCode,
@@ -311,20 +276,21 @@
  ],
  () => getStaticsList(),
  { immediate: true }
);
)
onMounted(() => {
  getStaticsList();
});
  getStaticsList()
})
</script>
<style scoped lang="scss">
.statistics {
  // height: 174px;
   height: pxToVh(174);
  height: pxToVh(174);
  background: #ffffff;
  border-radius: 8px 8px 8px 8px;
  margin-bottom: 10px;
  font-size: clamp(12px, 2vw, 24px);
  .title {
    padding: 14px 14px 0 21px;
    display: flex;
@@ -357,20 +323,25 @@
    padding: 0 14px 0 21px;
    // display: grid;
    // grid-template-columns: repeat(4, 1fr);
   height: pxToVh(80);
    height: pxToVh(80);
    display: flex;
    justify-content: space-around;
    :deep(.el-empty ){
    :deep(.el-empty) {
      width: 60px;
      height: pxToVh(60);
      .el-empty__image{
      width: 60px !important;
       height: pxToVh(60)!important;}
      .el-empty__image {
        width: 60px !important;
        height: pxToVh(60) !important;
      }
      .custom-text {
        font-size: 12px;
        color: #7c8091;
      }
    }
    .device-card {
      display: flex;
      align-items: center;
@@ -391,9 +362,11 @@
        font-weight: 400;
        font-size: 14px;
        color: #343434;
        div {
          margin-top: 5px;
        }
        .shu {
          font-weight: bold;
          font-size: 36px;
@@ -416,6 +389,7 @@
    .status-list {
      display: grid;
      margin-left: 5px;
      .status-item {
        display: flex;
        align-items: center;
@@ -424,11 +398,13 @@
        font-size: 12px;
        gap: 10px;
        margin-bottom: 5px;
        .indicator {
          width: 6px;
          height: 6px;
          border-radius: 50%;
        }
        .label {
          flex: 1;
          // font-size: clamp(12px, 1vw, 14px);
src/views/workspace/index.vue
@@ -111,7 +111,7 @@
            ],
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'dept_name',
            addDisplay:false,
            editDisplay:false,
@@ -119,13 +119,13 @@
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],
          },
          {
            label: '所属单位',
            label: '所属部门',
            prop: 'dept_id',
            hide: true,
            type: 'tree',
@@ -139,7 +139,7 @@
            rules: [
              {
                required: true,
                message: '请输入所属单位',
                message: '请输入所属部门',
                trigger: 'blur',
              },
            ],