SimpleMap.vue 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. <template>
  2. <div ref="mapContainer" class="map-container"></div>
  3. <div v-if="!isTopLevel" class="map-reset-btn">
  4. <Button type="primary" @click="reset">重置</Button>
  5. </div>
  6. </template>
  7. <script lang="ts" setup>
  8. import { ref, onMounted, onUnmounted, computed } from 'vue';
  9. import L from 'leaflet';
  10. import 'leaflet/dist/leaflet.css';
  11. import { Button } from 'ant-design-vue';
  12. import { getMarkers } from '/@/api/sys/map';
  13. import { DEFAULT_LAYER_ID, regionColorMap, tileLayerConfigs, generatePopupContent, getRootMarkerIcon, GEOJSON_LAYER_ID } from './simpleMap.data';
  14. import { useAppStore } from '/@/store/modules/app';
  15. import { isEmpty } from '/@/utils/is';
  16. import { useRoute } from 'vue-router';
  17. import { PageEnum } from '/@/enums/pageEnum';
  18. const appStore = useAppStore();
  19. const route = useRoute();
  20. const mapContainer = ref(null);
  21. const isTopLevel = computed(() => {
  22. return isEmpty(appStore.getSimpleMapParams);
  23. });
  24. let map: L.Map | null = null;
  25. // 图层组 Map(基于所有区域编码初始化)
  26. const layerMap = new Map<string, L.LayerGroup>([
  27. [DEFAULT_LAYER_ID, L.featureGroup()],
  28. [GEOJSON_LAYER_ID, L.featureGroup()],
  29. // // 西安
  30. // ['1566101', L.featureGroup()],
  31. // // 铜川
  32. // ['1566102', L.featureGroup()],
  33. // // 宝鸡
  34. // ['1566103', L.featureGroup()],
  35. // // 咸阳
  36. // ['1566104', L.featureGroup()],
  37. // // 渭南
  38. // ['1566105', L.featureGroup()],
  39. // // 延安
  40. // ['1566106', L.featureGroup()],
  41. // // 汉中
  42. // ['1566107', L.featureGroup()],
  43. // // 榆林
  44. // ['1566108', L.featureGroup()],
  45. // // 安康
  46. // ['1566109', L.featureGroup()],
  47. // // 商洛
  48. // ['1566111', L.featureGroup()],
  49. ]);
  50. onMounted(async () => {
  51. if (!mapContainer.value) return;
  52. reset();
  53. map = L.map(mapContainer.value, {
  54. center: [35.841, 108.94],
  55. zoom: 7,
  56. zoomControl: false,
  57. attributionControl: false,
  58. });
  59. initTileLayers();
  60. if (route.path === PageEnum.BASE_HOME) {
  61. await initMarkers();
  62. await initGeoJSON();
  63. }
  64. layerMap.get(DEFAULT_LAYER_ID)?.addTo(map);
  65. });
  66. onUnmounted(() => {
  67. map?.remove();
  68. });
  69. /** 初始化瓦片图层 */
  70. function initTileLayers() {
  71. tileLayerConfigs.forEach((config) => {
  72. const layer = L.tileLayer(config.url, config.options);
  73. if (config.addByDefault) {
  74. layer.addTo(map!);
  75. }
  76. });
  77. }
  78. /** 加载 GeoJSON 并添加到对应图层组 */
  79. async function initGeoJSON() {
  80. const response = await fetch('/js/shanxi.geo.json');
  81. const ShanXiGeoJSON = await response.json();
  82. const geoJsonLayer = L.geoJSON(ShanXiGeoJSON, {
  83. style(feature) {
  84. const gb = feature.properties.gb.slice(0, -2);
  85. return {
  86. fillColor: regionColorMap[gb] || '#14baff',
  87. fillOpacity: 0.2,
  88. weight: 2,
  89. opacity: 1,
  90. color: '#ff9100',
  91. };
  92. },
  93. onEachFeature(_, layer) {
  94. layer.on({
  95. mouseover: (e) => {
  96. e.target.setStyle({ weight: 2, fillOpacity: 0.6 });
  97. e.target.bringToFront();
  98. },
  99. mouseout: (e) => {
  100. geoJsonLayer.resetStyle(e.target);
  101. },
  102. });
  103. },
  104. });
  105. map.addLayer(geoJsonLayer);
  106. // geoJsonLayer.addTo(layerMap.get(GEOJSON_LAYER_ID));
  107. // layerMap.forEach((group, code) => {
  108. // const features = ShanXiGeoJSON.features.filter((feature: any) => {
  109. // if (code === DEFAULT_LAYER_ID) return true;
  110. // return feature.properties.gb.startsWith(code);
  111. // });
  112. // });
  113. }
  114. /** 初始化所有标记点 */
  115. async function initMarkers() {
  116. const records = await getMarkers({ pageNo: 0, pageSize: 9999 });
  117. records.forEach((item, index) => {
  118. if (!layerMap.has(item.id)) {
  119. layerMap.set(item.id, L.featureGroup());
  120. }
  121. // 创建根标记点
  122. const iconConfig = getRootMarkerIcon(40, item.children.length, Object.values(regionColorMap)[index]);
  123. const marker = L.marker([item.lat, item.lng], {
  124. icon: L.divIcon(iconConfig),
  125. riseOnHover: true,
  126. }).on('click', () => {
  127. layerMap.forEach((group, code) => {
  128. map?.removeLayer(group);
  129. if (code === item.id) {
  130. // map?.addLayer(group);
  131. // map?.flyToBounds(group.getBounds());
  132. map?.flyToBounds(layerMap.get(item.id).getBounds());
  133. }
  134. });
  135. appStore.setSimpleMapParams({ mineCode: item.mineCode });
  136. });
  137. marker.addTo(layerMap.get(DEFAULT_LAYER_ID)!);
  138. // 创建子标记点
  139. if (!item.children.length) return;
  140. item.children.forEach((child: any) => {
  141. const popupContent = generatePopupContent(child);
  142. const childMarker = L.marker([child.lat, child.lng]).bindPopup(popupContent);
  143. if (layerMap.has(child.rootId)) {
  144. childMarker.addTo(layerMap.get(child.rootId)!);
  145. }
  146. });
  147. });
  148. }
  149. /** 重置视图到默认图层 */
  150. function reset() {
  151. layerMap.forEach((group, code) => {
  152. map?.removeLayer(group);
  153. if (code === DEFAULT_LAYER_ID) {
  154. map?.addLayer(group);
  155. map?.flyToBounds(group.getBounds());
  156. }
  157. });
  158. appStore.setSimpleMapParams({});
  159. }
  160. </script>
  161. <style lang="less">
  162. .map-container {
  163. flex: 1;
  164. height: 100%;
  165. width: 100%;
  166. position: fixed;
  167. top: 0;
  168. left: 0;
  169. z-index: @simple-map-z-index;
  170. // filter: grayscale(80%) invert(0%) sepia(10%) hue-rotate(150deg) saturate(200%) brightness(100%) contrast(100%);
  171. }
  172. .light-blue-theme {
  173. filter: invert(0.1) hue-rotate(190deg) saturate(2);
  174. // filter: sepia(0.3) hue-rotate(180deg) saturate(1.8);
  175. }
  176. .leaflet-popup {
  177. color: @white;
  178. &-content-wrapper {
  179. background-color: @map-popup-bg;
  180. }
  181. &-tip {
  182. background-color: @map-popup-bg;
  183. }
  184. .leaflet-popup-content {
  185. margin: 13px 20px;
  186. min-width: 280px;
  187. // &__popup {
  188. // }
  189. &__divider {
  190. height: 1px;
  191. background-color: @white;
  192. margin: 10px 5px;
  193. }
  194. &__title {
  195. color: @white;
  196. text-align: center;
  197. }
  198. &__board {
  199. padding: 10px;
  200. border-radius: 5px;
  201. background-color: @map-popup-board-bg;
  202. display: flex;
  203. justify-content: space-between;
  204. }
  205. &__table {
  206. color: @white;
  207. text-align: center;
  208. thead {
  209. height: 20px;
  210. }
  211. tbody {
  212. tr {
  213. background-color: @map-popup-table-th-bg;
  214. }
  215. tr:nth-child(even) {
  216. background-color: @map-popup-table-theven-bg;
  217. }
  218. td {
  219. padding: 5px;
  220. }
  221. }
  222. }
  223. }
  224. }
  225. .map-reset-btn {
  226. position: absolute;
  227. right: 30%; /* 距离右边 20px */
  228. z-index: 2; /* 确保在地图控件之上 */
  229. top: @header-height;
  230. // padding: 10px 15px;
  231. margin: 10px 0;
  232. }
  233. </style>