index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718
  1. <template>
  2. <div class="mapBox">
  3. <div
  4. id="map"
  5. @dragover="e => e.preventDefault()"
  6. @dragleave="isCheck = false"
  7. @dragenter="isCheck = true"
  8. />
  9. <div
  10. v-if="isEdit"
  11. class="mapBox-menu"
  12. >
  13. <n-tree-select
  14. v-model:value="selected"
  15. :options="options"
  16. label-field="name"
  17. key-field="uuid"
  18. children-field="childList"
  19. check-strategy="child"
  20. placeholder="请选择车站"
  21. @update:value="changeMap"
  22. />
  23. <div class="mapBox-menu-content">
  24. <n-scrollbar>
  25. <template
  26. v-for="(item, index) in iconList"
  27. :key="index"
  28. >
  29. <div
  30. class="mapBox-menu-content-item"
  31. draggable="true"
  32. @dragstart="currentItem = item"
  33. @dragend="itemDragEnd"
  34. >
  35. <img
  36. :src="item.icon"
  37. alt=""
  38. srcset=""
  39. draggable="false"
  40. >
  41. <p>
  42. {{ item.name }}
  43. </p>
  44. </div>
  45. </template>
  46. </n-scrollbar>
  47. </div>
  48. </div>
  49. <div
  50. v-if="isEdit && showPoup"
  51. class="mapBox-poup"
  52. >
  53. <n-form
  54. ref="formRef"
  55. :model="formMode"
  56. label-placement="left"
  57. label-width="auto"
  58. size="small"
  59. :rules="rules"
  60. >
  61. <n-form-item
  62. label="元素编号"
  63. path="uuid"
  64. >
  65. <n-input
  66. v-model:value="formMode.uuid"
  67. disabled
  68. />
  69. </n-form-item>
  70. <n-form-item
  71. label="资产编号"
  72. path="guid"
  73. >
  74. <n-select
  75. v-model:value="formMode.guid"
  76. placeholder="请选择"
  77. label-field="name"
  78. value-field="guid"
  79. :options="assetOption"
  80. @update:value="selectChange"
  81. />
  82. </n-form-item>
  83. <n-form-item
  84. label="元素类型"
  85. path="type"
  86. >
  87. <n-input
  88. v-model:value="formMode.type"
  89. disabled
  90. />
  91. </n-form-item>
  92. <n-form-item
  93. label="x 坐 标 "
  94. path="x"
  95. >
  96. <n-input-number
  97. v-model:value="formMode.x"
  98. clearable
  99. :show-button="false"
  100. @blur="resetMarker('lng', formMode.x)"
  101. />
  102. </n-form-item>
  103. <n-form-item
  104. label="y 坐 标 "
  105. path="y"
  106. >
  107. <n-input-number
  108. v-model:value="formMode.y"
  109. clearable
  110. :show-button="false"
  111. @blur="resetMarker('lat', formMode.y)"
  112. />
  113. </n-form-item>
  114. <n-form-item
  115. label="横向缩放"
  116. path="scaleX"
  117. >
  118. <n-input-number
  119. v-model:value="formMode.scaleX"
  120. clearable
  121. :min="0.1"
  122. :show-button="false"
  123. @blur="resetMarker('scaleX', formMode.scaleX, '0')"
  124. />
  125. </n-form-item>
  126. <n-form-item
  127. label="纵向缩放"
  128. path="scaleY"
  129. >
  130. <n-input-number
  131. v-model:value="formMode.scaleY"
  132. clearable
  133. :min="0.1"
  134. :show-button="false"
  135. @blur="resetMarker('scaleY', formMode.scaleY, '1')"
  136. />
  137. </n-form-item>
  138. <n-form-item
  139. label="水平翻转"
  140. path="flipX"
  141. >
  142. <n-switch
  143. v-model:value="formMode.flipX"
  144. @update:value="resetMarker('flipX', formMode.flipX)"
  145. />
  146. </n-form-item>
  147. <n-form-item
  148. label="垂直翻转"
  149. path="flipY"
  150. >
  151. <n-switch
  152. v-model:value="formMode.flipY"
  153. @update:value="resetMarker('flipY', formMode.flipY)"
  154. />
  155. </n-form-item>
  156. </n-form>
  157. <div class="mapBox-poup-btns">
  158. <n-button
  159. type="error"
  160. ghost
  161. @click="del"
  162. >
  163. 删除
  164. </n-button>
  165. <n-button
  166. type="primary"
  167. @click="save"
  168. >
  169. 保存
  170. </n-button>
  171. </div>
  172. </div>
  173. </div>
  174. </template>
  175. <script setup lang='ts'>
  176. import * as L from 'leaflet'
  177. import {
  178. onMounted,
  179. onUnmounted,
  180. Ref, ref, watch
  181. } from 'vue'
  182. import '@/../node_modules/leaflet/dist/leaflet.css'
  183. import { LatLngExpression } from 'leaflet';
  184. // ----- 修复L 增加tooltip 缩放报错 重要!!
  185. (L.Tooltip as any).prototype._animateZoom = function (e: { zoom: any; center: any; }) {
  186. if (!(this as any)._map) {
  187. return
  188. }
  189. const pos = (this as any)._map._latLngToNewLayerPoint((this as any)._latlng, e.zoom, e.center) as any
  190. (this as any)._setPosition(pos)
  191. };
  192. (L.Tooltip as any).prototype._updatePosition = function () {
  193. if (!(this as any)._map) {
  194. return
  195. }
  196. const pos = (this as any)._map.latLngToLayerPoint((this as any)._latlng) as any
  197. (this as any)._setPosition(pos)
  198. }
  199. // ---------
  200. export interface Item {
  201. icon: string,
  202. x?: number,
  203. y?: number,
  204. scaleX?: number,
  205. scaleY?: number,
  206. guid?: string,
  207. type?: string,
  208. uuid?: string,
  209. item?: string,
  210. isAdd?: boolean,
  211. deviceId?: string,
  212. itemName?: string
  213. }
  214. export interface BaseMap {
  215. width: number,
  216. height: number,
  217. mapId: string,
  218. name?: string
  219. imgUrl: string,
  220. trackType?: number, // 1电扶梯 2给排水 3通风 4照明 5车站环境检测 6车站暖通 7车站动环
  221. showTip?: boolean,
  222. uuid?: string
  223. }
  224. const props = withDefaults(defineProps<{
  225. isEdit?: boolean, // 是否编辑
  226. baseMap?: BaseMap
  227. }>(), {
  228. isEdit: true,
  229. baseMap: () => ({
  230. imgUrl: '/system/track/djy.png', width: 1400, height: 430, mapId: 'd33d83d7-02cb-2ce3-37e1-e563320b93dd', showTip: false
  231. })
  232. })
  233. const emit = defineEmits<{(evt: 'markerClick', value: Item): void }>()
  234. const Map = ref()
  235. const iconList = ref([
  236. { name: '电扶梯', icon: 'https://gsm.jdjinsui.com/gsmImage/system/track/电扶梯正常.png' }
  237. ] as { icon: string, name: string }[])
  238. const currentItem: Ref<Item | undefined> = ref()
  239. const isCheck = ref(false)
  240. const options = ref([
  241. {
  242. imgUrl: '/system/track/djy.png', width: 1400, height: 430, mapId: 'd33d83d7-02cb-2ce3-37e1-e563320b93dd', name: 'test', uuid: 'e5cdf830-587a-11ef-a53a-f3fe72299e7d'
  243. }
  244. ] as BaseMap[])
  245. const selected = ref('')
  246. const assetOption = ref([])
  247. const formRef = ref()
  248. const formMode = ref({
  249. uuid: '',
  250. guid: '',
  251. type: '',
  252. x: 0,
  253. y: 0,
  254. scaleX: 1,
  255. scaleY: 1,
  256. flipX: false,
  257. flipY: false,
  258. icon: '',
  259. tip: '',
  260. isAdd: false
  261. })
  262. const rules = { guid: { required: true, message: '请选择资产' } }
  263. const showPoup = ref(false)
  264. const currentMap: Ref<BaseMap | undefined> = ref()
  265. const currentMarker = ref()
  266. const allMarker = ref([] as L.Marker[])
  267. // 资产下拉
  268. function selectChange(_: string, option: Item) {
  269. formMode.value.type = option.type || ''
  270. }
  271. // 重置marker样式
  272. function resetMarker(type: string, num: number | boolean, id?: string) {
  273. if (type === 'lat' || type === 'lng') {
  274. const obj = currentMarker.value.getLatLng()
  275. obj[type] = num
  276. currentMarker.value.setLatLng(obj)
  277. console.log('位置更新', obj)
  278. } else {
  279. const newIcon = currentMarker.value.getIcon()
  280. if (type === 'scaleX' || type === 'scaleY') {
  281. newIcon.options.iconSize[id!] = newIcon.options.iconSize[id!] * (num as number)
  282. } else {
  283. const newHtml = newIcon.options.html.replace(/style="[^=>]*"/g, (s: string) => {
  284. const a = s.split(' ')
  285. const c = type === 'flipY' ? 'rotateX' : 'rotateY'
  286. const n = type === 'flipY' ? 1 : 2
  287. a[n] = num ? `${c}(180deg)${n === 2 ? ';"' : ''}` : `${c}(0deg)${n === 2 ? ';"' : ''}`
  288. const b = a.join(' ')
  289. return b
  290. })
  291. newIcon.options.html = newHtml
  292. }
  293. currentMarker.value.setIcon(newIcon)
  294. }
  295. }
  296. // marker选中状态
  297. function selectMaker(marker: L.Marker) {
  298. for (let k = 0; k < allMarker.value.length; k++) {
  299. const el = allMarker.value[k]
  300. const dom = el.getElement()
  301. if ((el as Any)._leaflet_id === (marker as Any)._leaflet_id) {
  302. dom!.style.boxShadow = '0 0 4px 2px #ff891a'
  303. } else {
  304. dom!.style.boxShadow = 'none'
  305. }
  306. }
  307. currentMarker.value = marker
  308. }
  309. // 查询设备列表+设置poup
  310. function setPoupData(marker: L.Marker, option: Item) {
  311. // 查询设备列表
  312. // socketService.send('item.query', { itemtype: option.deviceId, isPaging: 0, region: selected.value }).then((res) => {
  313. // const { success, data: { list } } = res
  314. // if (success && list) {
  315. // assetOption.value = list
  316. // }
  317. // })
  318. formMode.value.uuid = option.uuid || ''
  319. formMode.value.guid = option.guid || option.item || ''
  320. formMode.value.type = option.type || ''
  321. formMode.value.x = option.x || 0
  322. formMode.value.y = option.y || 0
  323. formMode.value.isAdd = option.isAdd || false
  324. // 移除其它选中状态
  325. selectMaker(marker)
  326. showPoup.value = true
  327. }
  328. // 添加marker
  329. function addMarker(option: Item) {
  330. if (!Map.value) throw 'Map 未初始化'
  331. if (!option.icon) return console.log('缺少参数')
  332. // 自定义图标
  333. const img = `<img src="${option.icon}" style="width:100%;height:100%;transform: rotateX(0deg) rotateY(0deg);"/>`
  334. const ICON = L.divIcon({
  335. iconSize: [ (option.scaleX || 1) * 30, (option.scaleY || 1) * 30 ],
  336. html: img
  337. })
  338. const marker = L.marker([ option.y || 0, option.x || 0 ], {
  339. icon: ICON,
  340. draggable: props.isEdit, // 是否可通过鼠标/触摸拖动。
  341. riseOnHover: true
  342. }).addTo(Map.value)
  343. // add
  344. allMarker.value.push(marker)
  345. // 显示tip
  346. if (props.baseMap.showTip && option.itemName) marker.bindTooltip(option.itemName, { direction: 'top', className: 'resetTips' })
  347. // 显示poup
  348. if (isCheck.value) {
  349. console.log('添加marker', option)
  350. setPoupData(marker, option)
  351. }
  352. // 事件
  353. marker.addEventListener('click', () => {
  354. console.log('marker点击', option)
  355. setPoupData(marker, option)
  356. emit('markerClick', option)
  357. })
  358. marker.addEventListener('dragend', (e) => {
  359. console.log('marker拖动', option)
  360. const { lat, lng } = e.target.getLatLng()
  361. option.x = lng
  362. option.y = lat
  363. setPoupData(marker, option)
  364. })
  365. }
  366. // 拖拽=>添加marker
  367. function itemDragEnd(e: DragEvent) {
  368. e.preventDefault()
  369. const point = Map.value.containerPointToLayerPoint([ e.layerX, e.layerY ]) // 给定相对于origin pixel的相应像素坐标
  370. const lnglat = Map.value.layerPointToLatLng(point) // 给定地理坐标,转换为相对于origin pixel的相应像素坐标
  371. if (isCheck.value) {
  372. addMarker({
  373. ...currentItem.value, x: lnglat.lng, y: lnglat.lat, isAdd: true
  374. } as Item)
  375. }
  376. isCheck.value = false
  377. }
  378. // 查询marker+绑定
  379. async function bindMarker(mapId: string) {
  380. if (!mapId) return
  381. // 清除
  382. if (allMarker.value.length) {
  383. for (let k = 0; k < allMarker.value.length; k++) {
  384. const marker = allMarker.value[k]
  385. marker.remove()
  386. }
  387. allMarker.value = []
  388. }
  389. // 添加所有
  390. // let page = 1
  391. // let pages = 0
  392. // do {
  393. // const obj = {
  394. // map: mapId,
  395. // pageSize: 100,
  396. // isPaging: 0,
  397. // page: page++
  398. // } as { trackType?: number }
  399. // if (props.baseMap.trackType) obj.trackType = props.baseMap.trackType
  400. // const { data } = await socketService.send('monitor.itemQuery', obj)
  401. // store.setObtainedDevices(data.list)
  402. // console.log('底图关联的marker', data.list)
  403. // if (data.list) {
  404. // for (let k = 0; k < data.list.length; k++) {
  405. // const el = data.list[k]
  406. // const obj = {
  407. // parent: el,
  408. // ...el.mapElement,
  409. // icon: import.meta.env.VITE_IMG_URL + el.icon,
  410. // showTip: props.baseMap.showTip
  411. // }
  412. // addMarker(obj)
  413. // }
  414. // }
  415. // pages = data.pages
  416. // } while (page <= pages)
  417. }
  418. // 自定义marker
  419. function drawMarker(item: { latLng: number[], icon: string, children: any, label: string }) {
  420. if (!Map.value) return console.log('Map未初始化')
  421. if (!item.latLng || !item.latLng.length) return console.log('缺少参数')
  422. // 自定义图标
  423. const img = `<div class="newMark" style="background-image:url(${item.icon})">${item.label}</div>`
  424. const ICON = L.divIcon({
  425. iconSize: [ 100, 28 ],
  426. html: img,
  427. iconAnchor: [ -5, 14 ]
  428. })
  429. const marker = L.marker(item.latLng as LatLngExpression, {
  430. title: item.children[0],
  431. icon: ICON,
  432. draggable: false,
  433. riseOnHover: true // 该标记将位于其他标记的顶部
  434. })
  435. marker.addTo(Map.value)
  436. marker.addEventListener('click', (event) => {
  437. console.log('marker点击', event)
  438. // const { options: { title } } = event.target
  439. // const path = userData.getUserData().permission.find((el: { url: string }) => el.url === '/objHome')
  440. // if (path) {
  441. // // store.setCurrentTunnel({ ...title })
  442. // // store.setCurrentUuid(null)
  443. // localStorage.setItem('currentTunnel', JSON.stringify({ ...title }))
  444. // // router.push('/objHome')
  445. // } else {
  446. // window.$notification.warning({ title: '你没有权限,请联系管理员!', duration: 2000 })
  447. // }
  448. })
  449. return marker
  450. }
  451. // 切换底图
  452. async function changeMap(_: string, option: BaseMap) {
  453. console.log('切换底图', option)
  454. if (!Map.value) return
  455. // 清除layer
  456. Map.value.eachLayer((layer: L.Layer) => {
  457. Map.value.removeLayer(layer)
  458. })
  459. // 添加底图
  460. if (!option.imgUrl || !option.width || !option.height || !option.mapId) return window.$notification.error({ content: '底图缺少相关参数', duration: 3000, keepAliveOnHover: true })
  461. currentMap.value = option
  462. L.imageOverlay(`https://gsm.jdjinsui.com/gsmImage${option.imgUrl}`, [ [ 0, 0 ], [ option.height, option.width ] ]).addTo(Map.value)
  463. Map.value.setView([ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ], 0)
  464. // 获取底图绑定的maker
  465. bindMarker(option.mapId)
  466. }
  467. // 初始化底图
  468. function initMap(option: BaseMap) {
  469. if (!option) return
  470. Map.value = L.map('map', {
  471. zoom: 0,
  472. maxZoom: 1,
  473. minZoom: 0,
  474. zoomSnap: 0.1,
  475. center: [ option.height / 2, option.width / (props.isEdit ? 3.5 : 2) ],
  476. crs: L.CRS.Simple,
  477. attributionControl: false,
  478. zoomControl: false
  479. }).addEventListener('click', (e) => {
  480. console.log('map点击', e)
  481. if (currentMarker.value) currentMarker.value.getElement().style.boxShadow = 'none'
  482. formMode.value.flipX = false
  483. formMode.value.flipY = false
  484. formMode.value.scaleX = 1
  485. formMode.value.scaleY = 1
  486. formMode.value.uuid = ''
  487. showPoup.value = false
  488. })
  489. // 获取底图列表
  490. // socketService.send('region.map').then((res) => {
  491. // const { success, data } = res
  492. // if (success && data.length) {
  493. // options.value = data.map((el: { childList: Any[]; }) => {
  494. // if (el.childList) {
  495. // el.childList.forEach((es) => {
  496. // if (es.mapId === props.baseMap.mapId) selected.value = es.id
  497. // try {
  498. // const { width, height, imgUrl } = JSON.parse(es.mapConfig)
  499. // es.width = width
  500. // es.height = height
  501. // es.imgUrl = imgUrl
  502. // } catch (error) {
  503. // console.log('底图配置数据格式错误')
  504. // }
  505. // })
  506. // }
  507. // return el
  508. // })
  509. // }
  510. // })
  511. // 获取所有maker类型
  512. // socketService.send('itemType.search', { belongs: '3' }).then((res) => {
  513. // const { success, data } = res
  514. // if (success && data.length) iconList.value = data.map((el: { imageUrl: string; name: string; guid: string }) => ({ icon: import.meta.env.VITE_IMG_URL + el.imageUrl, name: el.name, deviceId: el.guid }))
  515. // })
  516. if (props.isEdit) { window.$notification.warning({ content: '请选择车站', duration: 3000, keepAliveOnHover: true }) } else {
  517. changeMap('', option)
  518. }
  519. }
  520. // 保存->更新marker
  521. async function save() {
  522. await formRef.value.validate()
  523. const mapElementList = [ {
  524. flipX: formMode.value.flipX,
  525. flipY: formMode.value.flipY,
  526. item: formMode.value.guid,
  527. map: currentMap.value!.mapId,
  528. scaleX: formMode.value.scaleX,
  529. scaleY: formMode.value.scaleY,
  530. type: formMode.value.type,
  531. uuid: formMode.value.uuid,
  532. visiable: true,
  533. x: formMode.value.x,
  534. y: formMode.value.y
  535. } ]
  536. console.log('保存', mapElementList, formMode.value.isAdd)
  537. // const url = formMode.value.isAdd ? 'mapElement.insert' : 'mapElement.update'
  538. // const { success } = await socketService.send(url, { mapElementList })
  539. // if (success) {
  540. // bindMarker(currentMap.value!.mapId)
  541. // window.$notification.success({ content: formMode.value.isAdd ? '添加成功' : '修改成功', duration: 2000 })
  542. // showPoup.value = false
  543. // }
  544. }
  545. // 删除当前
  546. async function del() {
  547. if (!currentMarker.value) return
  548. const fdid = allMarker.value.findIndex((el: Any) => el._leaflet_id === currentMarker.value._leaflet_id)
  549. if (fdid !== -1) {
  550. if (!formMode.value.isAdd) {
  551. // const { success } = await socketService.send('mapElement.delete', [ formMode.value.uuid ])
  552. // if (success) window.$notification.success({ content: '删除成功', duration: 2000 })
  553. }
  554. allMarker.value.splice(fdid, 1)
  555. currentMarker.value.remove()
  556. showPoup.value = false
  557. } else {
  558. throw '删除失败!'
  559. }
  560. }
  561. // 销毁
  562. function destory() {
  563. if (!Map.value) return
  564. console.log('销毁')
  565. Map.value.eachLayer((layer: L.Layer) => {
  566. Map.value.removeLayer(layer)
  567. })
  568. allMarker.value = []
  569. currentMarker.value = undefined
  570. currentMap.value = undefined
  571. Map.value = null
  572. }
  573. onMounted(() => {
  574. document.title = 'leaflet画图'
  575. initMap(props.baseMap)
  576. })
  577. watch(() => props.baseMap, (v) => {
  578. if (Map.value) {
  579. changeMap('', v)
  580. } else {
  581. initMap(v)
  582. }
  583. }, { deep: true })
  584. onUnmounted(() => destory())
  585. </script>
  586. <style lang="scss" scoped>
  587. .mapBox {
  588. width: 100vw;
  589. height: 100vh;
  590. background: none;
  591. position: relative;
  592. #map {
  593. width: 100%;
  594. height: 100%;
  595. background: #011719;
  596. :deep(.leaflet-div-icon) {
  597. background: none;
  598. border: none;
  599. border-radius: 6px;
  600. }
  601. :deep(.resetTips) {
  602. height: 33px;
  603. background: #06353A;
  604. border: 1px solid #34C0AE;
  605. color: white;
  606. &::before {
  607. border-top-color: #34C0AE;
  608. }
  609. p {
  610. margin: 0;
  611. font-size: 14px;
  612. }
  613. }
  614. }
  615. &-menu {
  616. position: absolute;
  617. left: 0;
  618. top: 0;
  619. z-index: 601;
  620. background: white;
  621. width: 380px;
  622. height: 100%;
  623. padding: 10px;
  624. &-content {
  625. width: 100%;
  626. height: calc(100% - 44px);
  627. :deep(.n-scrollbar-content) {
  628. display: flex;
  629. flex-wrap: wrap;
  630. align-content: flex-start;
  631. }
  632. &-item {
  633. text-align: center;
  634. width: 78px;
  635. height: 78px;
  636. padding: 10px 0;
  637. border: 1px solid #C9D3DD;
  638. cursor: grab;
  639. margin-top: 16px;
  640. &:not(:nth-child(4n)) {
  641. margin-right: 16px;
  642. }
  643. img {
  644. width: 50px;
  645. height: 40px;
  646. }
  647. p {
  648. margin: 0;
  649. color: #2c3e50;
  650. font-size: 12px;
  651. white-space: nowrap;
  652. overflow: hidden;
  653. text-overflow: ellipsis;
  654. }
  655. }
  656. }
  657. }
  658. &-poup {
  659. position: absolute;
  660. right: 0;
  661. top: 0;
  662. width: 334px;
  663. background: #FFFFFF;
  664. box-shadow: 0px 3px 6px 0px rgba(0, 0, 0, 0.1);
  665. backdrop-filter: blur(29.99999943901511px);
  666. z-index: 601;
  667. padding: 20px;
  668. &-btns {
  669. text-align: center;
  670. &>button:first-child {
  671. margin-right: 20px;
  672. }
  673. }
  674. }
  675. }
  676. </style>