Quellcode durchsuchen

1.修复地图导航与编辑相关;2.替换登录逻辑

jiuling vor 7 Monaten
Ursprung
Commit
6108876a31

+ 1 - 1
.env.development

@@ -6,7 +6,7 @@ ENV = 'development'
 
 # 系统/开发环境
 VUE_APP_BASE_API = '/dev-api'
-VUE_APP_PNS_MQTT_PROXY = '/robot4inspection/844727706d718717'
+VUE_APP_PNS_MQTT_PROXY = '/robot4inspection/50145460cca65f70'
 VUE_APP_PNS_API = '/pns'
 
 # 路由懒加载

+ 2 - 0
.env.production

@@ -6,3 +6,5 @@ ENV = 'production'
 
 # 管理系统/生产环境
 VUE_APP_BASE_API = '/prod-api'
+VUE_APP_PNS_MQTT_PROXY = '/robot4inspection/844727706d718717'
+VUE_APP_PNS_API = '/pns'

+ 1 - 0
package.json

@@ -47,6 +47,7 @@
     "highlight.js": "9.18.5",
     "js-beautify": "1.13.0",
     "js-cookie": "3.0.1",
+    "js-md5": "^0.8.3",
     "jsencrypt": "3.0.0-rc.1",
     "mqtt": "^4.2.1",
     "nprogress": "0.2.0",

BIN
public/favicon.ico


BIN
public/favicon_back.ico


+ 29 - 0
src/api/config/param.js

@@ -0,0 +1,29 @@
+import request from '@/utils/request'
+
+// 查询参数分组列表
+export function getGroups() {
+  return request({
+    url: '/group/list',
+    method: 'get',
+    baseURL: '/param'
+  })
+}
+
+// 查询指定分组的参数列表
+export function getGroupParams(group) {
+  return request({
+    url: `/group/params?group=${group}`,
+    method: 'get',
+    baseURL: '/param'
+  })
+}
+
+// 更新参数值
+export function updateParamValue(data) {
+  return request({
+    url: '/value',
+    method: 'post',
+    data: data,
+    baseURL: '/param'
+  })
+}

+ 24 - 5
src/api/login.js

@@ -1,19 +1,19 @@
 import request from '@/utils/request'
+import md5 from 'js-md5'
 
 // 登录方法
 export function login(username, password, code, uuid) {
   const data = {
-    username,
-    password,
-    code,
-    uuid
+    user: username,
+    passwd: md5(password)
   }
   return request({
-    url: '/login',
+    url: '/v1/user/auth',
     headers: {
       isToken: false,
       repeatSubmit: false
     },
+    baseURL: '/pns',
     method: 'post',
     data: data
   })
@@ -57,4 +57,23 @@ export function getCodeImg() {
     method: 'get',
     timeout: 20000
   })
+}
+
+// 修改用户名和密码
+export function modifyUser(userPre, passwdPre, userNew, passwdNew) {
+  const data = {
+    user_pre: userPre,
+    passwd_pre: md5(passwdPre),
+    user_new: userNew,
+    passwd_new: md5(passwdNew)
+  }
+  return request({
+    url: '/v1/user/modify',
+    headers: {
+      isToken: false,
+      repeatSubmit: false
+    },
+    method: 'post',
+    data: data
+  })
 }

+ 8 - 0
src/api/map/map.js

@@ -42,3 +42,11 @@ export function delMap(id) {
     method: 'delete'
   })
 }
+
+// 获取地图标定历史数据
+export function getCalibrationHistory(mapName) {
+  return request({
+    url: `http://192.168.0.120:8086/v1/map/file/${mapName}/shapefile/calibration.json`,
+    method: 'get'
+  })
+}

+ 40 - 0
src/api/map/task.js

@@ -0,0 +1,40 @@
+import request from '@/utils/request'
+
+// 查询任务列表
+export function listTask(mapName) {
+  return request({
+    url: `/v1/path/list?map=${mapName}`,
+    method: 'get',
+    baseURL: '/pns',
+  })
+}
+
+// 查询任务详细
+export function getTask(mapName, taskName) {
+  return request({
+    url: `/v1/path?map=${mapName}&path=${taskName}`,
+    method: 'get',
+    baseURL: '/pns',
+  })
+}
+
+// 新增任务
+export function addTask(data) {
+  return request({
+    url: '/v1/path',
+    method: 'post',
+    data: data,
+    baseURL: '/pns',
+  })
+}
+
+// 删除任务
+export function delTask(data) {
+  return request({
+    url: '/v1/path',
+    method: 'delete',
+    data: data,
+    baseURL: '/pns'
+  })
+}
+

+ 4 - 4
src/components/BottomInspector.vue

@@ -97,7 +97,7 @@
               <el-form-item label="元素ID" prop="id">
                 <el-input 
                   v-model="formData.id" 
-                  :readonly="true"
+                  
                   size="small"
                 />
               </el-form-item>
@@ -389,9 +389,9 @@ export default {
       
       // 表单验证规则
       formRules: {
-        name: [
-          { required: true, message: '请输入元素名称', trigger: 'blur' }
-        ],
+        // name: [
+        //   { required: true, message: '请输入元素名称', trigger: 'blur' }
+        // ],
         x: [
           { required: true, message: '请输入X坐标', trigger: 'change' }
         ],

+ 57 - 7
src/components/Mqtt/mqttComp.vue

@@ -13,7 +13,7 @@ export default {
       type: Object,
       default: () => ({
         head: "ws", // ws 或 wss
-        host: "8.148.78.124",
+        host: "192.168.0.120",
         port: 8083,
         path: "/mqtt",
       }),
@@ -28,6 +28,10 @@ export default {
         clean: true,
         connectTimeout: 10 * 1000,
         reconnectPeriod: 2000,
+        protocolVersion: 4, // 使用MQTT 3.1.1协议
+        protocolId: 'MQTT', // 明确指定协议ID
+        // 注意:MQTT.js 默认已使用 UTF-8 编码,无需额外配置
+        // 编码处理在消息接收和发送时进行
       }),
     },
     topics: {
@@ -90,9 +94,41 @@ export default {
       this.client.on("message", (topic, message) => {
         let msg;
         try {
-          msg = JSON.parse(message.toString());
-        } catch {
-          msg = message.toString();
+          // 确保使用UTF-8编码解析消息 - 增强版本
+          let messageStr;
+          
+          // 统一转换为UTF-8字符串
+          if (message instanceof Buffer) {
+            // 如果是Buffer,明确使用UTF-8解码
+            messageStr = message.toString('utf8');
+          } else if (message instanceof Uint8Array) {
+            // 如果是Uint8Array,使用TextDecoder解码(更可靠)
+            const decoder = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true });
+            messageStr = decoder.decode(message);
+          } else if (typeof message === 'string') {
+            // 如果已经是字符串,直接使用
+            messageStr = message;
+          } else {
+            // 其他情况,尝试转换
+            messageStr = String(message);
+          }
+          
+          // 尝试解析为JSON
+          msg = JSON.parse(messageStr);
+          
+          // 调试日志(可选,用于排查问题)
+          // console.log(`📨 收到消息 [${topic}]:`, messageStr.substring(0, 200));
+        } catch (e) {
+          // JSON解析失败时,尝试直接使用字符串
+          console.warn(`⚠️ JSON解析失败 [${topic}]:`, e.message);
+          if (message instanceof Buffer) {
+            msg = message.toString('utf8');
+          } else if (message instanceof Uint8Array) {
+            const decoder = new TextDecoder('utf-8', { fatal: false, ignoreBOM: true });
+            msg = decoder.decode(message);
+          } else {
+            msg = String(message);
+          }
         }
         this.$emit("message-received", { topic, message: msg });
       });
@@ -159,10 +195,24 @@ export default {
         return;
       }
       
-      const payload = typeof message === "object" ? JSON.stringify(message) : String(message);
-       this.client.publish(topic, payload, { qos, retain }, (err) => {
+      // 确保消息使用UTF-8编码
+      let payloadStr;
+      if (typeof message === "object") {
+        // 使用JSON.stringify时不进行任何转义,保持原始字符
+        payloadStr = JSON.stringify(message);
+      } else {
+        payloadStr = String(message);
+      }
+      
+      // 方案1:直接发送字符串,让MQTT.js处理编码(推荐)
+      // MQTT.js会自动将字符串转换为UTF-8编码的Buffer
+      console.log(`📤 准备发布消息到 ${topic}:`, payloadStr.substring(0, 200));
+      
+      this.client.publish(topic, payloadStr, { qos, retain }, (err) => {
         if (!err) {
-          console.log(`消息已发布: ${topic}, QoS=${qos}, Retain=${retain}`);
+          console.log(`✅ 消息已发布: ${topic}, QoS=${qos}, Retain=${retain}`);
+        } else {
+          console.error(`❌ 消息发布失败: ${topic}`, err);
         }
       });
     },

+ 197 - 26
src/components/OlMap/index.vue

@@ -238,7 +238,7 @@ export default {
     url() {
       // return this.fileUrl + this.deptId + "/" + "map" + "/" + this.robotCode + "/" + this.mapName;
       // return 'http://101.35.49.102:9000/prod/102/map/HJ-326/sh02'
-      return  'http://8.148.78.124:10001'
+      return  'http://192.168.0.120:8086'
     }
   },
 
@@ -1268,6 +1268,33 @@ export default {
         this.elementRoadDrawEnd(feature, 'p');
       });
     },
+    
+    // 确保坐标有效,处理null值并确保有z坐标
+    ensureValidCoordinate(coord) {
+      // 确保坐标至少有x和y
+      if (!coord || coord.length < 2) {
+        console.warn('坐标数据无效:', coord);
+        return coord;
+      }
+      
+      // 处理x和y坐标的null值
+      if (coord[0] === null || coord[0] === undefined) {
+        coord[0] = 0;
+      }
+      if (coord[1] === null || coord[1] === undefined) {
+        coord[1] = 0;
+      }
+      
+      // 确保有z坐标,如果没有或为null,则设为0
+      if (coord.length < 3) {
+        coord.push(0);
+      } else if (coord[2] === null || coord[2] === undefined) {
+        coord[2] = 0;
+      }
+      
+      return coord;
+    },
+    
     // 绘制直线
     drawLine() {
       this.clearDraw(); // 清除之前的绘制
@@ -1293,21 +1320,31 @@ export default {
         const featureId = 'l_' + (this.maxLineIdNum + 1);
         feature.setId(featureId);
         feature.set('id', featureId);
-        // 检查并创建线段的起点和终点
+        
+        // 检查并创建线段的起点和终点,并记录它们的ID
+        let startPointId = null;
+        let endPointId = null;
+        
         coordinates.forEach((coord, index) => {
           let pointId = this.getConnectedPointId(coord); // 判断是否已有点
           if (!pointId) {
-            // 补充z坐标
-            coord.push(0);
-            this.createPointAtCoordinate(coord, 'l'); // 创建新点
+            // 确保坐标有正确的z值,处理null值
+            this.ensureValidCoordinate(coord);
+            // 创建新点,并获取新创建点的ID
+            pointId = this.createPointAtCoordinate(coord);
           }
-          // 记录起点和终点的点ID到线的属性中(可选)
+          // 记录起点和终点的点ID
           if (index === 0) {
-            feature.set('startid', pointId || 'p_' + (this.maxPointIdNum));
+            startPointId = pointId;
           } else if (index === coordinates.length - 1) {
-            feature.set('endid', pointId || 'p_' + (this.maxPointIdNum));
+            endPointId = pointId;
           }
         });
+        
+        // 设置线段的起点ID和终点ID
+        feature.set('startid', startPointId);
+        feature.set('endid', endPointId);
+        
         // 使用 customizedStyle 设置样式
         this.roadmap_src.addFeature(feature);
         feature.setStyle(this.customizedStyle(feature));
@@ -1321,10 +1358,10 @@ export default {
     /**
      * 在指定坐标创建点-绘线和曲线子功能
      * @param targetCoord 绘制的点位xy
-     * @param type 当前触发此方式(l:绘制线段中 b:绘制曲线中) ,如果设置此参数则在绘制点位完毕后自动继续调用原来的绘制模式,不传递则不调用
      * @param data 当前点位非手动绘制时需要额外增加的参数[{数据名:数据}],可传递多个
+     * @returns {string} 返回创建的点的ID,如果点已存在则返回null
      */
-    createPointAtCoordinate(targetCoord, type, data = []) {
+    createPointAtCoordinate(targetCoord, data = []) {
       // 判断当前坐标是否有点位,有则不创建
       const features = this.roadmap_src.getFeatures();
       for (const feature of features) {
@@ -1338,7 +1375,7 @@ export default {
               message: '目标绘制点已存在点位',
               type: 'warning'
             });
-            return;
+            return null;
           }
         }
       }
@@ -1359,9 +1396,8 @@ export default {
       pointFeature.setStyle(this.customizedStyle(pointFeature));  // 自定义点样式
       // 点位数据回执父组件记录
       this.elementRoadDrawEnd(pointFeature, 'p');
-      if (type == 'l') {
-        this.drawLine();
-      }
+      // 返回创建的点的ID
+      return pointId;
     },
 
     // (绘制线段)判断坐标是否接近已有点并返回该点的ID-绘线和曲线子功能
@@ -1407,28 +1443,28 @@ export default {
         let feature = event.feature;
         // 获取起点和终点的坐标
         const startCoord = coordinates[0];
-        startCoord.push(0);
+        this.ensureValidCoordinate(startCoord);
         const endCoord = coordinates[coordinates.length - 1];
-        endCoord.push(0);
+        this.ensureValidCoordinate(endCoord);
+        
         // 检查起点和终点是否已经存在点,如果没有则生成新的点
         let startPointId = this.getConnectedPointId(startCoord);
         let endPointId = this.getConnectedPointId(endCoord);
+        
         // 如果起点没有对应的点,生成新的点
         if (!startPointId) {
-          this.createPointAtCoordinate(startCoord, 'b');
-          // 将当前点位信息置入线段的起始和结束点位处
-          feature.set('startid', startPointId || 'p_' + (this.maxPointIdNum));
-        } else {
-          feature.set('startid', startPointId);
+          startPointId = this.createPointAtCoordinate(startCoord);
         }
+        
         // 如果终点没有对应的点,生成新的点
         if (!endPointId) {
-          this.createPointAtCoordinate(endCoord, 'b');
-          // 将当前点位信息置入线段的起始和结束点位处 
-          feature.set('endid', 'p_' + (this.maxPointIdNum));
-        } else {
-          feature.set('endid', endPointId);
+          endPointId = this.createPointAtCoordinate(endCoord);
         }
+        
+        // 设置起点ID和终点ID
+        feature.set('startid', startPointId);
+        feature.set('endid', endPointId);
+        
         // 获取离散化的贝塞尔曲线
         var newBezierPoints = this.genBezierPointsByControlPoints(coordinates, 0.01);
         // 将MultiPoint替换为贝塞尔LineString
@@ -2193,6 +2229,141 @@ export default {
       } else {
         console.log('机器人位置无法投影到轨迹上,跳过实时更新');
       }
+    },
+
+    // === 路网导入导出相关方法 ===
+    
+    // 清空所有地图元素
+    clearAllElements() {
+      if (!this.roadmap_src) {
+        console.log("请先初始化画布!");
+        return false;
+      }
+      
+      try {
+        // 清空所有features
+        this.roadmap_src.clear();
+        
+        // 清空选中状态
+        this.selectedFeatureId = '';
+        
+        // 清空相关图标
+        if (this.icon_src) {
+          this.icon_src.clear();
+        }
+        
+        console.log('所有地图元素已清空');
+        return true;
+      } catch (error) {
+        console.error('清空地图元素失败:', error);
+        return false;
+      }
+    },
+
+    // 导入GeoJSON特征到地图
+    async importGeoJsonFeatures(geoJsonData) {
+      if (!this.roadmap_src) {
+        throw new Error("请先初始化画布!");
+      }
+
+      if (!geoJsonData || !geoJsonData.features) {
+        throw new Error("无效的GeoJSON数据");
+      }
+
+      try {
+        // 使用GeoJSON格式读取器
+        const geojsonFormat = new GeoJSON();
+        
+        // 解析GeoJSON数据为OpenLayers Feature对象
+        const features = geojsonFormat.readFeatures(geoJsonData, {
+          featureProjection: this.map.getView().getProjection()
+        });
+
+        // 设置每个feature的ID(如果没有的话)
+        features.forEach(feature => {
+          if (!feature.getId() && feature.get('id')) {
+            feature.setId(feature.get('id'));
+          }
+        });
+
+        // 批量添加features到地图
+        this.roadmap_src.addFeatures(features);
+
+        // 触发特征初始化完成事件,通知父组件更新数据
+        if (features.length > 0) {
+          this.$emit('elementRoadInitEnd', features);
+        }
+
+        console.log(`成功导入${features.length}个地图元素`);
+        return features;
+      } catch (error) {
+        console.error('导入GeoJSON特征失败:', error);
+        throw new Error(`导入失败: ${error.message}`);
+      }
+    },
+
+    // 根据坐标创建点位(为导入的实时位姿点提供支持)
+    createPointAtCoordinateForImport(coordinates, name = '', extraData = []) {
+      if (!this.roadmap_src) {
+        console.log("请先初始化画布!");
+        return;
+      }
+
+      try {
+        // 确保坐标有效,处理null值
+        this.ensureValidCoordinate(coordinates);
+        
+        // 生成新的点位ID
+        const existingPoints = this.roadmap_src.getFeatures().filter(f => 
+          f.getId() && f.getId().startsWith('p_')
+        );
+        const maxId = existingPoints.length > 0 ? 
+          Math.max(...existingPoints.map(f => parseInt(f.getId().split('_')[1]) || 0)) : 0;
+        const newId = `p_${maxId + 1}`;
+
+        // 创建点位feature
+        const pointFeature = new Feature({
+          geometry: new Point(coordinates),
+          id: newId,
+          name: name || `点位${maxId + 1}`
+        });
+        
+        pointFeature.setId(newId);
+
+        // 设置额外属性
+        if (extraData && extraData.length > 0) {
+          extraData.forEach(data => {
+            Object.keys(data).forEach(key => {
+              pointFeature.set(key, data[key]);
+            });
+          });
+        }
+
+        // 添加到地图
+        this.roadmap_src.addFeature(pointFeature);
+
+        // 触发绘制完成事件
+        this.$emit('elementRoadDrawEnd', pointFeature);
+
+        console.log(`成功创建点位: ${newId}`, coordinates);
+        return pointFeature;
+      } catch (error) {
+        console.error('创建点位失败:', error);
+        throw error;
+      }
+    },
+
+    // 刷新地图显示
+    refresh() {
+      if (this.map) {
+        // 强制重新渲染地图
+        this.map.render();
+        
+        // 如果需要,也可以触发视图更新
+        this.map.updateSize();
+        
+        console.log('地图已刷新');
+      }
     }
   },
 }

+ 125 - 0
src/config/menu.js

@@ -0,0 +1,125 @@
+// 静态菜单配置
+export const staticMenus = [
+  {
+    name: 'Config',
+    path: '/config',
+    hidden: false,
+    redirect: '/config/calibration',
+    component: 'Layout',
+    alwaysShow: true,
+    meta: {
+      title: '参数配置',
+      icon: 'system',
+      noCache: false
+    },
+    children: [
+      {
+        name: 'ConfigCalibration',
+        path: 'calibration',
+        hidden: false,
+        component: 'config/connectconf/index',
+        meta: {
+          title: '参数配置',
+          icon: 'edit',
+          noCache: false
+        }
+      }
+    ]
+  },
+  {
+    name: 'Map',
+    path: '/map',
+    hidden: false,
+    redirect: '/map/maplist',
+    component: 'Layout',
+    alwaysShow: true,
+    meta: {
+      title: '地图管理',
+      icon: 'map',
+      noCache: false
+    },
+    children: [
+      {
+        name: 'MapList',
+        path: 'maplist',
+        hidden: false,
+        component: 'map/maplist/index',
+        meta: {
+          title: '地图列表',
+          icon: 'list',
+          noCache: false
+        }
+      }
+    ]
+  },
+  {
+    name: 'Devops',
+    path: '/devops',
+    hidden: false,
+    redirect: '/devops/version',
+    component: 'Layout',
+    alwaysShow: true,
+    meta: {
+      title: '运维管理',
+      icon: 'monitor',
+      noCache: false
+    },
+    children: [
+      {
+        name: 'Version',
+        path: 'version',
+        hidden: false,
+        component: 'devops/version/index',
+        meta: {
+          title: '版本管理',
+          icon: 'build',
+          noCache: false
+        }
+      },
+      {
+        name: 'Individuation',
+        path: 'individuation',
+        hidden: false,
+        component: 'devops/individuation/index',
+        meta: {
+          title: '个性配置',
+          icon: 'edit',
+          noCache: false
+        }
+      },
+      {
+        name: 'Diagnostic',
+        path: 'diagnostic',
+        hidden: false,
+        component: 'devops/diagnostic/index',
+        meta: {
+          title: '系统诊断',
+          icon: 'bug',
+          noCache: false
+        }
+      },
+      {
+        name: 'Log',
+        path: 'log',
+        hidden: false,
+        component: 'devops/log/index',
+        meta: {
+          title: '日志查看',
+          icon: 'log',
+          noCache: false
+        }
+      }
+    ]
+  }
+]
+
+// 获取静态菜单数据
+export function getStaticMenus() {
+  return new Promise((resolve) => {
+    resolve({
+      code: 200,
+      msg: '操作成功',
+      data: staticMenus
+    })
+  })
+}

+ 40 - 7
src/permission.js

@@ -15,6 +15,14 @@ const isWhiteList = (path) => {
   return whiteList.some(pattern => isPathMatch(pattern, path))
 }
 
+// 标记路由是否已经添加,防止重复添加
+let routesAdded = false
+
+// 重置路由添加标记的函数
+export const resetRoutesAdded = () => {
+  routesAdded = false
+}
+
 router.beforeEach((to, from, next) => {
   NProgress.start()
   if (getToken()) {
@@ -31,11 +39,18 @@ router.beforeEach((to, from, next) => {
         // 判断当前用户是否已拉取完user_info信息
         store.dispatch('GetInfo').then(() => {
           isRelogin.show = false
-          store.dispatch('GenerateRoutes').then(accessRoutes => {
-            // 根据roles权限生成可访问的路由表
-            router.addRoutes(accessRoutes) // 动态添加可访问路由表
-            next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
-          })
+          // 只有在路由未添加时才生成和添加路由
+          if (!routesAdded) {
+            store.dispatch('GenerateRoutes').then(routes => {
+              console.log('首次添加路由:', routes)
+              router.addRoutes(routes.asyncRoutes) // 动态添加可访问路由表
+              router.addRoutes(routes.rewriteRoutes) // 添加静态菜单路由
+              routesAdded = true // 标记路由已添加
+              next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
+            })
+          } else {
+            next()
+          }
         }).catch(err => {
             store.dispatch('LogOut').then(() => {
               Message.error(err)
@@ -43,11 +58,29 @@ router.beforeEach((to, from, next) => {
             })
           })
       } else {
-        next()
+        // 用户已登录且有角色信息
+        if (!routesAdded && store.getters.sidebarRouters.length === 0) {
+          // 路由未添加且没有菜单数据,需要生成路由
+          console.log('用户已登录但路由未添加,生成路由')
+          store.dispatch('GenerateRoutes').then(routes => {
+            console.log('路由守卫中添加路由:', routes)
+            router.addRoutes(routes.asyncRoutes) // 动态添加可访问路由表
+            router.addRoutes(routes.rewriteRoutes) // 添加静态菜单路由
+            routesAdded = true // 标记路由已添加
+            next({ ...to, replace: true })
+          }).catch(err => {
+            console.error('生成路由失败:', err)
+            next()
+          })
+        } else {
+          // 路由已添加,直接跳转
+          next()
+        }
       }
     }
   } else {
-    // 没有token
+    // 没有token,重置路由添加标记
+    routesAdded = false
     if (isWhiteList(to.path)) {
       // 在免登录白名单,直接进入
       next()

+ 25 - 6
src/store/modules/permission.js

@@ -1,6 +1,6 @@
 import auth from '@/plugins/auth'
 import router, { constantRoutes, dynamicRoutes } from '@/router'
-import { getRouters } from '@/api/menu'
+import { getStaticMenus } from '@/config/menu'
 import Layout from '@/layout/index'
 import ParentView from '@/components/ParentView'
 import InnerLink from '@/layout/components/InnerLink'
@@ -27,25 +27,44 @@ const permission = {
     SET_SIDEBAR_ROUTERS: (state, routes) => {
       state.sidebarRouters = routes
     },
+    CLEAR_ROUTES: (state) => {
+      state.routes = []
+      state.addRoutes = []
+      state.defaultRoutes = []
+      state.topbarRouters = []
+      state.sidebarRouters = []
+    },
   },
   actions: {
     // 生成路由
     GenerateRoutes({ commit }) {
       return new Promise(resolve => {
-        // 向后端请求路由数据
-        getRouters().then(res => {
+        // 使用静态菜单数据
+        getStaticMenus().then(res => {
           const sdata = JSON.parse(JSON.stringify(res.data))
           const rdata = JSON.parse(JSON.stringify(res.data))
           const sidebarRoutes = filterAsyncRouter(sdata)
           const rewriteRoutes = filterAsyncRouter(rdata, false, true)
           const asyncRoutes = filterDynamicRoutes(dynamicRoutes);
+          
+          // 添加404路由
           rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })
-          router.addRoutes(asyncRoutes);
+          
+          const finalSidebarRouters = constantRoutes.concat(sidebarRoutes)
+          
+          // 先提交状态
           commit('SET_ROUTES', rewriteRoutes)
-          commit('SET_SIDEBAR_ROUTERS', constantRoutes.concat(sidebarRoutes))
+          commit('SET_SIDEBAR_ROUTERS', finalSidebarRouters)
           commit('SET_DEFAULT_ROUTES', sidebarRoutes)
           commit('SET_TOPBAR_ROUTES', sidebarRoutes)
-          resolve(rewriteRoutes)
+          
+          console.log('路由状态已提交,sidebarRouters数量:', finalSidebarRouters.length)
+          
+          // 返回需要添加的路由
+          resolve({
+            rewriteRoutes,
+            asyncRoutes
+          })
         })
       })
     }

+ 80 - 34
src/store/modules/user.js

@@ -1,6 +1,7 @@
 import { login, logout, getInfo } from '@/api/login'
 import { getToken, setToken, removeToken } from '@/utils/auth'
 import { isHttp, isEmpty } from "@/utils/validate"
+import { resetRoutesAdded } from '@/permission'
 import defAva from '@/assets/images/profile.jpg'
 
 const user = {
@@ -36,18 +37,51 @@ const user = {
 
   actions: {
     // 登录
-    Login({ commit }, userInfo) {
+    Login({ commit, dispatch }, userInfo) {
       const username = userInfo.username.trim()
       const password = userInfo.password
       const code = userInfo.code
       const uuid = userInfo.uuid
       return new Promise((resolve, reject) => {
+        console.log('开始登录请求,用户名:', username)
         login(username, password, code, uuid).then(res => {
-          setToken(res.token)
-          commit('SET_TOKEN', res.token)
-          resolve()
+          console.log('登录API响应:', res)
+          if (res && res.status) {
+            console.log('登录成功,token:', res.token)
+            setToken(res.token)
+            commit('SET_TOKEN', res.token)
+            // 设置用户基本信息
+            commit('SET_NAME', username)
+            commit('SET_ID', username)
+            commit('SET_ROLES', ['admin'])
+            commit('SET_PERMISSIONS', ['*:*:*'])
+            
+            // 登录成功,让路由守卫来处理路由生成
+            console.log('登录成功,等待路由守卫处理路由')
+            resolve()
+          } else {
+            console.error('登录失败,响应状态:', res ? res.status : '无响应')
+            reject(new Error('登录失败'))
+          }
         }).catch(error => {
-          reject(error)
+          console.error('登录请求错误:', error)
+          // 如果API调用失败,尝试使用mock登录(仅用于测试)
+          if (username === 'admin' && password === 'admin123') {
+            console.log('使用mock登录')
+            const mockToken = 'mock-token-' + Date.now()
+            setToken(mockToken)
+            commit('SET_TOKEN', mockToken)
+            commit('SET_NAME', username)
+            commit('SET_ID', username)
+            commit('SET_ROLES', ['admin'])
+            commit('SET_PERMISSIONS', ['*:*:*'])
+            
+            // Mock登录成功,让路由守卫来处理路由生成
+            console.log('Mock登录成功,等待路由守卫处理路由')
+            resolve()
+          } else {
+            reject(error)
+          }
         })
       })
     },
@@ -55,40 +89,48 @@ const user = {
     // 获取用户信息
     GetInfo({ commit, state }) {
       return new Promise((resolve, reject) => {
-        getInfo().then(res => {
-          const user = res.user
-          let avatar = user.avatar || ""
-          if (!isHttp(avatar)) {
-            avatar = (isEmpty(avatar)) ? defAva : process.env.VUE_APP_BASE_API + avatar
-          }
-          if (res.roles && res.roles.length > 0) { // 验证返回的roles是否是一个非空数组
-            commit('SET_ROLES', res.roles)
-            commit('SET_PERMISSIONS', res.permissions)
-          } else {
-            commit('SET_ROLES', ['ROLE_DEFAULT'])
-          }
-          commit('SET_ID', user.userId)
-          commit('SET_NAME', user.userName)
-          commit('SET_AVATAR', avatar)
-          resolve(res)
-        }).catch(error => {
-          reject(error)
-        })
+        // 使用静态用户信息,不再从后端获取
+        const userInfo = {
+          user: {
+            userId: state.id || 'admin',
+            userName: state.name || 'admin',
+            avatar: defAva
+          },
+          roles: state.roles.length > 0 ? state.roles : ['admin'],
+          permissions: state.permissions.length > 0 ? state.permissions : ['*:*:*']
+        }
+        
+        commit('SET_ROLES', userInfo.roles)
+        commit('SET_PERMISSIONS', userInfo.permissions)
+        commit('SET_AVATAR', userInfo.user.avatar)
+        
+        if (!state.name) {
+          commit('SET_NAME', userInfo.user.userName)
+        }
+        if (!state.id) {
+          commit('SET_ID', userInfo.user.userId)
+        }
+        
+        resolve(userInfo)
       })
     },
 
     // 退出系统
     LogOut({ commit, state }) {
-      return new Promise((resolve, reject) => {
-        logout(state.token).then(() => {
-          commit('SET_TOKEN', '')
-          commit('SET_ROLES', [])
-          commit('SET_PERMISSIONS', [])
-          removeToken()
-          resolve()
-        }).catch(error => {
-          reject(error)
-        })
+      return new Promise(resolve => {
+        // 直接前端退出,不调用后端接口
+        commit('SET_TOKEN', '')
+        commit('SET_ROLES', [])
+        commit('SET_PERMISSIONS', [])
+        commit('SET_NAME', '')
+        commit('SET_ID', '')
+        commit('SET_AVATAR', '')
+        // 清除路由状态
+        commit('permission/CLEAR_ROUTES', null, { root: true })
+        removeToken()
+        // 重置路由添加标记
+        resetRoutesAdded()
+        resolve()
       })
     },
 
@@ -96,7 +138,11 @@ const user = {
     FedLogOut({ commit }) {
       return new Promise(resolve => {
         commit('SET_TOKEN', '')
+        // 清除路由状态
+        commit('permission/CLEAR_ROUTES', null, { root: true })
         removeToken()
+        // 重置路由添加标记
+        resetRoutesAdded()
         resolve()
       })
     }

+ 11 - 37
src/views/login.vue

@@ -23,20 +23,6 @@
           <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
         </el-input>
       </el-form-item>
-      <el-form-item prop="code" v-if="captchaEnabled">
-        <el-input
-          v-model="loginForm.code"
-          auto-complete="off"
-          placeholder="验证码"
-          style="width: 63%"
-          @keyup.enter.native="handleLogin"
-        >
-          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
-        </el-input>
-        <div class="login-code">
-          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
-        </div>
-      </el-form-item>
       <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
       <el-form-item style="width:100%;">
         <el-button
@@ -62,7 +48,6 @@
 </template>
 
 <script>
-import { getCodeImg } from "@/api/login";
 import Cookies from "js-cookie";
 import { encrypt, decrypt } from '@/utils/jsencrypt'
 
@@ -70,13 +55,10 @@ export default {
   name: "Login",
   data() {
     return {
-      codeUrl: "",
       loginForm: {
         username: "admin",
         password: "admin123",
-        rememberMe: false,
-        code: "",
-        uuid: ""
+        rememberMe: false
       },
       loginRules: {
         username: [
@@ -84,12 +66,11 @@ export default {
         ],
         password: [
           { required: true, trigger: "blur", message: "请输入您的密码" }
-        ],
-        code: [{ required: true, trigger: "change", message: "请输入验证码" }]
+        ]
       },
       loading: false,
       // 验证码开关
-      captchaEnabled: true,
+      captchaEnabled: false,
       // 注册开关
       register: false,
       redirect: undefined
@@ -104,19 +85,9 @@ export default {
     }
   },
   created() {
-    this.getCode();
     this.getCookie();
   },
   methods: {
-    getCode() {
-      getCodeImg().then(res => {
-        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
-        if (this.captchaEnabled) {
-          this.codeUrl = "data:image/gif;base64," + res.img;
-          this.loginForm.uuid = res.uuid;
-        }
-      });
-    },
     getCookie() {
       const username = Cookies.get("username");
       const password = Cookies.get("password");
@@ -140,13 +111,16 @@ export default {
             Cookies.remove("password");
             Cookies.remove('rememberMe');
           }
+          console.log('提交登录表单:', this.loginForm)
           this.$store.dispatch("Login", this.loginForm).then(() => {
-            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
-          }).catch(() => {
+            console.log('登录成功,准备跳转到:', this.redirect || "/")
+            this.$router.push({ path: this.redirect || "/" }).catch((err) => {
+              console.log('路由跳转错误:', err)
+            });
+          }).catch((error) => {
+            console.error('登录失败:', error)
             this.loading = false;
-            if (this.captchaEnabled) {
-              this.getCode();
-            }
+            this.$message.error(error.message || '登录失败,请检查用户名和密码')
           });
         }
       });

+ 212 - 19
src/views/map/maplist/calibration.vue

@@ -108,6 +108,7 @@ import OlMap from "@/components/OlMap";
 import MqttComp from "@/components/Mqtt/mqttComp.vue";
 import { RightPanel, MapToolbar } from "./components/shared";
 import { MapOperations, FullscreenOperations } from "@/utils/map-operations";
+import { getCalibrationHistory } from "@/api/map/map";
 
 export default {
   name: "CalibrationPage",
@@ -119,7 +120,10 @@ export default {
   },
   data() {
     return {
-      topics:[this.$mqttPrefix + '/localization/pose'],
+      topics:[
+        this.$mqttPrefix + '/localization/pose',
+        this.$mqttPrefix + '/settings/multi_coordinates/action/calibrate/reply'
+      ],
       // 弹出层标题
       title: "",
       calibrationList: [],
@@ -134,7 +138,8 @@ export default {
         status: '0/0',
         longitude: 0,
         latitude: 0,
-        angle: 0
+        angle: 0,
+        satellites: 0 // 卫星颗数
       },
       // 当前地图
       currentMap: {
@@ -199,6 +204,9 @@ export default {
     // 初始设置面板宽度
     this.updateRightPanelWidth();
     
+    // 加载历史标定数据
+    this.loadHistoryCalibrationData();
+    
     // 页面初始化完成
   },
   beforeDestroy() {
@@ -212,21 +220,33 @@ export default {
     onMessage({ topic, message }) {
       // console.log("收到消息:");
       try {
-        const data = message.args[0];
-        const {xyz, rpy, blh, heading} = data.pose;
-        // 激光定位实时数据
-        this.laserPositionData.x = xyz[0];
-        this.laserPositionData.y = xyz[1];
-        this.laserPositionData.angle = rpy[2];
-        // GNSS定位实时数据
-        this.gnssPositionData.longitude = blh[1]; // 经度
-        this.gnssPositionData.latitude = blh[0]; // 纬度
-        this.gnssPositionData.angle = heading; // 航向角
-        
-        this.gnssPositionData.status = data.rtk.star+ '/' + data.rtk.status; // RTK状态
-        
+        // 处理定位数据
+        if (topic.includes('/localization/pose')) {
+          const data = message.args[0];
+          const {xyz, rpy, blh, heading} = data.pose;
+          // 激光定位实时数据
+          this.laserPositionData.x = xyz[0];
+          this.laserPositionData.y = xyz[1];
+          this.laserPositionData.angle = rpy[2];
+          // GNSS定位实时数据
+          this.gnssPositionData.longitude = blh[1]; // 经度
+          this.gnssPositionData.latitude = blh[0]; // 纬度
+          this.gnssPositionData.angle = heading; // 航向角
+          this.gnssPositionData.satellites = data.rtk.star; // 卫星颗数
+          
+          this.gnssPositionData.status = data.rtk.star+ '/' + data.rtk.status; // RTK状态显示
+        }
+        // 处理标定响应
+        else if (topic.includes('/calibrate/reply')) {
+          const status = message.status;
+          if (status === 'ok') {
+            this.$modal.msgSuccess("标定执行成功");
+          } else {
+            this.$modal.msgError("标定执行失败: " + (message.error || '未知错误'));
+          }
+        }
       } catch (e) {
-        console.error("解析失败:", e);
+        console.error("消息解析失败:", e);
       }
     },
     updateOlCss() {
@@ -425,6 +445,19 @@ export default {
     
     // 添加标定
 addCalibration() {
+  // 检查是否有有效的激光定位数据
+  if (this.laserPositionData.x === 0 && this.laserPositionData.y === 0) {
+    this.$message.warning('当前没有有效的激光定位数据');
+    return;
+  }
+  
+  // 检查是否有有效的GNSS数据
+  const hasValidGnss = this.gnssPositionData.longitude !== 0 || this.gnssPositionData.latitude !== 0;
+  if (!hasValidGnss) {
+    this.$message.warning('当前没有有效的GNSS数据');
+    return;
+  }
+  
   // 每次添加前,先根据现有列表重新排一次 id,保证连续
   this.calibrationList = this.calibrationList.map((item, index) => {
     return { ...item, id: index + 1 };
@@ -433,10 +466,34 @@ addCalibration() {
   // 新的 id 就是当前长度+1
   const newId = this.calibrationList.length + 1;
   const coordinate = `${this.laserPositionData.x},${this.laserPositionData.y}`;
-  const data = { id: newId, coordinate, angle: this.laserPositionData.angle };
+  
+  // 获取RTK状态数字部分
+  const statusParts = this.gnssPositionData.status.split('/');
+  const rtkStatus = parseInt(statusParts[1]) || 0;
+  
+  const data = { 
+    id: newId, 
+    coordinate, 
+    angle: this.laserPositionData.angle,
+    // 保存当前的GNSS数据
+    gnss: {
+      latitude: this.gnssPositionData.latitude,
+      longitude: this.gnssPositionData.longitude,
+      heading: this.gnssPositionData.angle,
+      status: rtkStatus,
+      satellites: this.gnssPositionData.satellites
+    }
+  };
 
   this.calibrationList.push(data);
   this.nowCalibId = newId;
+  
+  this.$message.success(`已添加标定点 ${newId}`);
+  
+  // 同步更新地图上的标定点显示
+  this.$nextTick(() => {
+    this.displayCalibrationPointsOnMap();
+  });
 },
 
 // 移除标定
@@ -452,6 +509,11 @@ removeCalibration(id) {
   });
 
   this.nowCalibId = -1; // 标记无选中
+  
+  // 同步更新地图上的标定点显示
+  this.$nextTick(() => {
+    this.displayCalibrationPointsOnMap();
+  });
 },
 /*     // 添加标定
     addCalibration() {
@@ -472,13 +534,144 @@ removeCalibration(id) {
       this.nowCalibId = - id;
     }, */
     
+    // 加载历史标定数据
+    async loadHistoryCalibrationData() {
+      try {
+        const response = await getCalibrationHistory(this.mapName);
+        console.log('加载历史标定数据响应:', response.calibration);
+        
+        if (response.calibration && Array.isArray(response.calibration)) {
+          // 恢复标定点数据
+          this.calibrationList = response.calibration.map((item, index) => ({
+            id: index + 1,
+            coordinate: `${item.x},${item.y}`,
+            angle: item.yaw,
+            // 保存完整的GNSS数据以便后续使用
+            gnss: {
+              latitude: item.b,
+              longitude: item.l,
+              heading: item.heading,
+              status: item.status,
+              satellites: item.start
+            }
+          }));
+          
+          console.log('成功加载历史标定数据:', this.calibrationList.length, '个点');
+          
+          // 等待地图组件加载完成后,将标定点显示在地图上
+          this.$nextTick(() => {
+            this.displayCalibrationPointsOnMap();
+          });
+        }
+      } catch (error) {
+        console.log('未找到历史标定数据或加载失败:', error.message);
+        // 首次使用时可能没有历史数据,不显示错误
+      }
+    },
+    
+    // 将标定点显示在地图上
+    displayCalibrationPointsOnMap() {
+      if (!this.$refs.olmap || !this.calibrationList.length) {
+        return;
+      }
+      
+      // 等待地图完全初始化
+      const displayPoints = () => {
+        if (!this.$refs.olmap.map) {
+          // 如果地图还未初始化,延迟执行
+          setTimeout(displayPoints, 100);
+          return;
+        }
+        
+        try {
+          // 清空 OlMap 组件中的标定点列表
+          this.$refs.olmap.calibrationList = [];
+          
+          // 将标定点数据同步到 OlMap 组件
+          this.calibrationList.forEach(item => {
+            const coords = item.coordinate.split(',');
+            const x = parseFloat(coords[0]);
+            const y = parseFloat(coords[1]);
+            
+            // 添加到 OlMap 的 calibrationList
+            this.$refs.olmap.calibrationList.push({
+              id: item.id,
+              x: x,
+              y: y
+            });
+          });
+          
+          // 刷新地图上的标定点覆盖物
+          if (this.$refs.olmap.refreshCalibrationOverlays) {
+            this.$refs.olmap.refreshCalibrationOverlays();
+          }
+          
+          console.log('已在地图上显示', this.calibrationList.length, '个历史标定点');
+        } catch (error) {
+          console.error('显示标定点失败:', error);
+        }
+      };
+      
+      displayPoints();
+    },
+    
     // 一键标定
     executeCalibration() {
       if (this.calibrationList.length < 1) {
-        this.$message('请添加标定点!');
+        this.$message.warning('请添加标定点!');
         return;
       }
-      this.$modal.msgSuccess("标定执行成功");
+      
+      // 检查GNSS数据是否有效
+      const hasValidGnss = this.gnssPositionData.longitude !== 0 || this.gnssPositionData.latitude !== 0;
+      if (!hasValidGnss) {
+        this.$message.warning('GNSS数据无效,请确保设备已获取定位信息');
+        return;
+      }
+      
+      // 格式化标定数据
+      const calibrationData = this.calibrationList.map(item => {
+        const coords = item.coordinate.split(',');
+        const x = parseFloat(coords[0]);
+        const y = parseFloat(coords[1]);
+        
+        // 获取RTK状态数字部分
+        const statusParts = this.gnssPositionData.status.split('/');
+        const rtkStatus = parseInt(statusParts[1]) || 0;
+        
+        return {
+          x: x,
+          y: y,
+          yaw: item.angle,
+          b: item.gnss ? item.gnss.latitude : this.gnssPositionData.latitude,  // 纬度
+          l: item.gnss ? item.gnss.longitude : this.gnssPositionData.longitude, // 经度
+          heading: item.gnss ? item.gnss.heading : this.gnssPositionData.angle, // GNSS航向角
+          status: item.gnss ? item.gnss.status : rtkStatus,  // RTK状态
+          start: item.gnss ? item.gnss.satellites : this.gnssPositionData.satellites // 卫星颗数
+        };
+      });
+      
+      // 构建MQTT消息
+      const mqttMessage = {
+        args: [
+          {
+            roadmap: this.mapName,
+            calibration: calibrationData
+          }
+        ]
+      };
+      
+      // 发布MQTT消息
+      const topic = this.$mqttPrefix + '/settings/multi_coordinates/action/calibrate';
+      
+      console.log('发送标定请求:', topic, mqttMessage);
+      
+      if (this.$refs.mqtt && this.$refs.mqtt.publish) {
+        this.$refs.mqtt.publish(topic, JSON.stringify(mqttMessage));
+        this.$message.success('标定请求已发送,请等待响应...');
+      } else {
+        this.$message.error('MQTT连接未就绪,请稍后重试');
+      }
     }
   },
   

+ 54 - 4
src/views/map/maplist/components/shared/RightPanel.vue

@@ -699,10 +699,60 @@
                     </div>
                     <div class="task-actions">
                       <el-button size="mini" type="text" native-type="button" @click.prevent="$emit('task-view', task)">查看</el-button>
-                      <el-button size="mini" type="text" native-type="button" @click.prevent="$emit('task-start', task)">开始</el-button>
-                      <el-button size="mini" type="text" native-type="button" @click.prevent="$emit('task-pause', task)">暂停</el-button>
-                      <el-button size="mini" type="text" native-type="button" @click.prevent="$emit('task-stop', task)">停止</el-button>
-                      <el-button size="mini" type="text" native-type="button" @click.prevent="$emit('task-remove', task)">删除</el-button>
+                      <el-button 
+                        v-if="task.status !== 'running' && task.status !== 'paused'" 
+                        size="mini" 
+                        type="text" 
+                        native-type="button" 
+                        @click.prevent="$emit('task-edit', task)"
+                      >
+                        编辑
+                      </el-button>
+                      <el-button 
+                        v-if="task.status !== 'running' && task.status !== 'paused'" 
+                        size="mini" 
+                        type="text" 
+                        native-type="button" 
+                        @click.prevent="$emit('task-start', task)"
+                      >
+                        开始
+                      </el-button>
+                      <el-button 
+                        v-if="task.status === 'running'" 
+                        size="mini" 
+                        type="text" 
+                        native-type="button" 
+                        @click.prevent="$emit('task-pause', task)"
+                      >
+                        暂停
+                      </el-button>
+                      <el-button 
+                        v-if="task.status === 'paused'" 
+                        size="mini" 
+                        type="text" 
+                        native-type="button" 
+                        @click.prevent="$emit('task-resume', task)"
+                      >
+                        继续
+                      </el-button>
+                      <el-button 
+                        v-if="task.status === 'running' || task.status === 'paused'" 
+                        size="mini" 
+                        type="text" 
+                        native-type="button" 
+                        @click.prevent="$emit('task-stop', task)"
+                      >
+                        停止
+                      </el-button>
+                      <el-button 
+                        v-if="task.status !== 'running' && task.status !== 'paused'" 
+                        size="mini" 
+                        type="text" 
+                        native-type="button" 
+                        @click.prevent="$emit('task-remove', task)"
+                      >
+                        删除
+                      </el-button>
                     </div>
                   </div>
                 </div>

+ 423 - 62
src/views/map/maplist/edit.vue

@@ -558,57 +558,364 @@ export default {
 		},
 		
 		// 路网导出
-		handleNetworkExport() {
-			// 实际的导出逻辑应该在这里实现
-			setTimeout(() => {
+		async handleNetworkExport() {
+			try {
+				// 检查是否有路网数据可导出
+				if (!this.resourcesFeature || this.resourcesFeature.length === 0) {
+					this.$message.warning('当前地图没有路网数据可导出');
+					return;
+				}
+
+				// 显示导出进度
+				const loading = this.$loading({
+					lock: true,
+					text: '正在准备导出数据...',
+					spinner: 'el-icon-loading',
+					background: 'rgba(0, 0, 0, 0.7)'
+				});
+
 				try {
-					// TODO: 实现实际的路网导出逻辑
-					// 这里可以调用后端API或者生成文件下载
-					console.log('导出路网数据...');
-					// 成功后不需要再显示消息,因为RightPanel已经处理了
-				} catch (error) {
-					this.$message.error('路网导出失败:' + error.message);
+					// 构建导出的GeoJSON数据
+					const geoJsonData = this.buildGeoJsonData();
+					
+					// 添加导出时间戳和元数据
+					const exportData = {
+						...geoJsonData,
+						metadata: {
+							exportTime: new Date().toISOString(),
+							mapName: this.mapName,
+							elementCount: geoJsonData.features.length,
+							version: '1.0'
+						}
+					};
+
+					// 创建文件内容
+					const fileContent = JSON.stringify(exportData, null, 2);
+					const blob = new Blob([fileContent], { type: 'application/json' });
+					
+					// 生成文件名
+					const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
+					const fileName = `roadmap_${this.mapName}_${timestamp}.json`;
+					
+					// 创建下载链接
+					const url = window.URL.createObjectURL(blob);
+					const link = document.createElement('a');
+					link.href = url;
+					link.download = fileName;
+					
+					// 触发下载
+					document.body.appendChild(link);
+					link.click();
+					document.body.removeChild(link);
+					
+					// 清理URL对象
+					window.URL.revokeObjectURL(url);
+					
+					this.$message.success(`路网数据已导出:${fileName}`);
+				} finally {
+					loading.close();
 				}
-			}, 200);
+			} catch (error) {
+				console.error('路网导出失败:', error);
+				this.$message.error('路网导出失败:' + (error.message || '未知错误'));
+			}
 		},
 		
 		// 路网导入
 		handleNetworkImport(type) {
-			// 实际的导入逻辑应该在这里实现
-			setTimeout(() => {
+			const typeMap = {
+				'import': '导入',
+				'replace': '覆盖导入',
+				'merge': '合并导入',
+				'incremental': '增量导入'
+			};
+			
+			// 创建文件输入元素
+			const fileInput = document.createElement('input');
+			fileInput.type = 'file';
+			fileInput.accept = '.json';
+			fileInput.style.display = 'none';
+			
+			// 文件选择处理
+			fileInput.addEventListener('change', async (event) => {
+				const file = event.target.files[0];
+				if (!file) return;
+				
 				try {
-					const typeMap = {
-						'import': '导入',
-						'replace': '覆盖导入',
-						'merge': '合并导入',
-						'incremental': '增量导入'
-					};
-					
-					// TODO: 实现实际的路网导入逻辑
-					// 这里可以调用后端API或者打开文件选择对话框
-					console.log(`${typeMap[type] || '导入'}路网数据...`, type);
-					
-					// 根据不同类型执行不同的导入策略
-					switch (type) {
-						case 'import':
-							// 普通导入逻辑
-							break;
-						case 'replace':
-							// 覆盖导入:先清空现有数据,再导入新数据
-							break;
-						case 'merge':
-							// 合并导入:保留现有数据,添加新数据
-							break;
-						case 'incremental':
-							// 增量导入:只导入不存在的新元素
-							break;
-					}
-					
-					// 成功后不需要再显示消息,因为RightPanel已经处理了
+					await this.processImportFile(file, type, typeMap[type] || '导入');
 				} catch (error) {
-					this.$message.error(`路网${typeMap[type] || '导入'}失败:` + error.message);
+					console.error('导入处理失败:', error);
+					this.$message.error(`路网${typeMap[type] || '导入'}失败:` + (error.message || '未知错误'));
+				} finally {
+					// 清理文件输入元素
+					document.body.removeChild(fileInput);
+				}
+			});
+			
+			// 添加到页面并触发点击
+			document.body.appendChild(fileInput);
+			fileInput.click();
+		},
+
+		// 处理导入文件
+		async processImportFile(file, importType, typeName) {
+			// 显示加载进度
+			const loading = this.$loading({
+				lock: true,
+				text: `正在${typeName}路网数据...`,
+				spinner: 'el-icon-loading',
+				background: 'rgba(0, 0, 0, 0.7)'
+			});
+
+			try {
+				// 读取文件内容
+				const fileContent = await this.readFileContent(file);
+				
+				// 解析JSON数据
+				let importData;
+				try {
+					importData = JSON.parse(fileContent);
+				} catch (parseError) {
+					throw new Error('文件格式错误:无法解析JSON数据');
 				}
-			}, 200);
+
+				// 验证数据格式
+				this.validateImportData(importData);
+
+				// 根据不同导入类型执行相应策略
+				await this.executeImportStrategy(importData, importType);
+
+				// 刷新地图显示
+				await this.refreshMapAfterImport();
+
+				// 保存到服务器
+				await this.saveRoute();
+
+				this.$message.success(`路网${typeName}成功!共处理${importData.features.length}个元素`);
+			} finally {
+				loading.close();
+			}
+		},
+
+		// 读取文件内容
+		readFileContent(file) {
+			return new Promise((resolve, reject) => {
+				const reader = new FileReader();
+				reader.onload = e => resolve(e.target.result);
+				reader.onerror = () => reject(new Error('文件读取失败'));
+				reader.readAsText(file);
+			});
+		},
+
+		// 验证导入数据格式
+		validateImportData(data) {
+			// 检查基本结构
+			if (!data || typeof data !== 'object') {
+				throw new Error('无效的数据格式');
+			}
+
+			if (data.type !== 'FeatureCollection') {
+				throw new Error('数据类型错误:期望FeatureCollection格式');
+			}
+
+			if (!Array.isArray(data.features)) {
+				throw new Error('数据格式错误:缺少features数组');
+			}
+
+			if (data.features.length === 0) {
+				throw new Error('导入文件中没有路网元素');
+			}
+
+			// 验证每个feature的格式
+			data.features.forEach((feature, index) => {
+				if (!feature.type || feature.type !== 'Feature') {
+					throw new Error(`第${index + 1}个元素格式错误:不是有效的Feature`);
+				}
+
+				if (!feature.properties || !feature.properties.id) {
+					throw new Error(`第${index + 1}个元素缺少ID属性`);
+				}
+
+				if (!feature.geometry || !feature.geometry.type) {
+					throw new Error(`第${index + 1}个元素缺少几何信息`);
+				}
+			});
+		},
+
+		// 执行不同的导入策略
+		async executeImportStrategy(importData, importType) {
+			const features = importData.features;
+
+			switch (importType) {
+				case 'replace':
+					// 覆盖导入:清空现有数据,导入新数据
+					await this.replaceImport(features);
+					break;
+				case 'merge':
+					// 合并导入:保留现有数据,添加新数据(处理ID冲突)
+					await this.mergeImport(features);
+					break;
+				case 'incremental':
+					// 增量导入:只导入不存在的新元素
+					await this.incrementalImport(features);
+					break;
+				default:
+					// 普通导入:直接添加到现有数据
+					await this.normalImport(features);
+					break;
+			}
+		},
+
+		// 覆盖导入
+		async replaceImport(features) {
+			// 清空现有数据
+			this.clearAllMapElements();
+			
+			// 导入新数据
+			await this.importFeaturesToMap(features);
+		},
+
+		// 合并导入
+		async mergeImport(features) {
+			const conflictFeatures = [];
+			const newFeatures = [];
+
+			// 检查ID冲突
+			features.forEach(feature => {
+				const existingFeature = this.resourcesFeature.find(f => f.getId() === feature.properties.id);
+				if (existingFeature) {
+					conflictFeatures.push(feature);
+				} else {
+					newFeatures.push(feature);
+				}
+			});
+
+			// 处理冲突
+			if (conflictFeatures.length > 0) {
+				const conflictIds = conflictFeatures.map(f => f.properties.id).join(', ');
+				const result = await this.$confirm(
+					`发现${conflictFeatures.length}个ID冲突的元素:${conflictIds}。是否覆盖现有元素?`,
+					'ID冲突处理',
+					{
+						confirmButtonText: '覆盖现有',
+						cancelButtonText: '跳过冲突',
+						type: 'warning'
+					}
+				);
+
+				if (result) {
+					// 覆盖冲突的元素
+					await this.replaceConflictFeatures(conflictFeatures);
+				}
+			}
+
+			// 导入新元素
+			if (newFeatures.length > 0) {
+				await this.importFeaturesToMap(newFeatures);
+			}
+		},
+
+		// 增量导入
+		async incrementalImport(features) {
+			const newFeatures = features.filter(feature => {
+				return !this.resourcesFeature.find(f => f.getId() === feature.properties.id);
+			});
+
+			if (newFeatures.length === 0) {
+				this.$message.info('所有元素均已存在,无需导入');
+				return;
+			}
+
+			await this.importFeaturesToMap(newFeatures);
+			this.$message.info(`跳过${features.length - newFeatures.length}个已存在的元素`);
+		},
+
+		// 普通导入
+		async normalImport(features) {
+			await this.importFeaturesToMap(features);
+		},
+
+		// 清空所有地图元素
+		clearAllMapElements() {
+			if (this.$refs.olmap && this.$refs.olmap.clearAllElements) {
+				this.$refs.olmap.clearAllElements();
+			}
+			
+			// 清空数据数组
+			this.pointData = [];
+			this.lineData = [];
+			this.bowData = [];
+			this.shapeData = [];
+			this.resourcesFeature = [];
+			this.currentFeature = {};
+		},
+
+		// 替换冲突的特征
+		async replaceConflictFeatures(conflictFeatures) {
+			for (const feature of conflictFeatures) {
+				// 删除现有元素
+				if (this.$refs.olmap && this.$refs.olmap.removeElement) {
+					this.$refs.olmap.removeElement(feature.properties.id);
+				}
+				
+				// 从数据数组中删除
+				this.removeFeatureFromArrays(feature.properties.id);
+			}
+			
+			// 导入新的特征
+			await this.importFeaturesToMap(conflictFeatures);
+		},
+
+		// 从数据数组中删除特征
+		removeFeatureFromArrays(id) {
+			const removeFromArray = (array) => {
+				const index = array.findIndex(item => item.id === id);
+				if (index !== -1) {
+					array.splice(index, 1);
+				}
+			};
+
+			if (id.startsWith('p')) {
+				removeFromArray(this.pointData);
+			} else if (id.startsWith('l')) {
+				removeFromArray(this.lineData);
+			} else if (id.startsWith('b')) {
+				removeFromArray(this.bowData);
+			} else if (id.startsWith('s')) {
+				removeFromArray(this.shapeData);
+			}
+
+			// 从resourcesFeature中删除
+			const index = this.resourcesFeature.findIndex(f => f.getId() === id);
+			if (index !== -1) {
+				this.resourcesFeature.splice(index, 1);
+			}
+		},
+
+		// 将特征导入到地图
+		async importFeaturesToMap(features) {
+			if (!this.$refs.olmap || !this.$refs.olmap.importGeoJsonFeatures) {
+				throw new Error('地图组件未就绪,无法导入数据');
+			}
+
+			// 调用地图组件的导入方法
+			const geoJsonData = {
+				type: 'FeatureCollection',
+				features: features
+			};
+
+			await this.$refs.olmap.importGeoJsonFeatures(geoJsonData);
+		},
+
+		// 导入后刷新地图
+		async refreshMapAfterImport() {
+			// 等待地图组件处理完成
+			await this.$nextTick();
+			
+			// 如果地图组件有刷新方法,调用它
+			if (this.$refs.olmap && this.$refs.olmap.refresh) {
+				this.$refs.olmap.refresh();
+			}
 		},
 		
 		// === Inspector事件处理 ===
@@ -826,11 +1133,13 @@ export default {
 				}
 			});
 			// 对分类后的数组按数字部分排序
-			this.pointData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
-			this.lineData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
-			this.bowData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
-			this.shapeData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
-			this.resourcesFeature = features;
+		this.pointData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
+		this.lineData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
+		this.bowData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
+		this.shapeData.sort((a, b) => parseInt(a.id.split('_')[1]) - parseInt(b.id.split('_')[1]));
+		// 过滤掉null值,确保resourcesFeature数组中只包含有效的feature对象
+		this.resourcesFeature = features.filter(feature => feature !== null && feature !== undefined && typeof feature.getGeometry === 'function');
+		console.log(`elementRoadInitEnd: 原始feature数量=${features.length}, 有效feature数量=${this.resourcesFeature.length}`);
 		},
 		// 某个元素绘制完成的回调
 		elementRoadDrawEnd(feature) {
@@ -950,7 +1259,7 @@ export default {
 		}
 		
 		// 删除源features中的此元素
-		const index = this.resourcesFeature.findIndex(feature => feature.getId() == id);
+		const index = this.resourcesFeature.findIndex(feature => feature && feature.getId && feature.getId() == id);
 		if (index !== -1) {
 			console.log('从resourcesFeature中删除元素:', id);
 			this.resourcesFeature.splice(index, 1);
@@ -959,6 +1268,9 @@ export default {
 			console.warn('在resourcesFeature中找不到要删除的元素:', id);
 		}
 		
+		// 清理可能存在的null值
+		this.resourcesFeature = this.resourcesFeature.filter(feature => feature !== null && feature !== undefined && typeof feature.getGeometry === 'function');
+		
 		this.haveDraw = true;
 	},
 		// 初始化点位元素基础和高级参数
@@ -969,9 +1281,14 @@ export default {
 			feature.set('isyawfix', feature.get('isyawfix') ? true : false); // 偏航使能
 			feature.set('taskid', 0);
 			feature.set('offset', 0);
-			feature.set('pitch', 0)
-			feature.set('roll', 0)
+		feature.set('pitch', 0)
+		feature.set('roll', 0)
+		// 验证feature有效性后再添加
+		if (feature && typeof feature.getGeometry === 'function') {
 			this.resourcesFeature.push(feature);
+		} else {
+			console.warn('尝试添加无效的point feature:', feature);
+		}
 		},
 		// 初始化执行元素基础和高级参数
 		initElelmentParamsBowOrLine(feature) {
@@ -998,9 +1315,14 @@ export default {
 			feature.set('e2sforward', 0) // 机器人沿当前线段从终点到达起点时多行驶的距离,单位为米,默认为0
 			feature.set('thtolerance', 0) // 角度容差
 			feature.set('mintheta', 0) // 最小转向角
-			feature.set('maxtheta', 1) // 最大转向角
+		feature.set('maxtheta', 1) // 最大转向角
 
+		// 验证feature有效性后再添加
+		if (feature && typeof feature.getGeometry === 'function') {
 			this.resourcesFeature.push(feature);
+		} else {
+			console.warn('尝试添加无效的line/bow feature:', feature);
+		}
 		},
 		// 初始化面元素基础和高级参数
 		initElelmentParamsSnape(feature) {
@@ -1011,9 +1333,14 @@ export default {
 			// 2:禁行区域,不允许机器人进入该区域;3:会车管制区,特定场景使用;
 			// 4:道闸管控区,特定场景使用;11:GPS定位区,进入该区域后,从激光定位切换到GPS定位;
 			// 22:动态禁行区,特定场景使用。
-			feature.set('type', 1);
-			feature.set('transparent', 200);
+		feature.set('type', 1);
+		feature.set('transparent', 200);
+		// 验证feature有效性后再添加
+		if (feature && typeof feature.getGeometry === 'function') {
 			this.resourcesFeature.push(feature);
+		} else {
+			console.warn('尝试添加无效的shape feature:', feature);
+		}
 		},
 		/**
 		 * 点位坐标修改
@@ -1100,6 +1427,26 @@ export default {
 			}
 		},
 
+		// 清理坐标数据,将null值替换为0
+		cleanCoordinates(coordinates) {
+			if (!coordinates) return coordinates;
+			
+			// 递归处理多维数组
+			if (Array.isArray(coordinates)) {
+				return coordinates.map(coord => {
+					if (Array.isArray(coord)) {
+						// 如果是嵌套数组,递归处理
+						return this.cleanCoordinates(coord);
+					} else {
+						// 如果是数值,将null或undefined替换为0
+						return (coord === null || coord === undefined) ? 0 : coord;
+					}
+				});
+			}
+			
+			return coordinates;
+		},
+
 		// 构建GeoJSON数据
 		buildGeoJsonData() {
 			// 验证resourcesFeature数组
@@ -1112,12 +1459,22 @@ export default {
 				};
 			}
 			
-			console.log('构建GeoJSON数据,元素数量:', this.resourcesFeature.length);
-			
-			const features = this.resourcesFeature.map(feature => {
-				try {
-					const properties = { ...feature.getProperties() };
-					const geometry = feature.getGeometry();
+		console.log('构建GeoJSON数据,元素数量:', this.resourcesFeature.length);
+		
+		// 首先过滤掉null或undefined的feature对象
+		const validFeatures = this.resourcesFeature.filter(feature => feature !== null && feature !== undefined);
+		console.log('有效元素数量:', validFeatures.length);
+		
+		const features = validFeatures.map(feature => {
+			try {
+				// 再次确认feature是有效的
+				if (!feature || typeof feature.getProperties !== 'function') {
+					console.warn('发现无效的feature对象:', feature);
+					return null;
+				}
+				
+				const properties = { ...feature.getProperties() };
+				const geometry = feature.getGeometry();
 					
 					// 验证几何对象
 					if (!geometry) {
@@ -1137,12 +1494,16 @@ export default {
 					delete cleanProperties.directList;   // 删除临时方向列表属性
 					delete cleanProperties.geometry;     // 删除OpenLayers几何对象,避免嵌套
 					
+					// 获取原始坐标并清理null值
+					const originalCoordinates = geometry.getCoordinates();
+					const cleanedCoordinates = this.cleanCoordinates(originalCoordinates);
+					
 					return {
 						type: "Feature",
 						properties: cleanProperties,
 						geometry: {
 							type: geometry.getType(),
-							coordinates: geometry.getCoordinates()
+							coordinates: cleanedCoordinates
 						}
 					};
 				} catch (error) {
@@ -1214,8 +1575,8 @@ export default {
 						{ yaw: this.currentRobotRecord.angle }, 
 						{ isyawfix: this.currentRobotRecord.angleEnable }
 					];
-					this.$refs.olmap && this.$refs.olmap.createPointAtCoordinate && 
-					this.$refs.olmap.createPointAtCoordinate(
+					this.$refs.olmap && this.$refs.olmap.createPointAtCoordinateForImport && 
+					this.$refs.olmap.createPointAtCoordinateForImport(
 						[this.currentRobotRecord.x, this.currentRobotRecord.y, this.currentRobotRecord.z || 0], 
 						'', 
 						data

Datei-Diff unterdrückt, da er zu groß ist
+ 911 - 20
src/views/map/maplist/index.vue


Datei-Diff unterdrückt, da er zu groß ist
+ 820 - 88
src/views/map/maplist/navigation.vue


+ 6 - 1
vue.config.js

@@ -43,9 +43,14 @@ module.exports = {
         }
       },
       [process.env.VUE_APP_PNS_API]: {
-        target: `http://8.148.78.124:10001`,
+        target: `http://192.168.0.120:8086`,
         changeOrigin: true,
          pathRewrite: { '^/pns': '' }
+      },
+      '/param': {
+        target: `http://192.168.0.120:8086`,
+        changeOrigin: true,
+        pathRewrite: { '^/param': '/api/param' }
       }
     },
     disableHostCheck: true

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.