index.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. <template>
  2. <div ref="containerRef" class="map-container">
  3. <div id="aMap" class="map-container2" :style="{ width: width, height: height }"></div>
  4. <!-- 右下工具 -->
  5. <div v-show="mapState.showScale" class="zoom-text">{{ mapState.zoom }}级</div>
  6. <div class="right-tool">
  7. <!-- 快捷缩放 -->
  8. <QuickZoom v-model:zoom="mapState.zoom" @change-step="setMapZoom" />
  9. <div class="flex" style="margin-top: 5px">
  10. <div class="mask-btn" @click="handleShowMask">{{ showMask ? '显' : '隐' }}</div>
  11. <div class="model-btn" @click="switchThreeDimensional">{{ mapState.isThreeDimensional ? '3D' : '2D' }}</div>
  12. <div class="ruler-icon" style="margin-left: 5px" @click="changeScaleControl"></div>
  13. </div>
  14. </div>
  15. </div>
  16. </template>
  17. <script setup lang="ts" name="Map">
  18. import QuickZoom from './quickZoom.vue';
  19. import { useAMap } from '@/hooks/AMap/useAMap';
  20. import { useDrawTool } from '@/hooks/AMap/useDrawTool';
  21. import { getPointInfoList } from '@/api/globalMap';
  22. import { getDictLabel } from '@/utils/dict';
  23. import { PointType } from '@/api/globalMap/type';
  24. import {
  25. getEmergencyExpertDetails,
  26. getEmergencyShelterTypeDetails,
  27. getGasolinestationDetails,
  28. getHospitalDetails,
  29. getMiningcompanyDetails,
  30. getWarehouseDetails,
  31. getChemicalcompanyDetails,
  32. getSchoolDetails,
  33. getWaterloggedRoadsDetails,
  34. getShipRealtilmePositionDetails,
  35. getUAVDetails,
  36. getRainbowDetails,
  37. getTouristAttractionDetails,
  38. getConstructionSitesDetails,
  39. getYardSitesDetails,
  40. getStationInfoDetails,
  41. getMajorHazardSourceDetails,
  42. getBuildingProjectDetails,
  43. getChemicalWarehouseDetails,
  44. getMiningOperationsDetails,
  45. getEmergencyTransportResourcesDetails,
  46. getEmergencyDisasterInfoOfficerDetails,
  47. getMidmapDzzhDetails,
  48. getVehicleDetails,
  49. getMDPUnitDetails,
  50. getWaterList,
  51. getVideoDrowning,
  52. getVideoForestFire,
  53. getVideoDisasterPrevention
  54. } from '@/api/globalMap/spatialAnalysis';
  55. import { pointDetailTemplate } from '@/views/globalMap/data/mapData';
  56. import useAppStore from '@/store/modules/app';
  57. import { getRescueTeamsInfo } from '@/api/globalMap/rescueTeam';
  58. interface Props {
  59. activeMap: string;
  60. pointType: PointType[];
  61. showMask: boolean;
  62. }
  63. const containerScale = inject('containerScale');
  64. const props = withDefaults(defineProps<Props>(), {});
  65. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  66. const { point_type } = toRefs<any>(proxy?.useDict('point_type'));
  67. const appStore = useAppStore();
  68. const emits = defineEmits([
  69. 'update:drawing',
  70. 'update:showMask',
  71. 'selectGraphics',
  72. 'unSelectGraphics',
  73. 'showTextEditBox',
  74. 'onDrawCompleted',
  75. 'handleShowVideo',
  76. 'handleShowWarehouse',
  77. 'handleShowPeople'
  78. ]);
  79. const containerRef = ref();
  80. const width = ref('100%');
  81. const height = ref('100%');
  82. const mapState = reactive({
  83. center: [110.925175, 21.678955],
  84. zoom: 7.9,
  85. minZoom: 6,
  86. maxZoom: 20,
  87. isThreeDimensional: false,
  88. // 是否显示比例尺
  89. showScale: true
  90. });
  91. let AMap, map, scale, placeSearch;
  92. // 鼠标绘制工具
  93. const drawTool = useDrawTool();
  94. // 初始化地图
  95. const mapUtils = useAMap({
  96. key: '30d3d8448efd68cb0b284549fd41adcf', // 申请好的Web端开发者Key,首次调用 load 时必填
  97. version: '2.0', // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
  98. pitch: mapState.isThreeDimensional ? 45 : 0,
  99. zoom: mapState.zoom,
  100. center: [mapState.center[0], mapState.center[1]],
  101. dragEnable: true,
  102. scrollWheel: true,
  103. showScale: true,
  104. enableMouseTool: true,
  105. // 加载完成事件
  106. onLoadCompleted: () => {
  107. AMap = getAMap();
  108. map = getMap();
  109. scale = getScale();
  110. if (!['logical', 'vectorgraph'].includes(props.activeMap)) {
  111. switchMap(props.activeMap);
  112. } else {
  113. map.removeLayer();
  114. }
  115. placeSearch = new AMap.PlaceSearch({
  116. pageSize: 30,
  117. pageIndex: 1,
  118. city: '0668',
  119. extensions: 'base'
  120. });
  121. map.on('zoomchange', zoomChangeHandler);
  122. // 添加遮罩
  123. if (props.showMask) {
  124. creatMask([{ strokeWeight: 2 }]);
  125. }
  126. drawTool.initMouseTool({ container: 'aMap', map, AMap });
  127. handleResize();
  128. },
  129. onMarkerClick: (data) => {
  130. // 多点位
  131. if (data.type === '1') {
  132. let path = [];
  133. props.pointType.forEach((item) => {
  134. path.push(item.component);
  135. });
  136. getPointInfoList({
  137. option: path.toString(),
  138. longitude: data.longitude.toString(),
  139. latitude: data.latitude.toString()
  140. }).then((res) => {
  141. const data2 = res.data.list;
  142. let content = document.createElement('div');
  143. // content.style.cssText = 'transform: scale(' + containerScale().scaleX + ');';
  144. content.className = 'point-info';
  145. let content2 = '';
  146. content2 += '<div class="title-box"><div class="gradient-text">多点位信息</div></div>';
  147. content2 += '<div class="icon1"></div>';
  148. content2 += '<div class="icon2"></div>';
  149. content2 += '<div class="icon3"></div>';
  150. content2 += '<div class="icon4"></div>';
  151. content.innerHTML = content2;
  152. let tableBox = document.createElement('div');
  153. tableBox.className = 'table-box';
  154. let table = document.createElement('div');
  155. table.className = 'table';
  156. table.innerHTML = '<div class="point-item"><div class="td3">主题</div><div class="td3">名称</div></div>';
  157. data2.forEach((item) => {
  158. item.longitude = data.longitude;
  159. item.latitude = data.latitude;
  160. const div = document.createElement('div');
  161. div.className = 'point-item point-item-hover';
  162. div.innerHTML =
  163. '<div class="td4">' + getDictLabel(point_type.value, item.dataType.toString()) + '</div><div class="td4">' + item.name + '</div>';
  164. div.addEventListener('click', () => {
  165. handlePointDetails(item);
  166. });
  167. table.appendChild(div);
  168. });
  169. tableBox.appendChild(table);
  170. content.appendChild(tableBox);
  171. let closeBtn = document.createElement('div');
  172. closeBtn.className = 'close';
  173. closeBtn.onclick = hideInfo;
  174. content.appendChild(closeBtn);
  175. showInfo(content, [data.longitude, data.latitude], true);
  176. });
  177. } else {
  178. handlePointDetails(data);
  179. }
  180. }
  181. });
  182. const {
  183. getAMap,
  184. getMap,
  185. switchMap,
  186. addMarker,
  187. addSearchMarker,
  188. clearMarker,
  189. getMarkers,
  190. getScale,
  191. showInfo,
  192. hideInfo,
  193. handleHover,
  194. creatMask,
  195. removeMask,
  196. trackPlayback
  197. } = { ...mapUtils };
  198. const handlePointDetails = (data) => {
  199. let methodList = {
  200. '1': getEmergencyExpertDetails,
  201. '2': getWarehouseDetails,
  202. '3': getEmergencyShelterTypeDetails,
  203. '4': getWaterloggedRoadsDetails,
  204. '5': getSchoolDetails,
  205. '6': getHospitalDetails,
  206. '7': getGasolinestationDetails,
  207. '8': getMiningcompanyDetails,
  208. // '9': getChemicalcompanyDetails,
  209. '10': getShipRealtilmePositionDetails,
  210. '11': getChemicalcompanyDetails,
  211. '12': getChemicalcompanyDetails,
  212. '13': getChemicalcompanyDetails,
  213. '14': getChemicalcompanyDetails,
  214. '15': getUAVDetails,
  215. '16': getRainbowDetails,
  216. '17': getMidmapDzzhDetails,
  217. '18': getMiningOperationsDetails,
  218. '20': getMDPUnitDetails,
  219. '21': getBuildingProjectDetails,
  220. '22': getChemicalWarehouseDetails,
  221. '23': getMajorHazardSourceDetails,
  222. '24': getStationInfoDetails,
  223. '25': getYardSitesDetails,
  224. '26': getTouristAttractionDetails,
  225. '27': getConstructionSitesDetails,
  226. '28': getEmergencyTransportResourcesDetails,
  227. '29': getEmergencyDisasterInfoOfficerDetails,
  228. '30': getVehicleDetails,
  229. '31': getWaterList,
  230. '32': getVideoDrowning,
  231. '33': getVideoForestFire,
  232. '34': getVideoDisasterPrevention,
  233. '41': getRescueTeamsInfo
  234. };
  235. let titleList = {
  236. '1': '专家信息',
  237. '2': '物资与装备仓库信息',
  238. '3': '避难所信息',
  239. '4': '易涝点信息',
  240. '5': '学校信息',
  241. '6': '医院信息',
  242. '7': '加油站信息',
  243. '8': '非煤矿山企业信息',
  244. '9': '危化企业信息',
  245. '10': '船舶动态信息',
  246. '11': '危险化学品经营企业信息',
  247. '12': '危险化学品生产企业信息',
  248. '13': '危险化学品使用企业(使用许可)信息',
  249. '14': '化工企业(不发使用许可)信息',
  250. '15': '无人机信息',
  251. '16': '雨窝点',
  252. '17': '地质灾害隐患点',
  253. '18': '矿山施工',
  254. '19': '工矿商贸',
  255. '20': '气象灾害防御重点单位',
  256. '21': '建筑工程',
  257. '22': '储罐信息',
  258. '23': '重大危险源',
  259. '24': '客运站',
  260. '25': '堆场',
  261. '26': '旅游场所',
  262. '27': '在建工地',
  263. '28': '运输资源',
  264. '29': '灾害信息员',
  265. '30': '重点车辆信息',
  266. '31': '江湖河库',
  267. '32': '防溺水',
  268. '33': '森林防火',
  269. '34': '防灾救援',
  270. '41': '救援队伍'
  271. };
  272. let method = methodList[data.dataType];
  273. let title = !!titleList[data.dataType] ? titleList[data.dataType] : '信息';
  274. if (!method) return;
  275. method(data.id).then((res) => {
  276. if (!!pointDetailTemplate[data.dataType]) {
  277. let div = document.createElement('div');
  278. // div.style.cssText = 'transform: scale(' + containerScale().scaleX + ');';
  279. div.className = 'point-info';
  280. let titleDom = document.createElement('div');
  281. titleDom.className = 'title-box';
  282. titleDom.innerHTML = '<div class="gradient-text">' + title + '</div></div>';
  283. div.appendChild(titleDom);
  284. if (data.dataType === 2) {
  285. let btnBox = document.createElement('div');
  286. let btn = document.createElement('div');
  287. btnBox.className = 'flex';
  288. btn.className = 'btn';
  289. btn.innerHTML = '<div class="video-icon"></div><div>物资详情</div>';
  290. btn.onclick = () => {
  291. emits('handleShowWarehouse', data);
  292. };
  293. btnBox.appendChild(btn);
  294. div.appendChild(btnBox);
  295. } else if (data.dataType === 4) {
  296. let btnBox = document.createElement('div');
  297. let btn = document.createElement('div');
  298. btnBox.className = 'flex';
  299. btn.className = 'btn';
  300. btn.innerHTML = '<div class="video-icon"></div><div>附近视频</div>';
  301. btn.onclick = () => {
  302. emits('handleShowVideo', data);
  303. };
  304. btnBox.appendChild(btn);
  305. div.appendChild(btnBox);
  306. } else if (data.dataType === 41) {
  307. let btnBox = document.createElement('div');
  308. let btn = document.createElement('div');
  309. btnBox.className = 'flex';
  310. btn.className = 'btn';
  311. btn.innerHTML = '<div class="video-icon"></div><div>人员列表</div>';
  312. btn.onclick = () => {
  313. emits('handleShowPeople', data);
  314. };
  315. btnBox.appendChild(btn);
  316. div.appendChild(btnBox);
  317. }
  318. let icon1 = document.createElement('div');
  319. icon1.className = 'icon1';
  320. let icon2 = document.createElement('div');
  321. icon2.className = 'icon2';
  322. let icon3 = document.createElement('div');
  323. icon3.className = 'icon3';
  324. let icon4 = document.createElement('div');
  325. icon4.className = 'icon4';
  326. div.appendChild(icon1);
  327. div.appendChild(icon2);
  328. div.appendChild(icon3);
  329. div.appendChild(icon4);
  330. let table = document.createElement('div');
  331. table.className = 'table-box';
  332. let content = '';
  333. content += '<div class="table">';
  334. const newData = filterTd(res.rows[0], data.dataType);
  335. newData.forEach((item) => {
  336. if (item.type === 'shortText') {
  337. content += '<div class="tr">';
  338. item.data.forEach((item2) => {
  339. content += '<div class="point-item">';
  340. content += '<div class="td1">' + item2.label + '</div><div class="td2">' + item2.value + '</div>';
  341. content += '</div>';
  342. });
  343. content += '</div>';
  344. } else {
  345. content += '<div class="point-item2">';
  346. content += '<div class="td1">' + item.data[0].label + '</div><div class="td2">' + item.data[0].value + '</div>';
  347. content += '</div>';
  348. }
  349. });
  350. content += '</div>';
  351. table.innerHTML = content;
  352. div.appendChild(table);
  353. let closeBtn = document.createElement('div');
  354. closeBtn.className = 'close';
  355. closeBtn.onclick = hideInfo;
  356. div.appendChild(closeBtn);
  357. showInfo(div, [data.longitude, data.latitude], true);
  358. }
  359. });
  360. };
  361. const filterTd = (obj, dataType) => {
  362. let data = [];
  363. let tempData = {};
  364. let i = 0;
  365. for (let key in obj) {
  366. let keyLabel = pointDetailTemplate[dataType][key];
  367. if (!!keyLabel) {
  368. if (i === 2) {
  369. i = 0;
  370. }
  371. const value = !!obj[key] ? obj[key] : '';
  372. if (value && value.length > 8) {
  373. if (i === 0) {
  374. data.push({ type: 'longText', data: [{ label: keyLabel, value: value }] });
  375. i = 0;
  376. } else {
  377. tempData = { type: 'longText', data: [{ label: keyLabel, value: value }] };
  378. }
  379. } else {
  380. if (i === 0) {
  381. data.push({ type: 'shortText', data: [{ label: keyLabel, value: value }] });
  382. } else {
  383. data[data.length - 1].data.push({ label: keyLabel, value: value });
  384. }
  385. i++;
  386. if (!!tempData && JSON.stringify(tempData) !== '{}') {
  387. data.push(tempData);
  388. tempData = {};
  389. i = 0;
  390. }
  391. }
  392. }
  393. }
  394. if (!!tempData && JSON.stringify(tempData) !== '{}') {
  395. data.push(tempData);
  396. }
  397. if (data[data.length - 1].data && data[data.length - 1].data.length === 1 && data[data.length - 1].type === 'shortText') {
  398. data[data.length - 1].data[1] = { label: '', value: '' };
  399. }
  400. return data;
  401. };
  402. // 监听地图类型变化
  403. watch(
  404. () => props.activeMap,
  405. (value, oldValue) => {
  406. switchMap(props.activeMap);
  407. }
  408. );
  409. watch(
  410. () => props.showMask,
  411. () => {
  412. if (props.showMask) {
  413. creatMask([{ strokeWeight: 2 }]);
  414. } else {
  415. removeMask();
  416. }
  417. }
  418. );
  419. // watc h(
  420. // () => props.mouseToolState,
  421. // () => {
  422. // if (props.drawing) {
  423. // drawGraphics(props.mouseToolState);
  424. // } else {
  425. // closeDraw();
  426. // }
  427. // },
  428. // {
  429. // deep: true
  430. // }
  431. // );
  432. // 缩放级别变化
  433. const zoomChangeHandler = () => {
  434. mapState.zoom = map.getZoom();
  435. };
  436. // 切换比例尺
  437. const changeScaleControl = () => {
  438. mapState.showScale = !mapState.showScale;
  439. if (mapState.showScale) {
  440. map.addControl(scale);
  441. } else {
  442. map.removeControl(scale);
  443. }
  444. };
  445. // 设置地图层级
  446. const setMapZoom = (value) => {
  447. if (!map) return;
  448. if (value === 1) {
  449. map.setCenter([110.925175, 21.678955]);
  450. map.setZoom(7.9);
  451. } else if (value === 2) {
  452. map.setCenter([110.925175, 21.6789558]);
  453. map.setZoom(9.21);
  454. } else if (value === 3) {
  455. map.setCenter([110.925175, 21.678955]);
  456. map.setZoom(11.38);
  457. } else if (value === 4) {
  458. map.setCenter([110.925175, 21.678955]);
  459. map.setZoom(12.83);
  460. }
  461. };
  462. // 切换2D、3D
  463. const switchThreeDimensional = () => {
  464. mapState.isThreeDimensional = !mapState.isThreeDimensional;
  465. const pitch = mapState.isThreeDimensional ? 45 : 0;
  466. map.setPitch(pitch);
  467. };
  468. const setCenter = (item) => {
  469. map.setCenter([item.longitude, item.latitude]);
  470. };
  471. const getMapUtils = () => {
  472. return mapUtils;
  473. };
  474. const getPlaceSearch = () => {
  475. return placeSearch;
  476. };
  477. const handleShowMask = () => {
  478. emits('update:showMask', !props.showMask);
  479. };
  480. defineExpose({
  481. addMarker,
  482. addSearchMarker,
  483. setCenter,
  484. getMarkers,
  485. clearMarker,
  486. getMap,
  487. drawTool,
  488. handleHover,
  489. trackPlayback,
  490. getMapUtils,
  491. getPlaceSearch
  492. });
  493. const handleResize = () => {
  494. const containerWidth = containerRef.value.clientWidth * containerScale().scaleX;
  495. const containerHeight = containerRef.value.clientHeight * containerScale().scaleY;
  496. width.value = containerWidth + 'px';
  497. height.value = containerHeight + 'px';
  498. map.resize();
  499. };
  500. watch(
  501. () => appStore.showLeftSection,
  502. () => {
  503. nextTick(() => {
  504. handleResize();
  505. });
  506. }
  507. );
  508. watch(
  509. () => appStore.showRightSection,
  510. () => {
  511. nextTick(() => {
  512. handleResize();
  513. });
  514. }
  515. );
  516. onMounted(() => {
  517. window.addEventListener('resize', handleResize);
  518. });
  519. // 卸载事件
  520. onUnmounted(() => {
  521. if (map) {
  522. map.off('zoomchange', zoomChangeHandler);
  523. }
  524. window.removeEventListener('resize', handleResize);
  525. });
  526. </script>
  527. <style lang="scss" scoped>
  528. @import 'map.scss';
  529. .map-container2 {
  530. width: 100%;
  531. height: 100%;
  532. position: relative;
  533. overflow: hidden;
  534. }
  535. .map-container {
  536. width: 100%;
  537. height: 100%;
  538. .zoom-text {
  539. position: absolute;
  540. bottom: 120px;
  541. right: 170px;
  542. color: #eaf3fc;
  543. font-size: 25.73px;
  544. }
  545. .right-tool {
  546. position: absolute;
  547. bottom: 160px;
  548. right: 175px;
  549. z-index: 2;
  550. }
  551. .mask-btn {
  552. background-color: #04327c;
  553. font-size: 25.73px;
  554. border: 1px solid #91cfff;
  555. color: #eaf3fc;
  556. cursor: pointer;
  557. padding: 3px 3px;
  558. border-radius: 5px;
  559. margin-right: 5px;
  560. cursor: pointer;
  561. }
  562. .model-btn {
  563. background-color: #04327c;
  564. font-size: 25.73px;
  565. font-family: 'SourceHanSansCN';
  566. border: 1px solid #91cfff;
  567. color: #eaf3fc;
  568. cursor: pointer;
  569. padding: 3px 3px;
  570. border-radius: 5px;
  571. }
  572. .ruler-icon {
  573. width: 42px;
  574. height: 42px;
  575. background: url('@/assets/images/map/ruler.png') no-repeat;
  576. background-size: 100% 100%;
  577. cursor: pointer;
  578. }
  579. :deep(.amap-scalecontrol) {
  580. left: unset !important;
  581. background-color: unset !important;
  582. right: 56px;
  583. bottom: 17px !important;
  584. }
  585. :deep(.amap-scale-text) {
  586. text-align: left !important;
  587. padding-left: 20px;
  588. color: #eaf3fc;
  589. font-size: vw(32);
  590. font-family: 'SourceHanSansCN';
  591. }
  592. :deep(.amap-scale-edgeleft),
  593. :deep(.amap-scale-middle),
  594. :deep(.amap-scale-edgeright) {
  595. border: 1px solid #bfe1fc !important;
  596. background: transparent !important;
  597. }
  598. :deep(.amap-logo),
  599. :deep(.amap-copyright) {
  600. display: none !important;
  601. }
  602. /* 自定义测距工具样式 */
  603. :deep(.amap-ranger) {
  604. background-color: #ffffff;
  605. border: 1px solid #888888;
  606. border-radius: 5px;
  607. padding: 5px;
  608. }
  609. :deep(.amap-ranger .amap-toolbar) {
  610. color: #333;
  611. font-size: 12px;
  612. padding: 5px;
  613. }
  614. :deep(.amap-ranger .amap-toolbar button) {
  615. background-color: #022577;
  616. color: #fff;
  617. border: none;
  618. border-radius: 4px;
  619. padding: 5px;
  620. margin-right: 5px;
  621. cursor: pointer;
  622. }
  623. :deep(.amap-ranger .amap-toolbar button:hover) {
  624. background-color: #1a3c75;
  625. }
  626. :deep(.amap-ranger .amap-toolbar .amap-toolbar-text) {
  627. color: #444;
  628. font-size: 14px;
  629. }
  630. :deep(.amap-marker) {
  631. font-size: 16px;
  632. }
  633. :deep(.amap-marker-label) {
  634. border: 1px solid #474747;
  635. background-color: rgba(255, 255, 255, 0.65);
  636. padding: 3px 8px;
  637. border-radius: 4px;
  638. }
  639. }
  640. </style>