PanelSjwg.vue 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  1. <template>
  2. <div class="panel-sjwg flex flex-col aside-left-inner">
  3. <div class="title-main shrink-0">数据网格可视化</div>
  4. <div class="title-sub my-4 shrink-0">
  5. 底板数据
  6. <i @click="toggleContentShow('b1')" class="drop-down" :class="{ reverse: contentShow.b1 }"></i>
  7. </div>
  8. <Transition>
  9. <div v-if="contentShow.b1" v-collapse="'scroll'" class="pr-1" style="flex: 2">
  10. <template v-for="item in basicList">
  11. <div class="title-shade">
  12. <span>{{ item.label }}</span>
  13. <i
  14. v-if="'active' in item"
  15. class="btn-selectall"
  16. :class="{ active: item.active }"
  17. @click="basicCheckAll(item)"></i>
  18. <i class="drop-down" @click="toggleB1Show(item)" :class="{ reverse: item.show }"></i>
  19. </div>
  20. <Transition>
  21. <ul v-if="item.show" v-collapse>
  22. <li v-for="child in item.children" class="list-item" :class="{ disabled: child.disabled }">
  23. <img :src="getAssetsFile(`resources/${child.icon}.png`)" alt="" />
  24. <span>{{ child.label }}</span>
  25. <i title="查看" @click="handleBaseClick(child)" :class="{ active: child.active }"></i>
  26. </li>
  27. </ul>
  28. </Transition>
  29. </template>
  30. </div>
  31. </Transition>
  32. <div class="title-sub my-4 shrink-0">
  33. 低空要素数据<i @click="toggleContentShow('b2')" class="drop-down" :class="{ reverse: contentShow.b2 }"></i>
  34. </div>
  35. <Transition>
  36. <div v-if="contentShow.b2" v-collapse="'scroll'" class="pr-1" style="flex: 3">
  37. <template v-for="item in featureLists">
  38. <div class="title-shade">
  39. <span @click="handleCheckShiFei(item.label)">{{ item.label }}</span>
  40. <i class="btn-selectall" :class="{ active: item.check }" @click="handleCheckAll(item)"></i>
  41. <i class="drop-down" @click="toggleB2Show(item)" :class="{ reverse: item.show }"></i>
  42. </div>
  43. <Transition>
  44. <ul v-if="item.show" v-collapse>
  45. <li v-for="child in item.children" class="list-item">
  46. <img :src="getAssetsFile(`resources/${item.icon}.png`)" alt="" />
  47. <span>{{ child.name }}</span>
  48. <i title="查看" @click="handleCheck(child, item.type)" :class="{ active: child.check }"></i>
  49. <i title="网格" @click="handleMesh(child, item.type)" :class="{ active: child.mesh }"></i>
  50. <el-popconfirm title="确定删除?" @confirm="handleDelete(child, item.type)">
  51. <template #reference>
  52. <i title="删除"></i>
  53. </template>
  54. </el-popconfirm>
  55. </li>
  56. <li v-if="!item.children.length" class="no-data">无数据</li>
  57. </ul>
  58. </Transition>
  59. </template>
  60. </div>
  61. </Transition>
  62. <Transition name="emerge-left">
  63. <div class="feature-legend" v-if="layoutStore.floatPanels.layers_legend">
  64. <div class="title-sub">图例</div>
  65. <ul>
  66. <template v-for="item in featureLists">
  67. <li>
  68. <i v-if="item.color" :style="{ background: item.color }"></i>
  69. <span :class="{ bold: item.colors }">{{ item.label }}</span>
  70. </li>
  71. <template v-if="item.colors">
  72. <div v-for="(childColor, childLabel) in item.colors">
  73. <i :style="{ background: childColor }"></i>
  74. <span>{{ childLabel }}</span>
  75. </div>
  76. </template>
  77. </template>
  78. </ul>
  79. </div>
  80. </Transition>
  81. <!-- 空域点查询 -->
  82. <Transition name="emerge-left">
  83. <FloatPanelAirSpace v-if="layoutStore.floatPanels.air_space" />
  84. </Transition>
  85. </div>
  86. </template>
  87. <script setup>
  88. import shifei from '@/data/shifei.json'
  89. import { DeleteArea, DeletePlot, DeleteRoute, GetAreaList, GetPlotList, GetRouteList } from '@/service/http'
  90. import useLayoutStore from '@/store/layout'
  91. import { useMapStore } from '@/store/map.js'
  92. import usePanelStore from '@/store/panel'
  93. import { AddSingleLayer, heatMap, InspectCube, showShapes, toggleFeaturesClickEvent } from '@/utils/map/addLayer'
  94. import { airSpaceTypes } from '@/utils/options'
  95. import { getAssetsFile, getData } from '@/utils/require'
  96. import { hexToRgb } from '@/utils/tools'
  97. import { renderShapes } from '@/utils/ueActions'
  98. import { onBeforeMount, onBeforeUnmount, reactive, ref, watch } from 'vue'
  99. import FloatPanelAirSpace from './FloatPanelAirSpace.vue'
  100. const panelStore = usePanelStore()
  101. const layoutStore = useLayoutStore()
  102. const mapStore = useMapStore()
  103. let shifeiStatus = false
  104. onBeforeMount(() => {
  105. getLists()
  106. addAreaColors()
  107. if (layoutStore.sceneType === 'ue') {
  108. contentShow.b1 = false
  109. }
  110. toggleFeaturesClickEvent(true)
  111. })
  112. function addAreaColors() {
  113. featureLists.value.forEach((i) => {
  114. if (!i.color && !i.colors) {
  115. i.color = airSpaceTypes.find((t) => t.label === i.label).color
  116. }
  117. })
  118. layoutStore.toggleFloatPanel('layers_legend', true)
  119. }
  120. function showWarning(message) {
  121. ElMessage({ offset: 90, type: 'warning', message })
  122. }
  123. function getLists() {
  124. Promise.all([
  125. GetAreaList()
  126. .then((res) => {
  127. if (!Array.isArray(res.data.data)) {
  128. showWarning('空域列表查询失败')
  129. return
  130. }
  131. const resList = res.data.data.map((row) => ({
  132. ...row,
  133. check: false,
  134. mesh: false,
  135. }))
  136. featureLists.value[0].children = resList.filter((r) => r.spaceType === '1')
  137. featureLists.value[1].children = resList.filter((r) => r.spaceType === '2')
  138. featureLists.value[2].children = resList.filter((r) => r.spaceType === '3')
  139. featureLists.value[3].children = resList.filter((r) => r.spaceType === '6')
  140. featureLists.value[0].show = true
  141. })
  142. .catch(() => {
  143. showWarning('空域列表查询失败')
  144. }),
  145. GetPlotList()
  146. .then((res) => {
  147. if (!Array.isArray(res.data.data)) {
  148. showWarning('起降场列表查询失败')
  149. return
  150. }
  151. featureLists.value[5].children = res.data.data.map((row) => ({
  152. ...row,
  153. check: false,
  154. mesh: false,
  155. }))
  156. })
  157. .catch(() => {
  158. showWarning('起降场列表查询失败')
  159. }),
  160. GetRouteList()
  161. .then((res) => {
  162. if (!Array.isArray(res.data.data)) {
  163. showWarning('航线列表查询失败')
  164. return
  165. }
  166. featureLists.value[4].children = res.data.data.map((row) => ({
  167. ...row,
  168. check: false,
  169. mesh: false,
  170. }))
  171. })
  172. .catch(() => {
  173. showWarning('航线列表查询失败')
  174. }),
  175. ]).finally(() => {
  176. // 恢复选中状态
  177. handleRestoreChecked()
  178. })
  179. }
  180. function basicCheckAll(item) {
  181. item.active = !item.active
  182. item.children.forEach((c) => {
  183. c.active = !item.active
  184. handleBaseClick(c)
  185. })
  186. }
  187. const basicList = ref([
  188. {
  189. label: '地形',
  190. show: true,
  191. children: [
  192. { label: '影像', alias: '影像底图', icon: 'dxing', active: false },
  193. { label: '政务底图', alias: '政务底图', icon: 'dxing', active: false },
  194. { label: '暗色底图', alias: '暗色底图', icon: 'dxing', active: false },
  195. ],
  196. },
  197. {
  198. label: '低空障碍物',
  199. show: false,
  200. active: false,
  201. children: [
  202. { label: '全市建筑物', alias: '全市白模', icon: 'qsjzwu', active: false },
  203. { label: '高精度模型', alias: '五角场精模', icon: 'gjdmxing', active: false },
  204. ],
  205. },
  206. {
  207. label: '电磁干扰场域',
  208. show: false,
  209. active: false,
  210. children: [
  211. { label: '无线通信基站', active: false, icon: 'wxtxjzhan', disabled: true },
  212. { label: '电磁干扰', active: false, icon: 'dcgrao', disabled: true },
  213. ],
  214. },
  215. {
  216. label: '其它社会信息',
  217. show: false,
  218. active: false,
  219. children: [
  220. { label: '道路', alias: ['快速路', '高速公路', '地面道路'], icon: 'dlu', active: false },
  221. { label: '河流', alias: '全市河流', icon: 'hliu', active: false },
  222. { label: '轨道交通', icon: 'gdjtong', active: false },
  223. { label: '铁路', icon: 'tlu', active: false },
  224. { label: '绿化', alias: '公园绿地', icon: 'lhua', active: false },
  225. { label: '学校', icon: 'xxiao', active: false },
  226. { label: '医院', alias: '医院', icon: 'yyuan', active: false },
  227. { label: '人口', alias: '人口', icon: 'rkou', active: false },
  228. { label: '政府部门', icon: 'zfbmen', active: false },
  229. ],
  230. },
  231. ])
  232. const featureLists = ref([
  233. { label: '禁飞区', type: 'area', show: false, check: false, icon: 'jfqu', children: [] },
  234. { label: '净空区', type: 'area', show: false, check: false, icon: 'jkqu', children: [] },
  235. { label: '适飞区', type: 'area', show: false, check: false, icon: 'sfqu', children: [] },
  236. {
  237. label: '已批复空域',
  238. type: 'area',
  239. show: false,
  240. check: false,
  241. icon: 'ypfkyu',
  242. children: [],
  243. colors: { '120米以下': '#ccff66', '300米以下': '#ffcc66', '600米以下': '#feb2b2' },
  244. },
  245. { label: '航线', type: 'route', show: false, check: false, icon: 'hxian', children: [], color: '#ffff00' },
  246. { label: '起降场', type: 'plot', show: false, check: false, icon: 'qjchang', children: [], color: '#45dcb5' },
  247. ])
  248. let resources
  249. async function handleBaseClick(layer) {
  250. layer.active = !layer.active
  251. if (layer.alias) {
  252. const aliasArr = Array.isArray(layer.alias) ? layer.alias : [layer.alias]
  253. if (!resources) {
  254. resources = await getData('resources.json')
  255. }
  256. const targetServices = resources.filter((r) => aliasArr.some((i) => i === r.title))
  257. if (targetServices.length === 0) return
  258. targetServices.forEach((service) => {
  259. if (service.type == 'feature') {
  260. heatMap({
  261. ...service,
  262. visible: layer.active,
  263. })
  264. } else {
  265. AddSingleLayer({
  266. ...service,
  267. visible: layer.active,
  268. opacity: 1,
  269. })
  270. }
  271. })
  272. }
  273. }
  274. function toggleB1Show(item) {
  275. const target = basicList.value.find((i) => i.label === item.label)
  276. target.show = !target.show
  277. basicList.value.forEach((i) => {
  278. if (i.show && i.label !== item.label) {
  279. i.show = false
  280. }
  281. })
  282. }
  283. function toggleB2Show(item) {
  284. const target = featureLists.value.find((i) => i.label === item.label)
  285. target.show = !target.show
  286. featureLists.value.forEach((i) => {
  287. if (i.show && i.label !== item.label) {
  288. i.show = false
  289. }
  290. })
  291. }
  292. function handleCheck(item, type) {
  293. // console.log(item)
  294. item.check = !item.check
  295. let color
  296. const shapeObj = JSON.parse(item.shape)
  297. let additional = {}
  298. switch (type) {
  299. case 'area':
  300. if (item.spaceType !== '6') {
  301. color = hexToRgb(airSpaceTypes.find((i) => i.value === item.spaceType).color, 0.5)
  302. } else {
  303. const colorArr = Object.values(featureLists.value.find((i) => i.label === '已批复空域').colors)
  304. color = +shapeObj.height < 120 ? colorArr[0] : +shapeObj.height < 300 ? colorArr[1] : colorArr[2]
  305. color = hexToRgb(color, 0.5)
  306. }
  307. additional = {
  308. attributes: {
  309. id: item.id,
  310. name: item.name,
  311. height: shapeObj.height,
  312. },
  313. }
  314. break
  315. case 'plot':
  316. color = hexToRgb(featureLists.value.find((i) => i.type === 'plot').color, 0.7)
  317. const { height, coneHeight, cylinderHeight } = shapeObj
  318. additional = {
  319. attributes: {
  320. id: item.id,
  321. name: item.name,
  322. height: height || coneHeight + cylinderHeight,
  323. },
  324. }
  325. break
  326. case 'route':
  327. color = hexToRgb(featureLists.value.find((i) => i.type === 'route').color, 0.5)
  328. }
  329. const data = [
  330. {
  331. ...additional,
  332. type: type === 'route' ? item.type : item.geoType,
  333. shape: {
  334. ...shapeObj,
  335. color,
  336. },
  337. },
  338. ]
  339. if (layoutStore.sceneType === 'gis') {
  340. showShapes({
  341. id: item.id,
  342. data: item.check ? data : null,
  343. })
  344. } else {
  345. renderShapes({
  346. id: item.id,
  347. data: item.check ? data[0] : null,
  348. })
  349. }
  350. }
  351. function handleCheckShiFei(label) {
  352. if (label !== '适飞区') return
  353. shifeiStatus = !shifeiStatus
  354. if (shifeiStatus) {
  355. shifei.forEach((item) => {
  356. showShapes({
  357. id: item.attributes.FID,
  358. data: [
  359. {
  360. type: 'polygon',
  361. shape: {
  362. color: [0, 255, 0, 0.5],
  363. height: 120,
  364. rings: item.geometry.rings,
  365. },
  366. },
  367. ],
  368. })
  369. })
  370. } else {
  371. shifei.forEach((item) => {
  372. showShapes({
  373. id: item.attributes.FID,
  374. })
  375. })
  376. }
  377. }
  378. function handleDelete(item, type) {
  379. switch (type) {
  380. case 'area':
  381. DeleteArea(item.id).then((res) => {
  382. if (res.data.code === 200) {
  383. ElMessage.success('删除成功')
  384. getLists()
  385. }
  386. })
  387. break
  388. case 'plot':
  389. DeletePlot(item.id).then((res) => {
  390. if (res.data.code === 200) {
  391. ElMessage.success('删除成功')
  392. getLists()
  393. }
  394. })
  395. break
  396. case 'route':
  397. DeleteRoute(item.id).then((res) => {
  398. if (res.data.code === 200) {
  399. ElMessage.success('删除成功')
  400. getLists()
  401. }
  402. })
  403. }
  404. }
  405. function handleMesh(item, type) {
  406. item.mesh = !item.mesh
  407. if (item.mesh) {
  408. layoutStore.toggleGlobalLoading(true)
  409. }
  410. InspectCube({
  411. id: item.id,
  412. show: item.mesh,
  413. type: item.geoType || item.type,
  414. shape: JSON.parse(item.shape),
  415. })
  416. }
  417. const contentShow = reactive({
  418. b1: true,
  419. b2: true,
  420. })
  421. function toggleContentShow(id) {
  422. contentShow[id] = !contentShow[id]
  423. }
  424. function handleCheckAll(item) {
  425. item.check = !item.check
  426. item.children.forEach((c) => {
  427. if (item.check && !c.check) {
  428. handleCheck(c, item.type)
  429. } else if (!item.check && c.check) {
  430. handleCheck(c, item.type)
  431. }
  432. })
  433. }
  434. function handleStoreChecked() {
  435. panelStore.setSjwg({
  436. basicList: basicList.value,
  437. featureLists: featureLists.value.map((t) => ({
  438. label: t.label,
  439. show: t.show,
  440. check: t.check,
  441. children: t.children.map((c) => ({
  442. id: c.id,
  443. check: c.check,
  444. mesh: c.mesh,
  445. })),
  446. })),
  447. })
  448. }
  449. function handleRestoreChecked() {
  450. if (!Object.keys(panelStore.sjwg).length) return
  451. const temp = panelStore.sjwg
  452. basicList.value = temp.basicList
  453. featureLists.value.forEach((row) => {
  454. const tar_row = temp.featureLists.find((t) => t.label === row.label)
  455. row.check = tar_row.check
  456. row.show = tar_row.show
  457. row.children.forEach((c) => {
  458. const tar_c = tar_row.children.find((tc) => tc.id === c.id)
  459. c.check = tar_c.check
  460. c.mesh = tar_c.mesh
  461. })
  462. })
  463. }
  464. onBeforeUnmount(() => {
  465. handleStoreChecked()
  466. layoutStore.toggleFloatPanel('layers_legend', false)
  467. layoutStore.toggleFloatPanel('air_space', false)
  468. toggleFeaturesClickEvent(false)
  469. })
  470. const vCollapse = {
  471. beforeMount(el, binding) {
  472. el.style.height = '0'
  473. el.style.overflow = binding.value === 'scroll' ? 'auto' : 'hidden'
  474. el.style.transition = 'height 0.5s ease'
  475. },
  476. mounted(el) {
  477. const naturalHeight = el.scrollHeight
  478. el.style.height = `${naturalHeight}px`
  479. },
  480. beforeUnmount(el) {
  481. el.style.height = '0'
  482. },
  483. }
  484. watch(
  485. () => mapStore.cubeResult,
  486. (val) => {
  487. if (val.data == 'error') {
  488. ElMessage({ type: 'error', message: '核查结果为空' })
  489. }
  490. layoutStore.toggleGlobalLoading(false)
  491. },
  492. { deep: true }
  493. )
  494. watch(
  495. () => mapStore.clickResult,
  496. (val) => {
  497. if (!layoutStore.floatPanels.air_space) {
  498. layoutStore.toggleFloatPanel('air_space', true)
  499. }
  500. },
  501. { deep: true }
  502. )
  503. </script>
  504. <style lang="scss" scoped>
  505. .list-base {
  506. li {
  507. span {
  508. width: 75px;
  509. }
  510. img {
  511. width: 75px;
  512. height: 77px;
  513. }
  514. }
  515. }
  516. .drop-down {
  517. display: block;
  518. width: 20px;
  519. height: 20px;
  520. margin-left: 10px;
  521. background: url('../../../assets/images/buttons/btn-dropdown.png') no-repeat;
  522. background-size: 10px 7px;
  523. background-position: center;
  524. transition: transform 0.3s ease;
  525. cursor: pointer;
  526. &.reverse {
  527. transform: rotateZ(180deg);
  528. }
  529. }
  530. .title-shade {
  531. position: relative;
  532. height: 32px;
  533. background: linear-gradient(to right, rgba(101, 131, 190, 1) 0%, rgba(101, 131, 190, 0) 100%);
  534. display: flex;
  535. align-items: center;
  536. padding: 0 16px;
  537. margin-bottom: 10px;
  538. &::before {
  539. content: '';
  540. position: absolute;
  541. left: 0;
  542. top: 0;
  543. width: 4px;
  544. height: 32px;
  545. background: #c0d5ff;
  546. }
  547. span {
  548. text-shadow: 0px 4px 4px rgba(21, 41, 91, 0.45);
  549. margin-right: 10px;
  550. }
  551. .btn-selectall {
  552. display: block;
  553. width: 26px;
  554. height: 26px;
  555. background: url('../../../assets/images/buttons/btn-selectall.png');
  556. background-size: 100%;
  557. background-position: center 1px;
  558. cursor: pointer;
  559. filter: opacity(0.6);
  560. &.active {
  561. filter: opacity(1) drop-shadow(0 0 5px #379bff);
  562. transform: scale(1.1);
  563. }
  564. }
  565. .drop-down {
  566. margin-left: auto;
  567. }
  568. }
  569. .list-item {
  570. position: relative;
  571. height: 40px;
  572. padding: 0 15px;
  573. margin: 9px 0;
  574. display: flex;
  575. align-items: center;
  576. background: #4352704d;
  577. &.disabled {
  578. pointer-events: none;
  579. opacity: 0.5;
  580. }
  581. &::before {
  582. content: '';
  583. position: absolute;
  584. left: 0;
  585. top: 0;
  586. width: 1px;
  587. height: 40px;
  588. background: #808dc9;
  589. }
  590. &:last-child {
  591. margin-bottom: 15px;
  592. }
  593. img {
  594. width: 24px;
  595. height: 24px;
  596. margin-right: 10px;
  597. }
  598. span {
  599. flex: 1;
  600. }
  601. i {
  602. display: block;
  603. width: 34px;
  604. height: 34px;
  605. margin-left: 5px;
  606. cursor: pointer;
  607. filter: grayscale(1);
  608. &.active {
  609. filter: grayscale(0);
  610. }
  611. }
  612. i:nth-child(3) {
  613. background: url('../../../assets/images/buttons/btn-check.png');
  614. background-size: cover;
  615. }
  616. i:nth-child(4) {
  617. background: url('../../../assets/images/buttons/btn-mesh.png');
  618. background-size: cover;
  619. }
  620. i:nth-child(5) {
  621. background: url('../../../assets/images/buttons/btn-delete.png');
  622. background-size: 40px 40px;
  623. background-position: center;
  624. }
  625. }
  626. .no-data {
  627. display: block;
  628. width: 100%;
  629. text-align: center;
  630. margin: 5px 0 10px;
  631. color: #999;
  632. }
  633. .feature-legend {
  634. position: absolute;
  635. bottom: var(--panel-gap);
  636. left: calc(var(--panel-left) - var(--panel-gap));
  637. width: 140px;
  638. padding: 10px 15px 15px;
  639. background-color: rgba(0, 17, 50, 0.5);
  640. border: 1px solid #055f8d;
  641. border-radius: 5px;
  642. .title-sub {
  643. margin-left: -7px;
  644. }
  645. ul {
  646. margin-top: 5px;
  647. li,
  648. div {
  649. display: flex;
  650. align-items: center;
  651. i {
  652. display: block;
  653. width: 10px;
  654. height: 10px;
  655. margin-right: 10px;
  656. opacity: 0.6;
  657. }
  658. span {
  659. font-size: 14px;
  660. color: #ccc;
  661. }
  662. }
  663. li {
  664. &:not(:last-child) {
  665. margin: 5px 0;
  666. }
  667. span.bold {
  668. font-size: 15px;
  669. }
  670. }
  671. div {
  672. padding-left: 15px;
  673. }
  674. }
  675. }
  676. </style>