SimpleMap.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. <template>
  2. <div ref="mapContainer" class="map-container"></div>
  3. <div class="map-container cad-container" id="map2dContainer"></div>
  4. <div v-if="!isTopLevel" class="map-reset-btn">
  5. <Button :loading="mapLoading" type="primary" @click="revertStack">返回上级</Button>
  6. </div>
  7. </template>
  8. <script lang="ts" setup>
  9. import 'vjmap/dist/vjmap.min.css';
  10. import { ref, onMounted, onUnmounted, computed, createApp } from 'vue';
  11. import { Button } from 'ant-design-vue';
  12. import { useAppStore } from '/@/store/modules/app';
  13. // import { useRoute } from 'vue-router';
  14. import vjmap from 'vjmap';
  15. // import type vjmap3d from 'vjmap3d';
  16. import { getGeoJSON } from '/@/api/sys/map';
  17. import { useMineDepartmentStore } from '/@/store/modules/mine';
  18. import { get, last } from 'lodash-es';
  19. // import { env } from '/@/views/system/cadFile/env';
  20. import { StatusColorEnum } from '/@/enums/jeecgEnum';
  21. import { generateSimplePopup } from './hooks/popup';
  22. import { initMap2d, renderGoafMarkers } from '/@/views/system/cadFile/app';
  23. import { getGoafList } from '/@/views/system/cadFile/cad.api';
  24. import LeafPopup from './components/LeafPopup.vue';
  25. // import { useGlobSetting } from '/@/hooks/setting';
  26. const appStore = useAppStore();
  27. const mineStore = useMineDepartmentStore();
  28. // const globSetting = useGlobSetting();
  29. appStore.setSimpleMapParams({ deptId: mineStore.getRootId, isLeaf: mineStore.getRoot?.isLeaf });
  30. // const route = useRoute();
  31. const mapContainer = ref<HTMLElement>();
  32. // let svc = new vjmap.Service(env.serviceUrl, env.accessToken);
  33. let map: vjmap.Map;
  34. // let app: vjmap3d.App;
  35. const GEO_LAYER_ID = 'geojson-layer';
  36. const GEO_BORDER_ID = 'geoline-layer';
  37. const CIRCLE_LAYER_ID = 'circle-layer';
  38. const SYMBOL_LAYER_ID = 'symbol-layer';
  39. const DEFAULT_NODE = {
  40. id: mineStore.getRootId!,
  41. name: '根节点',
  42. parentId: null,
  43. longitude: 108.367283,
  44. latitude: 36.024691,
  45. zoom: 6,
  46. isLeaf: false,
  47. };
  48. const historyStack = ref([DEFAULT_NODE]); // 用于存储历史节点的栈,实现返回上一级功能
  49. const isTopLevel = computed(() => historyStack.value.length === 1);
  50. /** 根据GeoJSON文件绘制省市边界和填充色到地图上,其将作为一个新图层 */
  51. function initMapGeoJSON(map: vjmap.Map, geojson) {
  52. map.addSource('geojson-data', {
  53. type: 'geojson',
  54. data: geojson,
  55. generateId: true,
  56. });
  57. map.addFillLayer(GEO_LAYER_ID, 'geojson-data', {
  58. fillColor: ['get', 'fillColor'],
  59. fillOpacity: ['case', ['to-boolean', ['feature-state', 'hover']], 0.6, 0.2],
  60. fillOutlineColor: '#ff9100', // 边线颜色,没错,确实没有边线宽度这个选项,所以有了下面的LineLayer做边线
  61. filter: ['in', last(historyStack.value)!.id, ['get', 'deptIds']],
  62. });
  63. map.addLineLayer(GEO_BORDER_ID, 'geojson-data', {
  64. lineColor: '#ff9100',
  65. lineWidth: 2,
  66. filter: ['in', last(historyStack.value)!.id, ['get', 'deptIds']],
  67. });
  68. // map.hoverPointer([GEO_LAYER_ID]);
  69. map.hoverFeatureState(GEO_LAYER_ID);
  70. }
  71. /** 根据标记点信息绘制两个图层,一个圆标点图层和一个文本标点图层 */
  72. function initMapMarker(map: vjmap.Map, markers) {
  73. // 绘制背景圆圈
  74. const circles = new vjmap.Circle({
  75. layerId: CIRCLE_LAYER_ID,
  76. data: markers,
  77. // 根据hover不同的状态设置不同的颜色值
  78. circleColor: ['case', ['to-boolean', ['feature-state', 'hover']], StatusColorEnum.gray, ['get', 'fillColor']],
  79. // 根据hover不同的状态设置不同的宽度值
  80. circleStrokeWidth: 1,
  81. circleRadius: ['case', ['to-boolean', ['get', 'isLeaf']], 8, 16],
  82. // 根据用户所属部门进行初步筛选
  83. filter: ['==', last(historyStack.value)!.id, ['get', 'parentId']],
  84. });
  85. circles.addTo(map);
  86. // 设置 hover 鼠标指针样式和状态更新
  87. circles.hoverPointer();
  88. circles.hoverFeatureState();
  89. circles.hoverPopup(
  90. (f) => {
  91. if (!f.properties.isLeaf) {
  92. return generateSimplePopup(f.properties);
  93. }
  94. },
  95. {
  96. maxWidth: '300px',
  97. }
  98. );
  99. const symbols = new vjmap.Symbol({
  100. layerId: SYMBOL_LAYER_ID,
  101. data: markers,
  102. textField: ['case', ['to-boolean', ['get', 'isLeaf']], '', ['get', 'count']],
  103. textFont: ['Noto Sans Italic'],
  104. textSize: 16,
  105. textAllowOverlap: true,
  106. textColor: '#ffffff',
  107. filter: ['==', last(historyStack.value)!.id, ['get', 'parentId']],
  108. });
  109. symbols.addTo(map);
  110. circles.clickLayer((e) => {
  111. if (e.defaultPrevented) return; // 如果事件之前阻止了,则不再执行了
  112. historyStack.value.push(get(e, 'features[0].properties', DEFAULT_NODE));
  113. markerClickHandler();
  114. e.preventDefault(); // 阻止之后的事件执行
  115. });
  116. }
  117. /** 标记点点击后,如果不是叶节点那么聚焦到下一级,如果已经是叶节点了则显示该节点的CAD地图 */
  118. function markerClickHandler() {
  119. const node: any = last(historyStack.value);
  120. if (!node) return;
  121. appStore.setSimpleMapParams({ deptId: node.id, isLeaf: node.isLeaf });
  122. // 标点点击后,如果是叶节点需要显示CAD图
  123. if (!node.isLeaf) {
  124. map.setFilter(CIRCLE_LAYER_ID, ['==', node.id, ['get', 'parentId']]);
  125. map.setFilter(SYMBOL_LAYER_ID, ['==', node.id, ['get', 'parentId']]);
  126. map.setFilter(GEO_LAYER_ID, ['in', node.id, ['get', 'deptIds']]);
  127. map.setFilter(GEO_BORDER_ID, ['in', node.id, ['get', 'deptIds']]);
  128. map.flyTo({
  129. center: [node.longitude, node.latitude],
  130. zoom: node.zoom,
  131. });
  132. } else {
  133. const popup = new vjmap.Popup({ maxWidth: '1000' });
  134. const app = createApp(LeafPopup, {
  135. node,
  136. callback() {
  137. toggleCADMap(true, node).then(() => {
  138. // 将历史栈推一个进去,因为用户要点击返回上一级时需要
  139. historyStack.value.push(node);
  140. });
  141. },
  142. });
  143. const el = document.createElement('div');
  144. app.mount(el);
  145. // 将历史栈拉一个出来,因为点击后页面上并没有钻入下一级
  146. historyStack.value.pop();
  147. popup.setLngLat([node.longitude, node.latitude]).setDOMContent(el).addTo(map);
  148. }
  149. }
  150. /** 返回上一级操作,如果存在CAD地图,则切换至底图地图,否则回退历史记录栈 */
  151. async function revertStack() {
  152. if (historyStack.value.length === 1) return;
  153. historyStack.value.pop()!;
  154. toggleCADMap(false).then(() => {
  155. markerClickHandler();
  156. });
  157. }
  158. /** 初始化地图对象,仅负责初始化地图及其瓦片底图,待map加载完成后返回 */
  159. async function initMap(HTMLElement) {
  160. const map = new vjmap.Map({
  161. container: HTMLElement,
  162. // 这个zoom配合了getGeoJSON,要改两边都要改
  163. maxZoom: 20,
  164. minZoom: 1,
  165. center: [DEFAULT_NODE.longitude, DEFAULT_NODE.latitude],
  166. zoom: DEFAULT_NODE.zoom,
  167. // style: svc.rasterStyle('https://webrd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}'),
  168. style: `${window.location.origin}/js/shanxi.map.json`,
  169. });
  170. await map.onLoad();
  171. return map;
  172. }
  173. const mapLoading = ref(false);
  174. let cadOpened: boolean;
  175. /** 切换CAD地图和瓦片地图的显示,通过重新初始化进行切换,避免出现动画异常和多个DOM节点 */
  176. async function toggleCADMap(visiable: boolean, data?: any) {
  177. if (cadOpened === visiable) return;
  178. cadOpened = visiable;
  179. mapLoading.value = true;
  180. map?.destory();
  181. try {
  182. if (visiable) {
  183. const [m, res] = await Promise.all([
  184. initMap2d(mapContainer.value!, {
  185. // fileUrl: node.url
  186. // fileUrl: `${globSetting.apiUrl}/sys/common/static/webfile/hongliulin_default.dwg`,
  187. style: { backcolor: 0xe6f3ff },
  188. }),
  189. getGoafList({
  190. mineCode: data ? data.mineCode : mineStore.getRoot?.fax,
  191. deptId: data ? data.id : mineStore.getRootId,
  192. }),
  193. ]);
  194. map = m;
  195. renderGoafMarkers(res); // 渲染标记
  196. } else {
  197. // const hide = message.loading('地图加载中...', 0);
  198. const [m, res] = await Promise.all([
  199. initMap(mapContainer.value!),
  200. getGeoJSON({
  201. deptId: mineStore.getRootId,
  202. pageSize: 9999,
  203. }),
  204. ]);
  205. const [geojson, __, markers] = res;
  206. // hide();
  207. map = m;
  208. initMapGeoJSON(map, geojson);
  209. initMapMarker(map, markers);
  210. }
  211. } finally {
  212. mapLoading.value = false;
  213. }
  214. }
  215. onMounted(() => {
  216. toggleCADMap(mineStore.getRoot?.isLeaf || false);
  217. });
  218. onUnmounted(() => {
  219. map?.remove();
  220. // app?.destroy();
  221. });
  222. </script>
  223. <style lang="less">
  224. .map-container {
  225. flex: 1;
  226. height: 100%;
  227. width: 100%;
  228. position: fixed;
  229. top: 0;
  230. left: 0;
  231. z-index: @simple-map-z-index;
  232. // filter: grayscale(80%) invert(0%) sepia(10%) hue-rotate(150deg) saturate(200%) brightness(100%) contrast(100%);
  233. }
  234. .cad-container {
  235. display: none;
  236. z-index: @cad-map-z-index;
  237. background: @map-bg !important;
  238. }
  239. // .map-container canvas {
  240. .light-blue-theme {
  241. filter: invert(0.1) hue-rotate(190deg) saturate(2);
  242. // filter: sepia(0.3) hue-rotate(180deg) saturate(1.8);
  243. }
  244. .vjmapgis-popup {
  245. color: @white;
  246. // &-content-wrapper {
  247. // background-color: @map-popup-bg;
  248. // }
  249. &-anchor-bottom &-tip {
  250. border-top-color: @map-popup-bg;
  251. }
  252. &-content {
  253. // margin: 13px 20px;
  254. // min-width: 300px;
  255. background-color: @map-popup-bg;
  256. box-shadow: 0 0 10px #00000088;
  257. // &__popup {
  258. // }
  259. }
  260. }
  261. .map-reset-btn {
  262. position: absolute;
  263. right: 30%; /* 距离右边 20px */
  264. z-index: 2; /* 确保在地图控件之上 */
  265. top: @header-height;
  266. // padding: 10px 15px;
  267. margin: 10px 0;
  268. }
  269. </style>