Jelajahi Sumber

[Fix 0000] 修复CAD地图切换不成功以及地图管理表单问题修复

houzekong 1 bulan lalu
induk
melakukan
d0865b4293

+ 15 - 19
src/layouts/default/feature/SimpleMap.vue

@@ -28,17 +28,18 @@
   // import { env } from '/@/views/system/cadFile/env';
   import { StatusColorEnum } from '/@/enums/jeecgEnum';
   import { generateSimplePopup } from './hooks/popup';
-  import { initMap2d, renderGoafMarkers } from '/@/views/system/cadFile/app';
   import { getGoafList, getMineFileList } 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';
+  import { useMap2D } from '/@/views/system/cadFile/app';
 
   const props = defineProps<{
     slience: boolean;
   }>();
 
+  const { initMap2d, renderGoafMarkers } = useMap2D();
   const appStore = useAppStore();
   const mineStore = useMineDepartmentStore();
   const mapContainer = ref<HTMLElement>();
@@ -209,10 +210,7 @@
       node,
       callback() {
         // 切换显示CAD图
-        toggleCADMap(true).then(() => {
-          mineStore.setDepartById(node.id);
-          // historyStack.value.push(node);
-        });
+        mineStore.setDepartById(node.id);
       },
     });
     const el = document.createElement('div');
@@ -262,14 +260,7 @@
       }
 
       if (cadOpened) {
-        // 如果当前选项下没有已选中的地图,那么意味着需要新获取选项,否则无需发请求
-        if (!fileOptions.value.some((e) => e.wjId === fileId.value)) {
-          const { records } = await getMineFileList({ deptId: mineStore.getDepartId, pageSize: 999 });
-          fileOptions.value = records;
-          fileId.value = get(records, '[0].wjId');
-        }
-
-        const [m, res] = await Promise.all([
+        const [__, res] = await Promise.all([
           initMap2d(mapContainer.value!, { wjId: fileId.value, style: { backcolor: 0xe6f3ff } }),
           props.slience
             ? Promise.resolve([])
@@ -278,7 +269,6 @@
                 deptId: mineStore.getDepartId,
               }),
         ]);
-        map = m;
 
         renderGoafMarkers(res); // 渲染标记
       } else {
@@ -311,6 +301,16 @@
       if (props.slience) return toggleCADMap(mineStore.getRoot?.isLeaf || false);
 
       // 先判断显示什么图纸,若是矿端显示CAD图纸
+      if (mineStore.getDepart?.isLeaf) {
+        appStore.setSimpleMapParams({ deptId: id, isLeaf: true });
+
+        // 矿端需要先初始化图纸下拉框,下拉框触发一次change事件调用toggleCADMap,
+        // 如果当前选项下没有已选中的地图,那么意味着需要新获取选项,否则无需发请求
+        const { records } = await getMineFileList({ deptId: mineStore.getDepartId, pageSize: 999 });
+        fileOptions.value = records;
+        fileId.value = get(records, '[0].wjId');
+      }
+
       await toggleCADMap(mineStore.getDepart?.isLeaf || false);
 
       // 不论是不是矿端,历史记录都是一样的,用户点击返回上一级都能正常返回
@@ -318,10 +318,6 @@
 
       // 此外如果是非矿端那么模拟触发地图点击事件
       markerClickHandler();
-
-      if (mineStore.getDepart?.isLeaf) {
-        appStore.setSimpleMapParams({ deptId: id, isLeaf: true });
-      }
     }
   );
 
@@ -330,7 +326,7 @@
   });
 
   onUnmounted(() => {
-    map?.remove();
+    map?.destory();
   });
 </script>
 

+ 0 - 0
src/views/dashboard/SealedGoafMine/index.vue → src/views/dashboard/SealedGoafMine/index.txt


+ 432 - 461
src/views/system/cadFile/app.ts

@@ -1,4 +1,4 @@
-import vjmap, { Map, GeoBounds, GeoProjection, Service } from 'vjmap';
+import vjmap, { Map, GeoBounds, GeoProjection } from 'vjmap';
 // import { App, MapThreeLayer } from 'vjmap3d';
 import 'vjmap/dist/vjmap.min.css';
 import { useAppStore } from '/@/store/modules/app';
@@ -9,535 +9,506 @@ import GoafPopup from './components/GoafPopup.vue';
 import { get } from 'lodash-es';
 import { StatusColorEnum } from '/@/enums/jeecgEnum';
 
-// 图片路径
 import customImage from '/@/assets/icons/location-icon.svg';
 import customImage3D from '/@/assets/icons/location-icon3D.png';
 import { env } from './env';
+import { tryOnUnmounted } from '@vueuse/core';
 
-// ===================== 全局状态 =====================
-let map: Map | null = null;
-let svc: Service | null = null;
-let prj: GeoProjection | null = null;
-let allMarkers: any[] = [];
-let sensorId = 1;
-let currentPopup: any = null;
-
-// ===================== 地图初始化部分 =====================
-// 初始化地图
-export const initMap2d = async (container: HTMLElement, config: { fileUrl?: string; style?: vjmap.IMapStyleParam; wjId?: string } = {}) => {
-  if (!container) {
-    message.error('地图容器不存在!');
-    throw new Error('地图容器不存在!');
-  }
+export type UseMap2DOptions = {
+  // fileUrl?: string;
+  style?: vjmap.IMapStyleParam;
+  wjId?: string;
+};
 
-  const appStore = useAppStore();
-  const {
-    // fileUrl = 'https://vjmap.com/static/assets/data/gym.dwg',
-    style = vjmap.openMapLightStyle(),
-    wjId,
-  } = config;
-
-  container.style.background = 'transparent';
-  container.style.width = `${get(container, 'parentElement.clientWidth', 0) / appStore.widthScale}px`;
-  container.style.height = `${get(container, 'parentElement.clientHeight', 0) / appStore.heightScale}px`;
-
-  svc = new vjmap.Service(env.serviceUrl, env.accessToken);
-
-  const res = await svc.openMap({
-    mapid: wjId || 'c56c59154345', // 地图ID,上传文件后获得的mapid
-    fileid: wjId || 'c56c59154345', // 地图ID,上传文件后获得的mapid
-    mapopenway: vjmap.MapOpenWay.GeomRender,
-    style,
-  });
-
-  if (res.error) {
-    message.error(`地图加载失败:${res.error}`);
-    console.error(res.error);
-    // return null;
-  }
+export type GoafItem = Record<string, any>;
 
-  // 获取地图范围
-  const mapExtent = GeoBounds.fromString(res.bounds);
-  // const mapExtent = GeoBounds.fromString(res.drawBounds);
-  // 根据地图范围建立几何投影坐标系
-  prj = new GeoProjection(mapExtent);
-  // 地图对象
-  map = new vjmap.Map({
-    container, // DIV容器ID
-    style: svc.rasterStyle(),
-    center: prj.toLngLat(mapExtent.center()),
-    zoom: 8, // 设置地图缩放级别
-    // pitch: 0,
-    // trackResize: false,
-    antialias: true, // 反锯齿
-    renderWorldCopies: false, // 不显示多屏地图
-  });
-
-  // 关联服务对象和投影对象
-  map.attach(svc, prj);
-  map.fitMapBounds();
-
-  await map.onLoad();
-
-  return map;
-  // const mapLayer = new MapThreeLayer(map, {
-  //   stat: { show: false, left: '0' },
-  //   scene: {
-  //     showAxesHelper: false,
-  //     axesHelperSize: 1,
-  //     defaultLights: false,
-  //     gridHelper: {
-  //       visible: env.gridHelper ?? true,
-  //       args: [1000, 1000],
-  //       cellSize: 10,
-  //       sectionSize: 100,
-  //     },
-  //   },
-  // });
-  // const app: App = mapLayer.app;
-  // map.addLayer(new vjmap.ThreeLayer({ context: mapLayer as any }));
-  // map.doubleClickZoom.disable();
-
-  // return app;
+export type MarkerStatus = {
+  sensorId: any;
+  id: any;
+  data: any;
+  lnglat: any;
+  position: any;
 };
 
-// 对外导出地图初始化方法
-// export const initMap2d = (id?) => createMapApp(id);
-
-// ===================== 地图操作处理部分 =====================
-// 拾取点位
-const pickPoint = async (markerOptions: any) => {
-  if (!map) return null;
-  let marker: any = null;
-  const actionPoint = await vjmap.Draw.actionDrawPoint(map, {
-    updatecoordinate: (e: any) => {
-      if (!e.lnglat) return;
-      if (!marker) {
-        marker = new vjmap.createMarker(markerOptions);
-        marker.setLngLat(e.lnglat);
-        marker.addTo(map);
-      } else {
-        marker.setLngLat(e.lnglat);
-      }
-    },
-    contextMenu: (e: any) => {
-      new vjmap.ContextMenu({
-        event: e.event.originalEvent,
-        theme: 'dark',
-        width: '250px',
-        items: [
-          {
-            label: '取消',
-            onClick: () => map?.fire('keyup', { keyCode: 27 }),
-          },
-        ],
-      });
-    },
-  });
-  if (actionPoint.cancel) {
-    marker?.remove();
-    return null;
-  }
-  return marker;
+export type UseMap2DReturn = {
+  map: vjmap.Map | null;
+  initMap2d: (container: HTMLElement, config?: UseMap2DOptions) => Promise<Map>;
+  destroyMap: () => void;
+  bindPosition: (props?: any) => Promise<any | null>;
+  removePosition: (sensorId: number | string, id: string | number) => Promise<boolean>;
+  getMarkerStatusList: () => MarkerStatus[];
+  getGoafIsPositioned: (goafItem: any) => boolean;
+  renderGoafMarkers: (goafList: any[]) => void;
 };
 
-/**
- * 判断坐标是否有效
- */
-export const getGoafIsPositioned = (goafItem: any): boolean => {
-  const hasValidX = isValidSingleCoord(goafItem.xcoordinate);
-  const hasValidY = isValidSingleCoord(goafItem.ycoordinate);
-  return hasValidX && hasValidY;
-};
+export function useMap2D(): UseMap2DReturn {
+  const allMarkers: any[] = [];
+  let prj: GeoProjection | null = null;
+  let map: vjmap.Map | null = null;
+  let sensorId = 1;
+  let currentPopup: any = null;
 
-// 获取所有标记状态列表
-export const getMarkerStatusList = () => {
-  if (!map) return [];
-  return allMarkers.map((marker) => ({
-    sensorId: marker.data?.sensorId,
-    id: marker.data?.id,
-    data: marker.data,
-    lnglat: marker.getLngLat(),
-    position: map?.fromLngLat(marker.getLngLat()),
-  }));
-};
+  const svc = new vjmap.Service(env.serviceUrl, env.accessToken);
 
-// 移除指定传感器标记
-export const removePosition = async (sensorId: number | string, id: string | number) => {
-  if (!map) {
-    message.error('地图未初始化完成!');
-    return false;
-  }
+  const destroyMap = (): void => {
+    closeAndCleanPopup();
+    cleanMarkers();
+    if (map) {
+      map?.destory();
+    }
+    map = null;
+    prj = null;
+  };
 
-  const targetIndex = allMarkers.findIndex((marker) => marker.data?.sensorId === sensorId || marker.data?.id === id);
-  if (targetIndex === -1) {
-    message.warning('未找到该传感器标记!');
-    return false;
-  }
+  tryOnUnmounted(destroyMap);
+
+  async function initMap2d(container: HTMLElement, config: UseMap2DOptions = {}): Promise<Map> {
+    if (!container) {
+      message.error('地图容器不存在!');
+      throw new Error('地图容器不存在!');
+    }
+
+    destroyMap();
 
-  try {
-    await updatateGoaf({
-      id: id,
-      xcoordinate: '',
-      ycoordinate: '',
+    const appStore = useAppStore();
+    const { style = vjmap.openMapLightStyle(), wjId } = config;
+
+    container.style.background = 'transparent';
+    container.style.width = `${get(container, 'parentElement.clientWidth', 0) / appStore.widthScale}px`;
+    container.style.height = `${get(container, 'parentElement.clientHeight', 0) / appStore.heightScale}px`;
+
+    const res = await svc.openMap({
+      mapid: wjId || 'c56c59154345',
+      fileid: wjId || 'c56c59154345',
+      mapopenway: vjmap.MapOpenWay.GeomRender,
+      style,
+    });
+
+    if (res.error) {
+      message.error(`地图加载失败:${res.error}`);
+      console.error(res.error);
+    }
+
+    const mapExtent = GeoBounds.fromString(res.bounds);
+    prj = new GeoProjection(mapExtent);
+
+    map = new vjmap.Map({
+      container,
+      style: svc.rasterStyle(),
+      center: prj.toLngLat(mapExtent.center()),
+      zoom: 8,
+      antialias: true,
+      renderWorldCopies: false,
     });
-    console.info('移除布点成功!');
-  } catch (apiError) {
-    message.error(`同步移除信息失败:${(apiError as Error).message}`);
-    console.error('移除接口调用失败:', apiError);
+
+    map.attach(svc, prj);
+    map.fitMapBounds();
+    await map.onLoad();
+
+    return map;
   }
 
-  allMarkers[targetIndex].remove();
-  allMarkers.splice(targetIndex, 1);
-  syncLocalStorage();
+  function isValidSingleCoord(coord: any): boolean {
+    return coord && coord.trim() !== '' && !isNaN(Number(coord));
+  }
 
-  return true;
-};
+  function getGoafIsPositioned(goafItem: any): boolean {
+    const hasValidX = isValidSingleCoord(goafItem.xcoordinate);
+    const hasValidY = isValidSingleCoord(goafItem.ycoordinate);
+    return hasValidX && hasValidY;
+  }
 
-// 布点方法
-export const bindPosition = async (props?: any) => {
-  if (!map || !svc) {
-    message.error('地图未初始化完成,请先加载地图!');
-    return null;
+  function getMarkerStatusList(): MarkerStatus[] {
+    if (!map) return [];
+    return allMarkers.map((marker) => ({
+      sensorId: marker.data?.sensorId,
+      id: marker.data?.id,
+      data: marker.data,
+      lnglat: marker.getLngLat(),
+      position: map?.fromLngLat(marker.getLngLat()),
+    }));
   }
 
-  map.fire('keyup', { keyCode: 27 });
-
-  const el = createMarkerElement(
-    {
-      alarmLevel: props?.alarmLevel,
-      devicePos: props?.devicePos,
-    },
-    false
-  );
-  const markerOptions = {
-    element: el,
-    anchor: 'bottom',
-    offset: [0, 0],
-  };
+  function syncLocalStorage(): void {
+    if (!map) return;
+    const markerData = allMarkers.map((m) => ({
+      position: map?.fromLngLat(m.getLngLat()),
+      data: m.data,
+    }));
+    localStorage.setItem('marker_sensor', JSON.stringify(markerData));
+  }
 
-  let marker: any = null;
-  if (!props || !props.position) {
-    marker = await pickPoint(markerOptions);
-    if (!marker) return null;
-
-    const confirmResult = await new Promise<boolean>((resolve) => {
-      Modal.confirm({
-        title: '保存布点',
-        content: '是否确认保存该布点?',
-        okText: '确定',
-        cancelText: '取消',
-        maskClosable: false,
-        centered: true,
-        onOk: () => resolve(true),
-        onCancel: () => resolve(false),
-      });
+  function closeAndCleanPopup(): void {
+    currentPopup?.remove();
+    currentPopup = null;
+  }
+
+  async function pickPoint(markerOptions: any): Promise<any | null> {
+    if (!map) return null;
+
+    let marker: any = null;
+    const actionPoint = await vjmap.Draw.actionDrawPoint(map, {
+      updatecoordinate: (e: any) => {
+        if (!e.lnglat) return;
+        if (!marker) {
+          marker = vjmap.createMarker(markerOptions);
+          marker.setLngLat(e.lnglat);
+          marker.addTo(map);
+        } else {
+          marker.setLngLat(e.lnglat);
+        }
+      },
+      contextMenu: (e: any) => {
+        new vjmap.ContextMenu({
+          event: e.event.originalEvent,
+          theme: 'dark',
+          width: '250px',
+          items: [
+            {
+              label: '取消',
+              onClick: () => map?.fire('keyup', { keyCode: 27 }),
+            },
+          ],
+        });
+      },
     });
 
-    if (!confirmResult) {
-      marker.remove();
+    if (actionPoint.cancel) {
+      marker?.remove();
       return null;
     }
+    return marker;
+  }
 
-    const lnglat = marker.getLngLat();
-    const id = props?.id;
+  function bindMarkerEvents(marker: any, id: string | number): void {
+    marker.setDraggable(false);
+    let isDragging = false;
 
-    if (!id) {
-      marker.remove();
-      message.error('缺少密闭ID,无法同步布点信息到服务器');
-      throw new Error('缺少密闭ID,无法同步布点信息到服务器');
+    marker.on('dragstart', () => {
+      isDragging = false;
+    });
+    marker.on('dragend', () => {
+      setTimeout(() => {
+        isDragging = false;
+      }, 150);
+      const lnglat = marker.getLngLat();
+      if (id) {
+        updatateGoaf({
+          id,
+          xcoordinate: lnglat.lng.toString(),
+          ycoordinate: lnglat.lat.toString(),
+        }).catch((err: Error) => {
+          message.error(`拖拽后同步坐标失败:${err.message}`);
+        });
+      }
+    });
+
+    marker.getElement().addEventListener('click', () => {
+      if (!isDragging) {
+        showMarkerPopup(marker);
+      }
+    });
+  }
+
+  function createMarkerElement(goafItem: any, showInfo: boolean = false): HTMLDivElement {
+    const riskMap = {
+      '1': '低风险',
+      '2': '一般风险',
+      '3': '较高风险',
+      '4': '高风险',
+    };
+
+    const alarmLevelColor = [StatusColorEnum.blue, StatusColorEnum.yellow, StatusColorEnum.gold, StatusColorEnum.red];
+    const el = document.createElement('div');
+    el.className = 'marker';
+    el.style.cssText = `
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      cursor: move;
+    `;
+
+    const level = parseInt(goafItem.alarmLevel, 10);
+    const color = level >= 1 && level <= 4 ? alarmLevelColor[level - 1] : '#333';
+
+    if (showInfo) {
+      const alarmLevelDiv = document.createElement('div');
+      alarmLevelDiv.style.cssText = `
+        font-size: 14px;
+        white-space: nowrap;
+        font-weight: bold;
+        display: flex;
+        align-items: center;
+      `;
+
+      const labelSpan = document.createElement('span');
+      labelSpan.textContent = '风险等级:';
+      labelSpan.style.color = '#333';
+
+      const valueSpan = document.createElement('span');
+      const riskText = riskMap[String(goafItem.alarmLevel)] || '正常';
+      valueSpan.textContent = goafItem.alarmLevel ? riskText : '正常';
+      valueSpan.style.color = color;
+
+      alarmLevelDiv.appendChild(labelSpan);
+      alarmLevelDiv.appendChild(valueSpan);
+      el.appendChild(alarmLevelDiv);
     }
 
-    try {
-      await updatateGoaf({
-        id: id,
-        xcoordinate: lnglat.lng.toString(),
-        ycoordinate: lnglat.lat.toString(),
-      });
-    } catch (apiError) {
-      marker.remove();
-      message.error(`同步布点信息失败:${(apiError as Error).message}`);
-      throw new Error(`同步布点信息失败:${(apiError as Error).message}`);
+    const iconDiv = document.createElement('div');
+    iconDiv.style.cssText = `
+      background-image: url("${showInfo ? customImage3D : customImage}");
+      width: 40px;
+      height: 45px;
+      background-size: 100% 100%;
+      background-position: center;
+      background-repeat: no-repeat;
+    `;
+    el.appendChild(iconDiv);
+
+    if (showInfo) {
+      const devicePosDiv = document.createElement('div');
+      devicePosDiv.style.cssText = `
+        position: absolute;
+        font-size: 14px;
+        color: #333;
+        margin-top: 2px;
+        white-space: nowrap;
+        max-width: 120px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        bottom: -20px;
+      `;
+      devicePosDiv.textContent = goafItem.devicePos;
+      el.appendChild(devicePosDiv);
+    }
+
+    return el;
+  }
+
+  function showMarkerPopup(marker: any): void {
+    if (!map) return;
+
+    closeAndCleanPopup();
+
+    const lnglat = marker.getLngLat();
+    const data = marker.data || {};
+
+    const tempContainer = document.createElement('div');
+    const popupApp = createApp({
+      render: () => h(GoafPopup, { data }),
+    });
+    popupApp.mount(tempContainer);
+
+    currentPopup = new vjmap.Popup({
+      closeButton: true,
+      closeOnClick: false,
+      maxWidth: '800px',
+      offset: [0, 0],
+    })
+      .setLngLat(lnglat)
+      .setDOMContent(tempContainer)
+      .addTo(map);
+
+    currentPopup.on('close', () => {
+      popupApp.unmount();
+      tempContainer.remove();
+      currentPopup = null;
+    });
+  }
+
+  async function bindPosition(props?: any): Promise<any | null> {
+    if (!map) {
+      message.error('地图未初始化完成,请先加载地图!');
+      return null;
     }
 
-    const currentLngLat = marker.getLngLat();
-    marker.remove();
+    map.fire('keyup', { keyCode: 27 });
 
-    const finalEl = createMarkerElement(
+    const el = createMarkerElement(
       {
         alarmLevel: props?.alarmLevel,
         devicePos: props?.devicePos,
       },
-      true
+      false
     );
 
-    marker = vjmap.createMarker({
-      element: finalEl,
+    const markerOptions: vjmap.createMarkerOptions = {
+      element: el,
       anchor: 'bottom',
       offset: [0, 0],
-    });
-    marker.setLngLat(currentLngLat);
-    marker.addTo(map);
-    marker.data = {
-      sensorId: sensorId++,
-      id: id,
-      ...(props || {}),
-    };
-  } else {
-    if (!Array.isArray(props.position)) {
-      throw new Error('预设坐标position格式错误,必须是[x, y]数组');
-    }
-    const lngLat = map.toLngLat(props.position);
-    if (!lngLat) {
-      throw new Error('转换坐标失败,无效的position');
-    }
-
-    marker = vjmap.createMarker(markerOptions);
-    marker.setLngLat(lngLat);
-    marker.data = {
-      ...props.data,
-      sensorId: props.data?.sensorId || sensorId++,
-      id: props.data?.id || props.id,
-      ...(props || {}),
     };
-    marker.addTo(map);
-  }
 
-  bindMarkerEvents(marker, marker.data?.id);
-  allMarkers.push(marker);
-  syncLocalStorage();
+    let marker: any = null;
+    if (!props || !props.position) {
+      marker = await pickPoint(markerOptions);
+      if (!marker) return null;
+
+      const confirmResult = await new Promise<boolean>((resolve) => {
+        Modal.confirm({
+          title: '保存布点',
+          content: '是否确认保存该布点?',
+          okText: '确定',
+          cancelText: '取消',
+          maskClosable: false,
+          centered: true,
+          onOk: () => resolve(true),
+          onCancel: () => resolve(false),
+        });
+      });
 
-  return {
-    sensorId: marker.data.sensorId,
-    id: marker.data.id,
-    lnglat: marker.getLngLat(),
-    xcoordinate: marker.getLngLat().lng.toString(),
-    ycoordinate: marker.getLngLat().lat.toString(),
-  };
-};
+      if (!confirmResult) {
+        marker.remove();
+        return null;
+      }
 
-// 渲染密闭列表标记
-export const renderGoafMarkers = (goafList: any[]) => {
-  if (!map || !prj) {
-    message.error('地图或投影坐标系未初始化完成!');
-    return;
-  }
+      const lnglat = marker.getLngLat();
+      const id = props?.id;
+      if (!id) {
+        marker.remove();
+        message.error('缺少密闭ID,无法同步布点信息到服务器');
+        throw new Error('缺少密闭ID,无法同步布点信息到服务器');
+      }
 
-  allMarkers.forEach((marker) => marker.remove());
-  allMarkers = [];
-  sensorId = 1;
+      try {
+        await updatateGoaf({
+          id,
+          xcoordinate: lnglat.lng.toString(),
+          ycoordinate: lnglat.lat.toString(),
+        });
+      } catch (apiError) {
+        marker.remove();
+        message.error(`同步布点信息失败:${(apiError as Error).message}`);
+        throw new Error(`同步布点信息失败:${(apiError as Error).message}`);
+      }
 
-  const validGoafList = goafList.filter((item) => getGoafIsPositioned(item));
-  if (validGoafList.length === 0) {
-    console.info('当前密闭列表无有效布点坐标,暂无标记可渲染');
-    return;
-  }
+      const currentLngLat = marker.getLngLat();
+      marker.remove();
 
-  for (const goafItem of validGoafList) {
-    try {
-      const lnglat = new vjmap.LngLat(Number(goafItem.xcoordinate), Number(goafItem.ycoordinate));
-      const el = createMarkerElement(goafItem, true);
+      const finalEl = createMarkerElement(
+        {
+          alarmLevel: props?.alarmLevel,
+          devicePos: props?.devicePos,
+        },
+        true
+      );
 
-      const marker = vjmap.createMarker({
-        element: el,
+      marker = vjmap.createMarker({
+        element: finalEl,
         anchor: 'bottom',
         offset: [0, 0],
       });
-      marker.setLngLat(lnglat);
+      marker.setLngLat(currentLngLat);
+      marker.addTo(map);
       marker.data = {
         sensorId: sensorId++,
-        id: goafItem.id,
-        ...goafItem,
+        id,
+        ...(props || {}),
       };
-      marker.addTo(map);
+    } else {
+      if (!Array.isArray(props.position)) {
+        throw new Error('预设坐标position格式错误,必须是[x, y]数组');
+      }
+      const lngLat = map.toLngLat(props.position);
+      if (!lngLat) {
+        throw new Error('转换坐标失败,无效的position');
+      }
 
-      bindMarkerEvents(marker, goafItem.id);
-      allMarkers.push(marker);
-    } catch (err) {
-      console.error(`渲染密闭${goafItem.id}标记失败:`, err);
+      marker = vjmap.createMarker(markerOptions);
+      marker.setLngLat(lngLat);
+      marker.data = {
+        ...props.data,
+        sensorId: props.data?.sensorId || sensorId++,
+        id: props.data?.id || props.id,
+        ...(props || {}),
+      };
+      marker.addTo(map);
     }
-  }
-};
-
-// ===================== 内部辅助函数 =====================
-/**
- * 校验单个坐标是否有效
- */
-const isValidSingleCoord = (coord: any): boolean => {
-  return coord && coord.trim() !== '' && !isNaN(Number(coord));
-};
 
-/**
- * 同步标记数据到本地缓存
- */
-const syncLocalStorage = (): void => {
-  if (!map) return;
-  const markerData = allMarkers.map((m) => ({
-    position: map?.fromLngLat(m.getLngLat()),
-    data: m.data,
-  }));
-  localStorage.setItem('marker_sensor', JSON.stringify(markerData));
-};
+    bindMarkerEvents(marker, marker.data?.id);
+    allMarkers.push(marker);
+    syncLocalStorage();
 
-/**
- * 关闭并清理弹窗
- */
-const closeAndCleanPopup = (): void => {
-  if (currentPopup) {
-    currentPopup.remove();
-    currentPopup = null;
+    return {
+      sensorId: marker.data.sensorId,
+      id: marker.data.id,
+      lnglat: marker.getLngLat(),
+      xcoordinate: marker.getLngLat().lng.toString(),
+      ycoordinate: marker.getLngLat().lat.toString(),
+    };
   }
-};
 
-/**
- * 绑定标记的拖拽和点击事件
- */
-const bindMarkerEvents = (marker: any, id: string | number): void => {
-  marker.setDraggable(false);
-  let isDragging = false;
-
-  marker.on('dragstart', () => {
-    isDragging = false;
-  });
-  marker.on('dragend', () => {
-    setTimeout(() => {
-      isDragging = false;
-    }, 150);
-    const lnglat = marker.getLngLat();
-    if (id) {
-      updatateGoaf({
-        id,
-        xcoordinate: lnglat.lng.toString(),
-        ycoordinate: lnglat.lat.toString(),
-      }).catch((err: Error) => {
-        message.error(`拖拽后同步坐标失败:${err.message}`);
-      });
+  function renderGoafMarkers(goafList: any[]): void {
+    if (!map || !prj) {
+      message.error('地图或投影坐标系未初始化完成!');
+      return;
     }
-  });
 
-  marker.getElement().addEventListener('click', () => {
-    if (!isDragging) {
-      showMarkerPopup(marker);
-    }
-  });
-};
-/**
- * 创建传感器标记 DOM 元素
- */
-const createMarkerElement = (goafItem: any, showInfo: boolean = false): HTMLDivElement => {
-  const riskMap = {
-    '1': '低风险',
-    '2': '一般风险',
-    '3': '较高风险',
-    '4': '高风险',
-  };
+    cleanMarkers();
 
-  // 定义颜色映射,索引对应 alarmLevel - 1
-  // StatusColorEnum.blue, gold, purple, red 分别对应 1, 2, 3, 4
-  const alarmLevelColor = [StatusColorEnum.blue, StatusColorEnum.yellow, StatusColorEnum.gold, StatusColorEnum.red];
-
-  // const customImage = `/src/assets/icons/location-icon.svg`;
-  // const customImage3D = `/src/assets/icons/location-icon3D.png`;
-  const el = document.createElement('div');
-  el.className = 'marker';
-  el.style.cssText = `
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    cursor: move;
-  `;
-
-  // 获取当前等级对应的颜色
-  const level = parseInt(goafItem.alarmLevel, 10);
-  const color = level >= 1 && level <= 4 ? alarmLevelColor[level - 1] : '#333';
-
-  if (showInfo) {
-    const alarmLevelDiv = document.createElement('div');
-    alarmLevelDiv.style.cssText = `
-      font-size: 14px;
-      white-space: nowrap;
-      font-weight: bold;
-      display: flex; /* 使用 flex 布局让文字并排 */
-      align-items: center;
-    `;
-
-    // 1. 创建固定文本节点 "风险等级:"
-    const labelSpan = document.createElement('span');
-    labelSpan.textContent = '风险等级:';
-    labelSpan.style.color = '#333'; // 强制设置为深色/默认色
-
-    // 2. 创建动态文本节点 (应用动态颜色)
-    const valueSpan = document.createElement('span');
-    const riskText = riskMap[String(goafItem.alarmLevel)] || '正常';
-    valueSpan.textContent = goafItem.alarmLevel ? riskText : '正常';
-    valueSpan.style.color = color; // 应用动态颜色
+    const validGoafList = goafList.filter((item) => getGoafIsPositioned(item));
+    if (validGoafList.length === 0) {
+      console.info('当前密闭列表无有效布点坐标,暂无标记可渲染');
+      return;
+    }
 
-    // 组装
-    alarmLevelDiv.appendChild(labelSpan);
-    alarmLevelDiv.appendChild(valueSpan);
+    for (const goafItem of validGoafList) {
+      try {
+        const lnglat = new vjmap.LngLat(Number(goafItem.xcoordinate), Number(goafItem.ycoordinate));
+        const el = createMarkerElement(goafItem, true);
+
+        const marker = vjmap.createMarker({
+          element: el,
+          anchor: 'bottom',
+          offset: [0, 0],
+        });
+        marker.setLngLat(lnglat);
+        // @ts-ignore next-line
+        marker.data = {
+          sensorId: sensorId++,
+          id: goafItem.id,
+          ...goafItem,
+        };
+        marker.addTo(map);
 
-    el.appendChild(alarmLevelDiv);
+        bindMarkerEvents(marker, goafItem.id);
+        allMarkers.push(marker);
+      } catch (err) {
+        console.error(`渲染密闭${goafItem.id}标记失败:`, err);
+      }
+    }
   }
 
-  const iconDiv = document.createElement('div');
-  iconDiv.style.cssText = `
-    background-image: url("${showInfo ? customImage3D : customImage}");
-    width: 40px;
-    height: 45px;
-    background-size: 100% 100%;
-    background-position: center;
-    background-repeat: no-repeat;
-  `;
-  el.appendChild(iconDiv);
-
-  if (showInfo) {
-    const devicePosDiv = document.createElement('div');
-    devicePosDiv.style.cssText = `
-      position: absolute;
-      font-size: 14px;
-      color: #333;
-      margin-top: 2px;
-      white-space: nowrap;
-      max-width: 120px;
-      overflow: hidden;
-      text-overflow: ellipsis;
-      bottom: -20px;
-    `;
-    devicePosDiv.textContent = goafItem.devicePos;
-    el.appendChild(devicePosDiv);
+  function cleanMarkers(): void {
+    allMarkers.forEach((marker) => marker.remove());
+    allMarkers.length = 0;
+    sensorId = 1;
   }
 
-  return el;
-};
+  return {
+    map,
+    initMap2d,
+    destroyMap,
+    bindPosition,
+    removePosition,
+    getMarkerStatusList,
+    getGoafIsPositioned,
+    renderGoafMarkers,
+  };
+}
 
-// 显示标记信息弹窗
-const showMarkerPopup = (marker: any) => {
-  if (!map) return;
-
-  closeAndCleanPopup();
-
-  const lnglat = marker.getLngLat();
-  const data = marker.data || {};
-
-  const tempContainer = document.createElement('div');
-  const popupApp = createApp({
-    render: () => h(GoafPopup, { data }),
-  });
-  popupApp.mount(tempContainer);
-
-  currentPopup = new vjmap.Popup({
-    closeButton: true,
-    closeOnClick: false,
-    maxWidth: '800px',
-    offset: [0, 0],
-  })
-    .setLngLat(lnglat)
-    .setDOMContent(tempContainer)
-    .addTo(map);
-
-  currentPopup.on('close', () => {
-    popupApp.unmount();
-    tempContainer.remove();
-    currentPopup = null;
-  });
+const defaultMap2DInstance: { current: UseMap2DReturn | null } = { current: null };
+const getDefaultMap2D = (): UseMap2DReturn => {
+  if (!defaultMap2DInstance.current) {
+    defaultMap2DInstance.current = useMap2D();
+  }
+  return defaultMap2DInstance.current;
 };
+
+export const initMap2d = (container: HTMLElement, config: UseMap2DOptions = {}) => getDefaultMap2D().initMap2d(container, config);
+export const destroyMap = () => getDefaultMap2D().destroyMap();
+export const bindPosition = (props?: any) => getDefaultMap2D().bindPosition(props);
+export const removePosition = (sensorId: number | string, id: string | number) => getDefaultMap2D().removePosition(sensorId, id);
+export const getMarkerStatusList = () => getDefaultMap2D().getMarkerStatusList();
+export const getGoafIsPositioned = (goafItem: any) => getDefaultMap2D().getGoafIsPositioned(goafItem);
+export const renderGoafMarkers = (goafList: any[]) => getDefaultMap2D().renderGoafMarkers(goafList);

+ 544 - 0
src/views/system/cadFile/app.txt

@@ -0,0 +1,544 @@
+import vjmap, { Map, GeoBounds, GeoProjection, Service } from 'vjmap';
+// import { App, MapThreeLayer } from 'vjmap3d';
+import 'vjmap/dist/vjmap.min.css';
+import { useAppStore } from '/@/store/modules/app';
+import { message, Modal } from 'ant-design-vue';
+import { updatateGoaf } from './cad.api';
+import { createApp, h } from 'vue';
+import GoafPopup from './components/GoafPopup.vue';
+import { get } from 'lodash-es';
+import { StatusColorEnum } from '/@/enums/jeecgEnum';
+
+// 图片路径
+import customImage from '/@/assets/icons/location-icon.svg';
+import customImage3D from '/@/assets/icons/location-icon3D.png';
+import { env } from './env';
+
+// ===================== 全局状态 =====================
+let map: Map | null = null;
+let svc: Service | null = null;
+let prj: GeoProjection | null = null;
+let allMarkers: any[] = [];
+let sensorId = 1;
+let currentPopup: any = null;
+
+// ===================== 地图初始化部分 =====================
+// 初始化地图
+export const initMap2d = async (container: HTMLElement, config: { fileUrl?: string; style?: vjmap.IMapStyleParam; wjId?: string } = {}) => {
+  if (!container) {
+    message.error('地图容器不存在!');
+    throw new Error('地图容器不存在!');
+  }
+  map?.destory();
+
+  const appStore = useAppStore();
+  svc = new vjmap.Service(env.serviceUrl, env.accessToken);
+
+  const {
+    // fileUrl = 'https://vjmap.com/static/assets/data/gym.dwg',
+    style = vjmap.openMapLightStyle(),
+    wjId,
+  } = config;
+
+  container.style.background = 'transparent';
+  container.style.width = `${get(container, 'parentElement.clientWidth', 0) / appStore.widthScale}px`;
+  container.style.height = `${get(container, 'parentElement.clientHeight', 0) / appStore.heightScale}px`;
+
+  const res = await svc.openMap({
+    mapid: wjId || 'c56c59154345',
+    fileid: wjId || 'c56c59154345', // 地图ID,上传文件后获得的mapid
+    mapopenway: vjmap.MapOpenWay.GeomRender,
+    style,
+  });
+  console.log('debug map open', wjId, res, !!map);
+  if (res.error) {
+    message.error(`地图加载失败:${res.error}`);
+    console.error(res.error);
+    // return null;
+  }
+
+  // 获取地图范围
+  const mapExtent = GeoBounds.fromString(res.bounds);
+  // const mapExtent = GeoBounds.fromString(res.drawBounds);
+  // 根据地图范围建立几何投影坐标系
+  prj = new GeoProjection(mapExtent);
+  // 地图对象
+  map = new vjmap.Map({
+    container, // DIV容器ID
+    style: svc.rasterStyle(),
+    center: prj.toLngLat(mapExtent.center()),
+    zoom: 8, // 设置地图缩放级别
+    // pitch: 0,
+    // trackResize: false,
+    antialias: true, // 反锯齿
+    renderWorldCopies: false, // 不显示多屏地图
+  });
+
+  // 关联服务对象和投影对象
+  map.attach(svc, prj);
+  map.fitMapBounds();
+
+  await map.onLoad();
+
+  return map;
+  // const mapLayer = new MapThreeLayer(map, {
+  //   stat: { show: false, left: '0' },
+  //   scene: {
+  //     showAxesHelper: false,
+  //     axesHelperSize: 1,
+  //     defaultLights: false,
+  //     gridHelper: {
+  //       visible: env.gridHelper ?? true,
+  //       args: [1000, 1000],
+  //       cellSize: 10,
+  //       sectionSize: 100,
+  //     },
+  //   },
+  // });
+  // const app: App = mapLayer.app;
+  // map.addLayer(new vjmap.ThreeLayer({ context: mapLayer as any }));
+  // map.doubleClickZoom.disable();
+
+  // return app;
+};
+
+// 对外导出地图初始化方法
+// export const initMap2d = (id?) => createMapApp(id);
+
+// ===================== 地图操作处理部分 =====================
+// 拾取点位
+const pickPoint = async (markerOptions: any) => {
+  if (!map) return null;
+  let marker: any = null;
+  const actionPoint = await vjmap.Draw.actionDrawPoint(map, {
+    updatecoordinate: (e: any) => {
+      if (!e.lnglat) return;
+      if (!marker) {
+        marker = new vjmap.createMarker(markerOptions);
+        marker.setLngLat(e.lnglat);
+        marker.addTo(map);
+      } else {
+        marker.setLngLat(e.lnglat);
+      }
+    },
+    contextMenu: (e: any) => {
+      new vjmap.ContextMenu({
+        event: e.event.originalEvent,
+        theme: 'dark',
+        width: '250px',
+        items: [
+          {
+            label: '取消',
+            onClick: () => map?.fire('keyup', { keyCode: 27 }),
+          },
+        ],
+      });
+    },
+  });
+  if (actionPoint.cancel) {
+    marker?.remove();
+    return null;
+  }
+  return marker;
+};
+
+/**
+ * 判断坐标是否有效
+ */
+export const getGoafIsPositioned = (goafItem: any): boolean => {
+  const hasValidX = isValidSingleCoord(goafItem.xcoordinate);
+  const hasValidY = isValidSingleCoord(goafItem.ycoordinate);
+  return hasValidX && hasValidY;
+};
+
+// 获取所有标记状态列表
+export const getMarkerStatusList = () => {
+  if (!map) return [];
+  return allMarkers.map((marker) => ({
+    sensorId: marker.data?.sensorId,
+    id: marker.data?.id,
+    data: marker.data,
+    lnglat: marker.getLngLat(),
+    position: map?.fromLngLat(marker.getLngLat()),
+  }));
+};
+
+// 移除指定传感器标记
+export const removePosition = async (sensorId: number | string, id: string | number) => {
+  if (!map) {
+    message.error('地图未初始化完成!');
+    return false;
+  }
+
+  const targetIndex = allMarkers.findIndex((marker) => marker.data?.sensorId === sensorId || marker.data?.id === id);
+  if (targetIndex === -1) {
+    message.warning('未找到该传感器标记!');
+    return false;
+  }
+
+  try {
+    await updatateGoaf({
+      id: id,
+      xcoordinate: '',
+      ycoordinate: '',
+    });
+    console.info('移除布点成功!');
+  } catch (apiError) {
+    message.error(`同步移除信息失败:${(apiError as Error).message}`);
+    console.error('移除接口调用失败:', apiError);
+  }
+
+  allMarkers[targetIndex].remove();
+  allMarkers.splice(targetIndex, 1);
+  syncLocalStorage();
+
+  return true;
+};
+
+// 布点方法
+export const bindPosition = async (props?: any) => {
+  if (!map || !svc) {
+    message.error('地图未初始化完成,请先加载地图!');
+    return null;
+  }
+
+  map.fire('keyup', { keyCode: 27 });
+
+  const el = createMarkerElement(
+    {
+      alarmLevel: props?.alarmLevel,
+      devicePos: props?.devicePos,
+    },
+    false
+  );
+  const markerOptions = {
+    element: el,
+    anchor: 'bottom',
+    offset: [0, 0],
+  };
+
+  let marker: any = null;
+  if (!props || !props.position) {
+    marker = await pickPoint(markerOptions);
+    if (!marker) return null;
+
+    const confirmResult = await new Promise<boolean>((resolve) => {
+      Modal.confirm({
+        title: '保存布点',
+        content: '是否确认保存该布点?',
+        okText: '确定',
+        cancelText: '取消',
+        maskClosable: false,
+        centered: true,
+        onOk: () => resolve(true),
+        onCancel: () => resolve(false),
+      });
+    });
+
+    if (!confirmResult) {
+      marker.remove();
+      return null;
+    }
+
+    const lnglat = marker.getLngLat();
+    const id = props?.id;
+
+    if (!id) {
+      marker.remove();
+      message.error('缺少密闭ID,无法同步布点信息到服务器');
+      throw new Error('缺少密闭ID,无法同步布点信息到服务器');
+    }
+
+    try {
+      await updatateGoaf({
+        id: id,
+        xcoordinate: lnglat.lng.toString(),
+        ycoordinate: lnglat.lat.toString(),
+      });
+    } catch (apiError) {
+      marker.remove();
+      message.error(`同步布点信息失败:${(apiError as Error).message}`);
+      throw new Error(`同步布点信息失败:${(apiError as Error).message}`);
+    }
+
+    const currentLngLat = marker.getLngLat();
+    marker.remove();
+
+    const finalEl = createMarkerElement(
+      {
+        alarmLevel: props?.alarmLevel,
+        devicePos: props?.devicePos,
+      },
+      true
+    );
+
+    marker = vjmap.createMarker({
+      element: finalEl,
+      anchor: 'bottom',
+      offset: [0, 0],
+    });
+    marker.setLngLat(currentLngLat);
+    marker.addTo(map);
+    marker.data = {
+      sensorId: sensorId++,
+      id: id,
+      ...(props || {}),
+    };
+  } else {
+    if (!Array.isArray(props.position)) {
+      throw new Error('预设坐标position格式错误,必须是[x, y]数组');
+    }
+    const lngLat = map.toLngLat(props.position);
+    if (!lngLat) {
+      throw new Error('转换坐标失败,无效的position');
+    }
+
+    marker = vjmap.createMarker(markerOptions);
+    marker.setLngLat(lngLat);
+    marker.data = {
+      ...props.data,
+      sensorId: props.data?.sensorId || sensorId++,
+      id: props.data?.id || props.id,
+      ...(props || {}),
+    };
+    marker.addTo(map);
+  }
+
+  bindMarkerEvents(marker, marker.data?.id);
+  allMarkers.push(marker);
+  syncLocalStorage();
+
+  return {
+    sensorId: marker.data.sensorId,
+    id: marker.data.id,
+    lnglat: marker.getLngLat(),
+    xcoordinate: marker.getLngLat().lng.toString(),
+    ycoordinate: marker.getLngLat().lat.toString(),
+  };
+};
+
+// 渲染密闭列表标记
+export const renderGoafMarkers = (goafList: any[]) => {
+  if (!map || !prj) {
+    message.error('地图或投影坐标系未初始化完成!');
+    return;
+  }
+
+  allMarkers.forEach((marker) => marker.remove());
+  allMarkers = [];
+  sensorId = 1;
+
+  const validGoafList = goafList.filter((item) => getGoafIsPositioned(item));
+  if (validGoafList.length === 0) {
+    console.info('当前密闭列表无有效布点坐标,暂无标记可渲染');
+    return;
+  }
+
+  for (const goafItem of validGoafList) {
+    try {
+      const lnglat = new vjmap.LngLat(Number(goafItem.xcoordinate), Number(goafItem.ycoordinate));
+      const el = createMarkerElement(goafItem, true);
+
+      const marker = vjmap.createMarker({
+        element: el,
+        anchor: 'bottom',
+        offset: [0, 0],
+      });
+      marker.setLngLat(lnglat);
+      marker.data = {
+        sensorId: sensorId++,
+        id: goafItem.id,
+        ...goafItem,
+      };
+      marker.addTo(map);
+
+      bindMarkerEvents(marker, goafItem.id);
+      allMarkers.push(marker);
+    } catch (err) {
+      console.error(`渲染密闭${goafItem.id}标记失败:`, err);
+    }
+  }
+};
+
+// ===================== 内部辅助函数 =====================
+/**
+ * 校验单个坐标是否有效
+ */
+const isValidSingleCoord = (coord: any): boolean => {
+  return coord && coord.trim() !== '' && !isNaN(Number(coord));
+};
+
+/**
+ * 同步标记数据到本地缓存
+ */
+const syncLocalStorage = (): void => {
+  if (!map) return;
+  const markerData = allMarkers.map((m) => ({
+    position: map?.fromLngLat(m.getLngLat()),
+    data: m.data,
+  }));
+  localStorage.setItem('marker_sensor', JSON.stringify(markerData));
+};
+
+/**
+ * 关闭并清理弹窗
+ */
+const closeAndCleanPopup = (): void => {
+  if (currentPopup) {
+    currentPopup.remove();
+    currentPopup = null;
+  }
+};
+
+/**
+ * 绑定标记的拖拽和点击事件
+ */
+const bindMarkerEvents = (marker: any, id: string | number): void => {
+  marker.setDraggable(false);
+  let isDragging = false;
+
+  marker.on('dragstart', () => {
+    isDragging = false;
+  });
+  marker.on('dragend', () => {
+    setTimeout(() => {
+      isDragging = false;
+    }, 150);
+    const lnglat = marker.getLngLat();
+    if (id) {
+      updatateGoaf({
+        id,
+        xcoordinate: lnglat.lng.toString(),
+        ycoordinate: lnglat.lat.toString(),
+      }).catch((err: Error) => {
+        message.error(`拖拽后同步坐标失败:${err.message}`);
+      });
+    }
+  });
+
+  marker.getElement().addEventListener('click', () => {
+    if (!isDragging) {
+      showMarkerPopup(marker);
+    }
+  });
+};
+/**
+ * 创建传感器标记 DOM 元素
+ */
+const createMarkerElement = (goafItem: any, showInfo: boolean = false): HTMLDivElement => {
+  const riskMap = {
+    '1': '低风险',
+    '2': '一般风险',
+    '3': '较高风险',
+    '4': '高风险',
+  };
+
+  // 定义颜色映射,索引对应 alarmLevel - 1
+  // StatusColorEnum.blue, gold, purple, red 分别对应 1, 2, 3, 4
+  const alarmLevelColor = [StatusColorEnum.blue, StatusColorEnum.yellow, StatusColorEnum.gold, StatusColorEnum.red];
+
+  // const customImage = `/src/assets/icons/location-icon.svg`;
+  // const customImage3D = `/src/assets/icons/location-icon3D.png`;
+  const el = document.createElement('div');
+  el.className = 'marker';
+  el.style.cssText = `
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    cursor: move;
+  `;
+
+  // 获取当前等级对应的颜色
+  const level = parseInt(goafItem.alarmLevel, 10);
+  const color = level >= 1 && level <= 4 ? alarmLevelColor[level - 1] : '#333';
+
+  if (showInfo) {
+    const alarmLevelDiv = document.createElement('div');
+    alarmLevelDiv.style.cssText = `
+      font-size: 14px;
+      white-space: nowrap;
+      font-weight: bold;
+      display: flex; /* 使用 flex 布局让文字并排 */
+      align-items: center;
+    `;
+
+    // 1. 创建固定文本节点 "风险等级:"
+    const labelSpan = document.createElement('span');
+    labelSpan.textContent = '风险等级:';
+    labelSpan.style.color = '#333'; // 强制设置为深色/默认色
+
+    // 2. 创建动态文本节点 (应用动态颜色)
+    const valueSpan = document.createElement('span');
+    const riskText = riskMap[String(goafItem.alarmLevel)] || '正常';
+    valueSpan.textContent = goafItem.alarmLevel ? riskText : '正常';
+    valueSpan.style.color = color; // 应用动态颜色
+
+    // 组装
+    alarmLevelDiv.appendChild(labelSpan);
+    alarmLevelDiv.appendChild(valueSpan);
+
+    el.appendChild(alarmLevelDiv);
+  }
+
+  const iconDiv = document.createElement('div');
+  iconDiv.style.cssText = `
+    background-image: url("${showInfo ? customImage3D : customImage}");
+    width: 40px;
+    height: 45px;
+    background-size: 100% 100%;
+    background-position: center;
+    background-repeat: no-repeat;
+  `;
+  el.appendChild(iconDiv);
+
+  if (showInfo) {
+    const devicePosDiv = document.createElement('div');
+    devicePosDiv.style.cssText = `
+      position: absolute;
+      font-size: 14px;
+      color: #333;
+      margin-top: 2px;
+      white-space: nowrap;
+      max-width: 120px;
+      overflow: hidden;
+      text-overflow: ellipsis;
+      bottom: -20px;
+    `;
+    devicePosDiv.textContent = goafItem.devicePos;
+    el.appendChild(devicePosDiv);
+  }
+
+  return el;
+};
+
+// 显示标记信息弹窗
+const showMarkerPopup = (marker: any) => {
+  if (!map) return;
+
+  closeAndCleanPopup();
+
+  const lnglat = marker.getLngLat();
+  const data = marker.data || {};
+
+  const tempContainer = document.createElement('div');
+  const popupApp = createApp({
+    render: () => h(GoafPopup, { data }),
+  });
+  popupApp.mount(tempContainer);
+
+  currentPopup = new vjmap.Popup({
+    closeButton: true,
+    closeOnClick: false,
+    maxWidth: '800px',
+    offset: [0, 0],
+  })
+    .setLngLat(lnglat)
+    .setDOMContent(tempContainer)
+    .addTo(map);
+
+  currentPopup.on('close', () => {
+    popupApp.unmount();
+    tempContainer.remove();
+    currentPopup = null;
+  });
+};

+ 11 - 4
src/views/system/cadFile/cad.data.ts

@@ -73,6 +73,12 @@ export const formSchema: FormSchema[] = [
     component: 'Input',
     show: false,
   },
+  {
+    label: '',
+    field: 'filePath',
+    component: 'Input',
+    show: false,
+  },
   {
     label: '',
     field: 'wjId',
@@ -81,10 +87,11 @@ export const formSchema: FormSchema[] = [
   },
   {
     label: '煤矿名称',
-    field: 'mineCode',
+    field: 'deptId',
     component: 'MineCascader',
     componentProps: {
       changeOnSelect: false,
+      initFromStore: false,
     },
     required: true,
     dynamicRules: () => {
@@ -138,9 +145,9 @@ export const formSchema: FormSchema[] = [
     component: 'Input',
     slot: 'fileupload',
     required: true,
-    ifShow({ values }) {
-      return !values.id;
-    },
+    // ifShow({ values }) {
+    //   return !values.id;
+    // },
   },
   {
     label: '备注',

+ 4 - 2
src/views/system/cadFile/components/MapEditModal.vue

@@ -48,12 +48,14 @@
   const [registerModal, { setModalProps, closeModal }] = useModalInner((data) => {
     setModalProps({ confirmLoading: false });
 
+    data.deptId = mineStore.findDepart((n) => n.fax === data.mineCode, mineStore.getDepartTree)?.id;
+    data.fileAttach = [{ name: data.filePath }];
     resetFields().then(() => setFieldsValue(data));
   });
 
   async function handleSubmit() {
     const formData = await validate();
-    const dept = mineStore.findDepartById(formData.mineCode);
+    const dept = mineStore.findDepartById(formData.deptId);
 
     if (!dept || !dept.isLeaf) {
       message.info('请选择具体矿名');
@@ -85,7 +87,7 @@
       const svc = new vjmap.Service(env.serviceUrl, env.accessToken);
       const res = await svc.uploadMap(file);
 
-      setFieldsValue({ wjId: res.mapid });
+      setFieldsValue({ wjId: res.fieldId, filePath: file.name });
       onSuccess(res);
     } catch (err: any) {
       console.error('上传错误:', err);

+ 3 - 9
src/views/system/cadFile/components/MapView.vue

@@ -3,7 +3,7 @@
     <div ref="map3dContainer"></div>
     <!-- 操作按钮面板 -->
     <div class="map-operation-panel">
-      <button @click="drawerVisible = true" class="btn">老空区永久密闭列表{{ drawerVisible }}</button>
+      <button @click="drawerVisible = true" class="btn">老空区永久密闭列表</button>
       <button @click="emit('close')" class="btn ml-5px">返回列表</button>
     </div>
 
@@ -12,7 +12,7 @@
 </template>
 
 <script lang="ts" setup>
-  import { onMounted, onUnmounted, ref } from 'vue';
+  import { onMounted, ref } from 'vue';
   // 直接导入app.ts中导出的方法
   import { initMap2d } from '../app';
   // import { message } from 'ant-design-vue';
@@ -30,20 +30,14 @@
   const drawerVisible = ref(false);
   const map3dContainer = ref<HTMLElement>();
 
-  let map: any;
-
   // 初始化地图(确保先加载地图,再调用其他方法)
   onMounted(() => {
     // nextTick(async () => {
     initMap2d(map3dContainer.value!, {
       wjId: props.wjId as string,
-    }).then((m) => (map = m));
+    });
     // });
   });
-
-  onUnmounted(() => {
-    map?.destory();
-  });
 </script>
 
 <style scoped>

+ 0 - 1
src/views/system/cadFile/index.vue

@@ -45,7 +45,6 @@
   // import { useIntervalFn } from '@vueuse/core';
   import { useModal } from '/@/components/Modal';
   import MapEditModal from './components/MapEditModal.vue';
-  // import { initMap2d } from './app';
   import MapView from './components/MapView.vue';
 
   // 弹框注册