PanelHxhs.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655
  1. <!-- 航线划设面板 -->
  2. <template>
  3. <div class="panel-hxhs flex flex-col aside-left-inner">
  4. <div class="title-main">航线划设</div>
  5. <el-steps :active="currentStep" finish-status="success" align-center>
  6. <el-step title="航线划设" />
  7. <el-step title="航线评估" />
  8. </el-steps>
  9. <div class="flex-1 relative mb-4">
  10. <Transition name="emerge-left">
  11. <el-form ref="formRef" v-show="currentStep === 0" :model="form" :rules="rules" label-position="left"
  12. size="large" class="p-form p-main">
  13. <el-form-item label="名称" prop="name">
  14. <el-input v-model="form.name" clearable></el-input>
  15. </el-form-item>
  16. <el-form-item label="任务类型" prop="taskType">
  17. <el-select v-model="form.taskType" placeholder="">
  18. <el-option label="短途运输" value="01" />
  19. <el-option label="外卖配送" value="02" />
  20. </el-select>
  21. </el-form-item>
  22. <el-form-item label="飞行器" prop="uavType">
  23. <el-select v-model="form.uavType" placeholder="">
  24. <el-option label="微型无人机" value="01" />
  25. <el-option label="轻型无人机" value="02" />
  26. <el-option label="小型无人机" value="03" />
  27. <el-option label="中型无人机" value="04" />
  28. <el-option label="大型无人机" value="05" />
  29. </el-select>
  30. </el-form-item>
  31. <el-form-item label="航线选择" prop="dataType">
  32. <el-radio-group v-model="form.dataType" @change="changeDataType()" size="large">
  33. <el-radio label="起降场规划" value="02" />
  34. <el-radio label="手动划设" value="01" />
  35. <el-radio label="导入航线" value="03" />
  36. </el-radio-group>
  37. </el-form-item>
  38. <div v-if="form.dataType === '01'" @click="changeDataType()" class="msg-draw">
  39. <img src="../../../assets/images/page/icon-draw.png" alt="">
  40. <span>重新绘制</span>
  41. </div>
  42. <template v-if="form.dataType === '02'">
  43. <el-form-item label="起飞场" prop="fromPort">
  44. <el-select v-model="form.fromPort" @change="showPort('fromPort')" placeholder="">
  45. <el-option v-for="item in portOptions" :key="item.value" :label="item.label" :value="item.value"
  46. :disabled="item.value === form.toPort" />
  47. </el-select>
  48. </el-form-item>
  49. <el-form-item label="降落场" prop="toPort">
  50. <el-select v-model="form.toPort" @change="showPort('toPort')" placeholder="">
  51. <el-option v-for="item in portOptions" :key="item.value" :label="item.label" :value="item.value"
  52. :disabled="item.value === form.fromPort" />
  53. </el-select>
  54. </el-form-item>
  55. </template>
  56. <el-form-item label="上传文件" prop="file" v-if="form.dataType === '03'">
  57. <el-upload v-model:file-list="form.file" action="" class="single-uplaod">
  58. <div class="upload-trigger">点击上传文件</div>
  59. </el-upload>
  60. </el-form-item>
  61. <el-form-item label="保持半径" prop="radius">
  62. <el-input v-model="form.radius" type="number" class="flex-1">
  63. <template #suffix>
  64. <span>米</span>
  65. </template>
  66. </el-input>
  67. </el-form-item>
  68. <el-form-item label="飞行高度" prop="height">
  69. <el-input v-model="form.height1" type="number" class="flex-1">
  70. <template #suffix>
  71. <span>米</span>
  72. </template>
  73. </el-input>
  74. <div class="mx-3">--</div>
  75. <el-input v-model="form.height2" type="number" class="flex-1">
  76. <template #suffix>
  77. <span>米</span>
  78. </template>
  79. </el-input>
  80. </el-form-item>
  81. <el-form-item label="日期" prop="startDate">
  82. <el-date-picker v-model="form.startDate" value-format="YYYY-MM-DD" type="date"
  83. class="flex-1"></el-date-picker>
  84. <div class="mx-3">--</div>
  85. <el-date-picker v-model="form.endDate" value-format="YYYY-MM-DD" type="date"
  86. class="flex-1"></el-date-picker>
  87. </el-form-item>
  88. <el-form-item label="时间" prop="startTime">
  89. <el-time-select v-model="form.startTime" :max-time="form.endTime" placeholder="" start="00:00" step="00:15"
  90. end="23:45" class="flex-1" />
  91. <div class="mx-3">--</div>
  92. <el-time-select v-model="form.endTime" :min-time="form.startTime" placeholder="" start="00:00" step="00:15"
  93. end="23:45" class="flex-1" />
  94. </el-form-item>
  95. <el-form-item label="网格查询">
  96. <!-- <el-checkbox v-model="form.grade">评分</el-checkbox>-->
  97. <el-button class="btn-secondary ml-4" @click="queryCube()">查询网格</el-button>
  98. <el-button class="btn-secondary ml-4" @click="closeCube()">关闭网格</el-button>
  99. </el-form-item>
  100. <!-- <el-form-item label="辅助规划">-->
  101. <!-- <el-button class="btn-secondary" @click="getAutoPath()" >辅助规划</el-button>-->
  102. <!-- </el-form-item>-->
  103. </el-form>
  104. </Transition>
  105. <Transition name="emerge-right">
  106. <div v-show="currentStep === 1" class="p-main">
  107. <ul class="list-plans flex justify-evenly mb-4">
  108. <li v-for="item in plans" class="pt-7 cursor-pointer hover:brightness-125"
  109. :class="{ 'active': item.id === currentPlan }" @click="handlePickPlan(item)">
  110. <div class="text-base"><span class="tuli" :style="{backgroundColor:item.color}"></span>{{ item.feature }}</div>
  111. <div class="text-sm py-1">距离&nbsp;<span class="num">{{ item.distance }}</span>&nbsp;公里</div>
  112. <div class="text-sm">风险度:<span class="num">{{ item.risk }}</span></div>
  113. </li>
  114. </ul>
  115. <el-form-item label="网格查询">
  116. <!-- <el-checkbox v-model="form.grade">评分</el-checkbox>-->
  117. <el-button class="btn-secondary ml-4" @click="queryCube()">查询网格</el-button>
  118. <el-button class="btn-secondary ml-4" @click="closeCube()">关闭网格</el-button>
  119. </el-form-item>
  120. <div class="msg-safe mb-2">当前计划无冲突!</div>
  121. <div v-for="item in results" :key="item.title" class="mb-3">
  122. <div class="title-sub">
  123. {{ item.title }}
  124. <div class="flex-1 flex justify-between items-center pl-2">
  125. <span class="risk-label" :class="item.level === '低风险' ? 'green' : 'orange'">{{ item.level
  126. }}</span>
  127. <span class="btn-inline">重新评估</span>
  128. </div>
  129. </div>
  130. <table class="table-default stripe mt-2">
  131. <thead>
  132. <tr>
  133. <th v-for="th in item.cols">{{ th }}</th>
  134. <th class="w-20">地图显示</th>
  135. </tr>
  136. </thead>
  137. <tbody>
  138. <tr v-for="row in item.details">
  139. <td v-for="key in Object.keys(item.cols)">{{ row[key] }}</td>
  140. <td class="w-20">
  141. <el-checkbox size="large" v-model="row.display"></el-checkbox>
  142. </td>
  143. </tr>
  144. </tbody>
  145. </table>
  146. </div>
  147. </div>
  148. </Transition>
  149. </div>
  150. <div class="text-center">
  151. <el-button v-if="currentStep === 0" :disabled="!hasDraw" class="btn-main" @click="toNext">下一步</el-button>
  152. <template v-if="currentStep === 1">
  153. <el-button class="btn-main" @click="toPrev">上一步</el-button>
  154. <el-button class="btn-main" @click="handlePreview">计划预演</el-button>
  155. <el-button class="btn-main" @click="handleSave">保存</el-button>
  156. </template>
  157. </div>
  158. </div>
  159. </template>
  160. <script setup>
  161. import {ref, onMounted, watch, onBeforeUnmount, computed} from 'vue';
  162. import {routePlanAll, saveRoute, searchQJCList} from "@/service/panelHxhs.js";
  163. import {geometryMeshEffect, getPathCube, showAndRedrawPath} from "@/utils/map/addTool.js";
  164. import useLayoutStore from '@/store/layout'
  165. import {useMapStore} from "@/store/map.js";
  166. import {ElMessage} from "element-plus";
  167. const currentStep = ref(0)
  168. let currentPath = []; // 当前规划路径
  169. let allPathArr = []; // 所有路径
  170. let autoHeight = false //手动规划
  171. const layoutStore = useLayoutStore();
  172. const mapStore = useMapStore();
  173. const form = ref({
  174. taskType: '01',
  175. uavType: '01',
  176. dataType: '02',
  177. height1: 60,
  178. height2: 80,
  179. radius:5
  180. })
  181. const portOptions = []
  182. const rules = {
  183. name: [{ required: true, message: '请输入空域名称', trigger: 'none' }]
  184. }
  185. const formRef = ref(null)
  186. const results = ref([
  187. {
  188. title: '空域评估',
  189. level: '低风险',
  190. cols: { areaName: '冲突空域', height: '冲突高度' },
  191. details: [
  192. { areaName: '虹桥机场净空区', height: '112-120', display: false },
  193. { areaName: '虹桥机场净空区', height: '112-120', display: false },
  194. ]
  195. },
  196. {
  197. title: '计划冲突',
  198. level: '低风险',
  199. cols: { areaName: '冲突空域', height: '冲突高度' },
  200. details: [
  201. { areaName: '合生汇-黄兴公园美团飞行计划', height: '112-120', display: false },
  202. { areaName: '合生汇-黄兴公园美团飞行计划', height: '112-120', display: false },
  203. ]
  204. },
  205. {
  206. title: '障碍物分析',
  207. level: '高风险',
  208. cols: { object: '冲突空域', height: '冲突高度' },
  209. details: [
  210. { object: '建筑物', height: '112-120', display: false },
  211. { object: '建筑物', height: '112-120', display: false },
  212. ]
  213. },
  214. {
  215. title: '地面安全评估',
  216. level: '高风险',
  217. cols: { element: '要素', coverage: '覆盖率' },
  218. details: [
  219. { element: '路面', coverage: '40%', display: false },
  220. { element: '道路', coverage: '20%', display: false },
  221. { element: '综合占比', coverage: '60%', display: false },
  222. ]
  223. }
  224. ])
  225. let plans = ref([
  226. { id: '1',name:'originalPath', feature: '接近原方案',color:'#FFA500', distance: '', risk: '50' },
  227. { id: '2',name:'safePath', feature: '地面风险较低',color:'#00FF00', distance: '', risk: '10' },
  228. { id: '3',name:'shortestPath', feature: '距离最短',color:'#FFA5FF', distance: '', risk: '50' },
  229. ])
  230. const isSafe = computed(() => {
  231. return results.value.every(i => i.level === 1)
  232. })
  233. const hasDraw = ref(false)
  234. function handlePickPlan(plan) {
  235. currentPlan.value = plan.id;
  236. showAllPathByType()
  237. }
  238. const currentPlan = ref('1');
  239. function toNext() {
  240. formRef.value.validate(valid => {
  241. if (valid) {
  242. currentStep.value = 1;
  243. getAutoPath();
  244. }
  245. })
  246. }
  247. function toPrev() {
  248. currentStep.value = 0
  249. getPathCube({
  250. status:"hide",
  251. })
  252. hasDraw.value = false;
  253. showAndRedrawPath({
  254. status:"hide",
  255. });
  256. geometryMeshEffect({
  257. id: "fromPort",
  258. status:"hide"
  259. })
  260. geometryMeshEffect({
  261. id: "toPort",
  262. status:"hide"
  263. })
  264. geometryMeshEffect({
  265. "status": "hide",
  266. "id": "drawAllPathOne"
  267. })
  268. }
  269. //保存网格结果
  270. function handleSave() {
  271. saveRoute({
  272. ...form.value,
  273. shape: JSON.stringify({paths:[currentPath]})
  274. }).then(res =>{
  275. if(res.data.code == 200 && res.data.msg == "success"){
  276. ElMessage({
  277. type: 'success',
  278. message: '保存成功'
  279. })
  280. }else{
  281. ElMessage({
  282. type: 'error',
  283. message: '保存失败'
  284. })
  285. }
  286. }).catch(()=>{
  287. ElMessage({
  288. type: 'error',
  289. message: '保存失败'
  290. })
  291. })
  292. }
  293. function handlePreview() {
  294. }
  295. //获取起降数据
  296. function getQJCList(){
  297. searchQJCList().then(res=>{
  298. let data = res.data.data;
  299. data.forEach((item)=>{
  300. portOptions.push({
  301. label: item.name,
  302. value: item.id,
  303. shape: item.shape,
  304. geoType: item.geoType
  305. })
  306. })
  307. })
  308. }
  309. function showPort(id){
  310. geometryMeshEffect({
  311. id: id,
  312. status:"show",
  313. data: [{
  314. type: portOptions.find((item)=>item.value ==form.value[id]).geoType,
  315. shape: {
  316. ...JSON.parse(portOptions.find((item)=>item.value ==form.value[id]).shape),
  317. color: [0,255,0,0.7]
  318. }
  319. }]
  320. })
  321. showOriginPath()
  322. }
  323. //起降场选择完成生成原始直线
  324. function showOriginPath(){
  325. if(form.value.fromPort && form.value.toPort){
  326. let formPoint = JSON.parse(portOptions.find((item)=>item.value ==form.value.fromPort).shape).point;
  327. let toPoint = JSON.parse(portOptions.find((item)=>item.value ==form.value.toPort).shape).point;
  328. debugger
  329. currentPath = [
  330. [formPoint.x, formPoint.y,formPoint.z],
  331. [formPoint.x, formPoint.y,(form.value.height1 * 1 + form.value.height2 * 1)/2],
  332. [(formPoint.x + toPoint.x)/2, (formPoint.y + toPoint.y)/2,(form.value.height1 * 1 + form.value.height2 * 1)/2],
  333. [toPoint.x, toPoint.y,(form.value.height1 * 1 + form.value.height2 * 1)/2],
  334. [toPoint.x, toPoint.y,toPoint.z]
  335. ]
  336. hasDraw.value = true;
  337. showAndRedrawPath({
  338. status:"show",
  339. path:currentPath
  340. });
  341. }
  342. }
  343. //查询网格
  344. function queryCube(){
  345. getPathCube({
  346. status:"show",
  347. paths:[currentPath]
  348. })
  349. }
  350. //关闭网格
  351. function closeCube(){
  352. getPathCube({
  353. status:"hide"
  354. })
  355. }
  356. //辅助规划
  357. function getAutoPath(){
  358. let paramsPaths = [];
  359. currentPath.forEach((item) =>{
  360. paramsPaths.push({
  361. x:item[0],
  362. y:item[1],
  363. z:item[2]
  364. })
  365. })
  366. layoutStore.toggleGlobalLoading(true)
  367. routePlanAll({
  368. height1: form.value.height1,
  369. height2: form.value.height2,
  370. paths:paramsPaths
  371. }).then(res=>{
  372. layoutStore.toggleGlobalLoading(false)
  373. debugger
  374. let data = res.data.data;
  375. allPathArr = data;
  376. plans.value.forEach((item)=>{
  377. item.distance = (data[item.name].length/1000).toFixed(2)
  378. });
  379. showAllPathByType();
  380. });
  381. }
  382. function showAllPathByType(){
  383. let otherPathArr = [];
  384. if(currentPlan.value == 1){ //接近原路线
  385. if(allPathArr.safePath.path.length > 0){
  386. otherPathArr.push({
  387. "name": "安全路径",
  388. "type": "polyline",
  389. "shape":{
  390. "radius": 10,
  391. "paths": allPathArr.safePath.path,
  392. "color": [0,255,0,0.3]
  393. }
  394. })
  395. }
  396. if(allPathArr.shortestPath.path.length > 0){
  397. otherPathArr.push({
  398. "name": "贴合路径",
  399. "type": "polyline",
  400. "shape":{
  401. "radius": 10,
  402. "paths": allPathArr.shortestPath.path,
  403. "color": [255,165,255,0.3]
  404. }
  405. })
  406. }
  407. showAndRedrawPath({
  408. status:"show",
  409. path:allPathArr.originalPath.path,
  410. color:[255,234,0,0.7]
  411. });
  412. currentPath = allPathArr.shortestPath.path;
  413. }else if(currentPlan.value == 2){ //安全
  414. if(allPathArr.shortestPath.path.length > 0){
  415. otherPathArr.push({
  416. "name": "最短路径",
  417. "type": "polyline",
  418. "shape":{
  419. "radius": 10,
  420. "paths": allPathArr.shortestPath.path,
  421. "color": [255,165,255,0.3]
  422. }
  423. })
  424. }
  425. if(allPathArr.originalPath.path.length > 0){
  426. otherPathArr.push({
  427. "name": "贴合路径",
  428. "type": "polyline",
  429. "shape":{
  430. "radius": 10,
  431. "paths": allPathArr.originalPath.path,
  432. "color": [255,234,0,0.3]
  433. }
  434. })
  435. }
  436. showAndRedrawPath({
  437. status:"show",
  438. path:allPathArr.safePath.path,
  439. color:[0,255,0,0.7]
  440. });
  441. currentPath = allPathArr.safePath.path;
  442. }else if(currentPlan.value == 3){ //最短
  443. if(allPathArr.safePath.path.length > 0){
  444. otherPathArr.push({
  445. "name": "安全路径",
  446. "type": "polyline",
  447. "shape":{
  448. "radius": 10,
  449. "paths": allPathArr.safePath.path,
  450. "color": [0,255,0,0.3]
  451. }
  452. })
  453. }
  454. if(allPathArr.originalPath.path.length > 0){
  455. otherPathArr.push({
  456. "name": "贴合路径",
  457. "type": "polyline",
  458. "shape":{
  459. "radius": 10,
  460. "paths": allPathArr.originalPath.path,
  461. "color": [255,234,0,0.3]
  462. }
  463. })
  464. }
  465. showAndRedrawPath({
  466. status:"show",
  467. path:allPathArr.shortestPath.path,
  468. color:[ 225,165,255,0.7]
  469. });
  470. currentPath = allPathArr.shortestPath.path;
  471. }
  472. geometryMeshEffect({
  473. "status": "show",
  474. "id": "drawAllPathOne",
  475. "data":otherPathArr
  476. })
  477. }
  478. function getDrawGeometry(){
  479. if(mapStore.draw_geometry){
  480. if(autoHeight){
  481. let path = mapStore.draw_geometry.paths[0];
  482. let flyPathArr = [path[0]];
  483. path.forEach((item) => {
  484. flyPathArr.push([item[0],item[1],(form.value.height1*1 + form.value.height2*1)/2 ]);
  485. });
  486. flyPathArr.push([path[path.length-1][0],path[path.length-1][1],0]);
  487. hasDraw.value = true;
  488. showAndRedrawPath({
  489. status:"show",
  490. path:flyPathArr
  491. });
  492. autoHeight = false;
  493. currentPath = flyPathArr;
  494. }else{
  495. if(currentPlan.value == 1){ //最接近原航线,替换原航线数据
  496. allPathArr.originalPath.path = mapStore.draw_geometry.paths[0];
  497. }else if(currentPlan.value == 2){ //最安全
  498. allPathArr.safePath.path = mapStore.draw_geometry.paths[0];
  499. }else if(currentPlan.value == 3){ //最短
  500. allPathArr.shortestPath.path = mapStore.draw_geometry.paths[0];
  501. }
  502. currentPath = mapStore.draw_geometry.paths[0];
  503. }
  504. }
  505. }
  506. function changeDataType(){
  507. if(form.value.dataType == '01'){ //手动
  508. autoHeight = true;
  509. hasDraw.value = true;
  510. showAndRedrawPath({
  511. status:"show",
  512. radius:form.value.radius
  513. });
  514. geometryMeshEffect({
  515. id: "fromPort",
  516. status:"hide"
  517. })
  518. geometryMeshEffect({
  519. id: "toPort",
  520. status:"hide"
  521. })
  522. }else if(form.value.dataType == "02"){ //自动
  523. autoHeight = false;
  524. hasDraw.value = false;
  525. showAndRedrawPath({
  526. status:"hide"
  527. });
  528. }else if(form.value.dataType == "03"){ //导入航线
  529. autoHeight = false;
  530. hasDraw.value = false;
  531. showAndRedrawPath({
  532. status:"hide"
  533. });
  534. geometryMeshEffect({
  535. id: "fromPort",
  536. status:"hide"
  537. })
  538. geometryMeshEffect({
  539. id: "toPort",
  540. status:"hide"
  541. })
  542. }
  543. }
  544. //展示冲突结果
  545. onMounted(()=>{
  546. getQJCList()
  547. })
  548. watch(() => mapStore.draw_geometry, (val) => {
  549. getDrawGeometry()
  550. }, {
  551. deep: true
  552. })
  553. watch(() => mapStore.cubeResult, (val) => {
  554. //showConflict()
  555. }, { deep: true })
  556. onBeforeUnmount(() => {
  557. toPrev()
  558. })
  559. </script>
  560. <style lang="scss">
  561. @use '../../../assets/styles/panel-form.scss';
  562. </style>
  563. <style lang="scss" scoped>
  564. .panel-hxhs {
  565. :deep(.p-form) {
  566. .el-form-item__label {
  567. width: 105px;
  568. }
  569. }
  570. .msg-draw {
  571. height: 38px;
  572. margin: -5px 0 25px;
  573. background: #62729486;
  574. display: flex;
  575. justify-content: center;
  576. align-items: center;
  577. font-size: 16px;
  578. img {
  579. width: 14px;
  580. height: 14px;
  581. margin-right: 5px;
  582. }
  583. }
  584. .list-plans {
  585. li {
  586. width: 134px;
  587. height: 117px;
  588. background: url('../../../assets/images/page/bg-plan.png');
  589. background-size: 100% !important;
  590. text-align: center;
  591. .tuli{
  592. display: inline-block;
  593. margin-right: 4px;
  594. width: 10px;
  595. height: 10px;
  596. }
  597. .num {
  598. color: #8CBCFF;
  599. }
  600. &.active {
  601. background: url('../../../assets/images/page/bg-plan-h.png');
  602. }
  603. }
  604. }
  605. }
  606. </style>