index.vue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. <template>
  2. <div id="globalMap">
  3. <div class="global-map">
  4. <Map
  5. v-if="mapStore.isAMap"
  6. ref="mapRef"
  7. @handle-show-video="handleShowVideo"
  8. @handle-show-video-detail="handleShowVideo2"
  9. @handle-show-warehouse="handleShowWarehouse"
  10. @handle-show-people="handleShowPeople"
  11. @handle-show-track="handleShowTrack"
  12. @close-detail-dialog="handleCloseDetailDialog"
  13. @resize="mapMoveEnd"
  14. />
  15. <YztMap
  16. v-else-if="!!mapStore.activeMap"
  17. ref="map2Ref"
  18. @handle-show-video="handleShowVideo"
  19. @handle-show-video-detail="handleShowVideo2"
  20. @handle-show-warehouse="handleShowWarehouse"
  21. @handle-show-people="handleShowPeople"
  22. @handle-show-track="handleShowTrack"
  23. @close-detail-dialog="handleCloseDetailDialog"
  24. @resize="mapMoveEnd"
  25. />
  26. <!--左侧菜单-->
  27. <LeftMenu
  28. ref="leftMenuRef"
  29. style="position: absolute; top: 20px; left: 20px"
  30. @click-menu="clickMenu"
  31. @select-search-marker="selectSearchMarker"
  32. />
  33. <!--右侧功能模块-->
  34. <RightMenu ref="rightMenuRef" />
  35. <!--切换地图图层-->
  36. <SwitchMapTool class="tool-box" />
  37. <!--右下角地图工具 -->
  38. <RightTool />
  39. <!--时间轴-->
  40. <TimeAxis ref="timeAxisRef" />
  41. <!--绘制工具-->
  42. <DrawTools v-if="showDrawTools" @handle-analysis-data="handleAnalysisData" />
  43. <!--附近视频-->
  44. <NearbyVideos v-if="showNearbyVideos" v-model="showNearbyVideos" :location="location" />
  45. <!--格点雨量-->
  46. <GridPointRainfall v-if="showRainfall" v-model="showRainfall" :location="location" />
  47. <!--物资详情-->
  48. <MaterialDetail v-if="showWarehouse" v-model="showWarehouse" :warehouse-data="warehouseData" />
  49. <!--通讯保障-->
  50. <CommunicationSupport v-if="communicationSupport.show" @close="handleHideCommunicationSupport" />
  51. <!--应急人员详情-->
  52. <EmergencyCrew v-if="showPeople" :id="teamId" v-model="showPeople" />
  53. <!--视频详情-->
  54. <VideoDialog v-if="showVideoDetail" v-model="showVideoDetail" :videoMonitorData="videoDetail" />
  55. </div>
  56. </div>
  57. </template>
  58. <script lang="ts" setup name="globalMap">
  59. import Map from '@/components/Map/index.vue';
  60. import YztMap from '@/components/Map/YztMap/index.vue';
  61. import SwitchMapTool from './SwitchMapTool.vue';
  62. import LeftMenu from './LeftMenu.vue';
  63. import RightMenu from './RightMenu/index.vue';
  64. import MaterialDetail from './MaterialDetail.vue';
  65. import TimeAxis from '@/components/TimeAxis/index.vue';
  66. import DrawTools from './RightMenu/DrawTools.vue';
  67. import CommunicationSupport from './RightMenu/CommunicationSupport.vue';
  68. import GridPointRainfall from './RightMenu/GridPointRainfall.vue';
  69. import EmergencyCrew from './RightMenu/EmergencyCrew.vue';
  70. import useMapStore from '@/store/modules/map';
  71. import { getVehicleTrajectory } from '@/api/globalMap/KeyVehicles';
  72. import { getImageUrl, iconList } from './data/mapData';
  73. import { debounce, deepClone } from '@/utils';
  74. import { parseTime } from '@/utils/ruoyi';
  75. import { getPointInfo2 } from '@/api/videoMonitor';
  76. import { toLonLat } from 'ol/proj';
  77. import { getEventDetail } from '@/api/duty/eventing';
  78. import gcoord from 'gcoord';
  79. //dom元素
  80. const rightMenuRef = ref(null);
  81. const mapRef = ref(null);
  82. const map2Ref = ref(null);
  83. const leftMenuRef = ref(null);
  84. const timeAxisRef = ref(null);
  85. const route = useRoute();
  86. const eventId = ref('');
  87. // 地图
  88. let map: any = {};
  89. // 地图类
  90. let mapUtils: any = {};
  91. const mapStore = useMapStore();
  92. // 附近视频菜单数据
  93. let tempMenu = ref({
  94. name: '',
  95. checked: false
  96. });
  97. const communicationSupport = reactive({
  98. show: false,
  99. data: {}
  100. });
  101. let addMarkersTimer;
  102. // 添加打点
  103. const addMarkers = (item) => {
  104. const dom = mapStore.isAMap ? mapRef.value : map2Ref.value;
  105. if (dom) {
  106. if (!item.checked) {
  107. let index = mapStore.pointType.findIndex((item2) => item.component === item2.component);
  108. if (index > -1) {
  109. mapStore.pointType.splice(index, 1);
  110. }
  111. } else {
  112. // 右侧图层分析状态
  113. item.checked2 = true;
  114. mapStore.pointType.push(item);
  115. }
  116. mapStore.setPointOption();
  117. }
  118. };
  119. const adjustPoint = (data) => {
  120. data.forEach((item2) => {
  121. // 获取图标
  122. if (iconList[item2.dataType]) {
  123. item2.icon = iconList[item2.dataType].image;
  124. item2.image = iconList[item2.dataType].image;
  125. item2.imageHover = iconList[item2.dataType].imageHover;
  126. item2.size = iconList[item2.dataType].size;
  127. } else {
  128. item2.icon = iconList['common'].image;
  129. item2.image = iconList['common'].image;
  130. item2.imageHover = iconList['common'].imageHover;
  131. item2.size = iconList['common'].size;
  132. }
  133. if (item2.materia_name) {
  134. item2.name = item2.materia_name;
  135. }
  136. if (item2.dataType === 43) {
  137. item2.showName = true;
  138. }
  139. item2.lnglat = [item2.longitude, item2.latitude];
  140. });
  141. return data;
  142. };
  143. const addMarkersMethod = debounce(
  144. function () {
  145. if (!mapUtils || Object.keys(mapUtils).length === 0) return;
  146. const queryParams: PointParams = {
  147. zoom_level: mapStore.mapState.zoom,
  148. latitude_min: '',
  149. latitude_max: '',
  150. longitude_min: '',
  151. longitude_max: '',
  152. option: mapStore.pointParams.option,
  153. dict_value: mapStore.pointParams.dict_value.toString()
  154. };
  155. if (!queryParams.option && !queryParams.dict_value) {
  156. return mapUtils.clearMarker2();
  157. }
  158. if (mapStore.isAMap) {
  159. const AMap = mapUtils.getAMap();
  160. const pixel = new AMap.Pixel(0, 0);
  161. const size = mapRef.value.getMapDomSize();
  162. const pixel2 = new AMap.Pixel(size[0], size[1]);
  163. const lnglat = map.containerToLngLat(pixel);
  164. const lnglat2 = map.containerToLngLat(pixel2);
  165. queryParams.longitude_min = lnglat.lng;
  166. queryParams.latitude_max = lnglat.lat;
  167. queryParams.longitude_max = lnglat2.lng;
  168. queryParams.latitude_min = lnglat2.lat;
  169. } else {
  170. // 获取地图容器尺寸
  171. const size = map.getSize();
  172. // 获取左上角和右下角像素坐标
  173. const topLeftPixel = [0, 0];
  174. const bottomRightPixel = [size[0], size[1]];
  175. // 转换为地图投影坐标
  176. const topLeftMap = map.getCoordinateFromPixel(topLeftPixel);
  177. const bottomRightMap = map.getCoordinateFromPixel(bottomRightPixel);
  178. // 转换为经纬度
  179. const lnglat = toLonLat(topLeftMap, map.getView().getProjection());
  180. const lnglat2 = toLonLat(bottomRightMap, map.getView().getProjection());
  181. queryParams.longitude_min = lnglat[0];
  182. queryParams.latitude_max = lnglat[1];
  183. queryParams.longitude_max = lnglat2[0];
  184. queryParams.latitude_min = lnglat2[1];
  185. }
  186. getPointInfo2(queryParams).then((res) => {
  187. // 如果接口返回前,取消所有选择,不赋值
  188. if (!mapStore.pointParams.option && !mapStore.pointParams.dict_value.toString()) {
  189. return;
  190. }
  191. const data = res.data ? res.data : [];
  192. // 通用详情
  193. if (!!detailsData.value.id) {
  194. data.forEach((item) => {
  195. if (item.id === detailsData.value.id) {
  196. item.isHover = true;
  197. }
  198. });
  199. detailsData.value = {
  200. id: '',
  201. dataType: ''
  202. };
  203. } else if (showVideoDetail.value && videoDetail.value) {
  204. data.forEach((item) => {
  205. if (item.id === videoDetail.value.id) {
  206. item.isHover = true;
  207. }
  208. });
  209. }
  210. mapUtils.addMarker2(data);
  211. });
  212. if (addMarkersTimer) {
  213. clearInterval(addMarkersTimer);
  214. addMarkersTimer = null;
  215. }
  216. if (mapStore.pointParams.option.includes('43')) {
  217. addMarkersTimer = setInterval(addMarkersMethod, 60 * 1000);
  218. }
  219. },
  220. 300,
  221. false
  222. );
  223. // 跳转指定地点
  224. const toAddress = (item) => {
  225. const dom = mapStore.activeMap === 'imageMap' ? map2Ref.value : mapRef.value;
  226. dom.setCenter(item);
  227. };
  228. let showDrawTools = ref(false);
  229. // 点击菜单
  230. const clickMenu = (item, dataList) => {
  231. if (item.path === '1') {
  232. // 空间分析
  233. if (item.component === 'spatial') {
  234. showDrawTools.value = !showDrawTools.value;
  235. }
  236. rightMenuRef.value.updateMenu(item.checked ? '1' : '2', item);
  237. } else if (item.path === '2') {
  238. if (item.name === '附近视频') {
  239. // 附近视频
  240. tempMenu.value = item;
  241. //为地图注册click事件获取鼠标点击出的经纬度坐标
  242. map.on('click', handleClickMap);
  243. mapStore.setIsMapSelect(true);
  244. } else {
  245. let checked = item.checked ? '1' : '2';
  246. // 打点信息
  247. addMarkers(item);
  248. if (['易涝隐患点', '省政务无人机', '铁塔运行监测', '通讯保障', '救援队伍', '重点车辆'].includes(item.name) || item.isVideo) {
  249. rightMenuRef.value.updateMenu(checked, item);
  250. } else {
  251. let index = findChecked(dataList, item.name);
  252. if (item.checked && index > 0) {
  253. // 已有图层分析直接切换到该index
  254. rightMenuRef.value.showIndexMenu('图层分析');
  255. } else if (item.checked || (!item.checked && index === 0)) {
  256. rightMenuRef.value.updateMenu(checked, { name: '图层分析', meta: { icon: 'icon1' } });
  257. }
  258. }
  259. }
  260. } else if (item.path === '3') {
  261. // 通讯保障
  262. communicationSupport.show = !communicationSupport.show;
  263. communicationSupport.data = item;
  264. } else if (item.path === '4' && ['定点分析', '格点雨量'].includes(item.name)) {
  265. if (item.name === '定点分析' && !item.checked) {
  266. closeClickMap();
  267. rightMenuRef.value.updateMenu('2', item, []);
  268. } else {
  269. // 定点分析、格点雨量
  270. tempMenu.value = item;
  271. //为地图注册click事件获取鼠标点击出的经纬度坐标
  272. map.on('click', handleClickMap);
  273. mapStore.setIsMapSelect(true);
  274. }
  275. }
  276. // 菜单关闭,移除车辆
  277. if (['手机工作台', '重点车辆', '移动指挥车', '机动无人机', '卫星电话'].includes(item.name) && !item.checked) {
  278. timeAxisRef.value.clearData(item.name);
  279. }
  280. if (item.component == '43' && !item.checked && !mapStore.pointParams.option.includes('43') && addMarkersTimer) {
  281. clearInterval(addMarkersTimer);
  282. addMarkersTimer = null;
  283. }
  284. };
  285. const handleHideCommunicationSupport = () => {
  286. communicationSupport.show = false;
  287. leftMenuRef.value.setMenuChange(communicationSupport.data, false);
  288. };
  289. const findChecked = (dataList, name) => {
  290. let index = 0;
  291. dataList.forEach((item) => {
  292. if (item.name !== name && !item.isVideo && item.path === '2' && !!item.checked) {
  293. index++;
  294. }
  295. if (item.children && item.children.length > 0) {
  296. let res = findChecked(item.children, name);
  297. index += res;
  298. }
  299. });
  300. return index;
  301. };
  302. // 点击搜索结果,添加标注
  303. const selectSearchMarker = (item) => {
  304. const dom = mapStore.isAMap ? mapRef.value : map2Ref.value;
  305. let item2 = deepClone(item);
  306. // 获取图标
  307. if (iconList[item2.dataType]) {
  308. item2.icon = iconList[item2.dataType].imageHover;
  309. item2.image = iconList[item2.dataType].image;
  310. item2.imageHover = iconList[item2.dataType].imageHover;
  311. item2.size = iconList[item2.dataType].size;
  312. } else {
  313. item2.icon = iconList['common'].imageHover;
  314. item2.image = iconList['common'].image;
  315. item2.imageHover = iconList['common'].imageHover;
  316. item2.size = iconList['common'].size;
  317. }
  318. item2.lnglat = [item2.longitude, item2.latitude];
  319. dom.addSearchMarker(item2);
  320. };
  321. const handleAnalysisData = (data) => {
  322. rightMenuRef.value.handleMenu('空间分析', data);
  323. };
  324. const getMapUtils = () => {
  325. const domRef = mapStore.isAMap ? mapRef.value : map2Ref.value;
  326. return !!domRef ? domRef.getMapUtils() : {};
  327. };
  328. // 获取地图元素操作
  329. const getMap = () => {
  330. const domRef = mapStore.isAMap ? mapRef.value : map2Ref.value;
  331. return !!domRef ? domRef.getMap() : {};
  332. };
  333. let detailsData = ref({
  334. id: '',
  335. dataType: ''
  336. });
  337. // 显示打点详情
  338. const showDetail = (data, dataType) => {
  339. const domRef = mapStore.isAMap ? mapRef.value : map2Ref.value;
  340. if (!!domRef) {
  341. const newMap = mapStore.isAMap ? map : map.getView();
  342. newMap.setCenter([data.lng, data.lat]);
  343. newMap.setZoom(18);
  344. detailsData.value = {
  345. id: data.id.toString(),
  346. dataType: dataType
  347. };
  348. }
  349. };
  350. const getDrawTool = () => {
  351. return mapStore.isAMap ? mapRef.value.drawTool : mapUtils;
  352. };
  353. const trackPlayback = (data) => {
  354. const domRef = mapStore.isAMap ? mapRef.value : mapUtils;
  355. if (!!domRef) {
  356. domRef.trackPlayback(data);
  357. }
  358. };
  359. let showNearbyVideos = ref(false);
  360. let showRainfall = ref(false);
  361. let location = ref([]);
  362. watch(showNearbyVideos, () => {
  363. if (!!showNearbyVideos.value) {
  364. closeClickMap();
  365. } else {
  366. clearData();
  367. }
  368. });
  369. watch(showRainfall, () => {
  370. if (!!showRainfall.value) {
  371. closeClickMap();
  372. } else {
  373. clearData();
  374. }
  375. });
  376. const clearData = () => {
  377. location.value = [];
  378. if (!!tempMenu.value && !!tempMenu.value.name) {
  379. leftMenuRef.value.setMenuChange(tempMenu.value, false);
  380. tempMenu.value = {
  381. name: '',
  382. checked: false
  383. };
  384. }
  385. };
  386. const closeClickMap = () => {
  387. if (mapStore.isAMap) {
  388. map.off('click', handleClickMap);
  389. } else {
  390. map.un('click', handleClickMap);
  391. }
  392. mapStore.setIsMapSelect(false);
  393. };
  394. // 显示附近视频
  395. const handleShowVideo = (data) => {
  396. location.value = [data.longitude, data.latitude];
  397. showNearbyVideos.value = true;
  398. };
  399. const showVideoDetail = ref(false);
  400. const videoDetail = ref({});
  401. const handleShowVideo2 = (data, flag) => {
  402. const newMap = mapStore.isAMap ? map : map.getView();
  403. if (!flag) {
  404. newMap.setCenter([data.longitude, data.latitude]);
  405. newMap.setZoom(18);
  406. }
  407. videoDetail.value = data;
  408. showVideoDetail.value = true;
  409. };
  410. const handleClickMap = (e) => {
  411. if (mapStore.isAMap) {
  412. location.value = [e.lnglat.lng, e.lnglat.lat];
  413. } else {
  414. location.value = e.coordinate;
  415. }
  416. if (!!tempMenu.value && tempMenu.value.name === '附近视频') {
  417. showNearbyVideos.value = true;
  418. } else if (!!tempMenu.value && tempMenu.value.name === '格点雨量') {
  419. showRainfall.value = true;
  420. } else if (!!tempMenu.value && tempMenu.value.name === '定点分析') {
  421. const item = deepClone(tempMenu.value);
  422. const nowLocation = deepClone(location.value);
  423. tempMenu.value = {
  424. name: '',
  425. checked: false
  426. };
  427. if (mapStore.isAMap) {
  428. map.off('click', handleClickMap);
  429. } else {
  430. map.un('click', handleClickMap);
  431. }
  432. mapStore.setIsMapSelect(false);
  433. rightMenuRef.value.updateMenu(item.checked ? '1' : '2', item, nowLocation);
  434. }
  435. };
  436. let showWarehouse = ref(false);
  437. let warehouseData = ref('');
  438. const handleShowWarehouse = (data) => {
  439. warehouseData.value = data;
  440. showWarehouse.value = true;
  441. };
  442. // 救援队伍人员列表
  443. let showPeople = ref(false);
  444. let teamId = ref('');
  445. const handleShowPeople = (data) => {
  446. teamId.value = data.id;
  447. showPeople.value = true;
  448. };
  449. watch(
  450. () => mapStore.trackState.show,
  451. (show) => {
  452. if (!!show) {
  453. const dom = mapStore.isAMap ? mapRef.value : map2Ref.value;
  454. dom?.addMarker(adjustPoint(mapStore.trackState.data));
  455. mapStore.trackState.data = [];
  456. mapStore.trackState.show = false;
  457. }
  458. }
  459. );
  460. const handleShowTrack = (data) => {
  461. getVehicleTrajectory(data.id).then((res) => {
  462. const trajectory = [];
  463. if (res.data && res.data.list) {
  464. res.data.list.forEach((item) => {
  465. trajectory.push({
  466. time: !!item.gpsDate ? parseTime(item.gpsDate, '{h}:{i}') : '',
  467. lnglat: [item.lng, item.lat]
  468. });
  469. });
  470. initDataToPlay({ id: data.id, type: 'track', data: trajectory, name: data.title }, true);
  471. }
  472. });
  473. };
  474. // 传递数据给时间轴
  475. const initDataToPlay = (data, isDetail?: boolean) => {
  476. if (!!timeAxisRef.value) {
  477. timeAxisRef.value.initDataToPlay(data, isDetail);
  478. }
  479. };
  480. // 点位详情弹窗关闭后
  481. const handleCloseDetailDialog = (data) => {
  482. timeAxisRef.value.clearData(data.title, true);
  483. };
  484. const mapMoveEnd = () => {
  485. if (!mapStore.pointParams.dict_value && !mapStore.pointParams.option) return;
  486. addMarkersMethod();
  487. };
  488. watch(
  489. () => mapStore.mapLoaded,
  490. (loaded) => {
  491. if (loaded) {
  492. map = getMap();
  493. mapUtils = getMapUtils();
  494. if (!!map && Object.keys(map).length !== 0) {
  495. map.on('moveend', mapMoveEnd);
  496. }
  497. if (eventId.value) {
  498. getEventDetail({ event_id: eventId.value }).then((res) => {
  499. const lnglat = gcoord.transform([res.data.longitude, res.data.latitude], gcoord.GCJ02, gcoord.WGS84);
  500. const newMap = mapStore.isAMap ? map : map.getView();
  501. newMap.setCenter(lnglat);
  502. mapUtils.setAddress({
  503. longitude: lnglat[0],
  504. latitude: lnglat[1],
  505. image: getImageUrl('address.png'),
  506. imageHover: getImageUrl('address_hover.png'),
  507. size: [45, 48],
  508. name: '灾害地点'
  509. });
  510. });
  511. }
  512. }
  513. },
  514. {
  515. immediate: true
  516. }
  517. );
  518. watch(
  519. () => mapStore.updateAddress,
  520. (lnglat) => {
  521. if (lnglat && lnglat.length == 2) {
  522. const newMap = mapStore.isAMap ? map : map.getView();
  523. newMap.setCenter(lnglat);
  524. mapUtils.setAddress({
  525. longitude: lnglat[0],
  526. latitude: lnglat[1],
  527. image: getImageUrl('address.png'),
  528. imageHover: getImageUrl('address_hover.png'),
  529. size: [45, 48],
  530. name: '灾害地点'
  531. });
  532. }
  533. }
  534. );
  535. // 监听视频打点
  536. watch(
  537. () => mapStore.pointParams,
  538. () => {
  539. if (mapStore.updateMenu && !!mapStore.updateMenu.name) {
  540. // 视频修改的值,需要显示左侧辅助分析、显示右侧菜单
  541. leftMenuRef.value.setMenuIndex(2);
  542. rightMenuRef.value.updateMenu('1', mapStore.updateMenu);
  543. mapStore.setUpdateMenu({});
  544. }
  545. addMarkersMethod(true);
  546. },
  547. {
  548. deep: true
  549. }
  550. );
  551. // 监听层级变化
  552. watch(
  553. () => mapStore.mapState.zoom,
  554. () => {
  555. mapMoveEnd();
  556. },
  557. {
  558. deep: true
  559. }
  560. );
  561. onMounted(() => {
  562. mapStore.initData();
  563. eventId.value = route.query.event_id;
  564. });
  565. onBeforeUnmount(() => {
  566. if (!!map && Object.keys(map).length === 0) {
  567. if (mapStore.isAMap) {
  568. map.off('click', handleClickMap);
  569. map.off('moveend', handleClickMap);
  570. } else {
  571. map.un('click', handleClickMap);
  572. map.un('moveend', handleClickMap);
  573. }
  574. mapStore.setIsMapSelect(false);
  575. }
  576. if (!!addMarkersTimer) {
  577. clearInterval(addMarkersTimer);
  578. addMarkersTimer = null;
  579. }
  580. });
  581. provide('getMapUtils', getMapUtils);
  582. provide('getMap', getMap);
  583. provide('trackPlayback', trackPlayback);
  584. provide('showDetail', showDetail);
  585. provide('getDrawTool', getDrawTool);
  586. provide('initDataToPlay', initDataToPlay);
  587. provide('handleShowVideo2', handleShowVideo2);
  588. </script>
  589. <style lang="scss" scoped>
  590. #globalMap {
  591. width: 100%;
  592. height: 100%;
  593. }
  594. .global-map {
  595. width: 100%;
  596. height: 100%;
  597. position: relative;
  598. //overflow: hidden;
  599. .tool-box {
  600. position: absolute;
  601. right: 430px;
  602. bottom: 180px;
  603. z-index: 10;
  604. color: #fff;
  605. }
  606. }
  607. .box {
  608. position: absolute;
  609. top: 20px;
  610. right: 20px;
  611. width: 300px;
  612. background-color: #041d55;
  613. display: flex;
  614. flex-direction: column;
  615. padding: 20px;
  616. color: #fff;
  617. font-size: 16px;
  618. height: 500px;
  619. max-height: 90%;
  620. div {
  621. line-height: 30px;
  622. cursor: pointer;
  623. }
  624. }
  625. .fixed {
  626. position: fixed !important;
  627. }
  628. </style>