Просмотр исходного кода

[Pref 0000] 地图层级管理及交互优化

houzekong 2 недель назад
Родитель
Сommit
3f6b7e93a8

+ 106 - 77
src/layouts/default/feature/SimpleMap.vue

@@ -1,7 +1,7 @@
 <template>
   <div ref="mapContainer" class="map-container"></div>
   <div v-if="!isTopLevel" class="map-reset-btn">
-    <Button :loading="mapLoading" type="primary" @click="revertStack">返回上级</Button>
+    <Button :loading="mapLoading" type="primary" @click="revertStack">返回上级{{ historyStack.length }}</Button>
   </div>
 </template>
 
@@ -15,7 +15,7 @@
   // import type vjmap3d from 'vjmap3d';
   import { getGeoJSON, getShanxiGeoJSON } from '/@/api/sys/map';
   import { useMineDepartmentStore } from '/@/store/modules/mine';
-  import { find, get, last } from 'lodash-es';
+  import { get, last } from 'lodash-es';
   // import { env } from '/@/views/system/cadFile/env';
   import { StatusColorEnum } from '/@/enums/jeecgEnum';
   import { generateSimplePopup } from './hooks/popup';
@@ -23,6 +23,7 @@
   import { getGoafList } from '/@/views/system/cadFile/cad.api';
   import LeafPopup from './components/LeafPopup.vue';
   import { watchTriggerable } from '@vueuse/core';
+  import { findPath, listToTree } from '/@/utils/helper/treeHelper';
   // import { useGlobSetting } from '/@/hooks/setting';
 
   const props = defineProps<{
@@ -31,14 +32,19 @@
 
   const appStore = useAppStore();
   const mineStore = useMineDepartmentStore();
-  // const globSetting = useGlobSetting();
+  const mapContainer = ref<HTMLElement>();
+  const mapLoading = ref(false);
+
+  // 设置地图中当前的参数以供其他页面使用
   appStore.setSimpleMapParams({ deptId: mineStore.getRootId, isLeaf: mineStore.getRoot?.isLeaf });
-  // const route = useRoute();
 
-  const mapContainer = ref<HTMLElement>();
   // let svc = new vjmap.Service(env.serviceUrl, env.accessToken);
-  let map: vjmap.Map;
   // let app: vjmap3d.App;
+  let map: vjmap.Map;
+  // 标记单位集合,用来为后续计算历史记录提供支持
+  let markerCollection = [];
+  // CAD是否可见
+  let cadOpened: boolean;
 
   const GEO_LAYER_ID = 'geojson-layer';
   const GEO_BORDER_ID = 'geoline-layer';
@@ -48,13 +54,18 @@
     id: mineStore.getRootId!,
     name: '根节点',
     parentId: null,
-    longitude: 108.367283,
-    latitude: 36.024691,
+    longitude: 109.425873,
+    latitude: 35.83317,
     zoom: 6,
     isLeaf: false,
   };
+  const DEFAULT_TREE_CONFIG = {
+    id: 'id',
+    pid: 'parentId',
+  };
 
-  const historyStack = ref([DEFAULT_NODE]); // 用于存储历史节点的栈,实现返回上一级功能
+  // 用于存储历史节点的栈,实现返回上一级功能
+  const historyStack = ref([DEFAULT_NODE]);
   const isTopLevel = computed(() => historyStack.value.length === 1);
 
   /** 根据GeoJSON文件绘制省市边界和填充色到地图上,其将作为一个新图层 */
@@ -79,7 +90,7 @@
     map.hoverFeatureState(GEO_LAYER_ID);
   }
 
-  /** 根据标记点信息绘制两个图层,一个圆标点图层和一个文本标点图层 */
+  /** 根据标记点信息绘制两个图层,一个圆标点图层和一个文本标点图层,添加标记点注记及点击事件处理逻辑 */
   function initMapMarker(map: vjmap.Map, markers) {
     // 绘制背景圆圈
     const circles = new vjmap.Circle({
@@ -121,9 +132,14 @@
     symbols.addTo(map);
 
     circles.clickLayer((e) => {
-      if (e.defaultPrevented) return; //  如果事件之前阻止了,则不再执行了
-      historyStack.value.push(get(e, 'features[0].properties', DEFAULT_NODE));
-      markerClickHandler();
+      if (e.defaultPrevented) return;
+      const node = get(e, 'features[0].properties', DEFAULT_NODE);
+      // 用户从地图点到矿节点需要显示详情框,不需要走set-watch的路线重新初始化地图
+      if (node.isLeaf) {
+        laefNodeClickHandler(node);
+      } else {
+        mineStore.setDepartById(node.id);
+      }
       e.preventDefault(); // 阻止之后的事件执行
     });
   }
@@ -146,61 +162,76 @@
     return map;
   }
 
-  /** 标记点点击后,如果不是叶节点那么聚焦到下一级,如果已经是叶节点了则显示该节点的CAD地图 */
+  /** 标记点点击事件处理,如果不是叶节点那么聚焦到下一级 */
   function markerClickHandler() {
     const node: any = last(historyStack.value);
     if (!node) return;
+    if (node.isLeaf) return;
 
-    // 避免依赖isLeaf的首页进入矿端模式
     appStore.setSimpleMapParams({ deptId: node.id, isLeaf: false });
 
     // 标点点击后,如果是叶节点需要显示详情框,点击标题后进CAD图
-    if (!node.isLeaf) {
-      map.setFilter(CIRCLE_LAYER_ID, ['==', node.id, ['get', 'parentId']]);
-      map.setFilter(SYMBOL_LAYER_ID, ['==', node.id, ['get', 'parentId']]);
-      map.setFilter(GEO_LAYER_ID, ['in', node.id, ['get', 'deptIds']]);
-      map.setFilter(GEO_BORDER_ID, ['in', node.id, ['get', 'deptIds']]);
-
-      map.flyTo({
-        center: [node.longitude, node.latitude],
-        zoom: node.zoom,
-      });
-    } else {
-      const popup = new vjmap.Popup({ maxWidth: '1000' });
-      const app = createApp(LeafPopup, {
-        node,
-        callback() {
-          toggleCADMap(true, node).then(() => {
-            appStore.setSimpleMapParams({ deptId: node.id, isLeaf: true });
-            // 将历史栈推一个进去,因为用户要点击返回上一级时需要
-            historyStack.value.push(node);
-          });
-        },
-      });
-      const el = document.createElement('div');
-      app.mount(el);
-      // 将历史栈拉一个出来,因为点击后页面上并没有钻入下一级
-      historyStack.value.pop();
-
-      popup.setLngLat([node.longitude, node.latitude]).setDOMContent(el).addTo(map);
-    }
+    map.setFilter(CIRCLE_LAYER_ID, ['==', node.id, ['get', 'parentId']]);
+    map.setFilter(SYMBOL_LAYER_ID, ['==', node.id, ['get', 'parentId']]);
+    map.setFilter(GEO_LAYER_ID, ['in', node.id, ['get', 'deptIds']]);
+    map.setFilter(GEO_BORDER_ID, ['in', node.id, ['get', 'deptIds']]);
+
+    map.flyTo({
+      center: [node.longitude, node.latitude],
+      zoom: node.zoom,
+    });
   }
 
-  /** 返回上一级操作,如果存在CAD地图,则切换至底图地图,否则回退历史记录栈 */
-  async function revertStack() {
-    if (historyStack.value.length === 1) return;
-    historyStack.value.pop()!;
+  /** 叶节点标记点击事件处理,叶节点需要显示该节点的详情框 */
+  function laefNodeClickHandler(node: any) {
+    if (!node) return;
+    if (!node.isLeaf) return;
 
-    toggleCADMap(false).then(() => {
-      markerClickHandler();
+    // 避免依赖isLeaf的首页进入矿端模式
+    appStore.setSimpleMapParams({ deptId: node.id, isLeaf: false });
+    // 这里推出去一个是因为点击到矿端之后不需要再下钻,那么历史栈应该保持在上一层以确保返回上一级可用
+    // 待到用户确实点击了详情框标题后在推一项历史可保证历史栈的正确
+    // historyStack.value.pop();
+
+    const popup = new vjmap.Popup({ maxWidth: '1000' });
+    const app = createApp(LeafPopup, {
+      node,
+      callback() {
+        // 切换显示CAD图
+        toggleCADMap(true, node).then(() => {
+          appStore.setSimpleMapParams({ deptId: node.id, isLeaf: true });
+          historyStack.value.push(node);
+        });
+      },
+    });
+    const el = document.createElement('div');
+    app.mount(el);
+
+    popup.setLngLat([node.longitude, node.latitude]).setDOMContent(el).addTo(map);
+    map.flyTo({
+      center: [node.longitude, node.latitude + 0.2],
+      zoom: node.zoom,
     });
   }
 
-  const mapLoading = ref(false);
-  let cadOpened: boolean;
-  /** 切换CAD地图和瓦片地图的显示,通过重新初始化进行切换,避免出现动画异常和多个DOM节点 */
+  /** 返回上一级操作,如果存在CAD底图,则切换至地图,否则回退历史记录栈 */
+  async function revertStack() {
+    if (isTopLevel.value) return;
+    historyStack.value.pop();
+
+    // 用户点击返回后绝对不可能停留在CAD底图上,因此这里不区分
+    const id = last(historyStack.value)!.id;
+    if (id === mineStore.getDepartId) {
+      trigger();
+    } else {
+      mineStore.setDepartById(id);
+    }
+  }
+
+  /** 切换CAD底图和瓦片地图底图的显示,通过重新初始化进行切换,避免出现动画异常和多个DOM节点 */
   async function toggleCADMap(visiable: boolean, data?: any) {
-    if (cadOpened === visiable) return;
+    // CAD底图不显示的时候无需再次初始化地图,显示时需要根据data来切换不同图纸
+    if (cadOpened === visiable && visiable === false) return;
 
     cadOpened = visiable;
     mapLoading.value = true;
@@ -229,14 +260,15 @@
         const [m, res] = await Promise.all([
           initMap(mapContainer.value!),
           props.slience
-            ? getShanxiGeoJSON().then((r) => [r, {}, []])
+            ? getShanxiGeoJSON().then((r) => [r, [], []])
             : getGeoJSON({
                 deptId: mineStore.getRootId,
                 pageSize: 9999,
               }),
         ]);
-        const [geojson, __, markers] = res;
+        const [geojson, records, markers] = res;
         // hide();
+        markerCollection = listToTree(records, DEFAULT_TREE_CONFIG);
         map = m;
 
         initMapGeoJSON(map, geojson);
@@ -247,39 +279,36 @@
     }
   }
 
-  // 监听全局部门变化,因为部门可能是叶部门所以也负责切换地图
+  // 监听全局矿井ID的方法,同时也是负责初始化地图的方法
+  // 两种方式触发watch,一种是点击全局矿井选择器
+  // 如果全局矿井选择了非矿端,需要显示地图并根据全局矿井ID设置好历史记录
+  // 如果是选择了矿端的页面,需要设置历史记录并显示矿端CAD图纸
+  // 另一种方式是点击地图上的标记点,普通标点下钻到下一级,矿端标点不走watch的逻辑
   const { trigger } = watchTriggerable(
     () => mineStore.getDepartId,
     async (id) => {
-      if (props.slience) {
-        return toggleCADMap(mineStore.getRoot?.isLeaf || false);
-      } else {
-        await toggleCADMap(mineStore.getDepart?.isLeaf || mineStore.getRoot?.isLeaf || false);
-
-        const { source } = map.getLayer(CIRCLE_LAYER_ID) as any;
-        const { features } = map.getSourceData(source);
-        const element = find(features, (e) => e.id === id)?.properties;
-
-        if (!element) {
-          historyStack.value = [DEFAULT_NODE];
-        } else {
-          historyStack.value.push(element);
-        }
-        markerClickHandler();
-      }
+      if (!mapContainer.value) return;
+
+      // 如果是静默模式就简单展示默认地图/图纸即可
+      if (props.slience) return toggleCADMap(mineStore.getRoot?.isLeaf || false);
+
+      // 先判断显示什么图纸,若是矿端显示CAD图纸
+      await toggleCADMap(mineStore.getDepart?.isLeaf || false, mineStore.getDepart);
+
+      // 不论是不是矿端,历史记录都是一样的,用户点击返回上一级都能正常返回
+      historyStack.value = findPath(markerCollection, (n) => n.id === id) as any[];
+
+      // 此外如果是非矿端那么模拟触发地图点击事件
+      markerClickHandler();
     }
   );
 
   onMounted(() => {
     trigger();
-    // toggleCADMap(mineStore.getRoot?.isLeaf || false).then(() => {
-    //   triggerWatch();
-    // });
   });
 
   onUnmounted(() => {
     map?.remove();
-    // app?.destroy();
   });
 </script>
 

+ 2 - 2
src/layouts/default/feature/components/LeafPopup.vue

@@ -90,10 +90,10 @@
   const alarmLevelColor = ref([StatusColorEnum.red, StatusColorEnum.blue, StatusColorEnum.gold, StatusColorEnum.purple, StatusColorEnum.red]);
 
   const coalSeamList = computed(() => {
-    return JSON.parse(props.node.coalSeamList);
+    return props.node.coalSeamList;
   });
   const goafDataList = computed(() => {
-    return JSON.parse(props.node.goafDataList);
+    return props.node.goafDataList;
   });
 </script>
 <style lang="less">

+ 6 - 13
src/layouts/default/header/index.vue

@@ -9,7 +9,7 @@
 
     <!-- menu start -->
     <div :class="`${prefixCls}-menu`">
-      <MineCascader v-model:value="deptId" :changeOnSelect="true" :initFromStore="true" @change="setGlobDept">
+      <MineCascader v-model:value="getDepartId" :changeOnSelect="true" :initFromStore="true" @change="setDepartById">
         <template #clearIcon>X</template>
       </MineCascader>
     </div>
@@ -67,8 +67,9 @@
   import { useUserStore } from '/@/store/modules/user';
   import { useI18n } from '/@/hooks/web/useI18n';
   import MineCascader from '/@/components/Form/src/jeecg/components/MineCascader/MineCascader.vue';
-  import { useAppStore } from '/@/store/modules/app';
+  // import { useAppStore } from '/@/store/modules/app';
   import { useMineDepartmentStore } from '/@/store/modules/mine';
+  import { storeToRefs } from 'pinia';
 
   const { t } = useI18n();
 
@@ -167,16 +168,8 @@
         showLoginSelect();
       });
 
-      const appStore = useAppStore();
       const mineStore = useMineDepartmentStore();
-      const deptId = ref('');
-      function setGlobDept(deptId: string) {
-        mineStore.setDepartById(deptId);
-        appStore.setSimpleMapParams({
-          deptId: mineStore.getDepartId || mineStore.getRootId,
-          isLeaf: mineStore.getDepart?.isLeaf || mineStore.getRoot?.isLeaf,
-        });
-      }
+      const { getDepartId } = storeToRefs(mineStore);
 
       return {
         prefixCls,
@@ -197,8 +190,8 @@
         loginSelectRef,
         title,
         t,
-        deptId,
-        setGlobDept,
+        getDepartId,
+        setDepartById: mineStore.setDepartById,
       };
     },
   });