SimpleMap.vue 10 KB

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