PanelKyhs.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <!-- 空域划设面板 -->
  2. <template>
  3. <div class="panel-kyhs 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" hide-required-asterisk>
  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="areaType">
  17. <el-select v-model="form.areaType" placeholder="">
  18. <el-option v-for="item in airSpaceTypes" :label="item.label" :value="item.value" />
  19. </el-select>
  20. </el-form-item>
  21. <el-form-item label="时间类型" prop="type">
  22. <el-radio-group v-model="form.type" size="large">
  23. <el-radio-button label="长期" value="长期" />
  24. <el-radio-button label="临时" value="临时" />
  25. </el-radio-group>
  26. </el-form-item>
  27. <template v-if="form.type === '临时'">
  28. <el-form-item label="日期" prop="startDate">
  29. <el-date-picker v-model="form.startDate" value-format="YYYY-MM-DD" type="date"
  30. class="flex-1"></el-date-picker>
  31. <div class="mx-3">--</div>
  32. <el-date-picker v-model="form.endDate" value-format="YYYY-MM-DD" type="date"
  33. class="flex-1"></el-date-picker>
  34. </el-form-item>
  35. <el-form-item label="时间" prop="startTime">
  36. <el-time-select v-model="form.startTime" :max-time="form.endTime" placeholder="" start="00:00"
  37. step="00:15" end="23:45" class="flex-1" />
  38. <div class="mx-3">--</div>
  39. <el-time-select v-model="form.endTime" :min-time="form.startTime" placeholder="" start="00:00"
  40. step="00:15" end="23:45" class="flex-1" />
  41. </el-form-item>
  42. <el-form-item label="划设方式" prop="dataType">
  43. <el-radio-group v-model="form.dataType" size="large">
  44. <el-radio label="手动划设" value="手动划设" />
  45. <el-radio label="导入" value="导入" />
  46. </el-radio-group>
  47. </el-form-item>
  48. </template>
  49. <el-form-item label="类型" prop="drawType" v-if="form.dataType === '手动划设'">
  50. <el-radio-group v-model="form.drawType" size="large" class="content-between h-24">
  51. <el-radio-button v-for="item in drawTypes" :label="item.label" :value="item.value" />
  52. </el-radio-group>
  53. </el-form-item>
  54. <el-form-item label="上传文件" prop="file" v-else>
  55. <el-upload v-model:file-list="form.file" action="" class="single-uplaod">
  56. <div class="upload-trigger">点击上传文件</div>
  57. </el-upload>
  58. </el-form-item>
  59. <el-form-item label="高度" prop="height">
  60. <el-input v-model="form.height" type="number" clearable>
  61. <template #suffix>
  62. <span>米</span>
  63. </template>
  64. </el-input>
  65. </el-form-item>
  66. <el-form-item label="顶半径" prop="topRadius" v-if="['mesh', 'point'].includes(form.drawType)">
  67. <el-input v-model="form.topRadius" type="number" clearable>
  68. <template #suffix>
  69. <span>米</span>
  70. </template>
  71. </el-input>
  72. </el-form-item>
  73. <el-form-item label="底半径" prop="bottomRadius" v-if="form.drawType === 'mesh'">
  74. <el-input v-model="form.bottomRadius" type="number" clearable>
  75. <template #suffix>
  76. <span>米</span>
  77. </template>
  78. </el-input>
  79. </el-form-item>
  80. <el-form-item label="绘制空域">
  81. <el-button class="btn-secondary" @click="handleDraw">绘制</el-button>
  82. </el-form-item>
  83. </el-form>
  84. </Transition>
  85. <Transition name="emerge-right">
  86. <div v-show="currentStep === 1" class="p-main flex flex-col pr-0">
  87. <el-form-item label="网格查询">
  88. <el-button class="btn-secondary ml-4" @click="handleInspect(true)">查询网格</el-button>
  89. <el-button class="btn-secondary ml-4" @click="handleInspect(false)">关闭网格</el-button>
  90. </el-form-item>
  91. <div class="flex-1 overflow-y-auto pr-2">
  92. <div v-if="isSafe" class="msg-safe mb-2">当前空域无冲突!</div>
  93. <template v-else>
  94. <div v-for="item in results" :key="item.title" class="mb-3">
  95. <div class="title-sub">
  96. {{ item.title }}
  97. <div class="flex-1 flex justify-between items-center pl-2">
  98. <span v-if="item.level" class="risk-label"
  99. :class="riskTypes.find(i => i.value === item.level).color">{{
  100. riskTypes.find(i => i.value === item.level).label
  101. }}</span>
  102. <i v-else></i>
  103. <span class="btn-inline" @click="handleReInspect">重新评估</span>
  104. </div>
  105. </div>
  106. <table class="table-default stripe mt-2">
  107. <thead>
  108. <tr>
  109. <th v-for="th in item.cols">{{ th }}</th>
  110. <th class="w-20">地图显示</th>
  111. </tr>
  112. </thead>
  113. <tbody>
  114. <tr v-for="row in item.details">
  115. <td v-for="key in Object.keys(item.cols)">{{ row[key] }}</td>
  116. <td class="w-20">
  117. <el-checkbox size="large" v-model="row.display"
  118. :disabled="(row.id !== '3-3' && !row.cubes.length) || (row.id === '3-3' && item.details.every(r => r.cubes.length === 0))"
  119. @change="val => toggleCubeDetail(row, val)"></el-checkbox>
  120. </td>
  121. </tr>
  122. </tbody>
  123. </table>
  124. </div>
  125. </template>
  126. </div>
  127. </div>
  128. </Transition>
  129. </div>
  130. <div class="text-center">
  131. <template v-if="currentStep === 0">
  132. <el-button :disabled="!hasDraw" class="btn-main" @click="toNext">下一步</el-button>
  133. </template>
  134. <template v-if="currentStep === 1">
  135. <el-button class="btn-main" @click="toPrev">上一步</el-button>
  136. <el-button class="btn-main" @click="handleSave" :loading="loading.save">保存</el-button>
  137. </template>
  138. </div>
  139. </div>
  140. </template>
  141. <script setup>
  142. import { computed, onBeforeUnmount, reactive, ref, watch } from 'vue';
  143. import { drawArea, clearDraw, InspectCube, showShapes, InspectPathCube } from '@/utils/map/addLayer'
  144. import { airSpaceTypes } from '@/utils/options';
  145. import { hexToRgb } from '@/utils/tools';
  146. import { useMapStore } from '@/store/map';
  147. import { AddArea } from '@/service/http';
  148. import { riskTypes } from '@/utils/options';
  149. import useLayoutStore from '@/store/layout';
  150. const mapStore = useMapStore()
  151. const layoutStore = useLayoutStore()
  152. const currentStep = ref(0)
  153. const form = ref({
  154. areaType: '1',
  155. dataType: '手动划设',
  156. type: '长期',
  157. drawType: 'rectangle',
  158. height: '100',
  159. topRadius: '150',
  160. bottomRadius: '50'
  161. })
  162. const rules = {
  163. name: [{ required: true, message: '请输入空域名称', trigger: 'none' }],
  164. height: [{ required: true, message: '此项不可为空', trigger: 'none' }],
  165. topRadius: [{ required: true, message: '此项不可为空', trigger: 'none' }],
  166. bottomRadius: [{ required: true, message: '此项不可为空', trigger: 'none' }],
  167. }
  168. const drawTypes = [
  169. { label: '矩形', value: 'rectangle', geoType: '2', shapeType: 'polygon' },
  170. { label: '圆柱', value: 'circle', geoType: '1', shapeType: 'polygon' },
  171. { label: '多面体', value: 'polygon', geoType: '3', shapeType: 'polygon' },
  172. { label: '圆台', value: 'mesh', geoType: '6', shapeType: 'frustum-cone' },
  173. { label: '圆锥', value: 'point', geoType: '4', shapeType: 'inverted-cone' },
  174. ]
  175. const results = ref([
  176. {
  177. title: '空域评估',
  178. level: null,
  179. cols: { areaName: '冲突空域', time: '冲突时段', height: '冲突高度' },
  180. details: []
  181. },
  182. {
  183. title: '计划冲突',
  184. level: null,
  185. cols: { areaName: '冲突空域', time: '冲突时段', height: '冲突高度' },
  186. details: []
  187. },
  188. {
  189. title: '障碍物分析',
  190. level: null,
  191. cols: { object: '冲突空域', height: '冲突高度' },
  192. details: []
  193. },
  194. {
  195. title: '地面安全评估',
  196. level: null,
  197. cols: { element: '要素', coverage: '覆盖率' },
  198. details: []
  199. }
  200. ])
  201. const isSafe = computed(() => {
  202. return results.value.every(i => i.level === 1)
  203. })
  204. const hasDraw = ref(false)
  205. function handleDraw() {
  206. formRef.value.validateField(['height', 'topRadius', 'bottomRadius'], async (isValid) => {
  207. if (!isValid) return
  208. if (hasDraw.value) {
  209. showShapes({
  210. id: 'kyhs_drew_non_editable',
  211. data: null
  212. })
  213. await new Promise((res) => {
  214. setTimeout(() => res(), 500);
  215. })
  216. }
  217. // 处理绘制
  218. const color = hexToRgb(airSpaceTypes.find(i => i.value === form.value.areaType).color, 0.5)
  219. drawArea({
  220. ...form.value,
  221. color
  222. })
  223. })
  224. }
  225. function getShapeInfo() {
  226. let res = {}
  227. const { height, topRadius, bottomRadius } = form.value
  228. const geoType = drawTypes.find(i => i.value === form.value.drawType).geoType
  229. const { x, y, z } = drawGeometry
  230. switch (geoType) {
  231. case '1':
  232. case '2':
  233. case '3':
  234. res = {
  235. height: +height,
  236. rings: drawGeometry.rings
  237. }
  238. break;
  239. case '4':
  240. res = {
  241. point: { x, y, z },
  242. height: +height,
  243. radius: +topRadius
  244. }
  245. break;
  246. case '6':
  247. res = {
  248. point: { x, y, z },
  249. height: +height,
  250. topRadius: +topRadius,
  251. bottomRadius: +bottomRadius
  252. }
  253. break;
  254. }
  255. return res
  256. }
  257. function handleInspect(show = true) {
  258. if (show) {
  259. layoutStore.toggleGlobalLoading(true)
  260. }
  261. InspectCube({
  262. type: drawTypes.find(i => i.value === form.value.drawType).shapeType,
  263. id: 'inspect_kyhs',
  264. show,
  265. shape: show ? getShapeInfo() : {}
  266. })
  267. }
  268. let drawGeometry
  269. watch(() => mapStore.draw_geometry, (val) => {
  270. console.log('draw_geometry:', val)
  271. drawGeometry = val
  272. hasDraw.value = true
  273. }, { deep: true })
  274. watch(() => mapStore.cubeResult, (val) => {
  275. console.log('cubeResult:', val)
  276. formatResult(val)
  277. }, { deep: true })
  278. function clearResults() {
  279. results.value.forEach(v => {
  280. v.details = []
  281. v.level = null
  282. })
  283. }
  284. function formatResult(res) {
  285. console.log('formatResult:', res)
  286. clearResults()
  287. clearAllCubeDetail()
  288. const { groundSafety, lineConflictArray, obstacle, spaceConflictArray, spaceRisk, lineRisk } = res
  289. results.value[0].level = spaceRisk
  290. results.value[1].level = lineRisk
  291. results.value[2].level = obstacle.risk
  292. results.value[3].level = groundSafety.risk
  293. results.value[0].details = spaceConflictArray.map((i, index) => ({
  294. id: `0-${index}`,
  295. areaName: i.gridName,
  296. height: i.heightRange,
  297. time: i.conflictTime,
  298. cubes: i.gridEntityList,
  299. display: false,
  300. }))
  301. results.value[1].details = lineConflictArray.map((i, index) => ({
  302. id: `1-${index}`,
  303. areaName: i.gridName,
  304. height: i.heightRange,
  305. time: i.conflictTime,
  306. cubes: i.gridEntityList,
  307. display: false
  308. }))
  309. results.value[2].details = [
  310. {
  311. id: '2-0', time: '01-249:00-01-25 10-00', object: '建筑物',
  312. height: obstacle.buildingConflictHeight, cubes: obstacle.buildingsConflictArray, display: false
  313. },
  314. {
  315. id: '2-1', time: '01-249:00-01-25 10-00', object: '建筑物缓冲区',
  316. height: obstacle.buildingBufferConflictHeight, cubes: obstacle.buildingsBufferConflictArray, display: false
  317. },
  318. ]
  319. const toPercent = (num) => {
  320. return Math.round(num * 100) + '%'
  321. }
  322. results.value[3].details = [
  323. { id: '3-0', element: '道路', coverage: toPercent(groundSafety.road), cubes: groundSafety.gridGoupMap.road, display: false },
  324. { id: '3-1', element: '河流', coverage: toPercent(groundSafety.river), cubes: groundSafety.gridGoupMap.river, display: false },
  325. { id: '3-2', element: '绿化', coverage: toPercent(groundSafety.green), cubes: groundSafety.gridGoupMap.green, display: false },
  326. { id: '3-3', element: '综合占比', coverage: toPercent(groundSafety.road + groundSafety.river + groundSafety.green), display: false },
  327. ]
  328. layoutStore.toggleGlobalLoading(false)
  329. }
  330. function handleDisplay(row, val) {
  331. console.log(row, val)
  332. }
  333. const formRef = ref(null)
  334. function toNext() {
  335. formRef.value.validate(valid => {
  336. if (valid) {
  337. layoutStore.toggleGlobalLoading(true)
  338. clearDraw()
  339. showShapes({
  340. id: 'kyhs_drew_non_editable',
  341. data: [{
  342. type: drawTypes.find(i => i.value === form.value.drawType).shapeType,
  343. shape: {
  344. ...getShapeInfo(),
  345. color: hexToRgb(airSpaceTypes.find(i => i.value===form.value.areaType).color, 0.5)
  346. }
  347. }]
  348. })
  349. handleInspect(true)
  350. currentStep.value = 1
  351. }
  352. })
  353. }
  354. function handleReInspect() {
  355. layoutStore.toggleGlobalLoading(true)
  356. handleInspect(false)
  357. setTimeout(() => {
  358. handleInspect(true)
  359. }, 500);
  360. }
  361. let addedCubeDetailIds = []
  362. function toggleCubeDetail(row, status) {
  363. console.log(row, status)
  364. if (row.id === '3-3') {
  365. // 处理综合占比
  366. results.value[3].details.forEach(gRow => {
  367. if (gRow.display !== status && gRow.cubes.length) {
  368. gRow.display = status
  369. toggleCubeDetail(gRow, status)
  370. }
  371. })
  372. } else {
  373. if (!row.cubes.length) return
  374. const id = 'hxhs_' + row.id
  375. if(status) {
  376. addedCubeDetailIds.push(id)
  377. } else {
  378. const targetIndex = addedCubeDetailIds.findIndex(i => i===id)
  379. addedCubeDetailIds.splice(targetIndex, 1)
  380. }
  381. InspectPathCube({
  382. id,
  383. show: status,
  384. points: row.cubes.map(c => ({
  385. x: c.x,
  386. y: c.y,
  387. z: c.z,
  388. color: c.color
  389. })),
  390. size: {
  391. x: row.cubes[0].boxSize.lonLength,
  392. y: row.cubes[0].boxSize.latLength,
  393. z: row.cubes[0].boxSize.height
  394. }
  395. })
  396. }
  397. }
  398. function clearAllCubeDetail() {
  399. addedCubeDetailIds.forEach(id => {
  400. InspectPathCube({
  401. id,
  402. show: false,
  403. })
  404. })
  405. addedCubeDetailIds = []
  406. }
  407. function toPrev() {
  408. currentStep.value = 0
  409. handleInspect(false)
  410. clearAllCubeDetail()
  411. clearResults()
  412. }
  413. const loading = reactive({
  414. save: false
  415. })
  416. function handleSave() {
  417. loading.save = true
  418. let params = {
  419. ...form.value,
  420. spaceType: form.value.areaType,
  421. geoType: drawTypes.find(i => i.value === form.value.drawType).geoType,
  422. upRadius: form.value.topRadius,
  423. downRadius: form.value.bottomRadius,
  424. shape: JSON.stringify(getShapeInfo())
  425. }
  426. AddArea(params).then(({ data: res }) => {
  427. if (res.msg === 'success') {
  428. ElMessage({ offset: 90, type: 'success', message: '保存成功' })
  429. } else {
  430. ElMessage({ offset: 90, type: 'error', message: `保存失败,${res.msg}` })
  431. }
  432. }).catch((e) => {
  433. ElMessage({ offset: 90, type: 'error', message: `保存失败,${e}` })
  434. }).finally(() => {
  435. loading.save = false
  436. })
  437. }
  438. onBeforeUnmount(() => {
  439. clearDraw()
  440. handleInspect(false)
  441. showShapes({
  442. id: 'kyhs_drew_non_editable',
  443. data: null
  444. })
  445. clearResults()
  446. clearAllCubeDetail()
  447. })
  448. </script>
  449. <style lang="scss">
  450. @use '../../../assets/styles/panel-form.scss';
  451. </style>
  452. <style lang="scss" scoped>
  453. .panel-kyhs {
  454. :deep(.p-form) {
  455. .el-form-item__label {
  456. width: 105px;
  457. }
  458. }
  459. }
  460. </style>