map-3d.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. <script setup lang='ts'>
  2. import {
  3. Viewer, ScreenSpaceEventType, Cartesian3,
  4. Math as CMath, Color, EventHelper, UrlTemplateImageryProvider,
  5. DistanceDisplayCondition, ScreenSpaceEventHandler, WebMercatorTilingScheme,
  6. VerticalOrigin, HeightReference, Cartesian2, Cartographic, GeoJsonDataSource, SceneMode, WebMapTileServiceImageryProvider, GeographicTilingScheme
  7. } from 'cesium'
  8. import {
  9. computed, onMounted, onUnmounted, ref, watch
  10. } from 'vue'
  11. // import useStore from '@/pages/store/index'
  12. import lnglatIcon from '@/assets/icons/lnglat.svg'
  13. import camera from '@/assets/icons/camera.svg'
  14. import broadcast from '@/assets/icons/broadcast.svg'
  15. import preBranke from '@/assets/icons/preBranke.svg'
  16. import carBranke from '@/assets/icons/carBranke.svg'
  17. import { NInput, useNotification, useDialog } from 'naive-ui'
  18. import EquipmentService, { TreeList, Device } from '@/services/equipmentTree.service'
  19. const notification = useNotification()
  20. const dialog = useDialog()
  21. const equipmentService = new EquipmentService()
  22. // const store = useStore()
  23. const props = defineProps({
  24. type: { default: true, type: Boolean },
  25. checkList: { default: [], type: Array<TreeList> },
  26. treeList: { default: [], type: Array<TreeList> }
  27. })
  28. const isAdmin = computed(() => props.type)
  29. let viewer = null as Any
  30. let Helper = new EventHelper() as Any
  31. let handler = null as Any
  32. const currenIcon = ref({ icon: '', x: 0, y: 0 })
  33. const icontypes = [
  34. {
  35. name: '摄像机', type: 'camera', icon: camera, deviceType: 0
  36. },
  37. {
  38. name: '广播', type: 'broadcast', icon: broadcast, deviceType: 1
  39. },
  40. {
  41. name: '人行闸机', type: 'preBranke', icon: preBranke, deviceType: 2
  42. },
  43. {
  44. name: '车行闸机', type: 'carBranke', icon: carBranke, deviceType: 3
  45. }
  46. ]
  47. const modelShow = ref(false)
  48. const model = ref({
  49. deviceId: '',
  50. itemId: -1,
  51. longitude: '',
  52. latitude: '',
  53. describeData: '',
  54. selfId: '',
  55. deviceType: 0
  56. })
  57. const rules = {
  58. deviceId: [ { required: true, message: '请输入设备ID' } ],
  59. itemId: [ { required: true, message: '请选择项目' } ],
  60. longitude: [ { required: true, message: '请输入经度' } ],
  61. latitude: [ { required: true, message: '请输入维度' } ],
  62. describeData: [ { required: true, message: '请输入安装位置描述' } ]
  63. }
  64. const options = ref([] as Array<TreeList>)
  65. const formRef = ref()
  66. const isAdd = ref(false)
  67. /**
  68. * 添加路径
  69. * @param lngLat 经纬度数组
  70. * @param displayMin 最小显示范围
  71. * @param displayMax 最大显示范围
  72. */
  73. function addLine(lngLat: Array<number>, displayMin?: number, displayMax?: number) {
  74. if (!viewer) return
  75. viewer.entities.add({
  76. position: Cartesian3.fromDegrees(lngLat[0], lngLat[1]),
  77. polyline: {
  78. positions: Cartesian3.fromDegreesArray(lngLat),
  79. width: 5,
  80. material: Color.fromCssColorString('#55F8F8'),
  81. clampToGround: true,
  82. distanceDisplayCondition: new DistanceDisplayCondition(displayMin, displayMax)
  83. }
  84. })
  85. }
  86. /**
  87. * 添加label
  88. * @param lng 经度
  89. * @param lat 纬度
  90. * @param icon 图标
  91. * @param text 文本内容
  92. * @param displayMin 最小显示范围
  93. * @param displayMax 最大显示范围
  94. */
  95. function addLable(lng: number, lat: number, icon: string, text: string, displayMin?: number, displayMax?: number, oldData?: Device) {
  96. if (!viewer) return
  97. viewer.entities.add({
  98. position: Cartesian3.fromDegrees(lng, lat),
  99. billboard: {
  100. image: icon || lnglatIcon,
  101. width: 25,
  102. height: 25,
  103. verticalOrigin: VerticalOrigin.BOTTOM,
  104. disableDepthTestDistance: 21618529,
  105. heightReference: HeightReference.CLAMP_TO_GROUND,
  106. distanceDisplayCondition: new DistanceDisplayCondition(displayMin, displayMax)
  107. },
  108. label: {
  109. text,
  110. font: '12px MicrosoftYaHei',
  111. backgroundColor: Color.fromCssColorString('#0F2830'),
  112. showBackground: true,
  113. fillColor: Color.WHITE,
  114. pixelOffset: new Cartesian2(0, -40),
  115. disableDepthTestDistance: 21618529,
  116. heightReference: HeightReference.CLAMP_TO_GROUND,
  117. distanceDisplayCondition: new DistanceDisplayCondition(displayMin, displayMax)
  118. },
  119. oldData
  120. })
  121. }
  122. /**
  123. * 加载geojson
  124. * @param path 文件路径
  125. * @param callback
  126. * @param displayMin
  127. * @param displayMax
  128. */
  129. function loadGeoJson(path:string, displayMin?: number, displayMax?: number) {
  130. GeoJsonDataSource.load(path, {
  131. stroke: Color.fromCssColorString('#55F8F8'),
  132. strokeWidth: 3,
  133. clampToGround: false,
  134. fill: Color.fromCssColorString('rgba(0,0,0,0)')
  135. }).then((res) => {
  136. const entities = res.entities.values
  137. // console.log(55555, entities)
  138. // for (let k = 0; k < entities.length; k++) {
  139. // const el = entities[k]
  140. // const polygon = el.polygon as Any
  141. // const polyline = el.polyline as Any
  142. // const billboard = el.billboard as Any
  143. // if (polygon || polyline || el.billboard) {
  144. // if (polygon) polygon.distanceDisplayCondition = new DistanceDisplayCondition(displayMin, displayMax)
  145. // if (polyline) polyline.distanceDisplayCondition = new DistanceDisplayCondition(displayMin, displayMax)
  146. // if (billboard) billboard.distanceDisplayCondition = new DistanceDisplayCondition(displayMin, displayMax)
  147. // }
  148. // }
  149. if (viewer) {
  150. viewer.dataSources.add(res)
  151. viewer.scene.requestRender()
  152. }
  153. })
  154. }
  155. /**
  156. * 移动
  157. * @param lng
  158. * @param lat
  159. * @param profundity 高度
  160. */
  161. function flyTo(lng: number, lat: number, profundity = 19633) {
  162. if (!viewer) return
  163. viewer.camera.flyTo({
  164. destination: Cartesian3.fromDegrees(lng, lat * 0.997, profundity),
  165. orientation: {
  166. heading: CMath.toRadians(0.0),
  167. pitch: CMath.toRadians(-60.0),
  168. roll: CMath.toRadians(0.0)
  169. }
  170. })
  171. }
  172. /**
  173. * 屏幕坐标转经纬度
  174. * @param position xy
  175. */
  176. function XYToLngLat(position: { x: number, y: number }) {
  177. // 二维屏幕坐标转为三维笛卡尔空间直角坐标(世界坐标)
  178. const cartesian3 = viewer.scene.globe.pick(viewer.camera.getPickRay(position), viewer.scene)
  179. // 第一步:笛卡尔空间直角坐标系转为地理坐标(弧度制)
  180. const cartographic = Cartographic.fromCartesian(cartesian3)
  181. // 第二步: 地理坐标(弧度制) 转为经纬度坐标
  182. const lat = CMath.toDegrees(cartographic.latitude)
  183. const lng = CMath.toDegrees(cartographic.longitude)
  184. return { lng, lat }
  185. }
  186. // 初始化移动添加标点
  187. function initMove() {
  188. // 添加标点
  189. const dom = document.querySelector('.maps') as HTMLElement
  190. dom.onmousedown = (e: Any) => {
  191. const item = e.target.parentElement.dataset.type
  192. if (item) {
  193. const isNoPro = props.checkList.find((el) => el.lastData === 0 || !el.lastData)
  194. if (isNoPro || !props.checkList.length || modelShow.value || props.checkList.length > 1) {
  195. let txt = ''
  196. if (isNoPro) txt = '请选择项目'
  197. if (!props.checkList.length) txt = '请先选择项目'
  198. if (modelShow.value) txt = '请完成你的操作再来!'
  199. if (props.checkList.length > 1) txt = '请不要勾选多个项目'
  200. notification.warning({
  201. content: txt,
  202. duration: 3000
  203. })
  204. return
  205. }
  206. let ismove = false
  207. modelShow.value = false
  208. dom.onmousemove = (em) => {
  209. // 移动样式
  210. if (!ismove) ismove = true
  211. if (currenIcon.value.icon !== item) currenIcon.value.icon = item
  212. currenIcon.value.x = em.x - 15 + window.scrollX
  213. currenIcon.value.y = em.y - 100 + window.scrollY
  214. }
  215. dom.onmouseup = (eu) => {
  216. currenIcon.value = { icon: '', x: 0, y: 0 }
  217. const { lng, lat } = XYToLngLat({ x: eu.x + window.scrollX, y: eu.y - 74 + window.scrollY })
  218. const items = icontypes.find((el) => el.type === item)
  219. if (ismove) {
  220. addLable(lng, lat, items?.icon!, items?.name!, 1, 16219740)
  221. model.value = {
  222. deviceId: '',
  223. itemId: +props.checkList[0].id,
  224. longitude: lng.toString(),
  225. latitude: lat.toString(),
  226. describeData: '',
  227. selfId: '',
  228. deviceType: icontypes.filter((el) => el.type === item)[0].deviceType
  229. }
  230. modelShow.value = true
  231. isAdd.value = true
  232. }
  233. // flyTo(lng, lat)
  234. viewer.scene.requestRender()
  235. console.log('经纬度', lng, lat, props.checkList, model.value)
  236. dom.onmousemove = null
  237. dom.onmouseup = null
  238. ismove = false
  239. }
  240. }
  241. }
  242. }
  243. // 初始化
  244. async function initMap(callback:()=>void) {
  245. // store.setLoading(true)
  246. // init
  247. viewer = new Viewer('cesiumContainer', {
  248. baseLayerPicker: false,
  249. geocoder: false,
  250. infoBox: false,
  251. homeButton: false,
  252. sceneModePicker: false,
  253. navigationHelpButton: false,
  254. animation: false,
  255. creditContainer: 'cesiumContainer',
  256. timeline: false,
  257. fullscreenButton: false,
  258. vrButton: false,
  259. requestRenderMode: true,
  260. scene3DOnly: true,
  261. selectionIndicator: false,
  262. navigationInstructionsInitiallyVisible: false
  263. // sceneMode: SceneMode.COLUMBUS_VIEW
  264. })
  265. // 增加地形属性
  266. viewer.scene.globe.depthTestAgainstTerrain = true
  267. // 缩放范围
  268. viewer.scene.screenSpaceCameraController.minimumZoomDistance = 100
  269. viewer.scene.screenSpaceCameraController.maximumZoomDistance = 7546388
  270. // viewer.imageryLayers.removeAll(true)
  271. // viewer.imageryLayers.get(0).show = false// 不显示底图
  272. // viewer.scene.globe.baseColor = Color.fromCssColorString('#07101a') // 设置地球颜色07101a
  273. viewer.imageryLayers.addImageryProvider(new UrlTemplateImageryProvider({
  274. url: 'https://webst02.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scale=1&style=8'
  275. }))
  276. // loadGeoJson('are.json', 1, 7546388)
  277. // loadGeoJson('road.json', 1, 7546388)
  278. // 加载图层
  279. // viewer.imageryLayers.addImageryProvider(new UrlTemplateImageryProvider({
  280. // url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
  281. // subdomains: [ '0', '1', '2', '3' ],
  282. // tilingScheme: new WebMercatorTilingScheme(),
  283. // maximumLevel: 14
  284. // }))
  285. // 加载地形
  286. // viewer.terrainProvider = await (CesiumTerrainProvider as any).fromUrl('https://www.supermapol.com/realspace/services/3D-stk_terrain/rest/realspace/datas/info/data/path', {
  287. // requestMetadata: true,
  288. // requestVertexNormals: true,
  289. // requestWaterMask: true
  290. // })
  291. viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_DOUBLE_CLICK) // 取消原双击事件
  292. viewer.cesiumWidget.screenSpaceEventHandler.removeInputAction(ScreenSpaceEventType.LEFT_CLICK) // 取消原单击事件
  293. // viewer.camera.changed.addEventListener(() => {
  294. // // 打印中心点坐标、高度
  295. // const { height } = viewer.scene.globe.ellipsoid.cartesianToCartographic(viewer.camera.position)
  296. // console.log(999999, height)
  297. // })
  298. // 重新注册点击事件
  299. handler = new ScreenSpaceEventHandler(viewer.scene.canvas)
  300. // 右键还原
  301. handler.setInputAction(() => {
  302. viewer.camera.flyTo({
  303. destination: Cartesian3.fromDegrees(101.84599, 30.04260, 7546388),
  304. orientation: {
  305. heading: CMath.toRadians(0.0),
  306. pitch: CMath.toRadians(-85.0),
  307. roll: CMath.toRadians(0.0)
  308. }
  309. })
  310. }, ScreenSpaceEventType.RIGHT_CLICK)
  311. // 点击左键
  312. handler.setInputAction((movement: Any) => {
  313. const pick = viewer.scene.pick(movement.position)
  314. if (!isAdmin.value || !pick) return
  315. if (isAdd.value) {
  316. notification.warning({
  317. content: '请完成你的操作再来!',
  318. duration: 3000
  319. })
  320. return
  321. }
  322. console.log('点击详情', pick.id)
  323. isAdd.value = false
  324. const { oldData } = pick.id
  325. model.value = { ...oldData, selfId: pick.id.id }
  326. modelShow.value = true
  327. }, ScreenSpaceEventType.LEFT_CLICK)
  328. // 监听加载结束
  329. Helper.add(viewer.scene.globe.tileLoadProgressEvent, (e: any) => {
  330. if (e === 0) {
  331. viewer.camera.flyTo({
  332. destination: Cartesian3.fromDegrees(101.84599, 30.04260, 7546388),
  333. orientation: {
  334. heading: CMath.toRadians(0.0),
  335. pitch: CMath.toRadians(-85.0),
  336. roll: CMath.toRadians(0.0)
  337. }
  338. })
  339. // store.setLoading(false)
  340. Helper.removeAll()
  341. // 初始化添加标点
  342. callback()
  343. }
  344. })
  345. }
  346. function close() {
  347. if (isAdd.value) {
  348. const { values } = viewer.entities
  349. if (values.length) viewer.entities.removeById(values[values.length - 1].id)
  350. viewer.scene.requestRender()
  351. isAdd.value = false
  352. }
  353. modelShow.value = false
  354. }
  355. async function submit() {
  356. await formRef.value.validate()
  357. // 提交数据
  358. const msg = isAdd.value ? await equipmentService.addDevice({ ...model.value }) : await equipmentService.upDevice({ ...model.value })
  359. notification.success({
  360. content: msg,
  361. duration: 3000
  362. })
  363. // 属性赋值
  364. const { values } = viewer.entities
  365. values[values.length - 1].oldData = { ...model.value, selfId: values[values.length - 1].id }
  366. modelShow.value = false
  367. isAdd.value = false
  368. }
  369. async function del() {
  370. dialog.info({
  371. title: '你确定要删除当前设备?',
  372. positiveText: '确定',
  373. onPositiveClick: async () => {
  374. const msg = await equipmentService.delDevice({ ...model.value })
  375. notification.success({
  376. content: msg,
  377. duration: 3000
  378. })
  379. viewer.entities.removeById(model.value.selfId)
  380. viewer.scene.requestRender()
  381. modelShow.value = false
  382. }
  383. })
  384. }
  385. /** 递归 */
  386. function recursion(arr: Array<TreeList>, newData: Array<TreeList>) {
  387. for (let k = 0; k < arr.length; k++) {
  388. const el = arr[k]
  389. if (el.lastData === 1 || el.lastData === 0) {
  390. newData.push({
  391. name: el.name,
  392. id: el.id,
  393. lastData: el.lastData,
  394. itemLongitude: el.itemLongitude,
  395. itemLatitude: el.itemLatitude,
  396. deviceDataList: el.deviceDataList
  397. })
  398. }
  399. if (el.children && el.children.length) recursion(el.children, newData)
  400. }
  401. }
  402. /** input 正则 */
  403. function onlyAllowNumber(value:string, type:boolean) {
  404. if (type) {
  405. return +(value) >= 0 && +(value) <= 180
  406. }
  407. return +(value) >= 0 && +(value) <= 90
  408. }
  409. // 选中时
  410. watch(() => props.checkList, (v) => {
  411. if (!isAdmin.value) return
  412. if (!v.length) {
  413. viewer.entities.removeAll()
  414. } else {
  415. equipmentService.getDevice(v[0]?.id as number).then((res) => {
  416. viewer.entities.removeAll()
  417. for (let k = 0; k < res.length; k++) {
  418. const el = res[k]
  419. if (el.del === 1) {
  420. const items = icontypes.find((es) => es.deviceType === el.deviceType)
  421. addLable(+(el.longitude!), +(el.latitude!), items!.icon, items!.name, 1, 16219740, el)
  422. }
  423. }
  424. viewer.scene.requestRender()
  425. })
  426. }
  427. viewer.scene.requestRender()
  428. close()
  429. })
  430. // 树列表
  431. watch(() => props.treeList, (v) => {
  432. options.value = v
  433. viewer.entities.removeAll()
  434. if (isAdmin.value || !v.length) return
  435. const newData = [] as TreeList[]
  436. recursion(v, newData)
  437. for (let k = 0; k < newData.length; k++) {
  438. const el = newData[k]
  439. if (el.lastData === 1) {
  440. // 项目
  441. if (el.itemLongitude) {
  442. const lngLat = el.itemLongitude?.split(',')
  443. addLable(+(lngLat![0]), +(lngLat![1]), lnglatIcon, el.nameReferred || el.name, 1038633, 4938633)
  444. }
  445. if (el.deviceDataList && el.deviceDataList.length) {
  446. // 设备
  447. for (let j = 0; j < el.deviceDataList.length; j++) {
  448. const es = el.deviceDataList[j]
  449. if (es.del === 1) {
  450. const items = icontypes.find((ed) => ed.deviceType === es.deviceType)
  451. addLable(+(es.longitude!), +(es.latitude!), items!.icon, items!.name, 1, 938633)
  452. }
  453. }
  454. }
  455. } else {
  456. // 公司
  457. }
  458. }
  459. viewer.scene.requestRender()
  460. })
  461. // 修改经纬度=>同步图标
  462. watch(() => [ model.value.longitude, model.value.latitude ], (v) => {
  463. if (isAdd.value) return
  464. const ens = viewer.entities.getById(model.value.selfId)
  465. ens.position = Cartesian3.fromDegrees(+v[0], +v[1])
  466. setTimeout(() => {
  467. viewer.scene.requestRender()
  468. }, 1500)
  469. })
  470. onMounted(() => {
  471. initMap(() => {
  472. if (isAdmin.value) initMove()
  473. // loadGeoJson('geo.json')
  474. // loadGeoJson('1.geojson')
  475. // loadGeoJson('2.geojson')
  476. // loadGeoJson('3.geojson')
  477. // loadGeoJson('4.geojson')
  478. // loadGeoJson('5.geojson')
  479. // loadGeoJson('6.geojson')
  480. // loadGeoJson('gm.geojson')
  481. // loadGeoJson('tmy.geojson')
  482. // loadGeoJson('dd.geojson')
  483. })
  484. })
  485. onUnmounted(() => {
  486. // store.setLoading(false)
  487. if (viewer) {
  488. if (viewer.entities) viewer.entities.removeAll()
  489. viewer.destroy()
  490. }
  491. viewer = null
  492. Helper = null
  493. handler = null
  494. const dom = (document.querySelector('.maps') as HTMLElement)
  495. if (dom && dom.onmousedown) dom.onmousedown = null
  496. })
  497. </script>
  498. <template>
  499. <div class="maps">
  500. <div id="cesiumContainer" />
  501. <template v-if="isAdmin">
  502. <div class="topbar">
  503. <template
  504. v-for="(item, index) in icontypes"
  505. :key="index"
  506. >
  507. <div>
  508. <Icon
  509. :name="item.type"
  510. :size="50"
  511. :data-type="item.type"
  512. />
  513. <p>{{ item.name }}</p>
  514. </div>
  515. </template>
  516. </div>
  517. <Icon
  518. :name="currenIcon.icon"
  519. :size="25"
  520. class="moveIcon"
  521. :style="`left:${currenIcon.x};top:${currenIcon.y}`"
  522. />
  523. <div
  524. v-if="modelShow"
  525. class="details"
  526. >
  527. <n-card
  528. style="width: 400px"
  529. :bordered="false"
  530. size="huge"
  531. role="dialog"
  532. aria-modal="true"
  533. class="n-modal"
  534. >
  535. <n-form
  536. ref="formRef"
  537. :model="model"
  538. :rules="rules"
  539. >
  540. <n-form-item
  541. path="deviceId"
  542. label="设备ID"
  543. >
  544. <n-input
  545. v-model:value="model.deviceId"
  546. :readonly="!isAdd"
  547. />
  548. </n-form-item>
  549. <n-form-item
  550. path="itemId"
  551. label="所属项目"
  552. >
  553. <n-tree-select
  554. v-model:value="model.itemId"
  555. :options="options"
  556. :render-label="(info:any)=>info.option.nameReferred || info.option.name"
  557. label-field="name"
  558. key-field="id"
  559. check-strategy="child"
  560. clearable
  561. filterable
  562. />
  563. </n-form-item>
  564. <n-form-item
  565. path="longitude"
  566. label="经度"
  567. >
  568. <n-input
  569. v-model:value="model.longitude"
  570. :allow-input="(e: string) => onlyAllowNumber(e, true)"
  571. />
  572. </n-form-item>
  573. <n-form-item
  574. path="latitude"
  575. label="纬度"
  576. >
  577. <n-input
  578. v-model:value="model.latitude"
  579. :allow-input="(e: string) => onlyAllowNumber(e, false)"
  580. />
  581. </n-form-item>
  582. <n-form-item
  583. path="describeData"
  584. label="安装位置描述"
  585. >
  586. <n-input
  587. v-model:value="model.describeData"
  588. type="textarea"
  589. size="small"
  590. :autosize="{
  591. minRows: 3,
  592. maxRows: 5
  593. }"
  594. :maxlength="150"
  595. />
  596. </n-form-item>
  597. </n-form>
  598. <template #footer>
  599. <n-button @click="close">
  600. 取消
  601. </n-button>
  602. <n-button
  603. v-if="!isAdd"
  604. type="error"
  605. @click="del"
  606. >
  607. 删除
  608. </n-button>
  609. <n-button
  610. type="primary"
  611. @click="submit"
  612. >
  613. 确定
  614. </n-button>
  615. </template>
  616. </n-card>
  617. </div>
  618. </template>
  619. </div>
  620. </template>
  621. <style scoped lang="scss">
  622. .maps {
  623. width: 100%;
  624. height: 100%;
  625. position: absolute;
  626. left: 0;
  627. top: 0;
  628. #cesiumContainer {
  629. width: 100%;
  630. height: 100%;
  631. }
  632. .topbar {
  633. position: absolute;
  634. top: 10px;
  635. left: 50%;
  636. width: 1080px;
  637. height: 104px;
  638. transform: translate(-50%, 0);
  639. background: linear-gradient(229deg, rgba(26, 65, 78, 0.85) 0%, rgba(3, 20, 26, 0.95) 100%);
  640. border: 1px solid rgba(105, 206, 206, 0.8);
  641. display: flex;
  642. align-items: center;
  643. div {
  644. display: flex;
  645. flex-flow: column;
  646. align-items: center;
  647. width: 65px;
  648. user-select: none;
  649. cursor: pointer;
  650. font-size: 14px;
  651. &:first-child {
  652. margin-left: 10px;
  653. }
  654. p {
  655. margin-top: 5px;
  656. text-align: center;
  657. }
  658. }
  659. }
  660. .moveIcon {
  661. position: absolute;
  662. top: 0;
  663. left: 0;
  664. }
  665. .details {
  666. position: absolute;
  667. right: 10px;
  668. top: 10px;
  669. min-height: 400px;
  670. }
  671. }
  672. </style>