Jelajahi Sumber

导航与编辑初步完成

jiuling 8 bulan lalu
induk
melakukan
1c9df56af0

+ 34 - 2
src/api/map/index.js

@@ -34,6 +34,7 @@ export function renameMap(data) {
   return request({
     url: '/v1/map/rename',
     method: 'post',
+    baseURL: '/pns',
     data
   })
 }
@@ -43,6 +44,7 @@ export function deleteMap(data) {
   return request({
     url: '/v1/map/delete',
     method: 'delete',
+    baseURL: '/pns',
     data
   })
 }
@@ -52,7 +54,9 @@ export function compressMapExport(data) {
   return request({
     url: '/v1/map/export/compress',
     method: 'post',
-    data
+    baseURL: '/pns',
+    data,
+    timeout: 120000 // 设置120秒超时,用于大地图压缩
   })
 }
 
@@ -61,7 +65,10 @@ export function downloadMapExport(params) {
   return request({
     url: '/v1/map/export',
     method: 'get',
-    params
+    baseURL: '/pns',
+    params,
+    responseType: 'blob',
+    timeout: 60000 // 设置60秒超时,用于大文件下载
   })
 }
 
@@ -70,6 +77,7 @@ export function importMap(data) {
   return request({
     url: '/v1/map/import',
     method: 'post',
+    baseURL: '/pns',
     data
   })
 }
@@ -79,6 +87,7 @@ export function getMapComponents(params) {
   return request({
     url: '/v1/map/components',
     method: 'get',
+    baseURL: '/pns',
     params
   })
 }
@@ -90,3 +99,26 @@ export function getMapFile(mapName, folderName, fileName) {
     method: 'get'
   })
 }
+
+// 获取路网数据 (GeoJSON格式)
+export function getRoadMapGeoJson(mapName) {
+  return request({
+    url: '/v1/roadmap/geojson',
+    baseURL: '/pns',
+    method: 'get',
+    params: { map: mapName }
+  })
+}
+
+// 保存路网数据 (GeoJSON格式)
+export function saveRoadMapGeoJson(data) {
+  return request({
+    url: '/v1/roadmap/geojson',
+    baseURL: '/pns',
+    method: 'post',
+    data,
+    headers: {
+      'Content-Type': 'application/json'
+    }
+  })
+}

+ 17 - 12
src/components/BottomInspector.vue

@@ -424,7 +424,7 @@ export default {
   watch: {
     visible(newVal) {
       if (newVal) {
-        this.initHeight()
+        // this.initHeight()
         this.$nextTick(() => {
           this.$el?.focus()
         })
@@ -556,8 +556,14 @@ export default {
     
     // === 事件处理 ===
     handleSave() {
-      this.$refs.formRef?.validate((valid) => {
-        if (valid) {
+      // 验证基础表单和高级表单
+      const basicFormValid = this.$refs.basicFormRef ? this.$refs.basicFormRef.validate() : Promise.resolve(true);
+      const advancedFormValid = this.$refs.advancedFormRef ? this.$refs.advancedFormRef.validate() : Promise.resolve(true);
+      
+      Promise.all([basicFormValid, advancedFormValid]).then((results) => {
+        const allValid = results.every(valid => valid === true);
+        
+        if (allValid) {
           this.saving = true
           
           // 准备保存数据
@@ -579,16 +585,15 @@ export default {
           // 发送保存事件
           this.$emit('save', saveData)
           
-          // 模拟保存延迟
-          setTimeout(() => {
-            this.saving = false
-            this.hasUnsavedChanges = false
-            this.$message.success('保存成功')
-          }, 300)
+          // 保存状态处理移到父组件
+          this.saving = false
+          this.hasUnsavedChanges = false
         } else {
           this.$message.error('请检查表单数据')
         }
-      })
+      }).catch(() => {
+        this.$message.error('表单验证失败')
+      });
     },
     
     handleCancel() {
@@ -663,7 +668,7 @@ export default {
   bottom: 0;
   
   /* 视觉与尺寸 */
-  min-height: 180px;
+  min-height: 280px;
   height: 20vh;            /* 默认高度 */
   max-height: 50vh;
   background: #fff;
@@ -1001,7 +1006,7 @@ export default {
 @media (max-width: 1024px) {
   .bottom-inspector {
     /* 平板默认高度调整为30% */
-    /* 这个逻辑在组件的计算属性中处理 */
+    min-height: 240px;
   }
 }
 </style>

+ 511 - 85
src/components/OlMap/index.vue

@@ -176,6 +176,11 @@ export default {
     poseCalibrationIndex: {
       type: Number,
       default: 0
+    },
+    // 是否显示机器人图标
+    isShowRobot: {
+      type: Boolean,
+      default: true
     }
   },
   data() {
@@ -222,7 +227,11 @@ export default {
       maxPolygonNum: 0,
       selectedFeatureId: '',  // 用于存储选中的 Feature id
       previewFeature: null, // 动态线段(位姿初始化)
-      calibrationList:[]
+      calibrationList:[],
+      // 轨迹相关
+      trajectorySource: null, // 轨迹图层source
+      currentTrajectory: null, // 当前轨迹数据
+      trajectoryProgress: 0 // 轨迹进度
     }
   },
   computed: {
@@ -235,9 +244,11 @@ export default {
 
   watch: {
     pointSwitch(newVal, oldVal) {
+      console.log('pointSwitch watch触发:', { newVal, oldVal });
       if (newVal !== oldVal) {
-        // 如果数据变动则重新加载
-        this.reloadMapCanvas();
+        // 🚨 临时禁用自动重新加载,防止清除用户数据
+        console.warn('🚨 pointSwitch变化,但已禁用自动重新加载以保护用户数据');
+        // this.reloadMapCanvas(); // 临时注释掉
       }
     },
     baseLayerShow(newVal, oldVal) {
@@ -322,79 +333,6 @@ export default {
     console.log("mapName地图组件:", this.mapName);
   },
   methods: {
-   // 初始化轨迹图层
-  initTrackLayer() {
-    if (this.trackLayer) {
-      this.map.removeLayer(this.trackLayer);
-    }
-    this.trackSource = new VectorSource();
-    this.trackLayer = new VectorLayer({
-      source: this.trackSource,
-      zIndex: 20
-    });
-    this.map.addLayer(this.trackLayer);
-  },
-
-  // 绘制轨迹线
-  drawTrackLine(start, end) {
-    this.initTrackLayer();
-
-    // 1. 绘制线
-    const line = new LineString([start, end]);
-    const lineFeature = new Feature(line);
-
-    // 计算长度
-    const length = getLength(line);
-    const lengthText = `${length.toFixed(2)}m`;
-
-    // 2. 线样式
-    lineFeature.setStyle(new Style({
-      stroke: new Stroke({
-        color: '#ff9800', // 轨迹颜色
-        width: 6
-      }),
-      text: new Text({
-        text: lengthText,
-        font: 'bold 14px sans-serif',
-        fill: new Fill({ color: '#00c387' }),
-        backgroundFill: new Fill({ color: 'rgba(255,255,255,0.8)' }),
-        offsetY: -15
-      })
-    }));
-
-    // 3. 起点
-    const startFeature = new Feature(new Point(start));
-    startFeature.setStyle(new Style({
-      image: new Icon({
-        src: require('@/assets/icons/olmap/dir_backward_red.png'), // 换成你的起点图标
-        scale: 0.18
-      }),
-      text: new Text({
-        text: 'S\n' + lengthText,
-        font: 'bold 14px sans-serif',
-        fill: new Fill({ color: '#00c387' }),
-        offsetY: -30
-      })
-    }));
-
-    // 4. 终点
-    const endFeature = new Feature(new Point(end));
-    endFeature.setStyle(new Style({
-      image: new Icon({
-        src: require('@/assets/icons/olmap/dir_backward_red.png'), // 换成你的终点图标
-        scale: 0.18
-      }),
-      text: new Text({
-        text: 'E\n' + lengthText,
-        font: 'bold 14px sans-serif',
-        fill: new Fill({ color: '#ff3b3b' }),
-        offsetY: -30
-      })
-    }));
-
-    // 5. 添加到轨迹图层
-    this.trackSource.addFeatures([lineFeature, startFeature, endFeature]);
-  },
     // 点击添加点位的操作
     addNowPoint() {
       // 追加当前选择的点位数据到数据列表
@@ -413,11 +351,22 @@ export default {
     },
     // 重新加载画布
     reloadMapCanvas() {
+      console.warn('🚨 reloadMapCanvas被调用!', {
+        hasGeojsonData: !!this.geojson_data,
+        currentFeatureCount: this.roadmap_src ? this.roadmap_src.getFeatures().length : 0,
+        stackTrace: new Error().stack
+      });
       this.initMapHaveDate();
       this.initRoad(this.geojson_data);
     },
     //加载地图数据
     loadMap() {
+      console.warn('🚨 loadMap被调用!', {
+        mapName: this.mapName,
+        hasExistingData: this.roadmap_src ? this.roadmap_src.getFeatures().length : 0,
+        stackTrace: new Error().stack
+      });
+      
       let time = new Date().getTime();
       let configJsonUrl = this.url + '/v1/tilemap/details?map=' + this.mapName + '&' + time;
       // let mapJsonUrl = this.url + "/shapefile/map.json?" + time
@@ -426,6 +375,7 @@ export default {
         // console.log("获取到无人车关联的地图信息:", res.data);
         this.config_json = res.data;
         axios.get(mapJsonUrl).then((res) => {
+          console.log('loadMap: 收到路网数据');
           this.geojson_data = res.data;
           this.initMapHaveDate();
           this.initRoad(this.geojson_data);
@@ -459,7 +409,9 @@ export default {
       }
       document.getElementById("mapParent").innerHTML = "<div id='tileMap' class='tileMap' style='height:" + this.height + "'></div>";
       this.initMap(this.config_json);
-      this.initRobot(); // 加载robot图标
+      if(this.isShowRobot){
+        this.initRobot(); // 加载robot图标
+      }
     },
 
     initMap(config_json) {      
@@ -748,17 +700,52 @@ export default {
     },
 
     initRoad(geojson_data) {
+      console.log('initRoad被调用,当前数据:', {
+        hasGeojsonData: !!geojson_data,
+        geojsonDataType: typeof geojson_data,
+        hasRoadmapSrc: !!this.roadmap_src,
+        currentFeatureCount: this.roadmap_src ? this.roadmap_src.getFeatures().length : 0,
+        stackTrace: new Error().stack
+      });
+      
       if (!geojson_data || geojson_data == "") {
+        console.log('initRoad: 没有数据,退出');
         return;
       }
-      // 创建一个矢量图层,用于显示地图路网(点、线、面)
-      this.roadmap_src = new VectorSource();
-      this.map.addLayer(
-        new VectorLayer({
-          source: this.roadmap_src,
-          style: this.customizedStyle,
-        })
-      );
+      
+      // 🛡️ 严格保护:如果已经有用户创建/编辑的数据,绝对不能清除
+      if (this.roadmap_src && this.roadmap_src.getFeatures().length > 0) {
+        console.warn('🛡️ 保护模式:地图已有数据,禁止重新加载以避免清除用户编辑的数据');
+        console.log('当前特征数量:', this.roadmap_src.getFeatures().length);
+        console.log('特征ID列表:', this.roadmap_src.getFeatures().map(f => f.getId()));
+        
+        // 🚨 紧急保护:即使被调用也绝对不能执行任何清除操作
+        console.error('🚨 严重警告:试图在有数据时重新初始化地图,已被阻止!');
+        return;
+      }
+      
+      // 🛡️ 双重保护:检查是否在编辑模式下(edit.vue页面)
+      if (this.$parent && this.$parent.$options.name === 'EditPage') {
+        console.warn('🛡️ 编辑模式保护:检测到编辑页面,额外保护机制启动');
+      }
+      
+      console.log('initRoad: 开始初始化路网数据');
+      
+      // 如果roadmap_src已经存在,先清除现有features,但保留source对象
+      if (this.roadmap_src) {
+        console.log('initRoad: 清除现有features');
+        this.roadmap_src.clear();
+      } else {
+        // 只有在第一次初始化时才创建新的VectorSource和图层
+        console.log('initRoad: 创建新的VectorSource和图层');
+        this.roadmap_src = new VectorSource();
+        this.map.addLayer(
+          new VectorLayer({
+            source: this.roadmap_src,
+            style: this.customizedStyle,
+          })
+        );
+      }
       // 将geojson_data转换成openlayers的features
       var features = new GeoJSON().readFeatures(geojson_data);
       // 批量补充正确的格式id(原有地图将id设置到了values内部,导致使用id获取元素无法获取)
@@ -1536,6 +1523,7 @@ export default {
       if (!this.roadmap_src) {
         console.log("请先初始化画布!");
         this.$emit('removeElementResult', '')
+        return;
       }
       const feature = this.roadmap_src.getFeatureById(id);
       if (feature) {
@@ -1562,6 +1550,10 @@ export default {
         console.log("请指定id!");
         return;
       }
+      if (!this.roadmap_src) {
+        console.log("请先初始化画布!");
+        return;
+      }   
       // 获取元素
       let feature = this.roadmap_src.getFeatureById(id);
       if (!feature) return;
@@ -1669,8 +1661,16 @@ export default {
      * @param poitision 坐标
      */
     pointPositionUpdate(id, position) {
+      if (!this.roadmap_src) {
+        console.log("请先初始化画布!");
+        return;
+      }
       // 移动点位
       let feature = this.roadmap_src.getFeatureById(id);
+      if (!feature) {
+        console.log(`未找到ID为 ${id} 的元素!`);
+        return;
+      }
       const geometry = feature.getGeometry();
       const coordinates = geometry.getCoordinates();
       // 获取原坐标
@@ -1746,7 +1746,15 @@ export default {
     },
     // 修改多边形的颜色和透明度
     editSnapeColor(id) {
+      if (!this.roadmap_src) {
+        console.log("请先初始化画布!");
+        return;
+      }
       let snapeFeature = this.roadmap_src.getFeatureById(id);
+      if (!snapeFeature) {
+        console.log(`未找到ID为 ${id} 的多边形元素!`);
+        return;
+      }
       let style = this.styleForPolygon(snapeFeature)
       snapeFeature.setStyle(style);
     },
@@ -1767,6 +1775,424 @@ export default {
       const dy = end[1] - start[1];
       const angle = Math.atan2(dy, dx) * (180 / Math.PI);
       return angle < 0 ? angle + 360 : angle;
+    },
+    
+    // 初始化轨迹图层
+    initTrajectoryLayer() {
+      if (!this.trajectorySource) {
+        this.trajectorySource = new VectorSource();
+        this.map.addLayer(
+          new VectorLayer({
+            source: this.trajectorySource,
+            style: null, // 在绘制轨迹时动态设置样式
+            zIndex: 5 // 设置较高的z-index确保轨迹显示在其他元素上方
+          })
+        );
+      }
+    },
+    
+    // 绘制轨迹
+    drawTrajectory(trajectory, options = {}) {
+      console.log("开始绘制轨迹:", trajectory, options);
+      
+      if (!trajectory || trajectory.length === 0) {
+        console.warn("轨迹数据为空");
+        return;
+      }
+      
+      // 初始化轨迹图层
+      this.initTrajectoryLayer();
+      
+      // 只有在不是强制重绘时才清除轨迹(避免重复清除)
+      if (!options.forceRedraw) {
+        this.clearTrajectory();
+        // 保存轨迹数据
+        this.currentTrajectory = trajectory;
+      }
+      
+      this.trajectoryProgress = options.currentProgress || 0;
+      
+      // 确保进度不超过轨迹点数
+      if (this.trajectoryProgress >= trajectory.length) {
+        this.trajectoryProgress = trajectory.length - 1;
+      }
+      
+      try {
+        // 获取机器人当前位置(如果有的话)
+        const robotPos = options.robotPosition || [0, 0];
+        
+        // 计算机器人在轨迹上的精确投影位置
+        const projectionInfo = this.findRobotProjectionOnTrajectory(trajectory, robotPos);
+        
+        if (projectionInfo && projectionInfo.segmentIndex >= 0) {
+          // 基于机器人实际位置实时绘制轨迹
+          this.drawRealtimeTrajectoryFromRobotPosition(trajectory, robotPos, projectionInfo);
+        } else {
+          // 回退到基于进度索引的绘制方式
+          this.drawTrajectoryByProgress(trajectory);
+        }
+        
+        // 添加起点标记
+        if (trajectory.length > 0) {
+          this.addTrajectoryMarker(trajectory[0], {
+            text: '起点',
+            color: '#ffffff',
+            backgroundColor: 'rgba(0, 255, 0, 0.8)'
+          });
+        }
+        
+        // 添加终点标记
+        if (trajectory.length > 1) {
+          this.addTrajectoryMarker(trajectory[trajectory.length - 1], {
+            text: '终点',
+            color: '#ffffff',
+            backgroundColor: 'rgba(255, 0, 0, 0.8)'
+          });
+        }
+        
+        // 如果有当前位置标记,添加当前位置
+        // if (this.trajectoryProgress > 0 && this.trajectoryProgress < trajectory.length) {
+        //   this.addTrajectoryMarker(trajectory[this.trajectoryProgress], {
+        //     text: '当前',
+        //     color: '#ffff00',
+        //     backgroundColor: 'rgba(255, 255, 0, 0.8)'
+        //   });
+        // }
+        
+        console.log("轨迹绘制完成,总点数:", trajectory.length, "当前进度:", this.trajectoryProgress);
+        
+      } catch (error) {
+        console.error("绘制轨迹失败:", error);
+      }
+    },
+    
+    // 找到机器人在轨迹上的投影位置
+    findRobotProjectionOnTrajectory(trajectory, robotPos) {
+      if (!trajectory || trajectory.length < 2) return null;
+      
+      let minDistance = Infinity;
+      let bestProjection = null;
+      
+      for (let i = 0; i < trajectory.length - 1; i++) {
+        const segmentStart = trajectory[i];
+        const segmentEnd = trajectory[i + 1];
+        
+        // 计算投影点
+        const projection = this.projectPointToLineSegment(robotPos, segmentStart, segmentEnd);
+        
+        if (projection.distance < minDistance) {
+          minDistance = projection.distance;
+          bestProjection = {
+            point: projection.point,
+            distance: projection.distance,
+            segmentIndex: i,
+            parameter: this.getParameterOnSegment(projection.point, segmentStart, segmentEnd)
+          };
+        }
+      }
+      
+      // 放宽距离限制,提高轨迹绘制的实时性
+      // 只要能找到合理的投影点就返回,距离限制适当放宽
+      if (bestProjection && bestProjection.distance < 10.0) {
+        console.log(`找到机器人投影点: 线段${bestProjection.segmentIndex}, 距离${bestProjection.distance.toFixed(2)}m`);
+        return bestProjection;
+      }
+      
+      console.log(`机器人距离轨迹太远: ${bestProjection ? bestProjection.distance.toFixed(2) : 'N/A'}m, 使用回退绘制方式`);
+      return null;
+    },
+    
+    // 计算点到线段的投影
+    projectPointToLineSegment(point, lineStart, lineEnd) {
+      const [px, py] = point;
+      const [x1, y1] = lineStart;
+      const [x2, y2] = lineEnd;
+      
+      const A = px - x1;
+      const B = py - y1;
+      const C = x2 - x1;
+      const D = y2 - y1;
+      
+      const dot = A * C + B * D;
+      const lenSq = C * C + D * D;
+      
+      if (lenSq === 0) {
+        return {
+          point: [x1, y1],
+          distance: Math.sqrt(A * A + B * B)
+        };
+      }
+      
+      let param = dot / lenSq;
+      param = Math.max(0, Math.min(1, param));
+      
+      const projectionX = x1 + param * C;
+      const projectionY = y1 + param * D;
+      
+      const distance = Math.sqrt(
+        Math.pow(px - projectionX, 2) + Math.pow(py - projectionY, 2)
+      );
+      
+      return {
+        point: [projectionX, projectionY],
+        distance: distance
+      };
+    },
+    
+    // 获取投影点在线段上的参数(0-1)
+    getParameterOnSegment(projectionPoint, segmentStart, segmentEnd) {
+      const [px, py] = projectionPoint;
+      const [x1, y1] = segmentStart;
+      const [x2, y2] = segmentEnd;
+      
+      if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) {
+        return (px - x1) / (x2 - x1);
+      } else {
+        return (py - y1) / (y2 - y1);
+      }
+    },
+    
+    // 基于机器人实际位置实时绘制轨迹
+    drawRealtimeTrajectoryFromRobotPosition(trajectory, robotPos, projectionInfo) {
+      console.log(`实时轨迹绘制: 机器人位置(${robotPos[0].toFixed(2)}, ${robotPos[1].toFixed(2)}), 投影在线段${projectionInfo.segmentIndex}`);
+      
+      // 1. 绘制已走过的轨迹:从起点到机器人当前位置
+      const completedPoints = [];
+      
+      // 添加起点到投影线段起点的所有点
+      for (let i = 0; i <= projectionInfo.segmentIndex; i++) {
+        completedPoints.push(trajectory[i]);
+      }
+      
+      // 添加机器人在轨迹上的投影点作为已走过轨迹的终点
+      completedPoints.push(projectionInfo.point);
+      
+      if (completedPoints.length >= 2) {
+        console.log(`绘制实时已走过轨迹: 起点到机器人投影位置, ${completedPoints.length}个点`);
+        this.drawTrajectorySegment(completedPoints, {
+          color: '#00ff00', // 绿色
+          width: 4,
+          lineDash: [10, 5],
+          type: 'completed'
+        });
+      }
+      
+      // 2. 绘制未走过的轨迹:从机器人当前位置到终点
+      const remainingPoints = [];
+      
+      // 从机器人投影点开始
+      remainingPoints.push(projectionInfo.point);
+      
+      // 添加投影线段的终点
+      if (projectionInfo.segmentIndex < trajectory.length - 1) {
+        remainingPoints.push(trajectory[projectionInfo.segmentIndex + 1]);
+      }
+      
+      // 添加后续所有点直到终点
+      for (let i = projectionInfo.segmentIndex + 2; i < trajectory.length; i++) {
+        remainingPoints.push(trajectory[i]);
+      }
+      
+      if (remainingPoints.length >= 2) {
+        console.log(`绘制实时未走过轨迹: 机器人投影位置到终点, ${remainingPoints.length}个点`);
+        this.drawTrajectorySegment(remainingPoints, {
+          color: '#ff0000', // 红色
+          width: 4,
+          lineDash: [10, 5],
+          type: 'remaining'
+        });
+      }
+      
+      // 3. 添加机器人当前位置标记
+      this.addRealtimeRobotMarker(robotPos, projectionInfo.point);
+    },
+    
+    // 添加机器人实时位置标记
+    addRealtimeRobotMarker(robotActualPos, robotProjectionPos) {
+      // 在投影点显示一个小的当前位置标记
+      const marker = new Feature({
+        geometry: new Point(robotProjectionPos)
+      });
+      
+      marker.setStyle(new Style({
+        image: new Icon({
+          src: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(`
+            <svg width="16" height="16" xmlns="http://www.w3.org/2000/svg">
+              <circle cx="8" cy="8" r="6" fill="#ffff00" stroke="#ff6600" stroke-width="2"/>
+              <circle cx="8" cy="8" r="2" fill="#ff6600"/>
+            </svg>
+          `),
+          scale: 1
+        }),
+        text: new Text({
+          text: '当前',
+          font: 'bold 10px Arial',
+          fill: new Fill({
+            color: '#333333'
+          }),
+          backgroundFill: new Fill({
+            color: 'rgba(255, 255, 255, 0.8)'
+          }),
+          padding: [2, 4, 2, 4],
+          offsetY: -25
+        })
+      }));
+      
+      marker.setId(`trajectory-current-position-${Date.now()}`);
+      this.trajectorySource.addFeature(marker);
+    },
+    
+    // 基于进度索引的回退绘制方式
+    drawTrajectoryByProgress(trajectory) {
+      // 绘制已走过的轨迹(绿色)
+      if (this.trajectoryProgress > 0) {
+        const completedTrajectory = trajectory.slice(0, this.trajectoryProgress + 1);
+        console.log(`绘制已完成轨迹: 点${0}到点${this.trajectoryProgress}, 共${completedTrajectory.length}个点`);
+        this.drawTrajectorySegment(completedTrajectory, {
+          color: '#00ff00', // 绿色
+          width: 4,
+          lineDash: [10, 5],
+          type: 'completed'
+        });
+      }
+      
+      // 绘制未走过的轨迹(红色)
+      if (this.trajectoryProgress < trajectory.length - 1) {
+        const remainingTrajectory = trajectory.slice(this.trajectoryProgress);
+        console.log(`绘制剩余轨迹: 点${this.trajectoryProgress}到点${trajectory.length-1}, 共${remainingTrajectory.length}个点`);
+        this.drawTrajectorySegment(remainingTrajectory, {
+          color: '#ff0000', // 红色
+          width: 4,
+          lineDash: [10, 5],
+          type: 'remaining'
+        });
+      }
+    },
+    
+    // 绘制轨迹段
+    drawTrajectorySegment(trajectorySegment, style) {
+      if (!trajectorySegment || trajectorySegment.length < 2) {
+        return;
+      }
+      
+      // 创建线段几何
+      const lineString = new LineString(trajectorySegment);
+      const feature = new Feature({
+        geometry: lineString
+      });
+      
+      // 设置样式
+      feature.setStyle(new Style({
+        stroke: new Stroke({
+          color: style.color,
+          width: style.width,
+          lineDash: style.lineDash
+        })
+      }));
+      
+      // 设置feature id以便后续管理
+      feature.setId(`trajectory-${style.type}-${Date.now()}`);
+      
+      // 添加到图层
+      this.trajectorySource.addFeature(feature);
+    },
+    
+    // 添加轨迹标记点
+    addTrajectoryMarker(position, options) {
+      const marker = new Feature({
+        geometry: new Point(position)
+      });
+      
+      marker.setStyle(new Style({
+        text: new Text({
+          text: options.text,
+          font: 'bold 12px Arial',
+          fill: new Fill({
+            color: options.color
+          }),
+          backgroundFill: new Fill({
+            color: options.backgroundColor
+          }),
+          padding: [3, 6, 3, 6],
+          offsetY: -20
+        }),
+        image: new Icon({
+          src: 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(`
+            <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg">
+              <circle cx="6" cy="6" r="5" fill="${options.color}" stroke="white" stroke-width="1"/>
+            </svg>
+          `),
+          scale: 1
+        })
+      }));
+      
+      marker.setId(`trajectory-marker-${options.text}-${Date.now()}`);
+      this.trajectorySource.addFeature(marker);
+    },
+    
+    // 清除轨迹
+    clearTrajectory() {
+      if (this.trajectorySource) {
+        this.trajectorySource.clear();
+      }
+      this.currentTrajectory = null;
+      this.trajectoryProgress = 0;
+    },
+    
+    // 更新轨迹进度
+    updateTrajectoryProgress(newProgress, robotPosition = null) {
+      if (!this.currentTrajectory || newProgress === this.trajectoryProgress) {
+        return;
+      }
+      
+      console.log(`更新轨迹进度: ${this.trajectoryProgress} -> ${newProgress},轨迹总长度: ${this.currentTrajectory.length}`);
+      
+      this.trajectoryProgress = newProgress;
+      
+      // 清除现有轨迹特征(但保留轨迹数据)
+      if (this.trajectorySource) {
+        this.trajectorySource.clear();
+      }
+      
+      // 使用传入的机器人位置或默认值
+      const currentRobotPos = robotPosition || [this.robotPoseData?.x || 0, this.robotPoseData?.y || 0];
+      
+      // 重新绘制轨迹以反映新的进度
+      this.drawTrajectory(this.currentTrajectory, {
+        currentProgress: this.trajectoryProgress,
+        forceRedraw: true,
+        robotPosition: currentRobotPos
+      });
+    },
+    
+    // 实时更新轨迹绘制(基于机器人当前位置)
+    updateRealtimeTrajectory(robotPosition) {
+      if (!this.currentTrajectory || !this.trajectorySource) {
+        return;
+      }
+      
+      // 清除现有的轨迹线段和位置标记(但保留起点终点标记)
+      const features = this.trajectorySource.getFeatures();
+      features.forEach(feature => {
+        const featureId = feature.getId();
+        if (featureId && (
+          featureId.startsWith('trajectory-completed-') || 
+          featureId.startsWith('trajectory-remaining-') ||
+          featureId.startsWith('trajectory-current-position-')
+        )) {
+          this.trajectorySource.removeFeature(feature);
+        }
+      });
+      
+      // 基于当前机器人位置重新计算并绘制轨迹
+      const projectionInfo = this.findRobotProjectionOnTrajectory(this.currentTrajectory, robotPosition);
+      
+      if (projectionInfo && projectionInfo.segmentIndex >= 0) {
+        // 使用实时绘制方法
+        this.drawRealtimeTrajectoryFromRobotPosition(this.currentTrajectory, robotPosition, projectionInfo);
+      } else {
+        console.log('机器人位置无法投影到轨迹上,跳过实时更新');
+      }
     }
   },
 }

+ 64 - 34
src/components/XtMapCard/index.vue

@@ -5,7 +5,7 @@
       <el-checkbox 
         :value="selected" 
         @change="handleSelectChange"
-        :aria-label="`选择地图${item.name}`"
+        :aria-label="`选择地图${item.map}`"
       />
     </div>
 
@@ -41,25 +41,25 @@
 
     <!-- 内容区 -->
     <div class="card-content">
-      <h3 class="card-title" @click="handleEdit" :title="item.name">
-        {{ item.name }}
+      <h3 class="card-title" @click="handleEdit" :title="item.map">
+        {{ item.map }}
       </h3>
       
       <p v-if="item.remark" class="card-remark" :title="item.remark">
         {{ item.remark }}
       </p>
       
-      <div class="card-meta">
+      <!-- <div class="card-meta">
         <span class="update-time">
           <i class="el-icon-time"></i>
           {{ formatDateTimeCompat(pickUpdatedAt(item)) }}
         </span>
-      </div>
+      </div> -->
     </div>
 
     <!-- 操作按钮区 -->
     <div class="card-actions" :class="{ 'show-on-mobile': true }">
-      <router-link :to="buildNavTo(this, item)" class="card-op-link">
+      <router-link :to="buildNavTo(item)" class="card-op-link">
         <el-button 
           type="primary" 
           size="mini" 
@@ -70,7 +70,7 @@
         </el-button>
       </router-link>
       
-      <router-link :to="buildEditTo(this, item)" class="card-op-link">
+      <router-link :to="buildEditTo(item)" class="card-op-link">
         <el-button 
           type="default" 
           size="mini" 
@@ -108,6 +108,14 @@
 import { formatDateTimeCompat, pickUpdatedAt } from '@/utils/datefmt'
 import { buildNavTo, buildEditTo, buildCalibrateTo, buildConstructTo, pickId } from '@/utils/route-helpers'
 
+// 导入地图API
+let mapApi
+try {
+  mapApi = require('@/api/map/index.js')
+} catch (error) {
+  mapApi = require('@/api/mock/maps')
+}
+
 export default {
   name: 'XtMapCard',
   
@@ -118,7 +126,7 @@ export default {
       default: () => ({
         id: '',
         name: '',
-        status: 'ok',
+        state: '',
         thumbUrl: '',
         remark: '',
         updatedAt: ''
@@ -142,11 +150,14 @@ export default {
     // 状态配置
     statusConfig() {
       const configs = {
-        ok: { type: 'success', icon: 'el-icon-check', text: '正常' },
-        down: { type: 'danger', icon: 'el-icon-close', text: '不可用' },
-        scanning: { type: 'warning', icon: 'el-icon-loading', text: '扫描中' }
+       available: { type: 'success', icon: 'el-icon-check', text: '正常' },
+        unavailable: { type: 'danger', icon: 'el-icon-close', text: '不可用' },
+        building : { type: 'warning', icon: 'el-icon-loading', text: '正在建图' },
+        recording  : { type: 'warning', icon: 'el-icon-loading', text: '正在录制' }
       }
-      return configs[this.item.status] || configs.ok
+      console.log('当前状态:', this.item);
+      
+      return configs[this.item.state] || configs.ok
     },
 
     // 缩略图 URL
@@ -197,6 +208,8 @@ export default {
 
     // 更多操作
     async handleMoreAction(cmd) {
+      console.log("执行操作水电费是的开发");
+      
       const row = this.item;
       const id = pickId(row);
       
@@ -207,21 +220,34 @@ export default {
           if (this.renameDialog) { this.renameDialog.visible = true; this.renameDialog.row = row; return; }
           // 兜底:Element prompt
           this.$prompt('请输入新的地图名称', '重命名', {
-            inputValue: row.mapName || row.name || '',
+            inputValue: row.mapName || row.map || row.name || '',
             inputPattern: /\S+/,
             inputErrorMessage: '名称不能为空'
-          }).then(({ value }) => {
-            // 若有旧方法:this.renameMap(id, value)
-            if (typeof this.renameMap === 'function') return this.renameMap(id, value);
-            row.mapName = value; 
-            row.name = value;
-            this.$message.success('已重命名'); // mock 刷新
+          }).then(async ({ value }) => {
+            try {
+              // 调用重命名API
+              const response = await mapApi.renameMap({
+                map: row.map || row.mapName || row.name,
+                rename: value
+              });
+              
+              if (response && response.status) {
+                this.$message.success('地图重命名成功');
+                // 通知父组件刷新列表
+                this.$emit('refresh');
+              } else {
+                this.$message.error('重命名失败');
+              }
+            } catch (error) {
+              console.error('重命名失败:', error);
+              this.$message.error('重命名失败: ' + (error.message || '网络错误'));
+            }
           }).catch(()=>{});
           break;
 
         case 'download':
           // 触发父组件的下载对话框
-          this.$emit('clickMore', this.item.id, 'download');
+          this.$emit('clickMore', this.item, 'download');
           break;
 
         case 'build':
@@ -237,21 +263,25 @@ export default {
           return this.$router.push(buildConstructTo(row));
 
         case 'delete':
-          // 优先:旧删除方法
-          if (typeof this.handleDelete === 'function') {
-            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
-              .then(() => this.handleDelete([id]))
-              .catch(() => {});
-          }
-          if (typeof this.deleteMap === 'function') {
-            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
-              .then(() => this.deleteMap(id))
-              .catch(() => {});
-          }
           this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
-            .then(() => { 
-              this.$emit && this.$emit('remove', row); 
-              this.$message.success('已删除(mock)'); 
+            .then(async () => { 
+              try {
+                // 调用删除API
+                const response = await mapApi.deleteMap({
+                  map: row.map || row.mapName || row.name
+                });
+                
+                if (response && response.status) {
+                  this.$message.success('地图删除成功');
+                  // 通知父组件刷新列表
+                  this.$emit('refresh');
+                } else {
+                  this.$message.error('删除失败');
+                }
+              } catch (error) {
+                console.error('删除失败:', error);
+                this.$message.error('删除失败: ' + (error.message || '网络错误'));
+              }
             })
             .catch(() => {});
           break;

+ 2 - 1
src/utils/datefmt.js

@@ -48,7 +48,8 @@ export function formatDateTimeCompat(input) {
 
 // 从一条记录中提取更新时间,支持多种字段名
 export function pickUpdatedAt(row) {
-  return row?.updatedAt ?? row?.updateTime ?? row?.updated_time ?? row?.updated_at ?? row?.updated ?? '';
+  if (!row) return '';
+  return row.updatedAt || row.updateTime || row.updated_time || row.updated_at || row.updated || '';
 }
 
 // 保持向后兼容

+ 3 - 3
src/utils/route-helpers.js

@@ -2,10 +2,10 @@
 
 // 读取记录的主键,兼容多种 ID 字段名
 export function pickId(row) {
-  return row?.id ?? row?.mapId ?? row?.map_id ?? row?.ID ?? row?.MapId;
+  return row && row.id;
 }
 export function pickName(row) {
-  return row?.map;
+  return row && row.map;
 }
 
 // === 根据真实路由配置构造跳转对象 ===
@@ -14,7 +14,7 @@ export function pickName(row) {
 export function buildNavTo(row) {
   const id = pickId(row);
   const mapName = pickName(row);
-  console.log("路由跳转mapName:", mapName);
+    console.log("id的值:", id);
   return { 
     name: 'MapNavigation', 
     params: { mapId: id, mapName:mapName } 

+ 153 - 11
src/views/map/maplist/components/shared/RightPanel.vue

@@ -85,6 +85,39 @@
                     </div>
                   </div>
                   
+                  <!-- 导航和急停状态 -->
+                  <div v-if="mode === 'nav'" class="nav-info-grid" style="margin-top: 16px;">
+                    <div class="nav-info-header">
+                      <h3 class="nav-info-title">系统状态</h3>
+                    </div>
+                    <div class="nav-info-content">
+                      <div class="nav-info-item">
+                        <span class="label">导航状态:</span>
+                        <span class="value" :class="getNavigationStatusClass(navigationStackStatus)">
+                          {{ getNavigationStatusText(navigationStackStatus) }}
+                        </span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">急停状态:</span>
+                        <span class="value" :class="getEmergencyStopClass(emergencyStopEnabled)">
+                          {{ emergencyStopEnabled ? '已启用' : '正常' }}
+                        </span>
+                      </div>
+                    </div>
+                    <div v-if="emergencyStopEnabled" style="padding: 16px; border-top: 1px solid #f0f0f0;">
+                      <el-button 
+                        type="success"
+                        size="small" 
+                        icon="el-icon-refresh-right"
+                        @click="$emit('emergency-stop-release')"
+                        style="width: 100%;"
+                        block
+                      >
+                        解除急停
+                      </el-button>
+                    </div>
+                  </div>
+                  
                   <!-- 编辑模式下添加"实时位姿"部分 -->
                   <div v-if="mode === 'edit'" class="nav-info-grid" style="margin-top: 16px;">
                     <div class="nav-info-header">
@@ -474,7 +507,7 @@
                   </div>
                   <div class="nav-info-item">
                     <span class="label">电池电量:</span>
-                    <span class="value">{{ realtimeInfo.batteryLevel }}</span>
+                    <span class="value">{{ realtimeInfo.batteryLevel || '0%' }}</span>
                   </div>
                   <div class="nav-info-item">
                     <span class="label">当前任务:</span>
@@ -814,15 +847,15 @@ export default {
     realtimeInfo: {
       type: Object,
       default: () => ({
-        currentMap: 'shanghai',
-        currentTask: '测试任务',
-        speed: '0.35m/s',
-        speedCommand: '0.22m/s',
-        coordinates: '(1.813, -63.931, 0.000)',
-        heading: '-79.6°',
-        totalDistance: '5965352.00m',
-        registrationError: '10.000',
-        batteryLevel: '67%'
+        currentMap: '',
+        currentTask: '暂无',
+        speed: '',
+        speedCommand: '',
+        coordinates: '(0.000, 0.000, 0.000)',
+        heading: '0°',
+        totalDistance: '0m',
+        registrationError: '0',
+        batteryLevel: '0%'
       })
     },
     
@@ -845,6 +878,16 @@ export default {
         network: false
       })
     },
+    // 急停状态
+    emergencyStopEnabled: {
+      type: Boolean,
+      default: false
+    },
+    // 导航堆栈状态
+    navigationStackStatus: {
+      type: String,
+      default: 'unknown'
+    },
     
     // === 编辑页专用props ===
     elementList: {
@@ -945,6 +988,27 @@ export default {
       return filtered
     }
   },
+  watch: {
+  // 监听目标点列表变化,自动清理无效选择
+  waypointList: {
+    handler(newList) {
+      if (this.selectedWaypoints.length > 0) {
+        // 过滤出仍然存在的选中项
+        const validSelections = this.selectedWaypoints.filter(selected => 
+          newList.some(waypoint => waypoint.id === selected.id)
+        );
+        
+        // 如果选择发生了变化,更新选择状态
+        if (validSelections.length !== this.selectedWaypoints.length) {
+          this.selectedWaypoints = validSelections;
+          this.$emit('wp-selection-change', this.selectedWaypoints);
+        }
+      }
+    },
+    deep: true,
+    immediate: false
+  }
+},
   created() {
     // 初始化标签页
     if (this.tabs.length > 0) {
@@ -1236,6 +1300,33 @@ export default {
         'polygon': '面'
       }
       return typeMap[this.activeElementType] || ''
+    },
+    
+    // === 导航和急停状态相关方法 ===
+    
+    // 获取导航状态显示文本
+    getNavigationStatusText(status) {
+      const statusMap = {
+        'unknown': '未知',
+        'started': '已启动',
+        'stopped': '已停止'
+      }
+      return statusMap[status] || '未知'
+    },
+    
+    // 获取导航状态样式类
+    getNavigationStatusClass(status) {
+      const classMap = {
+        'unknown': 'status-unknown',
+        'started': 'status-started',
+        'stopped': 'status-stopped'
+      }
+      return classMap[status] || 'status-unknown'
+    },
+    
+    // 获取急停状态样式类
+    getEmergencyStopClass(enabled) {
+      return enabled ? 'status-emergency' : 'status-normal'
     }
   }
 }
@@ -1965,7 +2056,7 @@ html.dark {
     display: flex;
     justify-content: space-between;
     align-items: center;
-    height: 36px;
+    // height: 36px;
     padding: 0 var(--spacing-4);
     border-bottom: 1px solid var(--color-border-tertiary);
     
@@ -2266,6 +2357,44 @@ html.dark {
             border-color: #fecaca;
             box-shadow: 0 1px 3px rgba(220, 38, 38, 0.1);
           }
+          
+          // 导航状态样式
+          &.status-unknown {
+            background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+            color: #64748b;
+            border-color: #cbd5e1;
+            box-shadow: 0 1px 3px rgba(100, 116, 139, 0.1);
+          }
+          
+          &.status-started {
+            background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
+            color: #15803d;
+            border-color: #bbf7d0;
+            box-shadow: 0 1px 3px rgba(21, 128, 61, 0.1);
+          }
+          
+          &.status-stopped {
+            background: linear-gradient(135deg, #fefbf3 0%, #fef3c7 100%);
+            color: #d97706;
+            border-color: #fed7aa;
+            box-shadow: 0 1px 3px rgba(217, 119, 6, 0.1);
+          }
+          
+          // 急停状态样式
+          &.status-normal {
+            background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
+            color: #15803d;
+            border-color: #bbf7d0;
+            box-shadow: 0 1px 3px rgba(21, 128, 61, 0.1);
+          }
+          
+          &.status-emergency {
+            background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
+            color: #dc2626;
+            border-color: #fecaca;
+            box-shadow: 0 1px 3px rgba(220, 38, 38, 0.1);
+            animation: pulse-emergency 2s infinite;
+          }
         }
       }
       
@@ -2927,5 +3056,18 @@ html.dark {
     }
   }
 }
+
+// 急停状态脉冲动画
+@keyframes pulse-emergency {
+  0% {
+    box-shadow: 0 1px 3px rgba(220, 38, 38, 0.1), 0 0 0 0 rgba(220, 38, 38, 0.4);
+  }
+  50% {
+    box-shadow: 0 1px 3px rgba(220, 38, 38, 0.2), 0 0 0 6px rgba(220, 38, 38, 0.1);
+  }
+  100% {
+    box-shadow: 0 1px 3px rgba(220, 38, 38, 0.1), 0 0 0 0 rgba(220, 38, 38, 0);
+  }
+}
 </style>
 

+ 428 - 127
src/views/map/maplist/edit.vue

@@ -24,6 +24,7 @@
           :height="olHeight + 'px'" 
           backgroundColor="#F5F5F5" 
           :mapName="mapName"
+					:isShowRobot="false"
           :pointDraSelectEnabled="pointDraSelectEnabled"
           :showDefaultControls="false"
           @elementRoadInitEnd="elementRoadInitEnd"
@@ -156,29 +157,36 @@
         </el-button>
 			</span>
 		</el-dialog>
+		<MqttComp ref="mqtt" :topics="topics" @message-received="onMessage" />
 	</div>
 </template>
 
 <script>
+import axios from "axios";
 import OlMap from "@/components/OlMap"
 import MapToolbar from "./components/shared/MapToolbar.vue"
 import RightPanel from "./components/shared/RightPanel.vue"
 import BottomInspector from "@/components/BottomInspector.vue"
-
+import MqttComp from "@/components/Mqtt/mqttComp.vue"
+import { saveRoadMapGeoJson, getRoadMapGeoJson } from "@/api/map/index"
 export default {
 	name: "EditPage",
 	components: {
 		OlMap,
 		MapToolbar,
 		RightPanel,
-		BottomInspector
+		BottomInspector,
+		MqttComp
 	},
 	data() {
 		return {
+			topics:[
+				{ topic:this.$mqttPrefix + '/localization/pose'},
+			],
 			// 地图相关
 			olWidth: 0,
 			olHeight: 0,
-			mapName: 'demo',
+			mapName: this.$route.params.mapName || '',
 			
 			// 编辑模式相关
 			currentMode: 'select', // 当前编辑模式
@@ -200,15 +208,15 @@ export default {
 			
 			// 实时信息数据
 			realtimeInfo: {
-				currentMap: 'shanghai',
+				currentMap: this.$route.params.mapName || 'Unknown',
 				currentTask: '地图编辑',
-				speed: '0.35m/s',
-				speedCommand: '0.22m/s',
-				coordinates: '(1.813, -63.931, 0.000)',
-				heading: '-79.6°',
-				totalDistance: '5965352.00m',
-				registrationError: '10.000',
-				batteryLevel: '67%'
+				speed: '0m/s',
+				speedCommand: '0m/s',
+				coordinates: '(0, 0, 0)',
+				heading: '0°',
+				totalDistance: '0m',
+				registrationError: '0',
+				batteryLevel: '0%'
 			},
 			
 			// 元素数据
@@ -269,7 +277,7 @@ export default {
 	},
 	created() {
 		// 获取地图ID
-		this.mapName = this.$route.params.mapId || 'demo';
+		// this.mapName = this.$route.params.mapId || 'demo';
 	},
 	beforeDestroy() {
 		window.removeEventListener("beforeunload", this.handleBeforeUnload);
@@ -311,7 +319,25 @@ export default {
 	},
 	methods: {
 		// === 页面初始化相关 ===
-		
+		onMessage({ topic, message }) {
+				if (topic === this.$mqttPrefix + '/localization/pose') {
+						this.handlePoseMessage(message);
+				}
+		},
+		handlePoseMessage(message) {
+			try {
+				const data = message.args[0];				
+        const {xyz, rpy, blh, heading} = data.pose;
+				this.realtimeInfo.coordinates = `(${xyz[0]},${xyz[1]},${xyz[2]})`;
+				this.realtimeInfo.heading = rpy[2] + '°'; // 航向角
+				this.realtimeInfo.speed = data.vel.heading+ 'm/s'; // 速度
+				this.realtimeInfo.speedCommand = data.vel.enu + 'm/s'; // 速度指令
+				this.realtimeInfo.totalDistance = data.odometer + 'm'; // 总里程
+				this.realtimeInfo.registrationError = data.score;
+			} catch (error) {
+				console.error('解析位姿消息失败:', error);
+			}
+		},
 		// 更新地图尺寸
 		updateMapSize() {
 			const mapContent = this.$refs.mapContent;
@@ -323,8 +349,9 @@ export default {
 		
 		// 初始化编辑模式
 		initEditMode() {
-			this.currentMode = 'select';
+			this.currentMode = 'select-mode'; // 使用工具栏的key
 			this.pointDraSelectEnabled = true;
+			console.log('编辑模式初始化完成,当前模式:', this.currentMode);
 		},
 		
 		// 处理页面刷新/关闭前的确认
@@ -345,34 +372,63 @@ export default {
 		
 		// 处理模式切换
 		handleModeChange(mode) {
-			this.currentMode = mode;
+			// 将工具栏的key映射到内部模式
+			const modeMap = {
+				'select-mode': 'select',
+				'draw-point': 'draw-point',
+				'draw-line': 'draw-line',
+				'draw-curve': 'draw-curve',
+				'draw-polygon': 'draw-polygon'
+			};
+			
+			const internalMode = modeMap[mode] || mode;
+			this.currentMode = mode; // 保存工具栏的key用于选中状态
 			
 			// 根据模式切换地图交互状态
-			switch (mode) {
+			switch (internalMode) {
 				case 'select':
 					this.pointDraSelectEnabled = true;
 					this.$refs.olmap && this.$refs.olmap.clearDraw && this.$refs.olmap.clearDraw();
 					this.$message.success('已切换到选择模式');
 					break;
 				case 'draw-point':
-			this.pointDraSelectEnabled = false;
-					this.$refs.olmap && this.$refs.olmap.drawPoint && this.$refs.olmap.drawPoint();
-					this.$message.success('已切换到绘点模式');
+					this.pointDraSelectEnabled = false;
+					if (this.$refs.olmap && this.$refs.olmap.drawPoint) {
+						this.$refs.olmap.drawPoint();
+						this.$message.success('已切换到绘点模式,点击地图添加点位');
+					} else {
+						this.$message.warning('地图组件未就绪,请稍后重试');
+					}
 					break;
 				case 'draw-line':
 					this.pointDraSelectEnabled = false;
-					this.$refs.olmap && this.$refs.olmap.drawLine && this.$refs.olmap.drawLine();
-					this.$message.success('已切换到绘线模式');
+					if (this.$refs.olmap && this.$refs.olmap.drawLine) {
+						this.$refs.olmap.drawLine();
+						this.$message.success('已切换到绘线模式,点击地图绘制线段');
+					} else {
+						this.$message.warning('地图组件未就绪,请稍后重试');
+					}
 					break;
 				case 'draw-curve':
 					this.pointDraSelectEnabled = false;
-					this.$refs.olmap && this.$refs.olmap.drawCurve && this.$refs.olmap.drawCurve();
-					this.$message.success('已切换到绘曲线模式');
+					if (this.$refs.olmap && this.$refs.olmap.drawCurve) {
+						this.$refs.olmap.drawCurve();
+						this.$message.success('已切换到绘曲线模式,点击地图绘制曲线');
+					} else {
+						this.$message.warning('地图组件未就绪,请稍后重试');
+					}
 					break;
 				case 'draw-polygon':
 					this.pointDraSelectEnabled = false;
-					this.$refs.olmap && this.$refs.olmap.drawPolygon && this.$refs.olmap.drawPolygon();
-					this.$message.success('已切换到绘区域模式');
+					if (this.$refs.olmap && this.$refs.olmap.drawPolygon) {
+						this.$refs.olmap.drawPolygon();
+						this.$message.success('已切换到绘区域模式,点击地图绘制多边形区域');
+					} else {
+						this.$message.warning('地图组件未就绪,请稍后重试');
+					}
+					break;
+				default:
+					console.warn('未知的绘制模式:', mode);
 					break;
 			}
 		},
@@ -398,8 +454,26 @@ export default {
 			this.$message.info('全屏切换');
 		},
 		
-		handleSaveMap() {
-			this.saveRoute();
+		async handleSaveMap() {
+			// 检查是否有未保存的更改
+			if (!this.haveDraw && this.resourcesFeature.length > 0) {
+				this.$message.info('当前没有需要保存的更改');
+				return;
+			}
+			
+			if (this.resourcesFeature.length === 0) {
+				this.$message.warning('当前地图没有任何元素可保存');
+				return;
+			}
+			
+			try {
+				this.$message.info('正在保存路网数据...');
+				await this.saveRoute();
+				// saveRoute方法内部已经有成功消息,这里不需要重复显示
+			} catch (error) {
+				// saveRoute方法内部已经有错误处理,这里不需要重复处理
+				console.error('保存失败:', error);
+			}
 		},
 		
 		// === 右侧面板事件处理 ===
@@ -413,7 +487,9 @@ export default {
 		handleElementSelect(element) {
 			this.showInspector(element, 'edit');
 			// 在地图上选中该元素
-			this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(element.id);
+			if (this.$refs.olmap && this.$refs.olmap.selectShowEle && element && element.id) {
+				this.$refs.olmap.selectShowEle(element.id);
+			}
 		},
 		
 		
@@ -424,19 +500,50 @@ export default {
 		
 		// 定位元素
 		handleElementLocate(element) {
-			this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(element.id);
-			this.$message.success(`已定位到元素 ${element.id}`);
+			if (this.$refs.olmap && this.$refs.olmap.selectShowEle && element && element.id) {
+				this.$refs.olmap.selectShowEle(element.id);
+				this.$message.success(`已定位到元素 ${element.id}`);
+			} else {
+				this.$message.error('无法定位元素');
+			}
 		},
 		
 		// 删除元素
-		handleElementRemove(element) {
-			this.$confirm(`确定要删除元素 ${element.id} 吗?`, '确认删除', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.$refs.olmap && this.$refs.olmap.removeElement && this.$refs.olmap.removeElement(element.id);
-			});
+		async handleElementRemove(element) {
+			try {
+				await this.$confirm(`确定要删除元素 ${element.id} 吗?`, '确认删除', {
+					confirmButtonText: '确定',
+					cancelButtonText: '取消',
+					type: 'warning'
+				});
+
+				// 显示删除进度
+				const loading = this.$loading({
+					lock: true,
+					text: '正在删除元素...',
+					spinner: 'el-icon-loading',
+					background: 'rgba(0, 0, 0, 0.7)'
+				});
+
+				try {
+					// 从地图组件中删除元素
+					if (this.$refs.olmap && this.$refs.olmap.removeElement) {
+						this.$refs.olmap.removeElement(element.id);
+					}
+
+					// 删除完成后自动保存到后端
+					await this.saveRoute();
+					
+					this.$message.success(`元素 ${element.id} 删除成功`);
+				} finally {
+					loading.close();
+				}
+			} catch (error) {
+				if (error !== 'cancel') {
+					console.error('删除元素失败:', error);
+					this.$message.error('删除元素失败: ' + (error.message || '未知错误'));
+				}
+			}
 		},
 		
 		// 路网导出
@@ -536,46 +643,99 @@ export default {
 		},
 		
 		// Inspector保存
-		handleInspectorSave(elementData) {
-			// 更新资源特征对象
-			this.resourcesFeature.forEach(item => {
-				if (item.getId() == elementData.id) {
-					// 清理临时属性
-					const cleanData = { ...elementData };
-					delete cleanData.typeEle;
-					delete cleanData.directList;
-					
-					item.setProperties(cleanData);
-					
-					// 特殊处理:更新坐标
-					if (elementData.position && item.getGeometry().getType() === 'Point') {
-						item.getGeometry().setCoordinates(elementData.position);
-						// 通知地图组件更新点位坐标
-						this.$refs.olmap && this.$refs.olmap.pointPositionUpdate && 
-						this.$refs.olmap.pointPositionUpdate(elementData.id, elementData.position);
-					}
-					
-					// 特殊处理:如果是多边形,需要更新颜色
-					if (elementData.id.startsWith('s')) {
-						this.$refs.olmap && this.$refs.olmap.editSnapeColor && this.$refs.olmap.editSnapeColor(elementData.id);
-					}
-				}
-			});
+		async handleInspectorSave(elementData) {
+			// 保存前验证数据
+			if (!elementData || !elementData.id) {
+				this.$message.error('保存失败:元素数据无效');
+				return;
+			}
 			
-			// 更新当前特征数据
-			this.currentFeature = { ...elementData };
-			this.haveDraw = true;
+			// 保存前检查resourcesFeature数组
+			if (!this.resourcesFeature || this.resourcesFeature.length === 0) {
+				this.$message.error('保存失败:找不到地图元素数据');
+				return;
+			}
 			
-			// 刷新右侧列表中的元素信息
-			this.updateElementInList(elementData);
+			console.log('保存前元素数量:', this.resourcesFeature.length);
+			console.log('保存前元素列表:', this.resourcesFeature.map(f => f.getId()));
 			
-			// 关闭Inspector
-			this.inspector.visible = false;
+			// 显示保存进度
+			const loading = this.$loading({
+				lock: true,
+				text: '正在保存元素...',
+				spinner: 'el-icon-loading',
+				background: 'rgba(0, 0, 0, 0.7)'
+			});
 			
-			// 在地图上保持选中状态
-			setTimeout(() => {
-				this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(elementData.id);
-			}, 100);
+			try {
+				// 查找要更新的元素
+				let targetFeature = null;
+				for (let i = 0; i < this.resourcesFeature.length; i++) {
+					if (this.resourcesFeature[i].getId() === elementData.id) {
+						targetFeature = this.resourcesFeature[i];
+						break;
+					}
+				}
+				
+				if (!targetFeature) {
+					throw new Error(`找不到ID为 ${elementData.id} 的元素`);
+				}
+				
+				// 创建元素数据的副本以避免直接修改
+				const cleanData = { ...elementData };
+				delete cleanData.typeEle;
+				delete cleanData.directList;
+				
+				// 更新元素属性
+				targetFeature.setProperties(cleanData);
+				
+				// 特殊处理:更新坐标
+				if (elementData.position && targetFeature.getGeometry().getType() === 'Point') {
+					targetFeature.getGeometry().setCoordinates(elementData.position);
+					// 通知地图组件更新点位坐标
+					if (this.$refs.olmap && this.$refs.olmap.pointPositionUpdate && elementData.id) {
+						this.$refs.olmap.pointPositionUpdate(elementData.id, elementData.position);
+					}
+				}
+				
+				// 特殊处理:如果是多边形,需要更新颜色
+				if (elementData.id && elementData.id.startsWith('s')) {
+					if (this.$refs.olmap && this.$refs.olmap.editSnapeColor) {
+						this.$refs.olmap.editSnapeColor(elementData.id);
+					}
+				}
+				
+				// 更新当前特征数据
+				this.currentFeature = { ...elementData };
+				this.haveDraw = true;
+				
+				// 刷新右侧列表中的元素信息
+				this.updateElementInList(elementData);
+				
+				console.log('保存前验证元素数量:', this.resourcesFeature.length);
+				
+				// 自动保存到后端
+				await this.saveRoute();
+				
+				console.log('保存后验证元素数量:', this.resourcesFeature.length);
+				
+				// 关闭Inspector
+				this.inspector.visible = false;
+				
+				// 在地图上保持选中状态
+				setTimeout(() => {
+					if (this.$refs.olmap && this.$refs.olmap.selectShowEle && elementData.id) {
+						this.$refs.olmap.selectShowEle(elementData.id);
+					}
+				}, 100);
+				
+				this.$message.success(`元素 ${elementData.id} 保存成功`);
+			} catch (error) {
+				console.error('保存元素失败:', error);
+				this.$message.error('保存元素失败: ' + (error.message || '未知错误'));
+			} finally {
+				loading.close();
+			}
 		},
 		
 		// Inspector取消
@@ -593,8 +753,12 @@ export default {
 		
 		// Inspector定位
 		handleInspectorLocate(element) {
-			this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(element.id);
-			// 可以添加地图缩放和居中逻辑
+			if (this.$refs.olmap && this.$refs.olmap.selectShowEle && element && element.id) {
+				this.$refs.olmap.selectShowEle(element.id);
+				// 可以添加地图缩放和居中逻辑
+			} else {
+				console.warn('无法定位元素:', element);
+			}
 		},
 		
 		
@@ -688,7 +852,11 @@ export default {
 		// 查看某个元素
 		watchEle(id) {
 			// 调用地图组件显示元素
-			this.$refs.olmap.selectShowEle(id);
+			if (this.$refs.olmap && this.$refs.olmap.selectShowEle && id) {
+				this.$refs.olmap.selectShowEle(id);
+			} else {
+				console.warn('无法查看元素:', id);
+			}
 		},
 		// 查看某个元素的子组件回调
 		selectShowEleResult(feature) {
@@ -730,48 +898,58 @@ export default {
 			this.$refs.olmap.removeElement(id);
 		},
 		// 移除元素操作的回调
-		removeElementResult(id) {
-			if (!id) {
-				this.$message.error('移除元素失败!');
-				return;
-			}
-			// 删除当前数据table中这个元素
-			if (id.startsWith('p')) {
-				const index = this.pointData.findIndex(item => item.id === id);
-				if (index !== -1) {
-					this.pointData.splice(index, 1);
-				}
-			}
-			if (id.startsWith('l')) {
-				const index = this.lineData.findIndex(item => item.id === id);
-				if (index !== -1) {
-					this.lineData.splice(index, 1);
-				}
-			}
-			if (id.startsWith('b')) {
-				const index = this.bowData.findIndex(item => item.id === id);
-				if (index !== -1) {
-					this.bowData.splice(index, 1);
-				}
+	removeElementResult(id) {
+		if (!id) {
+			this.$message.error('移除元素失败!');
+			return;
+		}
+		
+		console.log('删除元素前数量:', this.resourcesFeature.length);
+		console.log('要删除的元素ID:', id);
+		
+		// 删除当前数据table中这个元素
+		if (id.startsWith('p')) {
+			const index = this.pointData.findIndex(item => item.id === id);
+			if (index !== -1) {
+				this.pointData.splice(index, 1);
 			}
-			if (id.startsWith('s')) {
-				const index = this.shapeData.findIndex(item => item.id === id);
-				if (index !== -1) {
-					this.shapeData.splice(index, 1);
-				}
+		}
+		if (id.startsWith('l')) {
+			const index = this.lineData.findIndex(item => item.id === id);
+			if (index !== -1) {
+				this.lineData.splice(index, 1);
 			}
-			// 判断当前选择元素是否被删除,如果是则删除
-			if (this.currentFeature && this.currentFeature.id == id) {
-				this.currentFeature = {};
+		}
+		if (id.startsWith('b')) {
+			const index = this.bowData.findIndex(item => item.id === id);
+			if (index !== -1) {
+				this.bowData.splice(index, 1);
 			}
-			// 删除源features中的此元素
-			const index = this.resourcesFeature.findIndex(feature => feature.getId() == id);
-			// 如果找到了该元素,则删除
+		}
+		if (id.startsWith('s')) {
+			const index = this.shapeData.findIndex(item => item.id === id);
 			if (index !== -1) {
-				this.resourcesFeature.splice(index, 1);
+				this.shapeData.splice(index, 1);
 			}
-			this.haveDraw = true;
-		},
+		}
+		
+		// 判断当前选择元素是否被删除,如果是则删除
+		if (this.currentFeature && this.currentFeature.id == id) {
+			this.currentFeature = {};
+		}
+		
+		// 删除源features中的此元素
+		const index = this.resourcesFeature.findIndex(feature => feature.getId() == id);
+		if (index !== -1) {
+			console.log('从resourcesFeature中删除元素:', id);
+			this.resourcesFeature.splice(index, 1);
+			console.log('删除后数量:', this.resourcesFeature.length);
+		} else {
+			console.warn('在resourcesFeature中找不到要删除的元素:', id);
+		}
+		
+		this.haveDraw = true;
+	},
 		// 初始化点位元素基础和高级参数
 		initElelmentParamsPonint(feature) {
 			feature.set('name', '');
@@ -861,23 +1039,146 @@ export default {
 			this.haveDraw = true; // 是否操作过页面元素(新增修改或者删除元素) (在切换页面或者刷新时进行拦截,避免误操作退出页面)
 		},
 		// 保存路网
-		saveRoute() {
-			// 保存时清理当前所有对象的额外渲染参数
-			this.resourcesFeature.forEach(feature => {
-				const values = feature.getProperties(); // 获取feature的values_自定义属性
-				// 检查并删除特定的属性(如果存在)
-				if (values) {
-					if (values.hasOwnProperty('typeEle')) {
-						delete values.typeEle;  // 删除 typeEle 属性
+		async saveRoute(validateAfterSave = false) {
+			try {
+				console.log('保存前元素数量:', this.resourcesFeature.length);
+				console.log('保存前元素列表:', this.resourcesFeature.map(f => f.getId()));
+				
+				// 构建GeoJSON数据(不修改原始数据)
+				const geoJsonData = this.buildGeoJsonData();
+				console.log('构建的GeoJSON数据:', geoJsonData);
+				
+				// 验证要保存的数据
+				if (!geoJsonData.features || geoJsonData.features.length === 0) {
+					throw new Error('没有找到要保存的路网元素');
+				}
+				
+				// 调用API保存路网数据
+				await this.saveRoadMapToServer(geoJsonData);
+				
+				console.log('保存后元素数量:', this.resourcesFeature.length);
+				console.log('保存后元素列表:', this.resourcesFeature.map(f => f.getId()));
+				
+				// 可选的保存后验证(主要用于调试)
+				if (validateAfterSave) {
+					try {
+						console.log('正在验证保存结果...');
+						const savedData = await getRoadMapGeoJson(this.mapName);
+						if (savedData && savedData.features) {
+							console.log('服务器上的元素数量:', savedData.features.length);
+							if (savedData.features.length !== geoJsonData.features.length) {
+								console.warn('警告:保存的元素数量与本地数量不匹配', {
+									local: geoJsonData.features.length,
+									server: savedData.features.length
+								});
+							}
+						}
+					} catch (validateError) {
+						console.warn('保存后验证失败:', validateError);
+						// 验证失败不影响主流程
 					}
-					if (values.hasOwnProperty('position')) {
-						delete values.position;  // 删除 position 属性
+				}
+				
+				// 保存成功
+				this.haveDraw = false;
+				this.$message.success('路网数据保存成功');
+			} catch (error) {
+				console.error('保存路网数据失败:', error);
+				this.$message.error('保存路网数据失败: ' + (error.message || '未知错误'));
+				throw error; // 重新抛出错误,让调用者知道保存失败
+			}
+		},
+
+		// 构建GeoJSON数据
+		buildGeoJsonData() {
+			// 验证resourcesFeature数组
+			if (!this.resourcesFeature || this.resourcesFeature.length === 0) {
+				console.warn('buildGeoJsonData: resourcesFeature数组为空');
+				return {
+					type: "FeatureCollection",
+					features: [],
+					map: this.mapName
+				};
+			}
+			
+			console.log('构建GeoJSON数据,元素数量:', this.resourcesFeature.length);
+			
+			const features = this.resourcesFeature.map(feature => {
+				try {
+					const properties = { ...feature.getProperties() };
+					const geometry = feature.getGeometry();
+					
+					// 验证几何对象
+					if (!geometry) {
+						console.warn('发现无效的几何对象,跳过元素:', feature.getId());
+						return null;
 					}
-					if (values.hasOwnProperty('directList')) {
-						delete values.directList;  // 删除 directList 属性
+					
+					// 确保包含ID
+					if (!properties.id) {
+						properties.id = feature.getId();
 					}
+					
+					// 清理临时渲染属性(不修改原始数据,只在保存时清理)
+					const cleanProperties = { ...properties };
+					delete cleanProperties.typeEle;      // 删除临时类型属性
+					delete cleanProperties.position;     // 删除临时位置属性  
+					delete cleanProperties.directList;   // 删除临时方向列表属性
+					delete cleanProperties.geometry;     // 删除OpenLayers几何对象,避免嵌套
+					
+					return {
+						type: "Feature",
+						properties: cleanProperties,
+						geometry: {
+							type: geometry.getType(),
+							coordinates: geometry.getCoordinates()
+						}
+					};
+				} catch (error) {
+					console.error('处理元素时出错:', feature.getId(), error);
+					return null;
 				}
-			});
+			}).filter(feature => feature !== null); // 过滤掉无效的元素
+
+			const result = {
+				type: "FeatureCollection",
+				features: features,
+				map: this.mapName // 添加地图名称
+			};
+			
+			console.log('构建完成,有效元素数量:', features.length);
+			return result;
+		},
+
+		// 保存路网数据到服务器
+		async saveRoadMapToServer(geoJsonData) {
+			try {
+				console.log('正在发送路网数据到服务器:', { data: geoJsonData });
+				
+				// 使用封装好的API函数
+				const response = await saveRoadMapGeoJson(geoJsonData);
+				
+				console.log('服务器响应:', response);
+				
+				// 检查响应数据中的status字段
+				if (response && response.status === true) {
+					return response;
+				} else {
+					throw new Error('保存失败:服务器返回状态为false');
+				}
+			} catch (error) {
+				console.error('保存路网数据失败:', error);
+				if (error.response) {
+					// 服务器响应了错误状态码
+					throw new Error(`服务器错误: ${error.response.status} - ${error.response.data || error.response.statusText}`);
+				} else if (error.request) {
+					// 请求发送了但没有收到响应
+					throw new Error('网络错误: 无法连接到服务器');
+				} else {
+					// 其他错误
+					throw new Error(`请求配置错误: ${error.message}`);
+				}
+			}
 		},
 		// === 添加当前点对话框处理 ===
 		

+ 502 - 176
src/views/map/maplist/index.vue

@@ -21,9 +21,10 @@
           @change="handleFilterChange"
         >
           <el-option label="全部状态" value="all" />
-          <el-option label="正常" value="ok" />
-          <el-option label="不可用" value="down" />
-          <el-option label="扫描中" value="scanning" />
+          <el-option label="正常" value="available" />
+          <el-option label="不可用" value="unavailable" />
+          <el-option label="正在建图" value="building" />
+          <el-option label="正在录制" value="recording" />
         </el-select>
         
         <el-select
@@ -139,7 +140,7 @@
         <div v-else class="card-grid" :class="{ compact: isCompactMode }">
           <XtMapCard
             v-for="item in displayedList"
-            :key="item.id || item.mapId || item.map_id"
+            :key="item.id"
             :item="item"
             :selectable="selectedMaps.length > 0"
             :selected="selectedMaps.includes(item.id)"
@@ -148,6 +149,7 @@
             @clickEdit="handleEdit"
             @clickMore="handleCardAction"
             @selectChange="handleSelectChange"
+            @refresh="getList"
           />
         </div>
       </template>
@@ -224,10 +226,10 @@
                         <el-table-column label="操作" width="220" header-align="center" align="right">
               <template slot-scope="{ row }">
                 <div class="op-cell">
-                  <router-link :to="buildNavTo(this, row)" class="op-link">
+                  <router-link :to="buildNavTo(this,row)" class="op-link">
                     <i class="el-icon-position"></i><span class="op-text">导航</span>
                   </router-link>
-                  <router-link :to="buildEditTo(this, row)" class="op-link">
+                  <router-link :to="buildEditTo(this,row)" class="op-link">
                     <i class="el-icon-edit"></i><span class="op-text">编辑</span>
                   </router-link>
                   <el-dropdown @command="cmd=>onMore(row,cmd)">
@@ -253,7 +255,7 @@
     <!-- 分页 -->
     <div v-if="!loading && mapList.length > 0" class="pagination-section">
       <!-- 密度切换 -->
-      <div v-if="total > 60 && viewMode === 'card'" class="density-toggle">
+      <div v-if="totalCount > 60 && viewMode === 'card'" class="density-toggle">
         <el-button-group size="mini">
           <el-button 
             :type="isCompactMode ? 'default' : 'primary'"
@@ -353,11 +355,14 @@
         <span style="font-weight: bold;">请选择下载的地图组件:</span>
       </div>
       <el-checkbox-group v-model="downLoadTypes" style="margin-bottom: 10px;">
-        <el-checkbox label="tree" :checked="true">八叉树</el-checkbox>
-        <el-checkbox label="vector" :checked="true">矢量</el-checkbox>
-        <el-checkbox label="las" :checked="true">las数量</el-checkbox>
-        <el-checkbox label="tile" :checked="true">瓦片地图</el-checkbox>
-        <el-checkbox label="task" :checked="true">任务数据</el-checkbox>
+        <el-checkbox 
+          v-for="component in availableComponents" 
+          :key="component.label" 
+          :label="component.label"
+          :checked="true"
+        >
+          {{ component.name }}
+        </el-checkbox>
       </el-checkbox-group>
       <div slot="footer" class="dialog-footer">
         <el-button type="primary" @click="submitDownload">开 始</el-button>
@@ -392,6 +397,33 @@
       </div>
 
       <MqttComp ref="mqtt" :topics="topics" @message-received="onMessage" />
+      
+      <!-- 导入地图对话框 -->
+      <el-dialog title="导入地图" :visible.sync="importOpen" width="520px" append-to-body :close-on-click-modal="false">
+        <div style="margin-bottom: 20px;">
+          <span style="font-weight: bold;">选择要导入的地图文件(.zip):</span>
+        </div>
+        <el-upload
+          ref="upload"
+          :action="`/v1/map/import`"
+          :headers="uploadHeaders"
+          :on-success="handleUploadSuccess"
+          :on-error="handleUploadError"
+          :before-upload="beforeUpload"
+          :auto-upload="false"
+          name="filedata"
+          accept=".zip"
+          drag
+        >
+          <i class="el-icon-upload"></i>
+          <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+          <div class="el-upload__tip" slot="tip">只能上传.zip文件,且不超过100MB</div>
+        </el-upload>
+        <div slot="footer" class="dialog-footer">
+          <el-button type="primary" @click="submitImport">开始导入</el-button>
+          <el-button @click="importOpen = false">取消</el-button>
+        </div>
+      </el-dialog>
   </div>
 </template>
 
@@ -403,6 +435,8 @@ import XtMapCard from '@/components/XtMapCard'
 import { formatDateTimeCompat, pickUpdatedAt } from '@/utils/datefmt'
 // 导入路由辅助函数
 import { buildNavTo, buildEditTo, buildCalibrateTo, buildConstructTo, pickId } from '@/utils/route-helpers'
+// 导入文件保存库
+import { saveAs } from 'file-saver'
 // 安全获取数组函数
 function pickArray(...arrs){ 
   for(const a of arrs){ 
@@ -447,7 +481,6 @@ export default {
       
       // 数据
       mapList: [],
-      total: 0,
       currentPage: 1,
       pageSize: 12,
 
@@ -582,6 +615,8 @@ export default {
       remoteExploreOpen: false, // 遥控探索确认弹框
       downloadOpen: false,
       constructOpen: false,
+      importOpen: false, // 导入地图对话框
+      uploadHeaders: {}, // 上传请求头
       constructModle: 'hand',
       exploreParams: {
         maxTime: null,
@@ -589,7 +624,9 @@ export default {
         maxRange: null
       },
       downLoadTypes: [],
+      availableComponents: [], // 可用的组件列表
       constructTypes: [],
+      currentRow: null, // 当前操作的地图行数据
       rules: {
         maxTime: [
           { required: true, message: "最长时间不能为空", trigger: "blur" }
@@ -611,46 +648,49 @@ export default {
       return pickArray(this.mapList)
     },
     
-    // 供表格/卡片使用的数据(先不做筛选,只做分页裁切;如果你已有筛选,请把你现有的筛选结果放到最前面
+    // 供表格/卡片使用的数据(支持搜索过滤和分页
     displayedList(){
-      // 如果已有筛选后的数据,优先使用
-      /* if (this.searchKeyword.trim() || this.statusFilter !== 'all') {
-        // 使用现有的筛选逻辑
-        let filteredData = [...this.mockMapList]
-        
-        // 搜索过滤
-        if (this.searchKeyword.trim()) {
-          const keyword = this.searchKeyword.toLowerCase()
-          filteredData = filteredData.filter(item => 
-            item.name.toLowerCase().includes(keyword) ||
-            item.remark.toLowerCase().includes(keyword)
-          )
-        }
-        
-        // 状态筛选
-        if (this.statusFilter !== 'all') {
-          filteredData = filteredData.filter(item => item.status === this.statusFilter)
-        }
-        
-        // 排序
+      let filteredData = [...(this.rawList || [])]
+      
+      // 搜索过滤
+      if (this.searchKeyword.trim()) {
+        const keyword = this.searchKeyword.toLowerCase()
+        filteredData = filteredData.filter(item => {
+          // 搜索地图名称、备注等字段
+          const mapName = (item.map || item.mapName || item.name || '').toLowerCase()
+          const remark = (item.remark || '').toLowerCase()
+          
+          return mapName.includes(keyword) || remark.includes(keyword)
+        })
+      }
+      
+      // 状态筛选
+      if (this.statusFilter !== 'all') {
+        filteredData = filteredData.filter(item => {
+          const status = item.state || item.status
+          return status === this.statusFilter
+        })
+      }
+      
+      // 排序
+      if (this.sortField && this.sortField !== 'updated') {
         filteredData.sort((a, b) => {
           let aVal, bVal
           
           switch (this.sortField) {
             case 'name':
-              aVal = a.name.toLowerCase()
-              bVal = b.name.toLowerCase()
+              aVal = (a.map || a.mapName || a.name || '').toLowerCase()
+              bVal = (b.map || b.mapName || b.name || '').toLowerCase()
               break
             case 'status':
-              // 状态优先级:ok > scanning > down
-              const statusOrder = { ok: 2, scanning: 1, down: 0 }
-              aVal = statusOrder[a.status] || 0
-              bVal = statusOrder[b.status] || 0
+              // 状态优先级:available > building > recording > unavailable
+              const statusOrder = { available: 3, building: 2, recording: 1, unavailable: 0 }
+              aVal = statusOrder[a.state || a.status] || 0
+              bVal = statusOrder[b.state || b.status] || 0
               break
-            case 'updated':
             default:
-              aVal = new Date(a.updatedAt).getTime()
-              bVal = new Date(b.updatedAt).getTime()
+              aVal = a[this.sortField] || ''
+              bVal = b[this.sortField] || ''
               break
           }
 
@@ -660,47 +700,39 @@ export default {
             return aVal < bVal ? 1 : aVal > bVal ? -1 : 0
           }
         })
-        
-        // 分页
-        const start = (this.currentPage - 1) * this.pageSize
-        const end = start + this.pageSize
-        return filteredData.slice(start, end)
-      }
-       */
-      // 否则使用通用分页逻辑
-      const src = this.rawList || []
-      console.log('src', src);
+      }
       
+      // 分页
       const page = this.pagination?.page || this.pageNum || this.currentPage || 1
-      const size = this.pagination?.pageSize || this.pageSize || 10
-      const start = (page-1)*size
-      return src.slice(start, start+size)
+      const size = this.pagination?.pageSize || this.pageSize || 12
+      const start = (page - 1) * size
+      return filteredData.slice(start, start + size)
     },
     
     totalCount(){
-/*       if (this.searchKeyword.trim() || this.statusFilter !== 'all') {
-        // 使用现有的筛选逻辑计算总数
-        let filteredData = [...this.mockMapList]
-        
-        // 搜索过滤
-        if (this.searchKeyword.trim()) {
-          const keyword = this.searchKeyword.toLowerCase()
-          filteredData = filteredData.filter(item => 
-            item.name.toLowerCase().includes(keyword) ||
-            item.remark.toLowerCase().includes(keyword)
-          )
-        }
-        
-        // 状态筛选
-        if (this.statusFilter !== 'all') {
-          filteredData = filteredData.filter(item => item.status === this.statusFilter)
-        }
-        
-        return filteredData.length
-      } */
+      let filteredData = [...(this.rawList || [])]
       
-      const src = this.rawList || []
-      return src.length
+      // 搜索过滤
+      if (this.searchKeyword.trim()) {
+        const keyword = this.searchKeyword.toLowerCase()
+        filteredData = filteredData.filter(item => {
+          // 搜索地图名称、备注等字段
+          const mapName = (item.map || item.mapName || item.name || '').toLowerCase()
+          const remark = (item.remark || '').toLowerCase()
+          
+          return mapName.includes(keyword) || remark.includes(keyword)
+        })
+      }
+      
+      // 状态筛选
+      if (this.statusFilter !== 'all') {
+        filteredData = filteredData.filter(item => {
+          const status = item.state || item.status
+          return status === this.statusFilter
+        })
+      }
+      
+      return filteredData.length
     },
 
     // 是否有搜索或筛选条件
@@ -805,6 +837,7 @@ export default {
   
           // 合并成对象数组 [{ map: 'demo', state: 'available' }]
           this.mapList = maps.map((map, index) => ({
+            id: index + 1, // 如果有唯一ID字段,请替换
             map,
             state: states[index] || null
           }))
@@ -816,69 +849,13 @@ export default {
           
           
         }
-       
-        
-        // 使用 Mock 数据 - 确保 mapList 被设置
-        // this.mapList = [...this.mockMapList]
-        
-        let filteredData = [...this.mapList]
-        
-        // 搜索过滤
-        if (this.searchKeyword.trim()) {
-          const keyword = this.searchKeyword.toLowerCase()
-          filteredData = filteredData.filter(item => 
-            item.name.toLowerCase().includes(keyword) ||
-            item.remark.toLowerCase().includes(keyword)
-          )
-        }
-        
-        // 状态筛选
-        if (this.statusFilter !== 'all') {
-          filteredData = filteredData.filter(item => item.status === this.statusFilter)
-        }
-        
-        // 排序
-        filteredData.sort((a, b) => {
-          let aVal, bVal
-          
-          switch (this.sortField) {
-            case 'name':
-              aVal = a.name.toLowerCase()
-              bVal = b.name.toLowerCase()
-              break
-            case 'status':
-              // 状态优先级:ok > scanning > down
-              const statusOrder = { ok: 2, scanning: 1, down: 0 }
-              aVal = statusOrder[a.status] || 0
-              bVal = statusOrder[b.status] || 0
-              break
-            case 'updated':
-            default:
-              aVal = new Date(a.updatedAt).getTime()
-              bVal = new Date(b.updatedAt).getTime()
-              break
-          }
-
-          if (this.sortOrder === 'asc') {
-            return aVal > bVal ? 1 : aVal < bVal ? -1 : 0
-          } else {
-            return aVal < bVal ? 1 : aVal > bVal ? -1 : 0
-          }
-        })
-        
-        // 分页
-        this.total = filteredData.length
-        const start = (this.currentPage - 1) * this.pageSize
-        const end = start + this.pageSize
-        const pagedData = filteredData.slice(start, end)
         
-        // 处理后的数据仍然赋值给 mapList,但原始数据已保存
-        this.mapList = this.processMapList(pagedData)
+        // 保持原始数据不变,过滤、排序、分页交给computed属性处理
+        // this.mapList 保存完整的原始数据
         
       } catch (error) {
         this.$message.error('获取地图列表失败: ' + error.message)
         this.mapList = []
-        this.total = 0
       } finally {
         this.loading = false
         this.relayout()
@@ -918,27 +895,29 @@ export default {
     handleSearch() {
       clearTimeout(this.searchDebounceTimer)
       this.searchDebounceTimer = setTimeout(() => {
+        // 搜索时重置到第一页
         this.currentPage = 1
-        this.getList()
+        // 由于使用computed属性进行前端过滤,无需调用接口
+        // displayedList和totalCount会自动重新计算
       }, 300)
     },
 
     // 筛选变化处理
     handleFilterChange() {
       this.currentPage = 1
-      this.getList()
+      // 由于使用computed属性进行前端过滤,无需调用接口
     },
 
     // 排序变化处理
     handleSortChange() {
       this.currentPage = 1
-      this.getList()
+      // 由于使用computed属性进行前端排序,无需调用接口
     },
 
     // 页码变化
     handlePageChange(page) {
       this.currentPage = page
-      this.getList()
+      // 使用前端分页,无需调用接口
       // 滚动到顶部
       this.$nextTick(() => {
         window.scrollTo({ top: 0, behavior: 'smooth' })
@@ -949,7 +928,7 @@ export default {
     handleSizeChange(size) {
       this.pageSize = size
       this.currentPage = 1
-      this.getList()
+      // 使用前端分页,无需调用接口
     },
 
     // 视图模式切换
@@ -1024,23 +1003,34 @@ export default {
           if (this.renameDialog) { this.renameDialog.visible = true; this.renameDialog.row = row; return; }
           // 兜底:Element prompt
           this.$prompt('请输入新的地图名称', '重命名', {
-            inputValue: row.mapName || row.name || '',
+            inputValue: row.mapName || row.map || row.name || '',
             inputPattern: /\S+/,
             inputErrorMessage: '名称不能为空'
-          }).then(({ value }) => {
-            // 若有旧方法:this.renameMap(id, value)
-            if (typeof this.renameMap === 'function') return this.renameMap(id, value);
-            row.mapName = value; 
-            row.name = value;
-            this.$message.success('已重命名'); // mock 刷新
+          }).then(async ({ value }) => {
+            try {
+              // 调用重命名API
+              const response = await mapApi.renameMap({
+                map: row.map || row.mapName || row.name,
+                rename: value
+              });
+              
+              if (response && response.status) {
+                this.$message.success('地图重命名成功');
+                // 刷新列表
+                this.getList();
+              } else {
+                this.$message.error('重命名失败');
+              }
+            } catch (error) {
+              console.error('重命名失败:', error);
+              this.$message.error('重命名失败: ' + (error.message || '网络错误'));
+            }
           }).catch(()=>{});
           break;
 
         case 'download':
-          // 打开下载对话框
-          this.title = `下载地图 - ${row.name || row.mapName || ''}`;
-          this.currentRow = row;
-          this.downloadOpen = true;
+          // 先获取组件列表,再打开下载对话框
+          await this.loadMapComponents(row);
           break;
 
         case 'build':
@@ -1056,21 +1046,25 @@ export default {
           return this.$router.push(buildConstructTo(row));
 
         case 'delete':
-          // 优先:旧删除方法
-          if (typeof this.handleDelete === 'function') {
-            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
-              .then(() => this.handleDelete([id]))
-              .catch(() => {});
-          }
-          if (typeof this.deleteMap === 'function') {
-            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
-              .then(() => this.deleteMap(id))
-              .catch(() => {});
-          }
           this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
-            .then(() => { 
-              this.$emit && this.$emit('remove', row); 
-              this.$message.success('已删除(mock)'); 
+            .then(async () => { 
+              try {
+                // 调用删除API
+                const response = await mapApi.deleteMap({
+                  map: row.map || row.mapName || row.name
+                });
+                
+                if (response && response.status) {
+                  this.$message.success('地图删除成功');
+                  // 刷新列表
+                  this.getList();
+                } else {
+                  this.$message.error('删除失败');
+                }
+              } catch (error) {
+                console.error('删除失败:', error);
+                this.$message.error('删除失败: ' + (error.message || '网络错误'));
+              }
             })
             .catch(() => {});
           break;
@@ -1082,7 +1076,7 @@ export default {
           
         default:
           // 兼容原有的操作
-          return this.handleCardAction(row.id, cmd);
+          return this.handleCardAction(row, cmd);
       }
     },
 
@@ -1094,16 +1088,19 @@ export default {
     },
 
     // 卡片更多操作
-    async handleCardAction(id, action) {
+    async handleCardAction(row, action) {
+      console.log('Card action:', row, action);
+      
       switch (action) {
         case 'publish':
-          await this.handlePublish([id])
+          await this.handlePublish(row)
           break
         case 'copy':
-          await this.handleCopy(id)
+          await this.handleCopy(row)
           break
         case 'download':
-          await this.handleDownload(id)
+          // 先获取组件列表,再打开下载对话框
+          await this.loadMapComponents(row);
           break
         case 'delete':
           await this.handleDelete([id])
@@ -1135,7 +1132,53 @@ export default {
 
     // 导入地图
     handleImport() {
-      this.$message.info('导入功能开发中...')
+      this.importOpen = true
+    },
+
+    // 文件上传前的处理
+    beforeUpload(file) {
+      const isZip = file.type === 'application/zip' || file.name.endsWith('.zip')
+      const isLt100M = file.size / 1024 / 1024 < 100
+
+      if (!isZip) {
+        this.$message.error('只能上传.zip格式的文件!')
+        return false
+      }
+      if (!isLt100M) {
+        this.$message.error('上传文件大小不能超过100MB!')
+        return false
+      }
+      return true
+    },
+
+    // 提交导入
+    submitImport() {
+      const fileList = this.$refs.upload.uploadFiles
+      if (fileList.length === 0) {
+        this.$message.error('请选择要导入的地图文件')
+        return
+      }
+      
+      this.$refs.upload.submit()
+    },
+
+    // 上传成功处理
+    handleUploadSuccess(response, file) {
+      if (response && response.status) {
+        this.$message.success(`地图导入成功: ${response.map || file.name}`)
+        this.importOpen = false
+        this.$refs.upload.clearFiles()
+        // 刷新列表
+        this.getList()
+      } else {
+        this.$message.error('导入失败')
+      }
+    },
+
+    // 上传失败处理
+    handleUploadError(error, file) {
+      console.error('上传失败:', error)
+      this.$message.error('导入失败: ' + (error.message || '网络错误'))
     },
 
     // 发布地图
@@ -1342,16 +1385,202 @@ export default {
       this.remoteExploreOpen = false
     },
 
-    submitDownload() {
-      console.log('Download types:', this.downLoadTypes)
-      this.downloadOpen = false
-      this.$message.success('下载已开始')
+    async submitDownload() {
+      console.log('submitDownload', this.currentRow);
+      
+      if (!this.currentRow) {
+        this.$message.error('请选择要下载的地图');
+        return;
+      }
+      
+      if (this.downLoadTypes.length === 0) {
+        this.$message.error('请至少选择一个地图组件');
+        return;
+      }
+      
+      const mapName = this.currentRow.map || this.currentRow.mapName || this.currentRow.name;
+      
+      // 创建加载提示
+      const loading = this.$loading({
+        lock: true,
+        text: '正在压缩地图文件,请耐心等待...',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)',
+        customClass: 'large-loading-text',
+        target: document.body
+      });
+      
+      // 应用自定义样式
+      this.applyLoadingStyle();
+      
+      try {
+        // 计算需要忽略的组件(所有可用组件减去选中的组件)
+        const allComponents = this.availableComponents.map(comp => comp.label);
+        const ignoreComponents = allComponents.filter(comp => !this.downLoadTypes.includes(comp));
+        
+        // 第一步:压缩地图
+        loading.text = '正在压缩地图文件,大文件可能需要较长时间...';
+        const compressResponse = await mapApi.compressMapExport({
+          map: mapName,
+          ignore: ignoreComponents
+        });
+        
+        if (compressResponse && compressResponse.status) {
+          // 第二步:下载压缩包
+          loading.text = '正在下载压缩文件...';
+          
+          try {
+            const downloadResponse = await mapApi.downloadMapExport({
+              map: mapName
+            });
+            
+            // 检查响应是否为有效的blob
+            if (downloadResponse instanceof Blob && downloadResponse.size > 0) {
+              // 使用file-saver库进行下载
+              saveAs(downloadResponse, `${mapName}.zip`);
+              this.$message.success(`地图 "${mapName}" 下载完成`);
+              this.downloadOpen = false;
+            } else {
+              // 如果不是有效的blob,可能是错误响应
+              console.error('下载响应不是有效的文件:', downloadResponse);
+              this.$message.error('下载失败:服务器返回的不是有效文件');
+            }
+          } catch (downloadError) {
+            console.error('下载文件失败:', downloadError);
+            if (downloadError.message && downloadError.message.includes('timeout')) {
+              this.$confirm('下载超时,可能是文件较大或网络较慢。是否重试?', '下载超时', {
+                confirmButtonText: '重试',
+                cancelButtonText: '取消',
+                type: 'warning'
+              }).then(() => {
+                // 重试下载
+                this.retryDownload(mapName);
+              }).catch(() => {
+                this.downloadOpen = false;
+              });
+            } else if (downloadError.message && downloadError.message.includes('404')) {
+              this.$message.error('下载文件不存在,可能压缩失败,请重新尝试');
+            } else {
+              this.$message.error('下载文件失败: ' + (downloadError.message || '网络错误'));
+            }
+          }
+        } else {
+          this.$message.error('地图压缩失败,请检查地图文件是否完整');
+        }
+      } catch (error) {
+        console.error('下载失败:', error);
+        if (error.message && error.message.includes('timeout')) {
+          this.$message.error('压缩超时,请稍后重试或联系管理员');
+        } else {
+          this.$message.error('下载失败: ' + (error.message || '网络错误'));
+        }
+      } finally {
+        loading.close();
+      }
+    },
+
+    // 重试下载方法
+    async retryDownload(mapName) {
+      const loading = this.$loading({
+        lock: true,
+        text: '正在重试下载...',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)',
+        customClass: 'large-loading-text',
+        target: document.body
+      });
+      
+      // 应用自定义样式
+      this.applyLoadingStyle();
+      
+      try {
+        const downloadResponse = await mapApi.downloadMapExport({
+          map: mapName
+        });
+        
+        if (downloadResponse instanceof Blob && downloadResponse.size > 0) {
+          saveAs(downloadResponse, `${mapName}.zip`);
+          this.$message.success(`地图 "${mapName}" 下载完成`);
+          this.downloadOpen = false;
+        } else {
+          this.$message.error('重试失败:服务器返回的不是有效文件');
+        }
+      } catch (error) {
+        console.error('重试下载失败:', error);
+        this.$message.error('重试失败: ' + (error.message || '网络错误'));
+      } finally {
+        loading.close();
+      }
+    },
+
+    // 应用自定义loading样式的辅助方法
+    applyLoadingStyle() {
+      this.$nextTick(() => {
+        const loadingMask = document.querySelector('.el-loading-mask.large-loading-text');
+        if (loadingMask) {
+          const textEl = loadingMask.querySelector('.el-loading-text');
+          const spinnerEl = loadingMask.querySelector('.el-loading-spinner');
+          const iconEl = loadingMask.querySelector('.el-icon-loading');
+          
+          if (textEl) {
+            textEl.style.fontSize = '18px';
+            textEl.style.fontWeight = '500';
+            textEl.style.color = '#ffffff';
+            textEl.style.lineHeight = '1.5';
+            textEl.style.marginTop = '10px';
+          }
+          
+          if (spinnerEl) {
+            spinnerEl.style.fontSize = '32px';
+          }
+          
+          if (iconEl) {
+            iconEl.style.fontSize = '32px';
+            iconEl.style.color = '#ffffff';
+          }
+        }
+      });
     },
 
     submitConstruct() {
       console.log('Construct mode:', this.constructModle)
       this.constructOpen = false
       this.$message.success('构建已开始')
+    },
+
+    // 加载地图组件列表
+    async loadMapComponents(row) {
+      const mapName = row.map || row.mapName || row.name;
+      
+      if (!mapName) {
+        this.$message.error('无法获取地图名称');
+        return;
+      }
+
+      try {
+        // 调用组件列表接口
+        const response = await mapApi.getMapComponents({ map: mapName });
+        
+        if (response && response.status && response.components) {
+          // 保存可用组件列表
+          this.availableComponents = response.components;
+          
+          // 默认选中所有可用的组件
+          this.downLoadTypes = response.components.map(component => component.label);
+          
+          // 设置当前操作的行数据和标题
+          this.currentRow = row;
+          this.title = `下载地图 - ${row.name || row.mapName || row.map || ''}`;
+          
+          // 打开下载对话框
+          this.downloadOpen = true;
+        } else {
+          this.$message.error('获取地图组件列表失败');
+        }
+      } catch (error) {
+        console.error('获取地图组件列表失败:', error);
+        this.$message.error('获取地图组件列表失败: ' + (error.message || '网络错误'));
+      }
     }
   }
 };
@@ -2219,4 +2448,101 @@ html.dark {
     }
   }
 }
+
+/* 自定义加载蒙版样式 - 更大的字体 */
+::v-deep .large-loading-text .el-loading-text {
+  font-size: 18px !important;
+  font-weight: 500 !important;
+  color: #ffffff !important;
+  line-height: 1.5 !important;
+  margin-top: 10px !important;
+}
+
+::v-deep .large-loading-text .el-loading-spinner {
+  font-size: 32px !important;
+}
+
+::v-deep .large-loading-text .el-loading-spinner .el-icon-loading {
+  font-size: 32px !important;
+  color: #ffffff !important;
+}
+
+/* 全局样式备用方案 */
+.el-loading-mask.large-loading-text .el-loading-text {
+  font-size: 18px !important;
+  font-weight: 500 !important;
+  color: #ffffff !important;
+  line-height: 1.5 !important;
+  margin-top: 10px !important;
+}
+
+.el-loading-mask.large-loading-text .el-loading-spinner {
+  font-size: 32px !important;
+}
+
+.el-loading-mask.large-loading-text .el-loading-spinner .el-icon-loading {
+  font-size: 32px !important;
+  color: #ffffff !important;
+}
+</style>
+
+<style>
+/* 全局加载蒙版字体样式 - 不使用scoped */
+.el-loading-mask.large-loading-text .el-loading-text {
+  font-size: 18px !important;
+  font-weight: 500 !important;
+  color: #ffffff !important;
+  line-height: 1.5 !important;
+  margin-top: 10px !important;
+}
+
+.el-loading-mask.large-loading-text .el-loading-spinner {
+  font-size: 32px !important;
+}
+
+.el-loading-mask.large-loading-text .el-loading-spinner .el-icon-loading {
+  font-size: 32px !important;
+  color: #ffffff !important;
+}
+
+.el-loading-mask.large-loading-text .el-loading-spinner .circular {
+  width: 32px !important;
+  height: 32px !important;
+}
+
+/* 更强力的样式覆盖 */
+body .el-loading-mask.large-loading-text .el-loading-text {
+  font-size: 18px !important;
+  font-weight: 500 !important;
+  color: #ffffff !important;
+  line-height: 1.5 !important;
+  margin-top: 10px !important;
+}
+
+body .el-loading-mask.large-loading-text .el-loading-spinner {
+  font-size: 32px !important;
+}
+
+body .el-loading-mask.large-loading-text .el-loading-spinner .el-icon-loading {
+  font-size: 32px !important;
+  color: #ffffff !important;
+}
+
+/* 通用loading文字样式覆盖 */
+.large-loading-text .el-loading-text {
+  font-size: 18px !important;
+  font-weight: 500 !important;
+  color: #ffffff !important;
+  line-height: 1.5 !important;
+  margin-top: 10px !important;
+}
+
+.large-loading-text .el-loading-spinner {
+  font-size: 32px !important;
+}
+
+.large-loading-text .el-loading-spinner i {
+  font-size: 32px !important;
+  color: #ffffff !important;
+}
 </style>

File diff ditekan karena terlalu besar
+ 908 - 31
src/views/map/maplist/navigation.vue


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini