index.vue 17 KB

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