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

新增编辑页面以及参数配置调整

yawuga 8 месяцев назад
Родитель
Сommit
7bb4f85a98

+ 363 - 0
src/api/connection.js

@@ -0,0 +1,363 @@
+/**
+ * 连接配置 API - 支持动态分组
+ */
+
+// 模拟延迟
+const delay = (min = 300, max = 600) => {
+  const time = Math.random() * (max - min) + min
+  return new Promise(resolve => setTimeout(resolve, time))
+}
+
+// 模拟分组数据
+const mockGroups = [
+  { key: 'ip', name: 'IP配置' },
+  { key: 'hardware', name: '硬件接口' },
+  { key: 'proxy', name: '网络代理' },
+  { key: 'advanced', name: '高级设置' },
+  { key: 'security', name: '安全设置' },
+  { key: 'performance', name: '性能优化' },
+  { key: 'logging', name: '日志配置' },
+  { key: 'monitoring', name: '监控告警' }
+]
+
+// 模拟各分组的参数数据
+const mockParamsByGroup = {
+  ip: [
+    {
+      key: 'serverIp',
+      label: '服务器IP',
+      value: '192.168.1.100',
+      desc: '请输入服务器IP地址'
+    },
+    {
+      key: 'mqttBrokerWeb',
+      label: 'MQTT代理地址(网页)',
+      value: 'ws://broker.hivemq.com:8000',
+      desc: '网页端MQTT连接地址'
+    },
+    {
+      key: 'mqttBrokerNav',
+      label: 'MQTT代理地址(导航)',
+      value: 'mqtt://broker.hivemq.com:1883',
+      desc: '导航端MQTT连接地址'
+    },
+    {
+      key: 'mqttProductId',
+      label: 'MQTT产品ID',
+      value: 'smart_driving_v2',
+      desc: '请输入产品ID'
+    },
+    {
+      key: 'httpBase',
+      label: 'HTTP服务地址',
+      value: 'https://api.smartdriving.com',
+      desc: 'HTTP API基础地址'
+    }
+  ],
+  hardware: [
+    {
+      key: 'lidarNic',
+      label: '激光网卡',
+      value: 'enp0s31f6',
+      desc: '激光雷达网络接口名称'
+    },
+    {
+      key: 'packetFilterEnabled',
+      label: '网络包过滤使能',
+      value: 'true',
+      desc: '是否启用网络包过滤功能'
+    },
+    {
+      key: 'packetFilterRule',
+      label: '网络包过滤规则',
+      value: 'tcp and (port 80 or port 443)\nudp and port 53\nicmp',
+      desc: '请输入过滤规则,每行一条'
+    }
+  ],
+  proxy: [
+    {
+      key: 'netProxyEnabled',
+      label: '开启网络代理',
+      value: 'false',
+      desc: '是否启用网络代理服务'
+    },
+    {
+      key: 'netProxyAddr',
+      label: '代理服务地址',
+      value: 'proxy.company.com:8080',
+      desc: '代理服务器地址和端口'
+    },
+    {
+      key: 'map8086',
+      label: '8086映射参数',
+      value: 'localhost:8086',
+      desc: '8086端口映射配置'
+    },
+    {
+      key: 'map8088',
+      label: '8088映射参数',
+      value: 'localhost:8088',
+      desc: '8088端口映射配置'
+    }
+  ],
+  advanced: [
+    {
+      key: 'debugMode',
+      label: '调试模式',
+      value: 'false',
+      desc: '是否启用调试模式'
+    },
+    {
+      key: 'logLevel',
+      label: '日志级别',
+      value: 'INFO',
+      desc: '系统日志记录级别'
+    },
+    {
+      key: 'maxConnections',
+      label: '最大连接数',
+      value: '100',
+      desc: '系统允许的最大并发连接数'
+    }
+  ],
+  security: [
+    {
+      key: 'enableHttps',
+      label: '启用HTTPS',
+      value: 'true',
+      desc: '是否强制使用HTTPS协议'
+    },
+    {
+      key: 'authTimeout',
+      label: '认证超时时间',
+      value: '3600',
+      desc: '用户认证会话超时时间(秒)'
+    }
+  ],
+  performance: [
+    {
+      key: 'cacheEnabled',
+      label: '启用缓存',
+      value: 'true',
+      desc: '是否启用数据缓存'
+    },
+    {
+      key: 'threadPoolSize',
+      label: '线程池大小',
+      value: '20',
+      desc: '系统线程池大小'
+    }
+  ],
+  logging: [
+    {
+      key: 'logPath',
+      label: '日志路径',
+      value: '/var/log/system.log',
+      desc: '系统日志文件存储路径'
+    }
+  ],
+  monitoring: [
+    {
+      key: 'alertEnabled',
+      label: '启用告警',
+      value: 'true',
+      desc: '是否启用系统监控告警'
+    }
+  ]
+}
+
+// 存储当前配置(模拟数据库)
+let currentParamsByGroup = JSON.parse(JSON.stringify(mockParamsByGroup))
+
+/**
+ * 获取分组列表
+ */
+export const getGroups = async () => {
+  await delay()
+  
+  return {
+    code: 200,
+    message: '获取分组成功',
+    data: [...mockGroups]
+  }
+}
+
+/**
+ * 获取指定分组的参数
+ * @param {Object} params - 参数对象
+ * @param {string} params.group - 分组key
+ */
+export const getGroupParams = async ({ group }) => {
+  await delay()
+  
+  // 模拟错误场景(3% 概率)
+  if (Math.random() < 0.03) {
+    throw new Error('获取参数失败,请重试')
+  }
+  
+  if (!group || !currentParamsByGroup[group]) {
+    throw new Error(`未知的分组: ${group}`)
+  }
+  
+  return {
+    code: 200,
+    message: '获取参数成功',
+    data: [...currentParamsByGroup[group]]
+  }
+}
+
+/**
+ * 更新单个配置参数
+ * @param {Object} params - 参数对象
+ * @param {string} params.group - 分组key
+ * @param {string} params.key - 配置键名
+ * @param {any} params.value - 配置值
+ */
+export const updateParam = async ({ group, key, value }) => {
+  await delay()
+  
+  // 模拟错误场景(5% 概率)
+  if (Math.random() < 0.05) {
+    throw new Error('网络错误,请重试')
+  }
+  
+  // 验证参数
+  if (!group || !key) {
+    throw new Error('分组和参数键名不能为空')
+  }
+  
+  if (!currentParamsByGroup[group]) {
+    throw new Error(`未知的分组: ${group}`)
+  }
+  
+  const paramIndex = currentParamsByGroup[group].findIndex(p => p.key === key)
+  if (paramIndex === -1) {
+    throw new Error(`分组 ${group} 中未找到参数: ${key}`)
+  }
+  
+  // 更新配置
+  currentParamsByGroup[group][paramIndex].value = value
+  
+  return {
+    code: 200,
+    message: '参数更新成功',
+    data: {
+      group,
+      key,
+      value,
+      timestamp: Date.now()
+    }
+  }
+}
+
+/**
+ * 批量更新分组配置
+ * @param {Object} params - 参数对象
+ * @param {string} params.group - 分组key
+ * @param {Array} params.params - 参数数组
+ */
+export const updateGroupParams = async ({ group, params }) => {
+  await delay(500, 800)
+  
+  // 模拟错误场景
+  if (Math.random() < 0.03) {
+    throw new Error('服务器内部错误')
+  }
+  
+  if (!group || !currentParamsByGroup[group]) {
+    throw new Error(`未知的分组: ${group}`)
+  }
+  
+  const updates = {}
+  
+  for (const param of params) {
+    const paramIndex = currentParamsByGroup[group].findIndex(p => p.key === param.key)
+    if (paramIndex !== -1) {
+      currentParamsByGroup[group][paramIndex].value = param.value
+      updates[param.key] = param.value
+    }
+  }
+  
+  return {
+    code: 200,
+    message: `成功更新 ${Object.keys(updates).length} 个配置项`,
+    data: {
+      group,
+      updates,
+      timestamp: Date.now()
+    }
+  }
+}
+
+/**
+ * 重置分组配置到默认值
+ * @param {Object} params - 参数对象
+ * @param {string} params.group - 分组key
+ */
+export const resetGroupParams = async ({ group }) => {
+  await delay(400, 700)
+  
+  if (!group || !mockParamsByGroup[group]) {
+    throw new Error(`未知的分组: ${group}`)
+  }
+  
+  currentParamsByGroup[group] = JSON.parse(JSON.stringify(mockParamsByGroup[group]))
+  
+  return {
+    code: 200,
+    message: '配置已重置到默认值',
+    data: [...currentParamsByGroup[group]]
+  }
+}
+
+/**
+ * 测试分组连接
+ * @param {Object} params - 参数对象
+ * @param {string} params.group - 分组key
+ * @param {Object} params.config - 相关配置
+ */
+export const testGroupConnection = async ({ group, config }) => {
+  await delay(1000, 2000)
+  
+  // 模拟测试结果
+  const testResults = {
+    ip: {
+      success: Math.random() > 0.2, // 80% 成功率
+      message: Math.random() > 0.2 ? 'IP连接测试通过' : 'IP地址无法访问'
+    },
+    hardware: {
+      success: Math.random() > 0.25, // 75% 成功率
+      message: Math.random() > 0.25 ? '硬件接口检测正常' : '激光网卡未检测到'
+    },
+    proxy: {
+      success: Math.random() > 0.4, // 60% 成功率
+      message: Math.random() > 0.4 ? '代理连接测试通过' : '代理服务器无响应'
+    },
+    advanced: {
+      success: Math.random() > 0.1, // 90% 成功率
+      message: Math.random() > 0.1 ? '高级设置验证通过' : '配置参数有误'
+    }
+  }
+  
+  const result = testResults[group] || { success: false, message: '未知测试类型' }
+  
+  return {
+    code: 200,
+    message: '测试完成',
+    data: {
+      group,
+      success: result.success,
+      message: result.message,
+      timestamp: Date.now()
+    }
+  }
+}
+
+export default {
+  getGroups,
+  getGroupParams,
+  updateParam,
+  updateGroupParams,
+  resetGroupParams,
+  testGroupConnection
+}

+ 142 - 2
src/api/mock/connectionSimple.js

@@ -48,10 +48,11 @@ export const getConfig = async () => {
 /**
  * 更新单个配置参数
  * @param {Object} params - 参数对象
+ * @param {string} params.group - 分组key(可选,兼容新接口)
  * @param {string} params.key - 配置键名
  * @param {any} params.value - 配置值
  */
-export const updateParam = async ({ key, value }) => {
+export const updateParam = async ({ group, key, value }) => {
   await delay()
   
   // 模拟错误场景(5% 概率)
@@ -75,6 +76,7 @@ export const updateParam = async ({ key, value }) => {
     code: 200,
     message: '参数更新成功',
     data: {
+      group,
       key,
       value,
       timestamp: Date.now()
@@ -195,11 +197,149 @@ export const testConnection = async (type, config) => {
   }
 }
 
+/**
+ * 获取分组列表(新增接口)
+ */
+export const getGroups = async () => {
+  await delay()
+  
+  const groups = [
+    { key: 'ip', name: 'IP配置' },
+    { key: 'hardware', name: '硬件接口' },
+    { key: 'proxy', name: '网络代理' },
+    { key: 'advanced', name: '高级设置' },
+    { key: 'security', name: '安全设置' },
+    { key: 'performance', name: '性能优化' },
+    { key: 'logging', name: '日志配置' },
+    { key: 'monitoring', name: '监控告警' }
+  ]
+  
+  return {
+    code: 200,
+    message: '获取分组成功',
+    data: groups
+  }
+}
+
+/**
+ * 获取指定分组的参数(新增接口)
+ * @param {Object} params - 参数对象
+ * @param {string} params.group - 分组key
+ */
+export const getGroupParams = async ({ group }) => {
+  await delay()
+  
+  const paramsByGroup = {
+    ip: [
+      {
+        key: 'serverIp',
+        label: '服务器IP',
+        value: currentConfig.serverIp,
+        desc: '请输入服务器IP地址',
+        type: 'ip'
+      },
+      {
+        key: 'mqttBrokerWeb',
+        label: 'MQTT代理地址(网页)',
+        value: currentConfig.mqttBrokerWeb,
+        desc: '网页端MQTT连接地址',
+        type: 'url'
+      },
+      {
+        key: 'mqttBrokerNav',
+        label: 'MQTT代理地址(导航)',
+        value: currentConfig.mqttBrokerNav,
+        desc: '导航端MQTT连接地址',
+        type: 'url'
+      },
+      {
+        key: 'mqttProductId',
+        label: 'MQTT产品ID',
+        value: currentConfig.mqttProductId,
+        desc: '请输入产品ID',
+        type: 'text'
+      },
+      {
+        key: 'httpBase',
+        label: 'HTTP服务地址',
+        value: currentConfig.httpBase,
+        desc: 'HTTP API基础地址',
+        type: 'url'
+      }
+    ],
+    hardware: [
+      {
+        key: 'lidarNic',
+        label: '激光网卡',
+        value: currentConfig.lidarNic,
+        desc: '激光雷达网络接口名称',
+        type: 'text'
+      },
+      {
+        key: 'packetFilterEnabled',
+        label: '网络包过滤使能',
+        value: String(currentConfig.packetFilterEnabled),
+        desc: '是否启用网络包过滤功能',
+        type: 'switch'
+      },
+      {
+        key: 'packetFilterRule',
+        label: '网络包过滤规则',
+        value: currentConfig.packetFilterRule,
+        desc: '请输入过滤规则,每行一条',
+        type: 'textarea'
+      }
+    ],
+    proxy: [
+      {
+        key: 'netProxyEnabled',
+        label: '开启网络代理',
+        value: String(currentConfig.netProxyEnabled),
+        desc: '是否启用网络代理服务',
+        type: 'switch'
+      },
+      {
+        key: 'netProxyAddr',
+        label: '代理服务地址',
+        value: currentConfig.netProxyAddr,
+        desc: '代理服务器地址和端口',
+        type: 'text'
+      },
+      {
+        key: 'map8086',
+        label: '8086映射参数',
+        value: currentConfig.map8086,
+        desc: '8086端口映射配置',
+        type: 'text'
+      },
+      {
+        key: 'map8088',
+        label: '8088映射参数',
+        value: currentConfig.map8088,
+        desc: '8088端口映射配置',
+        type: 'text'
+      }
+    ]
+  }
+  
+  if (!group || !paramsByGroup[group]) {
+    throw new Error(`未知的分组: ${group}`)
+  }
+  
+  return {
+    code: 200,
+    message: '获取参数成功',
+    data: paramsByGroup[group]
+  }
+}
+
 export default {
   getConfig,
   updateParam,
   updateConfig,
   resetConfig,
   exportConfig,
-  testConnection
+  testConnection,
+  getGroups,
+  getGroupParams
 }

+ 1007 - 0
src/components/BottomInspector.vue

@@ -0,0 +1,1007 @@
+<template>
+  <div 
+    v-if="visible" 
+    class="bottom-inspector is-editing"
+    :style="{ height: currentHeight }"
+    @keyup.esc="handleEscKey"
+    tabindex="-1"
+  >
+    <!-- 顶部居中拖拽条 -->
+    <div class="drag-section">
+      <div 
+        class="drag-handle" 
+        @mousedown="startResize"
+        @dblclick="resetHeight"
+      ></div>
+    </div>
+    
+    <!-- 头部标题栏 -->
+    <div class="inspector-header">
+      <div class="header-left">
+        <div class="title">当前元素参数</div>
+        <div v-if="elementData" class="element-info">
+          <el-tag size="mini" :type="getElementTypeTag(elementData.type)">
+            {{ getElementTypeName(elementData.type) }}
+          </el-tag>
+          <span class="element-id">{{ elementData.id }}</span>
+          <span v-if="elementData.name" class="element-name">{{ elementData.name }}</span>
+        </div>
+      </div>
+      <div class="header-right">
+        <!-- 快速操作 -->
+        <div class="quick-actions">
+          <el-tooltip content="定位到元素" placement="top">
+            <el-button 
+              size="mini" 
+              type="text" 
+              icon="el-icon-location"
+              @click="locateElement"
+            />
+          </el-tooltip>
+        </div>
+        <!-- 操作按钮 -->
+        <div class="action-buttons">
+          <el-button 
+            size="mini"
+            @click="handleCancel"
+          >
+            取消
+          </el-button>
+          <el-button 
+            size="mini" 
+            type="primary"
+            :loading="saving"
+            @click="handleSave"
+          >
+            保存
+          </el-button>
+        </div>
+      </div>
+    </div>
+
+    <!-- 内容区域 -->
+    <div class="bottom-inspector__body" ref="contentRef">
+      <!-- 骨架屏 -->
+      <div v-if="loading" class="skeleton-content">
+        <div class="skeleton-section">
+          <div class="skeleton-title"></div>
+          <div class="skeleton-field"></div>
+          <div class="skeleton-field"></div>
+          <div class="skeleton-field"></div>
+        </div>
+        <div class="skeleton-section">
+          <div class="skeleton-title"></div>
+          <div class="skeleton-field"></div>
+          <div class="skeleton-field"></div>
+        </div>
+      </div>
+
+      <!-- 表单内容:左右两列布局 -->
+      <div v-else-if="elementData" class="inspector-sections">
+        <!-- 基础参数卡片 -->
+        <section class="inspector-card inspector-card--basic">
+          <div class="card-header">
+            <div class="section-header">
+              <span class="section-title">基础参数</span>
+            </div>
+          </div>
+          <div class="card-body">
+            <el-form 
+              ref="basicFormRef"
+              :model="formData"
+              :rules="formRules"
+              label-width="100px"
+              size="small"
+              class="inspector-form"
+            >
+              <el-form-item label="元素ID" prop="id">
+                <el-input 
+                  v-model="formData.id" 
+                  :readonly="true"
+                  size="small"
+                />
+              </el-form-item>
+              <el-form-item label="元素名称" prop="name">
+                <el-input 
+                  v-model="formData.name" 
+                  placeholder="请输入元素名称"
+                  size="small"
+                />
+              </el-form-item>
+              
+              <!-- 坐标信息 -->
+              <template v-if="hasCoordinates">
+                <el-form-item label="X坐标(m)" prop="x">
+                  <el-input-number
+                    v-model="formData.x"
+                    :precision="3"
+                    :step="coordinateStep"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="Y坐标(m)" prop="y">
+                  <el-input-number
+                    v-model="formData.y"
+                    :precision="3"
+                    :step="coordinateStep"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item v-if="formData.z !== undefined" label="Z坐标(m)" prop="z">
+                  <el-input-number
+                    v-model="formData.z"
+                    :precision="3"
+                    :step="coordinateStep"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+              </template>
+
+              <!-- 线段/曲线基础参数 -->
+              <template v-if="elementData.type === 'LineString'">
+                <el-form-item label="起点ID" prop="startid">
+                  <el-input 
+                    v-model="formData.startid" 
+                    :readonly="true"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="终点ID" prop="endid">
+                  <el-input 
+                    v-model="formData.endid" 
+                    :readonly="true"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="方向控制" prop="directList">
+                  <div class="direction-controls">
+                    <el-checkbox
+                      v-model="formData.directList[0]"
+                      size="small"
+                    >
+                      起点→终点(前行)
+                    </el-checkbox>
+                    <el-checkbox
+                      v-model="formData.directList[1]"
+                      size="small"
+                    >
+                      起点→终点(倒行)
+                    </el-checkbox>
+                    <el-checkbox
+                      v-model="formData.directList[2]"
+                      size="small"
+                    >
+                      终点→起点(前行)
+                    </el-checkbox>
+                    <el-checkbox
+                      v-model="formData.directList[3]"
+                      size="small"
+                    >
+                      终点→起点(倒行)
+                    </el-checkbox>
+                  </div>
+                </el-form-item>
+                <el-form-item label="最大限速(m/s)" prop="maxspeed">
+                  <el-input-number
+                    v-model="formData.maxspeed"
+                    :min="0"
+                    :max="20"
+                    :precision="1"
+                    :step="0.5"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="最小限速(m/s)" prop="minspeed">
+                  <el-input-number
+                    v-model="formData.minspeed"
+                    :min="0"
+                    :precision="1"
+                    :step="0.5"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="车道宽度(m)" prop="lanewidth">
+                  <el-input-number
+                    v-model="formData.lanewidth"
+                    :min="0.1"
+                    :precision="2"
+                    :step="0.1"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="左车道数" prop="leftlanenum">
+                  <el-input-number
+                    v-model="formData.leftlanenum"
+                    :min="0"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="右车道数" prop="rightlanenum">
+                  <el-input-number
+                    v-model="formData.rightlanenum"
+                    :min="0"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+              </template>
+            </el-form>
+          </div>
+        </section>
+
+        <!-- 高级参数卡片 -->
+        <section class="inspector-card inspector-card--advanced">
+          <div class="card-header">
+            <div class="section-header">
+              <span class="section-title">高级参数</span>
+            </div>
+          </div>
+          <div class="card-body">
+            <el-form 
+              ref="advancedFormRef"
+              :model="formData"
+              :rules="formRules"
+              label-width="100px"
+              size="small"
+              class="inspector-form"
+            >
+              <!-- 点位参数 -->
+              <template v-if="elementData.type === 'Point'">
+                <el-form-item label="航偏角(rad)" prop="yaw">
+                  <el-input-number
+                    v-model="formData.yaw"
+                    :precision="3"
+                    :step="0.1"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="航偏角使能" prop="isyawfix">
+                  <el-switch
+                    v-model="formData.isyawfix"
+                    size="small"
+                  />
+                </el-form-item>
+              </template>
+
+              <!-- 线段/曲线高级参数 -->
+              <template v-else-if="elementData.type === 'LineString'">
+                <el-form-item label="避障方式" prop="obstype">
+                  <el-select
+                    v-model="formData.obstype"
+                    size="small"
+                  >
+                    <el-option label="停车等待" :value="0" />
+                    <el-option label="车道绕障" :value="1" />
+                    <el-option label="路网绕障" :value="2" />
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="正向前移(m)" prop="s2eforward">
+                  <el-input-number
+                    v-model="formData.s2eforward"
+                    :precision="2"
+                    :step="0.1"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="逆向前移(m)" prop="e2sforward">
+                  <el-input-number
+                    v-model="formData.e2sforward"
+                    :precision="2"
+                    :step="0.1"
+                    controls-position="right"
+                    size="small"
+                  />
+                </el-form-item>
+              </template>
+
+              <!-- 多边形参数 -->
+              <template v-else-if="elementData.type === 'Polygon'">
+                <el-form-item label="区域类型" prop="type">
+                  <el-select
+                    v-model="formData.type"
+                    placeholder="请选择区域类型"
+                    size="small"
+                  >
+                    <el-option label="隔离区域" :value="0" />
+                    <el-option label="装饰区域" :value="1" />
+                    <el-option label="禁行区域" :value="2" />
+                    <el-option label="会车管制区" :value="3" />
+                    <el-option label="道闸管控区" :value="4" />
+                    <el-option label="GPS定位区" :value="11" />
+                    <el-option label="动态禁行区" :value="22" />
+                  </el-select>
+                </el-form-item>
+                <el-form-item label="填充颜色" prop="color">
+                  <el-color-picker
+                    v-model="formData.color"
+                    size="small"
+                  />
+                </el-form-item>
+                <el-form-item label="透明度" prop="transparent">
+                  <el-slider
+                    v-model="formData.transparent"
+                    :min="0"
+                    :max="255"
+                    :step="5"
+                    size="small"
+                  />
+                </el-form-item>
+              </template>
+            </el-form>
+          </div>
+        </section>
+      </div>
+
+      <!-- 空状态 -->
+      <div v-else class="empty-state">
+        <i class="el-icon-info"></i>
+        <p>请选择一个元素查看其参数信息</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'BottomInspector',
+  props: {
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    mode: {
+      type: String,
+      default: 'view', // 'view' | 'edit'
+      validator: value => ['view', 'edit'].includes(value)
+    },
+    data: {
+      type: Object,
+      default: null
+    },
+    loading: {
+      type: Boolean,
+      default: false
+    },
+  },
+  emits: ['edit', 'save', 'cancel', 'close', 'height-change', 'locate'],
+  data() {
+    return {
+      // 高度管理
+      currentHeight: '20vh',
+      minHeight: 180,
+      maxHeight: '50vh',
+      isResizing: false,
+      startY: 0,
+      startHeight: 0,
+      
+      // 表单数据
+      formData: {},
+      originalData: {},
+      
+      // 表单验证规则
+      formRules: {
+        name: [
+          { required: true, message: '请输入元素名称', trigger: 'blur' }
+        ],
+        x: [
+          { required: true, message: '请输入X坐标', trigger: 'change' }
+        ],
+        y: [
+          { required: true, message: '请输入Y坐标', trigger: 'change' }
+        ]
+      },
+      
+      // 坐标调整步进
+      coordinateStep: 0.1,
+      
+      // 保存状态
+      saving: false,
+      
+      // 未保存更改标记
+      hasUnsavedChanges: false
+    }
+  },
+  computed: {
+    elementData() {
+      return this.data
+    },
+    hasCoordinates() {
+      if (!this.elementData) return false
+      return this.elementData.type === 'Point' || 
+             this.elementData.position || 
+             (this.elementData.x !== undefined && this.elementData.y !== undefined)
+    }
+  },
+  watch: {
+    visible(newVal) {
+      if (newVal) {
+        this.initHeight()
+        this.$nextTick(() => {
+          this.$el?.focus()
+        })
+      }
+    },
+    data: {
+      handler(newData) {
+        if (newData) {
+          this.initFormData()
+        }
+      },
+      immediate: true,
+      deep: true
+    },
+    mode(newMode) {
+      if (newMode === 'edit') {
+        this.createSnapshot()
+        this.advancedExpanded = true // 编辑时自动展开高级参数
+      }
+    },
+    formData: {
+      handler() {
+        if (this.mode === 'edit') {
+          this.checkUnsavedChanges()
+        }
+      },
+      deep: true
+    }
+  },
+  mounted() {
+    document.addEventListener('mousemove', this.handleMouseMove);
+    document.addEventListener('mouseup', this.handleMouseUp);
+  },
+  beforeDestroy() {
+    document.removeEventListener('mousemove', this.handleMouseMove);
+    document.removeEventListener('mouseup', this.handleMouseUp);
+  },
+  methods: {
+    // === 高度管理 ===
+    startResize(event) {
+      this.isResizing = true;
+      this.startY = event.clientY;
+      // 获取当前高度的像素值
+      const currentPx = this.$el.offsetHeight;
+      this.startHeight = currentPx;
+      event.preventDefault();
+    },
+    
+    handleMouseMove(event) {
+      if (!this.isResizing) return;
+      
+      const deltaY = this.startY - event.clientY;
+      let next = this.startHeight + deltaY;
+      const max = Math.round(window.innerHeight * 0.5);  // 50vh
+      const min = 180;
+      next = Math.max(min, Math.min(max, next));
+      
+      this.currentHeight = `${next}px`;
+    },
+    
+    handleMouseUp() {
+      if (this.isResizing) {
+        this.isResizing = false;
+        this.$emit('height-change', this.currentHeight);
+      }
+    },
+    
+    resetHeight() {
+      this.currentHeight = '20vh';
+      this.$emit('height-change', this.currentHeight);
+    },
+    
+    // === 表单数据管理 ===
+    initFormData() {
+      if (!this.elementData) {
+        this.formData = {}
+        return
+      }
+      
+      const data = { ...this.elementData }
+      
+      // 处理坐标数据
+      if (data.position && Array.isArray(data.position)) {
+        data.x = data.position[0]
+        data.y = data.position[1]
+        data.z = data.position[2] || 0
+      }
+      
+      // 处理方向控制数据
+      if (data.direct !== undefined && data.type === 'LineString') {
+        const value = data.direct - 100 || 0
+        const binary = value.toString(2).padStart(4, '0')
+        data.directList = [
+          binary[0] === '1',
+          binary[1] === '1', 
+          binary[2] === '1',
+          binary[3] === '1'
+        ]
+      }
+      
+      // 对于多边形,确保type字段是区域类型而不是几何类型
+      if (this.elementData.type === 'Polygon') {
+        // 如果没有设置区域类型,默认为装饰区域
+        if (data.type === undefined || data.type === 'Polygon') {
+          data.type = 1 // 默认装饰区域
+        }
+      }
+      
+      this.formData = { ...data }
+      this.hasUnsavedChanges = false
+    },
+    
+    createSnapshot() {
+      this.originalData = JSON.parse(JSON.stringify(this.formData))
+    },
+    
+    restoreSnapshot() {
+      if (this.originalData) {
+        this.formData = JSON.parse(JSON.stringify(this.originalData))
+        this.hasUnsavedChanges = false
+      }
+    },
+    
+    checkUnsavedChanges() {
+      if (!this.originalData) return
+      
+      this.hasUnsavedChanges = JSON.stringify(this.formData) !== JSON.stringify(this.originalData)
+    },
+    
+    // === 事件处理 ===
+    handleSave() {
+      this.$refs.formRef?.validate((valid) => {
+        if (valid) {
+          this.saving = true
+          
+          // 准备保存数据
+          const saveData = { ...this.formData }
+          
+          // 处理方向控制数据
+          if (saveData.directList && this.elementData.type === 'LineString') {
+            const binaryString = saveData.directList.map(bit => (bit ? '1' : '0')).join('')
+            const value = parseInt(binaryString, 2)
+            saveData.direct = value + 100
+            delete saveData.directList
+          }
+          
+          // 处理坐标数据
+          if (saveData.x !== undefined && saveData.y !== undefined) {
+            saveData.position = [saveData.x, saveData.y, saveData.z || 0]
+          }
+          
+          // 发送保存事件
+          this.$emit('save', saveData)
+          
+          // 模拟保存延迟
+          setTimeout(() => {
+            this.saving = false
+            this.hasUnsavedChanges = false
+            this.$message.success('保存成功')
+          }, 300)
+        } else {
+          this.$message.error('请检查表单数据')
+        }
+      })
+    },
+    
+    handleCancel() {
+      if (this.hasUnsavedChanges) {
+        this.$confirm('是否放弃未保存的更改?', '确认', {
+          confirmButtonText: '放弃更改',
+          cancelButtonText: '继续编辑',
+          type: 'warning'
+        }).then(() => {
+          this.restoreSnapshot()
+          this.$emit('cancel')
+        })
+      } else {
+        this.$emit('cancel')
+      }
+    },
+    
+    handleClose() {
+      if (this.mode === 'edit' && this.hasUnsavedChanges) {
+        this.$confirm('当前有未保存的更改,是否放弃?', '确认关闭', {
+          confirmButtonText: '放弃更改',
+          cancelButtonText: '继续编辑',
+          type: 'warning'
+        }).then(() => {
+          this.$emit('close')
+        })
+      } else {
+        this.$emit('close')
+      }
+    },
+    
+    handleEscKey() {
+      if (!this.hasUnsavedChanges) {
+        this.handleClose()
+      }
+    },
+    
+    // === 快速操作 ===
+    
+    locateElement() {
+      this.$emit('locate', this.elementData)
+      this.$message.success(`已定位到元素 ${this.elementData.id}`)
+    },
+    
+    // === 工具方法 ===
+    getElementTypeTag(type) {
+      const typeMap = {
+        'Point': 'success',
+        'LineString': 'primary', 
+        'Polygon': 'warning'
+      }
+      return typeMap[type] || 'info'
+    },
+    
+    getElementTypeName(type) {
+      const typeMap = {
+        'Point': '点',
+        'LineString': '线',
+        'Polygon': '面'
+      }
+      return typeMap[type] || '未知'
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.bottom-inspector {
+  position: absolute;
+  left: var(--left-safe);
+  width: calc(100% - var(--left-safe) - var(--right-safe));
+  bottom: 0;
+  
+  /* 视觉与尺寸 */
+  min-height: 180px;
+  height: 20vh;            /* 默认高度 */
+  max-height: 50vh;
+  background: #fff;
+  border-radius: 12px 12px 0 0;
+  box-shadow: 0 8px 28px rgba(0,0,0,0.12);
+  display: flex;
+  flex-direction: column;
+  box-sizing: border-box;
+  
+  /* 层级:要在地图上方、在右侧面板下方 */
+  z-index: 9;
+  pointer-events: auto;
+  overflow: hidden;        /* 只让 body 滚动,不让容器外溢 */
+  
+  &:focus {
+    outline: none;
+  }
+  
+  &.is-editing {
+    border-top-color: #409eff;
+    box-shadow: 0 8px 32px rgba(64, 158, 255, 0.2);
+  }
+}
+
+/* 拖拽区域:顶部居中 */
+.drag-section {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 8px 0;
+  background: #fff;
+  flex-shrink: 0;
+  
+  .drag-handle {
+    width: 56px;
+    height: 4px;
+    border-radius: 2px;
+    background: rgba(0,0,0,.18);
+    cursor: ns-resize;
+    transition: background-color 0.2s ease;
+    
+    &:hover {
+      background: rgba(0,0,0,.28);
+    }
+  }
+}
+
+.inspector-header {
+  flex: 0 0 auto;
+  height: 44px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 16px;
+  border-bottom: 1px solid #e4e7ed;
+  background: #fafbfc;
+  box-sizing: border-box;
+  
+  .header-left {
+    display: flex;
+    align-items: center;
+    flex: 1;
+    
+    .title {
+      font-size: 16px;
+      font-weight: 600;
+      color: #303133;
+      margin-right: 16px;
+    }
+    
+    .element-info {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      color: #606266;
+      font-size: 14px;
+      
+      .element-id {
+        font-family: 'Monaco', 'Menlo', monospace;
+        background: #f0f2f5;
+        padding: 2px 6px;
+        border-radius: 4px;
+        font-size: 12px;
+      }
+      
+      .element-name {
+        color: #909399;
+      }
+    }
+  }
+  
+  .header-right {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  
+  .quick-actions {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    margin-right: 8px;
+    padding-right: 8px;
+    border-right: 1px solid #e4e7ed;
+  }
+  
+  .action-buttons {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+  
+}
+
+.bottom-inspector__body {
+  flex: 1 1 auto;
+  overflow-y: auto;
+  padding: 12px 16px;
+  box-sizing: border-box;
+}
+
+/* 基础/高级参数网格布局 */
+.inspector-sections {
+  display: grid;
+  grid-template-columns: 1fr 1fr;    /* 左右两列并排 */
+  gap: 16px;
+  align-items: start;
+}
+
+/* 卡片皮肤与粘性小标题 */
+.inspector-card {
+  background: #fff;
+  border: 1px solid #eef0f3;
+  border-radius: 10px;
+  overflow: hidden;
+}
+
+.inspector-card .card-header {
+  position: sticky; 
+  top: 0;
+  background: #f8fafc; 
+  z-index: 1;
+  padding: 10px 12px; 
+  border-bottom: 1px solid #eef0f3;
+}
+
+.inspector-card .card-body {
+  padding: 12px;
+}
+
+/* 表单在窄宽度下不挤压 */
+.inspector-card :deep(.el-form),
+.inspector-card :deep(.el-form-item),
+.inspector-card :deep(.el-input),
+.inspector-card :deep(.el-input__inner) { 
+  width: 100%; 
+}
+
+/* 避免内容被压缩 */
+.bottom-inspector .el-form,
+.bottom-inspector .el-form-item,
+.bottom-inspector .el-input,
+.bottom-inspector .el-input__inner {
+  width: 100%;
+  max-width: 100%;
+}
+
+.inspector-form {
+  .form-section {
+    background: #ffffff;
+    border: 1px solid #e4e7ed;
+    border-radius: 8px;
+    margin-bottom: 16px;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    .section-header {
+      display: flex;
+      align-items: center;
+      padding: 8px 12px;
+      background: #f8f9fa;
+      border-bottom: 1px solid #e4e7ed;
+      
+      .section-title {
+        font-size: 14px;
+        font-weight: 600;
+        color: #303133;
+      }
+    }
+    
+    
+    .section-content {
+      padding: 12px; /* 减少section content的padding */
+    }
+  }
+  
+  ::v-deep .el-form-item {
+    margin-bottom: 12px; /* 减少表单项间距 */
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    .el-form-item__label {
+      font-size: 13px;
+      line-height: 1.4;
+      padding-bottom: 4px;
+    }
+    
+    .el-form-item__content {
+      line-height: 1.6;
+    }
+  }
+  
+  /* 方向控制组件 */
+  .direction-controls {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+    
+    .el-checkbox {
+      margin-right: 0;
+    }
+  }
+}
+
+.skeleton-content {
+  .skeleton-section {
+    margin-bottom: 24px;
+    
+    .skeleton-title {
+      height: 16px;
+      background: linear-gradient(90deg, #f0f2f5 25%, #e6e8eb 50%, #f0f2f5 75%);
+      background-size: 200% 100%;
+      animation: skeleton-loading 1.5s infinite;
+      border-radius: 4px;
+      margin-bottom: 16px;
+      width: 120px;
+    }
+    
+    .skeleton-field {
+      height: 32px;
+      background: linear-gradient(90deg, #f0f2f5 25%, #e6e8eb 50%, #f0f2f5 75%);
+      background-size: 200% 100%;
+      animation: skeleton-loading 1.5s infinite;
+      border-radius: 4px;
+      margin-bottom: 12px;
+      
+      &:nth-child(2) { animation-delay: 0.1s; }
+      &:nth-child(3) { animation-delay: 0.2s; }
+      &:nth-child(4) { animation-delay: 0.3s; }
+    }
+  }
+}
+
+@keyframes skeleton-loading {
+  0% {
+    background-position: 200% 0;
+  }
+  100% {
+    background-position: -200% 0;
+  }
+}
+
+.empty-state {
+  text-align: center;
+  padding: 40px 20px;
+  color: #909399;
+  
+  i {
+    font-size: 48px;
+    margin-bottom: 16px;
+    display: block;
+    color: #c0c4cc;
+  }
+  
+  p {
+    font-size: 14px;
+    margin: 0;
+  }
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .bottom-inspector {
+    --right-panel-width: 360px; /* 中等屏幕右侧面板宽度 */
+  }
+}
+
+/* 小屏幕响应式:单列布局 */
+@media (max-width: 992px) {
+  .bottom-inspector {
+    left: 12px;
+    width: calc(100% - 24px); /* 左右12px 间隙 */
+    
+    .inspector-header {
+      .header-left .title {
+        font-size: 14px;
+        margin-right: 12px;
+      }
+      
+      .element-info {
+        display: none; /* 小屏幕隐藏元素信息 */
+      }
+      
+      .quick-actions {
+        display: none; /* 小屏幕隐藏快速操作 */
+      }
+    }
+    
+    .bottom-inspector__body {
+      padding: 12px;
+    }
+    
+    /* 小屏幕下改为单列 */
+    .inspector-sections {
+      grid-template-columns: 1fr;    /* 单列布局 */
+      gap: 12px;
+    }
+    
+    .inspector-card .card-body {
+      padding: 10px;
+    }
+    
+    .inspector-form {
+      ::v-deep .el-form-item .el-form-item__label {
+        font-size: 12px;
+      }
+    }
+  }
+}
+
+@media (max-width: 1024px) {
+  .bottom-inspector {
+    /* 平板默认高度调整为30% */
+    /* 这个逻辑在组件的计算属性中处理 */
+  }
+}
+</style>

+ 728 - 0
src/components/XtElementConfigPanel/index.vue

@@ -0,0 +1,728 @@
+<template>
+  <div class="element-config-panel" :class="{ 'is-collapsed': collapsed, 'is-visible': visible }">
+    <!-- 折叠/展开提示条 -->
+    <div class="panel-toggle-bar" @click="togglePanel">
+      <div class="toggle-content">
+        <i :class="collapsed ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
+        <span v-if="selectedElement.id">
+          {{ getElementTypeLabel(selectedElement.typeEle) }} - {{ selectedElement.id }}
+          <span v-if="hasUnsavedChanges" class="unsaved-indicator">*</span>
+        </span>
+        <span v-else>选择元素以编辑参数</span>
+      </div>
+      <div v-if="!collapsed && selectedElement.id" class="toggle-actions">
+        <el-button size="mini" @click.stop="resetForm">重置</el-button>
+        <el-button size="mini" @click.stop="cancelEdit">取消</el-button>
+        <el-button type="primary" size="mini" @click.stop="saveElement">保存</el-button>
+      </div>
+    </div>
+
+    <!-- 参数配置表单 -->
+    <div v-if="!collapsed && selectedElement.id" class="panel-content">
+      <el-form
+        ref="elementForm"
+        :model="formData"
+        :rules="formRules"
+        label-width="120px"
+        size="small"
+        class="element-form"
+        @input="handleFormChange"
+      >
+        <!-- 点类型参数 -->
+        <template v-if="selectedElement.typeEle === 'Point'">
+          <div class="form-section">
+            <div class="section-title">基础参数</div>
+            <el-form-item label="元素ID" prop="id">
+              <el-input v-model="formData.id" disabled />
+            </el-form-item>
+            <el-form-item label="名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入名称" />
+            </el-form-item>
+            <el-form-item label="X坐标(m)" prop="x">
+              <el-input-number 
+                v-model="formData.x" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="Y坐标(m)" prop="y">
+              <el-input-number 
+                v-model="formData.y" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="Z坐标(m)" prop="z">
+              <el-input-number 
+                v-model="formData.z" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">高级参数</div>
+            <el-form-item label="航偏角使能" prop="isyawfix">
+              <el-switch v-model="formData.isyawfix" />
+            </el-form-item>
+            <el-form-item label="航偏角(rad)" prop="yaw">
+              <el-input-number 
+                v-model="formData.yaw" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+          </div>
+        </template>
+
+        <!-- 线/曲线类型参数 -->
+        <template v-if="selectedElement.typeEle === 'LineString'">
+          <div class="form-section">
+            <div class="section-title">基础参数</div>
+            <el-form-item label="元素ID" prop="id">
+              <el-input v-model="formData.id" disabled />
+            </el-form-item>
+            <el-form-item label="名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入名称" />
+            </el-form-item>
+            <el-form-item label="起点ID" prop="startid">
+              <el-input v-model="formData.startid" disabled />
+            </el-form-item>
+            <el-form-item label="终点ID" prop="endid">
+              <el-input v-model="formData.endid" disabled />
+            </el-form-item>
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">运动方向</div>
+            <el-form-item label="起点→终点">
+              <div class="direction-checkboxes">
+                <el-checkbox v-model="formData.directList[0]">前行</el-checkbox>
+                <el-checkbox v-model="formData.directList[1]">倒行</el-checkbox>
+              </div>
+            </el-form-item>
+            <el-form-item label="终点→起点">
+              <div class="direction-checkboxes">
+                <el-checkbox v-model="formData.directList[2]">前行</el-checkbox>
+                <el-checkbox v-model="formData.directList[3]">倒行</el-checkbox>
+              </div>
+            </el-form-item>
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">速度参数</div>
+            <el-form-item label="最大限速(m/s)" prop="maxspeed">
+              <el-input-number 
+                v-model="formData.maxspeed" 
+                :min="0"
+                :precision="2"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="最小限速(m/s)" prop="minspeed">
+              <el-input-number 
+                v-model="formData.minspeed" 
+                :min="0"
+                :precision="2"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">车道参数</div>
+            <el-form-item label="车道宽度(m)" prop="lanewidth">
+              <el-input-number 
+                v-model="formData.lanewidth" 
+                :min="0"
+                :precision="2"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="左车道数" prop="leftlanenum">
+              <el-input-number 
+                v-model="formData.leftlanenum" 
+                :min="0"
+                :step="1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="右车道数" prop="rightlanenum">
+              <el-input-number 
+                v-model="formData.rightlanenum" 
+                :min="0"
+                :step="1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+          </div>
+
+          <div class="form-section">
+            <div class="section-title">高级参数</div>
+            <el-form-item label="避障方式" prop="obstype">
+              <el-select v-model="formData.obstype" style="width: 100%;">
+                <el-option label="停车等待" :value="0" />
+                <el-option label="车道绕障" :value="1" />
+                <el-option label="路网绕障" :value="2" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="正向前移(m)" prop="s2eforward">
+              <el-input-number 
+                v-model="formData.s2eforward" 
+                :precision="2"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="逆向前移(m)" prop="e2sforward">
+              <el-input-number 
+                v-model="formData.e2sforward" 
+                :precision="2"
+                :step="0.1"
+                controls-position="right"
+                style="width: 100%;"
+              />
+            </el-form-item>
+          </div>
+        </template>
+
+        <!-- 面类型参数 -->
+        <template v-if="selectedElement.typeEle === 'Polygon'">
+          <div class="form-section">
+            <div class="section-title">基础参数</div>
+            <el-form-item label="元素ID" prop="id">
+              <el-input v-model="formData.id" disabled />
+            </el-form-item>
+            <el-form-item label="名称" prop="name">
+              <el-input v-model="formData.name" placeholder="请输入名称" />
+            </el-form-item>
+            <el-form-item label="区域类型" prop="type">
+              <el-select v-model="formData.type" style="width: 100%;">
+                <el-option label="隔离区域" :value="0" />
+                <el-option label="装饰区域" :value="1" />
+                <el-option label="禁行区域" :value="2" />
+                <el-option label="会车管制区" :value="3" />
+                <el-option label="道闸管控区" :value="4" />
+                <el-option label="GPS定位区" :value="11" />
+                <el-option label="动态禁行区" :value="22" />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="颜色" prop="color">
+              <el-color-picker v-model="formData.color" />
+            </el-form-item>
+            <el-form-item label="透明度" prop="transparent">
+              <el-slider 
+                v-model="formData.transparent" 
+                :min="0" 
+                :max="255" 
+                :step="5"
+                style="width: 100%;"
+              />
+            </el-form-item>
+          </div>
+        </template>
+      </el-form>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'XtElementConfigPanel',
+  props: {
+    // 选中的元素
+    selectedElement: {
+      type: Object,
+      default: () => ({})
+    },
+    // 面板是否可见
+    visible: {
+      type: Boolean,
+      default: false
+    },
+    // 初始折叠状态
+    initialCollapsed: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      collapsed: this.initialCollapsed,
+      formData: {},
+      originalFormData: {},
+      hasUnsavedChanges: false,
+      formRules: {
+        name: [
+          { max: 50, message: '名称长度不能超过50个字符', trigger: 'blur' }
+        ],
+        x: [
+          { type: 'number', message: 'X坐标必须是数字', trigger: 'blur' }
+        ],
+        y: [
+          { type: 'number', message: 'Y坐标必须是数字', trigger: 'blur' }
+        ],
+        z: [
+          { type: 'number', message: 'Z坐标必须是数字', trigger: 'blur' }
+        ],
+        maxspeed: [
+          { type: 'number', min: 0, message: '最大限速必须大于等于0', trigger: 'blur' }
+        ],
+        minspeed: [
+          { type: 'number', min: 0, message: '最小限速必须大于等于0', trigger: 'blur' }
+        ],
+        lanewidth: [
+          { type: 'number', min: 0, message: '车道宽度必须大于0', trigger: 'blur' }
+        ]
+      }
+    }
+  },
+  computed: {
+    // 计算面板是否应该显示
+    shouldShow() {
+      return this.visible && this.selectedElement && this.selectedElement.id
+    }
+  },
+  watch: {
+    selectedElement: {
+      handler(newElement) {
+        if (newElement && newElement.id) {
+          this.initFormData(newElement)
+          this.collapsed = false
+        } else {
+          this.collapsed = true
+          this.formData = {}
+          this.hasUnsavedChanges = false
+        }
+      },
+      deep: true,
+      immediate: true
+    },
+    visible(newVal) {
+      if (!newVal) {
+        this.collapsed = true
+      }
+    }
+  },
+  mounted() {
+    // 监听 ESC 键
+    document.addEventListener('keydown', this.handleKeydown)
+  },
+  beforeDestroy() {
+    document.removeEventListener('keydown', this.handleKeydown)
+  },
+  methods: {
+    // 初始化表单数据
+    initFormData(element) {
+      this.formData = { ...element }
+      
+      // 处理点类型的坐标
+      if (element.typeEle === 'Point' && element.position) {
+        this.formData.x = element.position[0] || 0
+        this.formData.y = element.position[1] || 0
+        this.formData.z = element.position[2] || 0
+      }
+      
+      // 处理线类型的方向参数
+      if (element.typeEle === 'LineString' && !element.directList) {
+        // 如果没有directList,根据direct值生成
+        const value = (element.direct || 100) - 100
+        const binary = value.toString(2).padStart(4, '0')
+        this.formData.directList = [
+          binary[0] === '1',
+          binary[1] === '1', 
+          binary[2] === '1',
+          binary[3] === '1'
+        ]
+      }
+      
+      // 保存原始数据用于重置
+      this.originalFormData = JSON.parse(JSON.stringify(this.formData))
+      this.hasUnsavedChanges = false
+    },
+
+    // 切换面板展开/折叠
+    togglePanel() {
+      if (this.selectedElement.id) {
+        this.collapsed = !this.collapsed
+        this.$emit('panel-toggle', !this.collapsed)
+      }
+    },
+
+    // 处理表单变化
+    handleFormChange() {
+      this.hasUnsavedChanges = true
+    },
+
+    // 保存元素
+    saveElement() {
+      this.$refs.elementForm.validate((valid) => {
+        if (valid) {
+          // 处理特殊字段
+          const saveData = { ...this.formData }
+          
+          // 点类型:合并坐标
+          if (this.selectedElement.typeEle === 'Point') {
+            saveData.position = [
+              this.formData.x || 0,
+              this.formData.y || 0,
+              this.formData.z || 0
+            ]
+          }
+          
+          // 线类型:处理方向参数
+          if (this.selectedElement.typeEle === 'LineString' && this.formData.directList) {
+            const binaryString = this.formData.directList.map(bit => (bit ? '1' : '0')).join('')
+            const value = parseInt(binaryString, 2)
+            saveData.direct = value + 100
+          }
+          
+          this.$emit('save', saveData)
+          this.hasUnsavedChanges = false
+          this.originalFormData = JSON.parse(JSON.stringify(this.formData))
+        }
+      })
+    },
+
+    // 取消编辑
+    cancelEdit() {
+      if (this.hasUnsavedChanges) {
+        this.$confirm('有未保存的更改,确定要取消吗?', '确认', {
+          confirmButtonText: '确定',
+          cancelButtonText: '继续编辑',
+          type: 'warning'
+        }).then(() => {
+          this.resetForm()
+          this.$emit('cancel')
+        })
+      } else {
+        this.$emit('cancel')
+      }
+    },
+
+    // 重置表单
+    resetForm() {
+      this.formData = JSON.parse(JSON.stringify(this.originalFormData))
+      this.hasUnsavedChanges = false
+      this.$refs.elementForm && this.$refs.elementForm.clearValidate()
+    },
+
+    // 获取元素类型标签
+    getElementTypeLabel(type) {
+      const typeMap = {
+        'Point': '点',
+        'LineString': '线',
+        'Polygon': '面'
+      }
+      return typeMap[type] || type
+    },
+
+    // 处理键盘事件
+    handleKeydown(event) {
+      if (!this.visible || this.collapsed) return
+      
+      if (event.key === 'Escape') {
+        event.preventDefault()
+        this.cancelEdit()
+      } else if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) {
+        event.preventDefault()
+        this.saveElement()
+      }
+    },
+
+    // 检查是否有未保存的更改
+    checkUnsavedChanges() {
+      return this.hasUnsavedChanges
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.element-config-panel {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 1000;
+  background: #fff;
+  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
+  border-top: 1px solid #e4e7ed;
+  transform: translateY(100%);
+  transition: all 0.3s ease;
+
+  &.is-visible {
+    transform: translateY(0);
+  }
+
+  &.is-collapsed {
+    .panel-content {
+      height: 0;
+      overflow: hidden;
+    }
+  }
+
+  .panel-toggle-bar {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 12px 24px;
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    border-bottom: 1px solid #e2e8f0;
+    cursor: pointer;
+    user-select: none;
+    min-height: 48px;
+
+    &:hover {
+      background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
+    }
+
+    .toggle-content {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #475569;
+
+      i {
+        font-size: 16px;
+        transition: transform 0.2s ease;
+      }
+
+      .unsaved-indicator {
+        color: #f56c6c;
+        font-weight: bold;
+        margin-left: 4px;
+      }
+    }
+
+    .toggle-actions {
+      display: flex;
+      gap: 8px;
+      
+      .el-button {
+        padding: 6px 12px;
+        font-size: 12px;
+      }
+    }
+  }
+
+  .panel-content {
+    max-height: 60vh;
+    overflow-y: auto;
+    padding: 24px;
+    background: #fff;
+
+    // 自定义滚动条
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+
+    &::-webkit-scrollbar-track {
+      background: #f1f1f1;
+      border-radius: 3px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+      background: #c1c1c1;
+      border-radius: 3px;
+
+      &:hover {
+        background: #a8a8a8;
+      }
+    }
+  }
+
+  .element-form {
+    max-width: 1200px;
+    margin: 0 auto;
+
+    .form-section {
+      margin-bottom: 32px;
+      padding: 20px;
+      background: #f8fafc;
+      border-radius: 8px;
+      border: 1px solid #e2e8f0;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      .section-title {
+        font-size: 16px;
+        font-weight: 600;
+        color: #334155;
+        margin-bottom: 16px;
+        padding-bottom: 8px;
+        border-bottom: 2px solid #3b82f6;
+        position: relative;
+
+        &::before {
+          content: '';
+          position: absolute;
+          left: 0;
+          bottom: -2px;
+          width: 40px;
+          height: 2px;
+          background: #3b82f6;
+        }
+      }
+    }
+
+    .direction-checkboxes {
+      display: flex;
+      gap: 16px;
+
+      .el-checkbox {
+        margin-right: 0;
+      }
+    }
+
+    // 表单项样式优化
+    :deep(.el-form-item) {
+      margin-bottom: 20px;
+
+      .el-form-item__label {
+        font-weight: 500;
+        color: #374151;
+        line-height: 1.6;
+      }
+
+      .el-form-item__content {
+        line-height: 1.6;
+      }
+    }
+
+    // 输入框样式
+    :deep(.el-input) {
+      .el-input__inner {
+        border-radius: 6px;
+        border: 1px solid #d1d5db;
+        transition: all 0.2s ease;
+
+        &:focus {
+          border-color: #3b82f6;
+          box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+        }
+      }
+    }
+
+    // 数字输入框样式
+    :deep(.el-input-number) {
+      width: 100%;
+
+      .el-input__inner {
+        border-radius: 6px;
+        border: 1px solid #d1d5db;
+        text-align: left;
+
+        &:focus {
+          border-color: #3b82f6;
+          box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+        }
+      }
+    }
+
+    // 选择器样式
+    :deep(.el-select) {
+      .el-input__inner {
+        border-radius: 6px;
+        border: 1px solid #d1d5db;
+
+        &:focus {
+          border-color: #3b82f6;
+          box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+        }
+      }
+    }
+
+    // 开关样式
+    :deep(.el-switch) {
+      .el-switch__core {
+        border-radius: 12px;
+      }
+    }
+
+    // 滑块样式
+    :deep(.el-slider) {
+      .el-slider__runway {
+        background: #e2e8f0;
+        border-radius: 3px;
+      }
+
+      .el-slider__bar {
+        background: #3b82f6;
+        border-radius: 3px;
+      }
+
+      .el-slider__button {
+        border: 2px solid #3b82f6;
+        background: #fff;
+      }
+    }
+
+    // 复选框样式
+    :deep(.el-checkbox) {
+      .el-checkbox__input.is-checked .el-checkbox__inner {
+        background-color: #3b82f6;
+        border-color: #3b82f6;
+      }
+
+      .el-checkbox__inner {
+        border-radius: 4px;
+      }
+    }
+  }
+}
+
+// 响应式适配
+@media (max-width: 768px) {
+  .element-config-panel {
+    .panel-toggle-bar {
+      padding: 8px 16px;
+      flex-direction: column;
+      gap: 8px;
+
+      .toggle-actions {
+        width: 100%;
+        justify-content: flex-end;
+      }
+    }
+
+    .panel-content {
+      padding: 16px;
+    }
+
+    .element-form {
+      .form-section {
+        padding: 16px;
+        margin-bottom: 24px;
+      }
+
+      .direction-checkboxes {
+        flex-direction: column;
+        gap: 8px;
+      }
+    }
+  }
+}
+</style>

+ 18 - 2
src/components/XtParamItem/index.vue

@@ -506,12 +506,15 @@ export default {
   }
 
   .param-label {
-    flex: 0 0 120px;
+    flex: 0 0 180px;
     color: var(--color-text-primary);
     font-weight: var(--font-weight-medium);
     font-size: var(--font-size-sm);
     line-height: 36px;
     padding-right: var(--spacing-2);
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
   }
 
   .param-value {
@@ -655,7 +658,7 @@ export default {
     font-size: var(--font-size-xs);
     line-height: var(--line-height-normal);
     margin-top: var(--spacing-2);
-    margin-left: 124px;
+    margin-left: 184px;
     opacity: 0.8;
   }
 
@@ -806,6 +809,19 @@ html.dark {
   }
 }
 
+// 大屏优化
+@media (min-width: 1280px) {
+  .param-item {
+    .param-label {
+      flex: 0 0 220px; // 大屏下给更多空间
+    }
+
+    .error-message {
+      margin-left: 224px;
+    }
+  }
+}
+
 // 移动端适配
 @media (max-width: 768px) {
   .param-item {

+ 559 - 264
src/views/config/connectconf/index.vue

@@ -4,8 +4,8 @@
     <div class="page-header">
       <div class="header-content">
         <div class="header-main">
-          <h1 class="page-title">连接配置</h1>
-          <p class="page-desc">配置系统连接参数,逐项编辑保存</p>
+          <h1 class="page-title">参数配置</h1>
+          <p class="page-desc">配置系统参数,逐项编辑保存</p>
         </div>
       </div>
       
@@ -23,80 +23,47 @@
 
     <!-- 主内容区域 -->
     <div class="page-content">
-      <div class="config-cards" :class="{ 'two-columns': isLargeScreen }">
-        <!-- IP配置卡片 -->
-        <div class="config-card" v-show="hasVisibleItems('ip')">
-          <div class="card-header">
-            <h2 class="card-title">IP 配置</h2>
-            <p class="card-desc">网络地址和服务连接配置</p>
-          </div>
-          <div class="card-content">
-            <XtParamItem
-              v-for="item in ipConfigItems"
-              :key="item.key"
-              v-show="isItemVisible(item)"
-              :label="item.label"
-              :value="config[item.key]"
-              :type="item.type"
-              :placeholder="item.placeholder"
-              :search-keyword="searchKeyword"
-              @save="(value) => handleParamSave(item.key, value)"
-              @cancel="handleParamCancel"
-            />
-            <div v-if="!hasVisibleItems('ip')" class="empty-state">
-              <i class="el-icon-search"></i>
-              <p>无匹配的配置参数</p>
-            </div>
-          </div>
-        </div>
+      <!-- 分组标签页容器(粘顶) -->
+      <div class="group-tabs-wrap">
+        <el-tabs 
+          v-model="activeGroup" 
+          type="card" 
+          class="group-tabs"
+          @tab-click="(pane) => handleGroupChange(pane.name)"
+        >
+          <el-tab-pane 
+            v-for="g in groups" 
+            :key="g.key" 
+            :name="g.key" 
+            :label="g.name"
+            :title="g.name"
+          />
+        </el-tabs>
+      </div>
 
-        <!-- 硬件接口卡片 -->
-        <div class="config-card" v-show="hasVisibleItems('hardware')">
+      <!-- 配置卡片 -->
+      <div class="config-cards">
+        <div class="config-card">
           <div class="card-header">
-            <h2 class="card-title">硬件接口</h2>
-            <p class="card-desc">硬件设备和数据过滤配置</p>
+            <h2 class="card-title">{{ currentGroupName }}</h2>
+            <p class="card-desc">根据分组动态展示参数</p>
           </div>
           <div class="card-content">
-            <XtParamItem
-              v-for="item in hardwareConfigItems"
-              :key="item.key"
-              v-show="isItemVisible(item)"
-              :label="item.label"
-              :value="config[item.key]"
-              :type="item.type"
-              :placeholder="item.placeholder"
-              :search-keyword="searchKeyword"
-              @save="(value) => handleParamSave(item.key, value)"
-              @cancel="handleParamCancel"
-            />
-            <div v-if="!hasVisibleItems('hardware')" class="empty-state">
-              <i class="el-icon-search"></i>
-              <p>无匹配的配置参数</p>
+            <div class="param-list">
+              <XtParamItem
+                v-for="item in visibleParams"
+                :key="item.key"
+                :label="item.label || item.key"
+                :value="item.value"
+                :type="item.type || 'text'"
+                :placeholder="item.desc || ('请输入 ' + (item.label || item.key))"
+                :search-keyword="searchKeyword"
+                @save="(value) => handleParamSave(item.key, value)"
+                @cancel="handleParamCancel"
+              />
             </div>
-          </div>
-        </div>
 
-        <!-- 网络代理卡片 -->
-        <div class="config-card" v-show="hasVisibleItems('proxy')">
-          <div class="card-header">
-            <h2 class="card-title">网络代理</h2>
-            <p class="card-desc">代理服务和端口映射配置</p>
-          </div>
-          <div class="card-content">
-            <XtParamItem
-              v-for="item in proxyConfigItems"
-              :key="item.key"
-              v-show="isItemVisible(item)"
-              :label="item.label"
-              :value="config[item.key]"
-              :type="item.type"
-              :placeholder="item.placeholder"
-              :search-keyword="searchKeyword"
-              :disabled="item.key === 'netProxyAddr' && !config.netProxyEnabled"
-              @save="(value) => handleParamSave(item.key, value)"
-              @cancel="handleParamCancel"
-            />
-            <div v-if="!hasVisibleItems('proxy')" class="empty-state">
+            <div v-if="!loading && visibleParams.length === 0" class="empty-state">
               <i class="el-icon-search"></i>
               <p>无匹配的配置参数</p>
             </div>
@@ -108,7 +75,6 @@
 </template>
 
 <script>
-// 导入参数组件
 import XtParamItem from '@/components/XtParamItem'
 
 // 尝试导入真实 API,失败则使用 Mock
@@ -130,26 +96,11 @@ export default {
 
   data() {
     return {
-      // 配置数据
-      config: {
-        // IP配置
-        serverIp: '',
-        mqttBrokerWeb: '',
-        mqttBrokerNav: '',
-        mqttProductId: '',
-        httpBase: '',
-        
-        // 硬件接口
-        lidarNic: '',
-        packetFilterEnabled: false,
-        packetFilterRule: '',
-        
-        // 网络代理
-        netProxyEnabled: false,
-        netProxyAddr: '',
-        map8086: '',
-        map8088: ''
-      },
+      // 分组数据
+      groups: [],
+      activeGroup: null,
+      paramsByGroup: {},
+      originalByGroup: {},
 
       // 搜索关键字
       searchKeyword: '',
@@ -168,115 +119,18 @@ export default {
       return this.windowWidth >= 1440
     },
 
-    // IP配置项
-    ipConfigItems() {
-      return [
-        {
-          key: 'serverIp',
-          label: '服务器IP',
-          type: 'ip',
-          placeholder: '请输入服务器IP地址',
-          group: 'ip'
-        },
-        {
-          key: 'mqttBrokerWeb',
-          label: 'MQTT代理地址(网页)',
-          type: 'url',
-          placeholder: 'ws://broker.hivemq.com:8000',
-          group: 'ip'
-        },
-        {
-          key: 'mqttBrokerNav',
-          label: 'MQTT代理地址(导航)',
-          type: 'url',
-          placeholder: 'mqtt://broker.hivemq.com:1883',
-          group: 'ip'
-        },
-        {
-          key: 'mqttProductId',
-          label: 'MQTT产品ID',
-          type: 'text',
-          placeholder: '请输入产品ID',
-          group: 'ip'
-        },
-        {
-          key: 'httpBase',
-          label: 'HTTP服务地址',
-          type: 'url',
-          placeholder: 'https://api.smartdriving.com',
-          group: 'ip'
-        }
-      ]
-    },
-
-    // 硬件接口配置项
-    hardwareConfigItems() {
-      return [
-        {
-          key: 'lidarNic',
-          label: '激光网卡',
-          type: 'text',
-          placeholder: 'enp0s31f6',
-          group: 'hardware'
-        },
-        {
-          key: 'packetFilterEnabled',
-          label: '网络包过滤使能',
-          type: 'switch',
-          placeholder: '',
-          group: 'hardware'
-        },
-        {
-          key: 'packetFilterRule',
-          label: '网络包过滤规则',
-          type: 'textarea',
-          placeholder: '请输入过滤规则,每行一条',
-          group: 'hardware'
-        }
-      ]
-    },
-
-    // 网络代理配置项
-    proxyConfigItems() {
-      return [
-        {
-          key: 'netProxyEnabled',
-          label: '开启网络代理',
-          type: 'switch',
-          placeholder: '',
-          group: 'proxy'
-        },
-        {
-          key: 'netProxyAddr',
-          label: '代理服务地址',
-          type: 'text',
-          placeholder: 'proxy.company.com:8080',
-          group: 'proxy'
-        },
-        {
-          key: 'map8086',
-          label: '8086映射参数',
-          type: 'text',
-          placeholder: 'localhost:8086',
-          group: 'proxy'
-        },
-        {
-          key: 'map8088',
-          label: '8088映射参数',
-          type: 'text',
-          placeholder: 'localhost:8088',
-          group: 'proxy'
-        }
-      ]
+    // 当前分组名称
+    currentGroupName() {
+      const group = this.groups.find(g => g.key === this.activeGroup)
+      return group ? group.name : ''
     },
 
-    // 所有配置项
-    allConfigItems() {
-      return [
-        ...this.ipConfigItems,
-        ...this.hardwareConfigItems,
-        ...this.proxyConfigItems
-      ]
+    // 当前可见的参数
+    visibleParams() {
+      if (!this.activeGroup || !this.paramsByGroup[this.activeGroup]) {
+        return []
+      }
+      return this.paramsByGroup[this.activeGroup].filter(item => this.isItemVisible(item))
     }
   },
 
@@ -284,12 +138,26 @@ export default {
     // 监听窗口大小变化
     window.addEventListener('resize', this.handleResize)
     
-    // 加载配置数据
-    await this.loadConfig()
+    // 加载分组数据
+    await this.loadGroups()
+    
+    // 如果有活跃分组,加载参数
+    if (this.activeGroup) {
+      await this.loadParams(this.activeGroup)
+    }
+    
+    // 初始化滚动遮罩
+    this.$nextTick(() => {
+      this.setupScrollMask()
+    })
   },
 
   beforeDestroy() {
     window.removeEventListener('resize', this.handleResize)
+    // 清理滚动遮罩监听器
+    if (this._scrollCleanup) {
+      this._scrollCleanup()
+    }
   },
 
   methods: {
@@ -298,76 +166,149 @@ export default {
       this.windowWidth = window.innerWidth
     },
 
-    // 加载配置
-    async loadConfig() {
+    // 加载分组列表
+    async loadGroups() {
+      this.loading = true
+      try {
+        const response = await api.getGroups()
+        if (response.code === 200) {
+          this.groups = response.data
+          // 设置默认活跃分组
+          if (this.groups.length > 0) {
+            this.activeGroup = this.$route.query.group || this.groups[0].key
+          }
+        }
+      } catch (error) {
+        this.$message.error('加载分组失败: ' + error.message)
+      } finally {
+        this.loading = false
+      }
+    },
+
+    // 加载指定分组的参数
+    async loadParams(group) {
+      // 如果已有缓存则跳过
+      if (this.paramsByGroup[group]) {
+        return
+      }
+
       this.loading = true
       try {
-        const response = await api.getConfig()
+        const response = await api.getGroupParams({ group })
         if (response.code === 200) {
-          this.config = { ...this.config, ...response.data }
+          // 深拷贝数据
+          this.paramsByGroup[group] = JSON.parse(JSON.stringify(response.data))
+          this.originalByGroup[group] = JSON.parse(JSON.stringify(response.data))
+          
+          // 触发响应式更新
+          this.$set(this.paramsByGroup, group, this.paramsByGroup[group])
+          this.$set(this.originalByGroup, group, this.originalByGroup[group])
         }
       } catch (error) {
-        this.$message.error('加载配置失败: ' + error.message)
+        this.$message.error('加载参数失败: ' + error.message)
       } finally {
         this.loading = false
       }
     },
 
-    // 保存单个参数
+    // 处理分组切换
+    async handleGroupChange(key) {
+      this.activeGroup = key
+      
+      // 优先使用本地缓存,未命中再请求
+      if (this.paramsByGroup[key]) {
+        // 缓存命中,直接使用,保持体验稳定
+        return
+      }
+      
+      // 缓存未命中,异步加载
+      await this.loadParams(key)
+    },
+
+    // 保存单个参数(适配 XtParamItem)
     async handleParamSave(key, value) {
       try {
-        const response = await api.updateParam({ key, value })
+        const response = await api.updateParam({ 
+          group: this.activeGroup, 
+          key: key, 
+          value: value 
+        })
         if (response.code === 200) {
-          this.config[key] = value
-          this.$message.success('参数保存成功')
+          // 同步当前数据
+          const currentItem = this.paramsByGroup[this.activeGroup].find(p => p.key === key)
+          if (currentItem) {
+            currentItem.value = value
+          }
+          
+          // 同步 originalByGroup 中的值
+          const originalItem = this.originalByGroup[this.activeGroup].find(p => p.key === key)
+          if (originalItem) {
+            originalItem.value = value
+          }
+          
+          // 不显示消息,由XtParamItem内部处理
         } else {
           throw new Error(response.message || '保存失败')
         }
       } catch (error) {
-        this.$message.error('保存失败: ' + error.message)
-        throw error // 重新抛出错误,让组件显示错误状态
+        // 重新抛出错误,让XtParamItem处理错误显示
+        throw error
       }
     },
 
-    // 取消参数编辑
+    // 取消参数编辑(适配 XtParamItem)
     handleParamCancel() {
-      // 可以在这里添加取消编辑的逻辑
+      // XtParamItem内部处理取消逻辑,这里可以添加全局取消逻辑
       console.log('参数编辑已取消')
     },
 
+
     // 检查项目是否可见(搜索过滤)
     isItemVisible(item) {
       if (!this.searchKeyword.trim()) {
         return true
       }
       const keyword = this.searchKeyword.toLowerCase().trim()
-      return item.label.toLowerCase().includes(keyword)
+      const label = (item.label || '').toLowerCase()
+      const key = (item.key || '').toLowerCase()
+      return label.includes(keyword) || key.includes(keyword)
     },
 
-    // 检查分组是否有可见项目
-    hasVisibleItems(groupType) {
-      let items = []
-      switch (groupType) {
-        case 'ip':
-          items = this.ipConfigItems
-          break
-        case 'hardware':
-          items = this.hardwareConfigItems
-          break
-        case 'proxy':
-          items = this.proxyConfigItems
-          break
-        default:
-          return false
+    // 设置滚动遮罩
+    setupScrollMask() {
+      const navWrap = this.$el.querySelector('.el-tabs__nav-wrap')
+      const tabsWrap = this.$el.querySelector('.group-tabs-wrap')
+      
+      if (!navWrap || !tabsWrap) return
+      
+      const checkScroll = () => {
+        const { scrollWidth, clientWidth, scrollLeft } = navWrap
+        const hasScroll = scrollWidth > clientWidth
+        
+        if (hasScroll) {
+          tabsWrap.classList.add('has-scroll')
+        } else {
+          tabsWrap.classList.remove('has-scroll')
+        }
       }
       
-      return items.some(item => this.isItemVisible(item))
-    },
-
-    // 刷新配置
-    async handleRefresh() {
-      await this.loadConfig()
-      this.$message.success('配置已刷新')
+      // 初始检查
+      checkScroll()
+      
+      // 监听滚动和窗口大小变化
+      navWrap.addEventListener('scroll', checkScroll)
+      window.addEventListener('resize', checkScroll)
+      
+      // 监听tabs变化
+      const observer = new MutationObserver(checkScroll)
+      observer.observe(navWrap, { childList: true, subtree: true })
+      
+      // 保存清理函数
+      this._scrollCleanup = () => {
+        navWrap.removeEventListener('scroll', checkScroll)
+        window.removeEventListener('resize', checkScroll)
+        observer.disconnect()
+      }
     }
   }
 }
@@ -379,12 +320,13 @@ export default {
   background: var(--color-bg-secondary);
   padding-bottom: var(--spacing-6);
 
+  // 去掉页面头部的底边框(真正的灰线来源)
   .page-header {
     background: var(--color-bg-card);
-    border-bottom: 1px solid var(--color-border-primary);
+    border-bottom: none; // 关掉底边框,用阴影替代分隔感
     padding: var(--spacing-6);
     margin-bottom: var(--spacing-6);
-    box-shadow: 0 6px 18px rgba(2, 6, 23, 0.05);
+    box-shadow: 0 6px 18px rgba(2, 6, 23, 0.05); // 保持层次感
 
     .header-content {
       max-width: 1200px;
@@ -439,22 +381,241 @@ export default {
     margin: 0 auto;
     padding: 0 var(--spacing-6);
 
-    .config-cards {
-      display: grid;
-      gap: var(--spacing-6);
-      grid-template-columns: 1fr;
+    // 粘顶容器 - 确保不补线(去掉顶部灰线)
+    .group-tabs-wrap {
+      position: sticky;
+      top: 0;
+      z-index: 100;
+      background: var(--color-bg-card);
+      padding: 16px 24px;
+      margin-bottom: 14px;
+      // 只保留底部分隔线,禁止顶部边框和阴影
+      border-top: none;
+      border-bottom: 1px solid var(--color-border-primary);
+      box-shadow: none; // 确保无顶部阴影
+      backdrop-filter: blur(12px);
+      transition: all 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
+      position: relative;
+      
+      // 左右渐隐遮罩
+      &::before,
+      &::after {
+        content: '';
+        position: absolute;
+        top: 16px;
+        bottom: 16px;
+        width: 12px;
+        pointer-events: none;
+        z-index: 1;
+        transition: opacity 0.3s ease;
+      }
+      
+      // 左侧遮罩
+      &::before {
+        left: 24px;
+        background: linear-gradient(to right, var(--color-bg-card), transparent);
+        opacity: 0;
+      }
+      
+      // 右侧遮罩  
+      &::after {
+        right: 24px;
+        background: linear-gradient(to left, var(--color-bg-card), transparent);
+        opacity: 0;
+      }
+      
+      // 当可滚动时显示遮罩
+      &.has-scroll {
+        &::before,
+        &::after {
+          opacity: 1;
+        }
+      }
+    }
+
+    // 胶囊型Tab样式 - 完全重做(去掉顶部灰线)
+    .group-tabs {
+      position: relative;
+      
+      :deep(.el-tabs__header) {
+        margin: 0;
+        border: 0; // 去掉header边框
+        border-bottom: none;
+        box-shadow: none; // 去掉header阴影
+        position: relative;
+      }
+
+      // 兜底关闭卡片型tabs的所有边线
+      :deep(.el-tabs--card > .el-tabs__header) {
+        border-bottom: 0;
+      }
+      
+      :deep(.el-tabs--card > .el-tabs__header .el-tabs__nav) {
+        border: 0;
+      }
+
+      :deep(.el-tabs__nav-wrap) {
+        // 去掉Element默认分隔线
+        &::after {
+          display: none;
+          height: 0;
+          border: 0;
+        }
+        
+        // 去掉可滚动tabs的分隔线
+        &.is-scrollable::after {
+          display: none;
+          height: 0;
+          border: 0;
+        }
+        
+        // 横向滚动 - 隐藏滚动条但保持功能
+        overflow-x: auto;
+        overflow-y: hidden;
+        scroll-behavior: smooth;
+        scrollbar-width: none; // Firefox
+        -ms-overflow-style: none; // IE
+        
+        &::-webkit-scrollbar {
+          display: none; // Chrome/Safari
+        }
+      }
+
+      :deep(.el-tabs__nav) {
+        border: none;
+        display: flex;
+        gap: 8px;
+        padding: 6px;
+        background: var(--color-bg-secondary);
+        border-radius: 20px;
+        // border: 1px solid var(--color-border-secondary);
+        box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
+        min-width: max-content;
+        position: relative;
+        
+        // 内描边增强立体感
+        &::before {
+          content: '';
+          position: absolute;
+          inset: 1px;
+          border-radius: 19px;
+          // border: 1px solid var(--color-border-tertiary);
+          pointer-events: none;
+        }
+      }
+
+      :deep(.el-tabs__item) {
+        // 重置所有默认样式
+        border: none !important;
+        margin: 0 !important;
+        padding: 0;
+        height: 34px;
+        line-height: 1;
+        position: relative;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        min-width: fit-content;
+        
+        // 胶囊型外观
+        background: transparent;
+        color: var(--color-text-secondary);
+        border-radius: 16px;
+        font-weight: 500;
+        font-size: 14px;
+        white-space: nowrap;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        max-width: 140px;
+        padding: 0 14px;
+        cursor: pointer;
+        user-select: none;
+        
+        // 统一过渡动画
+        transition: all 0.15s cubic-bezier(0.4, 0.0, 0.2, 1);
+        
+        // 添加title属性显示完整文本
+        &[title] {
+          cursor: help;
+        }
+        
+        // 悬浮状态
+        &:hover:not(.is-active) {
+          color: var(--color-text-primary);
+          background: var(--color-bg-hover, rgba(0, 0, 0, 0.06));
+          transform: translateY(-0.5px);
+          box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
+        }
+        
+        // 选中状态 - 更精致的设计
+        &.is-active {
+          color: var(--color-primary);
+          background: linear-gradient(135deg, 
+            var(--color-primary-light-9, rgba(64, 158, 255, 0.12)), 
+            var(--color-primary-light-8, rgba(64, 158, 255, 0.08))
+          );
+          font-weight: 600;
+          transform: none;
+          box-shadow: 
+            0 1px 3px rgba(64, 158, 255, 0.3),
+            inset 0 1px 0 rgba(255, 255, 255, 0.2);
+          
+          // 底部激活指示线
+          &::after {
+            content: '';
+            position: absolute;
+            bottom: 3px;
+            left: 50%;
+            width: 18px;
+            height: 2px;
+            background: var(--color-primary);
+            border-radius: 1px;
+            transform: translateX(-50%) scaleX(1);
+            transition: transform 0.2s cubic-bezier(0.4, 0.0, 0.2, 1);
+            animation: activeSlide 0.3s cubic-bezier(0.4, 0.0, 0.2, 1);
+          }
+        }
+        
+        // 禁用状态
+        &.is-disabled {
+          color: var(--color-text-quaternary);
+          cursor: not-allowed;
+          opacity: 0.5;
+          
+          &:hover {
+            background: transparent;
+            transform: none;
+            box-shadow: none;
+          }
+        }
+        
+        // 键盘焦点
+        &:focus-visible {
+          outline: 2px solid var(--color-primary);
+          outline-offset: 2px;
+        }
+      }
 
-      &.two-columns {
-        grid-template-columns: repeat(2, 1fr);
+      // 隐藏内容面板
+      :deep(.el-tabs__content) {
+        display: none;
       }
+    }
+
+    .config-cards {
+      display: grid;
+      gap: 0; /* 紧贴Tab栏 */
+      grid-template-columns: 1fr !important; /* 仅一张卡 */
 
       .config-card {
         background: var(--color-bg-card);
-        border-radius: 12px;
-        box-shadow: 0 6px 18px rgba(2, 6, 23, 0.05);
+        border-radius: 8px; /* 恢复轻微圆角,更现代 */
+        border: 1px solid var(--color-border-tertiary); /* 使用更浅的边框 */
+        box-shadow: 0 4px 12px rgba(2, 6, 23, 0.05);
         overflow: hidden;
         transition: all var(--duration-200) var(--ease-out);
         animation: slideInUp 0.3s ease-out;
+        margin-top: 16px; /* 与Tab栏保持间距 */
 
         &:hover {
           box-shadow: 0 8px 24px rgba(2, 6, 23, 0.08);
@@ -484,6 +645,8 @@ export default {
           padding: 16px 20px 20px 20px;
           min-height: 200px;
 
+          // XtParamItem组件自带样式,无需额外定义参数列表样式
+
           .empty-state {
             display: flex;
             flex-direction: column;
@@ -518,6 +681,7 @@ html.dark {
   .connection-config-simple {
     .page-header {
       background: var(--color-bg-tertiary);
+      border-bottom: none; // 暗色主题下也去掉底边框
       box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
 
       .search-section {
@@ -531,23 +695,79 @@ html.dark {
       }
     }
 
-    .config-cards {
-      .config-card {
+    .page-content {
+      // 暗色主题适配 - 精致设计(去掉顶部灰线)
+      .group-tabs-wrap {
         background: var(--color-bg-tertiary);
-        box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
+        border-bottom-color: var(--color-border-tertiary);
+        // 去掉顶部阴影,保持干净
+        
+        // 遮罩渐变适配暗色
+        &::before {
+          background: linear-gradient(to right, var(--color-bg-tertiary), transparent);
+        }
+        
+        &::after {
+          background: linear-gradient(to left, var(--color-bg-tertiary), transparent);
+        }
+      }
 
-        &:hover {
-          box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
+      .group-tabs {
+        :deep(.el-tabs__nav) {
+          background: var(--color-bg-quaternary);
+          border-color: var(--color-border-tertiary);
+          box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.15);
+          
+          &::before {
+            border-color: var(--color-border-quaternary);
+          }
         }
 
-        .card-header {
-          border-bottom-color: var(--color-border-tertiary);
+        :deep(.el-tabs__item) {
+          color: var(--color-text-tertiary);
+
+          &:hover:not(.is-active) {
+            color: var(--color-text-primary);
+            background: var(--color-bg-hover, rgba(255, 255, 255, 0.08));
+            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+          }
+
+          &.is-active {
+            color: var(--color-primary);
+            background: linear-gradient(135deg, 
+              var(--color-primary-light-9, rgba(64, 158, 255, 0.18)), 
+              var(--color-primary-light-8, rgba(64, 158, 255, 0.12))
+            );
+            box-shadow: 
+              0 1px 3px rgba(64, 158, 255, 0.4),
+              inset 0 1px 0 rgba(255, 255, 255, 0.1);
+          }
+          
+          &.is-disabled {
+            color: var(--color-text-quaternary);
+          }
         }
+      }
 
-        .card-content {
-          .empty-state {
-            background: rgba(255, 255, 255, 0.03);
-            border-color: var(--color-border-tertiary);
+      .config-cards {
+        .config-card {
+          background: var(--color-bg-tertiary);
+          border-color: var(--color-border-quaternary); /* 暗色主题下更浅的边框 */
+          box-shadow: 0 6px 18px rgba(0, 0, 0, 0.15);
+
+          &:hover {
+            box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
+          }
+
+          .card-header {
+            border-bottom-color: var(--color-border-tertiary);
+          }
+
+          .card-content {
+            .empty-state {
+              background: rgba(255, 255, 255, 0.03);
+              border-color: var(--color-border-tertiary);
+            }
           }
         }
       }
@@ -555,14 +775,12 @@ html.dark {
   }
 }
 
-// 响应式设计
-@media (max-width: 1439px) {
+// 响应式设计 - 更精致的适配
+@media (min-width: 1440px) {
   .connection-config-simple {
     .page-content {
-      .config-cards {
-        &.two-columns {
-          grid-template-columns: 1fr;
-        }
+      .group-tabs-wrap {
+        padding: 16px 32px; // 大屏更大内边距
       }
     }
   }
@@ -595,6 +813,40 @@ html.dark {
 
     .page-content {
       padding: 0 var(--spacing-4);
+      
+      // 移动端Tab适配 - 胶囊风格
+      .group-tabs-wrap {
+        padding: 14px 16px;
+        margin-bottom: 12px;
+        
+        &::before,
+        &::after {
+          top: 14px;
+          bottom: 14px;
+          width: 8px;
+        }
+        
+        &::before {
+          left: 16px;
+        }
+        
+        &::after {
+          right: 16px;
+        }
+      }
+      
+      .group-tabs {
+        :deep(.el-tabs__nav) {
+          padding: 4px;
+        }
+        
+        :deep(.el-tabs__item) {
+          height: 32px; // 降低2px
+          font-size: 13px; // 字号降一级
+          padding: 0 12px;
+          max-width: 120px;
+        }
+      }
 
       .config-cards {
         gap: var(--spacing-4);
@@ -611,6 +863,25 @@ html.dark {
             .empty-state {
               height: 100px;
             }
+            
+            // 移动端参数行适配
+            .param-item {
+              grid-template-columns: 1fr;
+              gap: 8px;
+              
+              .param-label {
+                font-weight: 500;
+              }
+              
+              .param-actions {
+                justify-self: start;
+                
+                .el-button {
+                  padding: 4px 12px;
+                  font-size: 12px;
+                }
+              }
+            }
           }
         }
       }
@@ -629,4 +900,28 @@ html.dark {
     transform: translateY(0);
   }
 }
-</style>
+
+@keyframes activeSlide {
+  0% {
+    transform: translateX(-50%) scaleX(0);
+    opacity: 0;
+  }
+  50% {
+    transform: translateX(-50%) scaleX(1.2);
+    opacity: 0.8;
+  }
+  100% {
+    transform: translateX(-50%) scaleX(1);
+    opacity: 1;
+  }
+}
+
+@keyframes rotating {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+</style>

+ 114 - 4
src/views/map/maplist/components/shared/MapToolbar.vue

@@ -12,7 +12,7 @@
           >
             <el-button 
               :size="buttonSize" 
-              :icon="tool.icon"
+              :icon="tool.customIcon ? '' : tool.icon"
               :type="tool.buttonType || 'default'"
               circle
               :disabled="tool.disabled || isToolDisabled(tool.key)"
@@ -20,11 +20,53 @@
                 'toolbar-btn',
                 { 
                   'primary-btn': tool.buttonType === 'primary',
-                  'active': selectedKey === tool.key 
+                  'active': selectedKey === tool.key,
+                  'has-custom-icon': tool.customIcon
                 }
               ]"
               @click="handleToolClick(tool)"
-            />
+            >
+              <!-- 自定义图标 -->
+              <template v-if="tool.customIcon">
+                <!-- 选择工具图标 -->
+                <svg v-if="tool.customIcon === 'select'" viewBox="0 0 24 24" class="custom-icon">
+                  <g transform="scale(0.024)">
+                    <path d="M439.04 141.6192c17.5104 2.5088 29.6448 18.7392 27.136 36.1984l-12.2368 85.504a32 32 0 0 1-63.3344-9.0112l12.2368-85.504a32 32 0 0 1 36.1984-27.136zM783.36 163.84h-194.56a30.72 30.72 0 0 0 0 61.44h194.56q10.24 0 10.24 10.24v204.8a30.72 30.72 0 1 0 61.44 0v-204.8q0-29.696-20.992-50.688-20.992-20.992-50.688-20.992zM326.7072 252.672a32 32 0 0 1-50.5856 39.2192L221.184 220.9792a32 32 0 0 1 50.5856-39.2192L326.656 252.672z m-72.3456 95.232a32 32 0 0 1-9.0624 63.3856L159.744 399.0528a32 32 0 0 1 9.0624-63.3344l85.504 12.2368z m607.6416 217.856L422.8096 372.5312c-19.0464-8.2432-36.608 2.56-29.3888 24.32l157.184 472.7808c5.7344 17.5616 16.896 18.5344 24.9856 2.1504q40.192-83.2 75.9296-138.3424a10.0352 10.0352 0 0 1 16.0768-0.9728l98.9184 114.5344a26.7264 26.7264 0 0 0 38.912 2.2528l26.6752-25.4464a30.208 30.208 0 0 0 1.536-40.96l-99.6864-115.456a10.0864 10.0864 0 0 1 2.5088-15.36q52.48-30.6176 125.5424-60.8256c16.384-6.7584 16.384-18.2272 0-25.4464zM235.52 870.4H409.6a30.72 30.72 0 0 0 0-61.44H235.52q-10.24 0-10.24-10.24v-276.48a30.72 30.72 0 0 0-61.44 0v276.48q0 29.696 20.992 50.688 20.992 20.992 50.688 20.992z" 
+                          fill="currentColor"/>
+                  </g>
+                </svg>
+                
+                <!-- 绘制点图标 -->
+                <svg v-else-if="tool.customIcon === 'point'" viewBox="0 0 24 24" class="custom-icon">
+                  <circle cx="12" cy="12" r="4" fill="currentColor"/>
+                  <circle cx="12" cy="12" r="8" fill="none" stroke="currentColor" stroke-width="1.5" stroke-dasharray="2,2" opacity="0.6"/>
+                </svg>
+                
+                <!-- 绘制线图标 -->
+                <svg v-else-if="tool.customIcon === 'line'" viewBox="0 0 24 24" class="custom-icon">
+                  <path d="M4 20L20 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
+                  <circle cx="4" cy="20" r="2" fill="currentColor"/>
+                  <circle cx="20" cy="4" r="2" fill="currentColor"/>
+                </svg>
+                
+                <!-- 绘制曲线图标 -->
+                <svg v-else-if="tool.customIcon === 'curve'" viewBox="0 0 24 24" class="custom-icon">
+                  <path d="M3 20C3 20 7 4 12 12C17 20 21 4 21 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/>
+                  <circle cx="3" cy="20" r="2" fill="currentColor"/>
+                  <circle cx="21" cy="4" r="2" fill="currentColor"/>
+                </svg>
+                
+                <!-- 绘制区域图标 -->
+                <svg v-else-if="tool.customIcon === 'polygon'" viewBox="0 0 24 24" class="custom-icon">
+                  <path d="M12 2L22 8.5L18 20H6L2 8.5L12 2Z" stroke="currentColor" stroke-width="1.5" fill="none"/>
+                  <circle cx="12" cy="2" r="1.5" fill="currentColor"/>
+                  <circle cx="22" cy="8.5" r="1.5" fill="currentColor"/>
+                  <circle cx="18" cy="20" r="1.5" fill="currentColor"/>
+                  <circle cx="6" cy="20" r="1.5" fill="currentColor"/>
+                  <circle cx="2" cy="8.5" r="1.5" fill="currentColor"/>
+                </svg>
+              </template>
+            </el-button>
           </el-tooltip>
         </div>
         <!-- 分隔线类型 -->
@@ -106,7 +148,7 @@ export default {
     preset: {
       type: String,
       default: 'custom',
-      validator: value => ['custom', 'calibration', 'nav'].includes(value)
+      validator: value => ['custom', 'calibration', 'nav', 'edit'].includes(value)
     },
     // 自定义工具配置
     tools: {
@@ -224,6 +266,20 @@ export default {
           { key: 'init-pose', type: 'btn', icon: 'el-icon-position', tooltip: '初始化' },
           { key: 'reboot', type: 'btn', icon: 'el-icon-refresh', tooltip: '重启' },
           { key: 'emergency-stop', type: 'btn', icon: 'el-icon-switch-button', tooltip: '结束/急停' }
+        ],
+        edit: [
+          { key: 'zoom-in', type: 'btn', icon: 'el-icon-plus', tooltip: '放大' },
+          { key: 'zoom-out', type: 'btn', icon: 'el-icon-minus', tooltip: '缩小' },
+          { key: 'center-robot', type: 'btn', icon: 'el-icon-location', tooltip: '居中到机器人' },
+          { key: 'toggle-fullscreen', type: 'btn', icon: this.isFullscreen ? 'el-icon-aim' : 'el-icon-full-screen', tooltip: this.isFullscreen ? '退出全屏' : '进入全屏' },
+          { key: 'divider-1', type: 'divider' },
+          { key: 'select-mode', type: 'btn', customIcon: 'select', tooltip: '元素选择' },
+          { key: 'draw-point', type: 'btn', customIcon: 'point', tooltip: '绘制点' },
+          { key: 'draw-line', type: 'btn', customIcon: 'line', tooltip: '绘制线' },
+          { key: 'draw-curve', type: 'btn', customIcon: 'curve', tooltip: '绘制曲线' },
+          { key: 'draw-polygon', type: 'btn', customIcon: 'polygon', tooltip: '绘制区域' },
+          { key: 'divider-2', type: 'divider' },
+          { key: 'save-map', type: 'btn', icon: 'el-icon-check', tooltip: '保存', buttonType: 'primary' }
         ]
       }
       return presets[this.preset] || []
@@ -284,6 +340,18 @@ export default {
         this.$emit('toggle-fullscreen')
       } else if (tool.key === 'add-calibration') {
         this.$emit('add-calibration-point')
+      } else if (tool.key === 'select-mode') {
+        this.$emit('mode-change', 'select')
+      } else if (tool.key === 'draw-point') {
+        this.$emit('mode-change', 'draw-point')
+      } else if (tool.key === 'draw-line') {
+        this.$emit('mode-change', 'draw-line')
+      } else if (tool.key === 'draw-curve') {
+        this.$emit('mode-change', 'draw-curve')
+      } else if (tool.key === 'draw-polygon') {
+        this.$emit('mode-change', 'draw-polygon')
+      } else if (tool.key === 'save-map') {
+        this.$emit('save')
       }
     },
     
@@ -442,6 +510,48 @@ export default {
     background: rgba(0, 0, 0, 0.08);
     flex-shrink: 0;
   }
+
+  // 自定义图标样式
+  .custom-icon {
+    width: 16px;
+    height: 16px;
+    display: block;
+    color: currentColor;
+    flex-shrink: 0; // 防止图标被压缩
+    
+    // 确保图标跟随按钮状态变化
+    .toolbar-btn:hover &,
+    .toolbar-btn.active & {
+      color: currentColor;
+    }
+  }
+
+  // 确保包含自定义图标的按钮正确对齐
+  .toolbar-btn.has-custom-icon {
+    // 深度选择器确保覆盖 Element UI 默认样式
+    ::v-deep {
+      display: flex !important;
+      align-items: center !important;
+      justify-content: center !important;
+      
+      // 按钮内容居中
+      .el-button {
+        display: flex !important;
+        align-items: center !important;
+        justify-content: center !important;
+        padding: 0 !important; // 移除默认内边距
+      }
+      
+      // 确保按钮内容区域居中
+      .el-button > span {
+        display: flex !important;
+        align-items: center !important;
+        justify-content: center !important;
+        width: 100% !important;
+        height: 100% !important;
+      }
+    }
+  }
 }
 
 // 暗色主题适配

+ 761 - 61
src/views/map/maplist/components/shared/RightPanel.vue

@@ -1,15 +1,17 @@
 <template>
-  <div :class="['right-panel-container', mode === 'nav' ? 'rp--nav' : 'rp--calib']">
+  <div :class="['right-panel-container', mode === 'nav' ? 'rp--nav' : (mode === 'edit' ? 'rp--edit' : 'rp--calib')]">
     <!-- 标定页面板(保持原有结构不变) -->
     <template v-if="panelType !== 'nav'">
     <!-- 主面板 -->
     <div 
       class="right-panel" 
-      :class="{ 'panel-collapsed': !value }"
-      :style="{ width: value ? width + 'px' : '0px' }"
+      :class="{ 
+        'is-hidden': !value && (mode === 'edit' || mode === 'nav'), 
+        'panel-collapsed': !value && mode === 'calib' 
+      }"
     >
       <!-- 面板头部 -->
-      <div class="panel-header" v-if="!hideHeader">
+      <div class="panel-header" v-if="!hideHeader && panelType !== 'edit'">
         <div class="panel-title">
           <slot name="header">
             <h3>{{ title }}</h3>
@@ -38,57 +40,216 @@
             >
               <!-- 实时信息Tab -->
               <template v-if="tab.key === 'info'">
-                <div class="realtime-info-content">
-                  <div class="info-section">
-                    <div class="info-title">
-                      <div class="title-bar"></div>
-                      <span>当前地图</span>
+                <div class="nav-tab-content">
+                  <div class="nav-info-grid">
+                    <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">{{ realtimeInfo.currentMap }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">坐标:</span>
+                        <span class="value">{{ realtimeInfo.coordinates }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">航向:</span>
+                        <span class="value">{{ realtimeInfo.heading }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">速度:</span>
+                        <span class="value">{{ realtimeInfo.speed }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">指令速度:</span>
+                        <span class="value">{{ realtimeInfo.speedCommand }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">总里程:</span>
+                        <span class="value">{{ realtimeInfo.totalDistance }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">配准误差:</span>
+                        <span class="value">{{ realtimeInfo.registrationError }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">电池电量:</span>
+                        <span class="value">{{ realtimeInfo.batteryLevel }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">当前任务:</span>
+                        <span class="value">{{ realtimeInfo.currentTask }}</span>
+                      </div>
                     </div>
-                    <span class="info-content">{{ realtimeInfo.currentMap }}</span>
                   </div>
                   
-                  <div class="info-section">
-                    <div class="info-title">
-                      <div class="title-bar"></div>
-                      <span>当前任务</span>
+                  <!-- 编辑模式下添加"实时位姿"部分 -->
+                  <div v-if="mode === 'edit'" class="nav-info-grid" style="margin-top: 16px;">
+                    <div class="nav-info-header">
+                      <h3 class="nav-info-title">实时位姿</h3>
                     </div>
-                    <span class="info-content">{{ realtimeInfo.currentTask }}</span>
+                    <div class="nav-info-content">
+                      <div class="nav-info-item">
+                        <span class="label">X坐标:</span>
+                        <span class="value">{{ realtimeInfo.coordinates.split(',')[0] ? realtimeInfo.coordinates.split(',')[0].replace('(', '').trim() : '0.35' }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">Y坐标:</span>
+                        <span class="value">{{ realtimeInfo.coordinates.split(',')[1] ? realtimeInfo.coordinates.split(',')[1].trim() : '0.22' }}</span>
+                      </div>
+                      <div class="nav-info-item">
+                        <span class="label">Z坐标:</span>
+                        <span class="value">{{ realtimeInfo.coordinates.split(',')[2] ? realtimeInfo.coordinates.split(',')[2].replace(')', '').trim() : '0.22' }}</span>
+                      </div>
+                    </div>
+                    <div style="padding: 16px; border-top: 1px solid #f0f0f0;">
+                      <el-button 
+                        size="small" 
+                        icon="el-icon-circle-plus-outline"
+                        @click="$emit('add-current-point')"
+                        style="width: 100%;"
+                        block
+                      >
+                        添加当前点
+                      </el-button>
+                    </div>
+                  </div>
+                </div>
+              </template>
+              
+              <!-- 元素管理Tab -->
+              <template v-if="tab.key === 'elements'">
+                <div class="edit-elements-content">
+                  <!-- 元素类型筛选 -->
+                  <div class="element-filter">
+                    <el-tabs v-model="activeElementType" size="small" @tab-click="handleElementTypeChange">
+                      <el-tab-pane 
+                        v-for="type in elementTypes" 
+                        :key="type.key"
+                        :label="`${type.label}(${type.count})`"
+                        :name="type.key"
+                      />
+                    </el-tabs>
                   </div>
                   
-                  <div class="info-section">
-                    <div class="info-title">
-                      <div class="title-bar"></div>
-                      <span>实时信息</span>
+                  <!-- 搜索框 -->
+                  <div class="element-search">
+                    <el-input
+                      v-model="elementSearchKeyword"
+                      placeholder="搜索元素..."
+                      prefix-icon="el-icon-search"
+                      size="small"
+                      clearable
+                      @input="handleElementSearch"
+                    />
+                  </div>
+                  
+                  <!-- 元素列表 -->
+                  <div class="element-list">
+                    <div 
+                      v-for="element in filteredElements" 
+                      :key="element.id"
+                      class="element-item"
+                      :class="{ 'selected': selectedElement.id === element.id }"
+                      @click="handleElementSelect(element)"
+                    >
+                      <div class="element-info">
+                        <div class="element-id">{{ element.id }}</div>
+                        <div class="element-name">{{ element.name || '未命名' }}</div>
+                      </div>
+                      <div class="element-actions">
+                        <el-button size="mini" type="text" @click.stop="$emit('element-edit', element)">编辑</el-button>
+                        <el-button size="mini" type="text" @click.stop="$emit('element-locate', element)">定位</el-button>
+                        <el-popconfirm
+                          title="确定删除这个元素吗?"
+                          @confirm="$emit('element-remove', element)"
+                        >
+                          <el-button size="mini" type="text" slot="reference" @click.stop>删除</el-button>
+                        </el-popconfirm>
+                      </div>
+                    </div>
+                    
+                    <!-- 空状态 -->
+                    <div v-if="filteredElements.length === 0" class="empty-state">
+                      <i class="el-icon-box"></i>
+                      <p>暂无{{ getCurrentTypeLabel() }}元素</p>
+                    </div>
+                  </div>
+                </div>
+              </template>
+              
+              <!-- 路网操作Tab -->
+              <template v-if="tab.key === 'network'">
+                <div class="edit-network-content">
+                  <div class="network-operations">
+                    <div class="operation-section">
+                      <h4 class="section-title">路网操作</h4>
+                      <div class="operation-buttons">
+                        <el-button 
+                          type="primary" 
+                          size="small" 
+                          icon="el-icon-download"
+                          @click="handleNetworkOperation('export', $event)"
+                          block
+                        >
+                          导出路网
+                        </el-button>
+                        <el-button 
+                          size="small" 
+                          icon="el-icon-upload"
+                          @click="handleNetworkOperation('import', $event)"
+                          block
+                        >
+                          导入路网
+                        </el-button>
+                        <el-button 
+                          size="small" 
+                          icon="el-icon-plus"
+                          @click="handleNetworkOperation('merge', $event)"
+                          block
+                        >
+                          合并导入路网
+                        </el-button>
+                        <el-button 
+                          size="small" 
+                          icon="el-icon-refresh"
+                          @click="handleNetworkOperation('incremental', $event)"
+                          block
+                        >
+                          增量导入路网
+                        </el-button>
+                        <el-button 
+                          size="small" 
+                          icon="el-icon-refresh-left"
+                          @click="handleNetworkOperation('overwrite', $event)"
+                          block
+                        >
+                          覆盖导入路网
+                        </el-button>
+                      </div>
                     </div>
-                    <div class="info-details">
-                      <span class="info-item">
-                        <span class="info-label">速度:</span>
-                        <span class="info-value">{{ realtimeInfo.speed }}</span>
-                      </span>
-                      <span class="info-item">
-                        <span class="info-label">速度指令:</span>
-                        <span class="info-value">{{ realtimeInfo.speedCommand }}</span>
-                      </span>
-                      <span class="info-item">
-                        <span class="info-label">坐标:</span>
-                        <span class="info-value">{{ realtimeInfo.coordinates }}</span>
-                      </span>
-                      <span class="info-item">
-                        <span class="info-label">航向:</span>
-                        <span class="info-value">{{ realtimeInfo.heading }}</span>
-                      </span>
-                      <span class="info-item">
-                        <span class="info-label">累计里程:</span>
-                        <span class="info-value">{{ realtimeInfo.totalDistance }}</span>
-                      </span>
-                      <span class="info-item">
-                        <span class="info-label">配准误差:</span>
-                        <span class="info-value">{{ realtimeInfo.registrationError }}</span>
-                      </span>
-                      <span class="info-item">
-                        <span class="info-label">电量:</span>
-                        <span class="info-value">{{ realtimeInfo.batteryLevel }}</span>
-                      </span>
+                    
+                    <div class="operation-section">
+                      <h4 class="section-title">操作说明</h4>
+                      <div class="operation-tips">
+                        <div class="tip-item">
+                          <strong>导出路网:</strong>将当前地图的路网数据导出为文件
+                        </div>
+                        <div class="tip-item">
+                          <strong>导入路网:</strong>导入路网文件到当前地图
+                        </div>
+                        <div class="tip-item">
+                          <strong>合并导入路网:</strong>保留现有路网,添加新的路网数据
+                        </div>
+                        <div class="tip-item">
+                          <strong>增量导入路网:</strong>仅导入不存在的新元素
+                        </div>
+                        <div class="tip-item">
+                          <strong>覆盖导入路网:</strong>清空现有路网,导入新的路网数据
+                        </div>
+                      </div>
                     </div>
                   </div>
                 </div>
@@ -97,13 +258,22 @@
               <!-- 其他Tab使用插槽 -->
               <template v-else>
                 <slot :name="`tab-${tab.key}`">
-                  <div class="tab-placeholder">
-                    {{ tab.label }} 内容
-                  </div>
+                  <!-- 编辑页不需要占位符内容 -->
                 </slot>
               </template>
             </el-tab-pane>
           </el-tabs>
+          
+          <!-- 编辑页面的收起按钮 -->
+          <el-button 
+            v-if="mode === 'edit'"
+            class="edit-panel-close-btn"
+            type="text" 
+            size="mini" 
+            icon="el-icon-arrow-right"
+            @click="$emit('input', false)"
+            title="收起面板"
+          />
         </template>
 
         <!-- 默认内容模式 -->
@@ -246,11 +416,14 @@
     <!-- 展开拉手 (收起时显示) -->
     <div 
       v-if="overlay && !value" 
-      class="expand-handle"
-      @click="expandPanel"
-      :title="expandTooltip"
+      class="expand-handle edit-expand-handle"
+      @click="$emit('input', true)"
+      title="展开面板"
+      @mouseenter="$event.target.style.background = 'var(--color-primary, #409eff)'; $event.target.style.borderColor = 'var(--color-primary, #409eff)'; $event.target.style.transform = 'translateY(-50%) translateX(-2px)'; $event.target.querySelector('i').style.color = '#ffffff';"
+      @mouseleave="$event.target.style.background = 'var(--color-bg-card, #ffffff)'; $event.target.style.borderColor = 'var(--color-border-primary, #dcdfe6)'; $event.target.style.transform = 'translateY(-50%)'; $event.target.querySelector('i').style.color = 'var(--color-text-primary, #303133)';"
+      style="position: fixed !important; right: 0 !important; top: 50% !important; transform: translateY(-50%) !important; z-index: 9999 !important; width: 36px !important; height: 72px !important; background: var(--color-bg-card, #ffffff) !important; border: 1px solid var(--color-border-primary, #dcdfe6) !important; border-radius: 8px 0 0 8px !important; display: flex !important; align-items: center !important; justify-content: center !important; cursor: pointer !important; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important; transition: all 0.3s ease !important;"
     >
-      <i class="el-icon-arrow-left"></i>
+      <i class="el-icon-arrow-left" style="font-size: 16px !important; color: var(--color-text-primary, #303133) !important; font-weight: bold !important; transition: color 0.3s ease !important;"></i>
     </div>
     </template>
     
@@ -540,11 +713,11 @@
 export default {
   name: 'RightPanel',
   props: {
-    // 面板模式 - 区分导航/标定
+    // 面板模式 - 区分导航/标定/编辑
     mode: {
       type: String,
-      default: 'calib', // 'calib' 或 'nav'
-      validator: value => ['calib', 'nav'].includes(value)
+      default: 'calib', // 'calib' 或 'nav' 或 'edit'
+      validator: value => ['calib', 'nav', 'edit'].includes(value)
     },
     // 面板类型 - 新增
     panelType: {
@@ -671,6 +844,25 @@ export default {
         follow: false,
         network: false
       })
+    },
+    
+    // === 编辑页专用props ===
+    elementList: {
+      type: Array,
+      default: () => []
+    },
+    selectedElement: {
+      type: Object,
+      default: () => ({})
+    },
+    elementTypes: {
+      type: Array,
+      default: () => [
+        { key: 'point', label: '点', count: 0 },
+        { key: 'line', label: '线', count: 0 },
+        { key: 'curve', label: '弧', count: 0 },
+        { key: 'polygon', label: '面', count: 0 }
+      ]
     }
   },
   data() {
@@ -682,7 +874,10 @@ export default {
       startWidth: 0,
       // 目标点相关状态
       selectedWaypoints: [], // 选中的目标点
-      mapSelectMode: false // 地图选点模式
+      mapSelectMode: false, // 地图选点模式
+      // 编辑页相关状态
+      activeElementType: 'point', // 当前选中的元素类型
+      elementSearchKeyword: '' // 元素搜索关键词
     }
   },
   computed: {
@@ -706,7 +901,9 @@ export default {
         'settings': { key: 'settings', label: '功能设置' },
         'task': { key: 'task', label: '任务配置' },
         'points': { key: 'points', label: '目标点' },
-        'system': { key: 'system', label: '系统状态' }
+        'system': { key: 'system', label: '系统状态' },
+        'elements': { key: 'elements', label: '元素管理' },
+        'network': { key: 'network', label: '路网操作' }
       }
       
       return this.tabs.map(tab => {
@@ -715,6 +912,37 @@ export default {
         }
         return tab
       })
+    },
+    
+    // 编辑模式相关计算属性
+    filteredElements() {
+      if (!this.elementList || this.elementList.length === 0) {
+        return []
+      }
+      
+      let filtered = this.elementList
+      
+      // 按类型筛选
+      if (this.activeElementType !== 'all') {
+        filtered = filtered.filter(element => {
+          if (this.activeElementType === 'point') return element.id.startsWith('p')
+          if (this.activeElementType === 'line') return element.id.startsWith('l')
+          if (this.activeElementType === 'curve') return element.id.startsWith('b')
+          if (this.activeElementType === 'polygon') return element.id.startsWith('s')
+          return true
+        })
+      }
+      
+      // 按关键词搜索
+      if (this.elementSearchKeyword) {
+        const keyword = this.elementSearchKeyword.toLowerCase()
+        filtered = filtered.filter(element => 
+          element.id.toLowerCase().includes(keyword) ||
+          (element.name && element.name.toLowerCase().includes(keyword))
+        )
+      }
+      
+      return filtered
     }
   },
   created() {
@@ -889,6 +1117,125 @@ export default {
         1: '路网路径'
       }
       return typeMap[type] || '未知'
+    },
+    
+    // === 编辑页专用方法 ===
+    
+    // 处理元素类型切换
+    handleElementTypeChange(tab) {
+      this.activeElementType = tab.name
+      this.currentPage = 1
+      this.$emit('element-type-change', tab.name)
+    },
+    
+    // 处理元素搜索(防抖)
+    handleElementSearch() {
+      if (this.searchTimer) {
+        clearTimeout(this.searchTimer)
+      }
+      this.searchTimer = setTimeout(() => {
+        this.currentPage = 1
+        this.$emit('element-search', this.elementSearchKeyword)
+      }, 300)
+    },
+    
+    // 处理元素选择
+    handleElementSelect(element) {
+      this.$emit('element-select', element)
+    },
+    
+    
+    // 处理路网操作
+    handleNetworkOperation(operation, event) {
+      const operationMap = {
+        'export': {
+          title: '导出路网',
+          message: '确定要导出当前地图的路网数据吗?',
+          confirmText: '确定导出',
+          successMsg: '路网导出成功!',
+          errorMsg: '路网导出失败!'
+        },
+        'import': {
+          title: '导入路网',
+          message: '确定要导入路网文件吗?这将会添加新的路网数据到当前地图。',
+          confirmText: '确定导入',
+          successMsg: '路网导入成功!',
+          errorMsg: '路网导入失败!'
+        },
+        'merge': {
+          title: '合并导入路网',
+          message: '确定要合并导入路网吗?这将保留现有路网,并添加新的路网数据。',
+          confirmText: '确定合并',
+          successMsg: '路网合并导入成功!',
+          errorMsg: '路网合并导入失败!'
+        },
+        'incremental': {
+          title: '增量导入路网',
+          message: '确定要增量导入路网吗?这将只导入不存在的新元素。',
+          confirmText: '确定导入',
+          successMsg: '路网增量导入成功!',
+          errorMsg: '路网增量导入失败!'
+        },
+        'overwrite': {
+          title: '覆盖导入路网',
+          message: '确定要覆盖导入路网吗?这将清空现有路网,导入新的路网数据。此操作不可恢复!',
+          confirmText: '确定覆盖',
+          successMsg: '路网覆盖导入成功!',
+          errorMsg: '路网覆盖导入失败!'
+        }
+      }
+      
+      const config = operationMap[operation]
+      if (!config) return
+      
+      // 获取触发事件的按钮元素
+      const button = event ? event.currentTarget : null
+      
+      this.$confirm(config.message, config.title, {
+        confirmButtonText: config.confirmText,
+        cancelButtonText: '取消',
+        type: operation === 'overwrite' ? 'warning' : 'info',
+        dangerouslyUseHTMLString: false
+      }).then(() => {
+        // 发射事件给父组件处理具体逻辑
+        if (operation === 'export') {
+          this.$emit('network-export')
+        } else {
+          this.$emit('network-import', operation === 'import' ? 'import' : operation === 'overwrite' ? 'replace' : operation)
+        }
+        
+        // 模拟异步操作,实际应该在父组件中处理
+        setTimeout(() => {
+          this.$message.success(config.successMsg)
+          // 移除按钮焦点
+          if (button) {
+            button.blur()
+          }
+        }, 500)
+      }).catch(() => {
+        this.$message.info('已取消操作')
+        // 取消时也要移除按钮焦点,避免保持激活状态
+        if (button) {
+          button.blur()
+        }
+      })
+    },
+    
+    // 处理分页变化
+    handlePageChange(page) {
+      this.currentPage = page
+      this.$emit('page-change', page)
+    },
+    
+    // 获取当前类型标签
+    getCurrentTypeLabel() {
+      const typeMap = {
+        'point': '点',
+        'line': '线', 
+        'curve': '弧',
+        'polygon': '面'
+      }
+      return typeMap[this.activeElementType] || ''
     }
   }
 }
@@ -917,9 +1264,11 @@ export default {
     position: relative;
 
     &.panel-collapsed {
+      width: 0 !important;
       opacity: 0;
       pointer-events: none;
       overflow: hidden;
+      transform: translateX(100%);
     }
 
   .panel-header {
@@ -2221,11 +2570,362 @@ html.dark {
   }
 }
 
-.empty-hint {
+  .empty-hint {
     font-size: 12px;
     color: #999;
     margin-top: 8px;
   }
 }
+
+/* === 编辑页专用样式 === */
+.rp--edit {
+  // 复用导航页的基础面板样式
+  .right-panel {
+    position: fixed;                        // 与导航页定位方式一致
+    right: 16px;                           // 与导航页位置一致
+    top: 100px;                            // 与导航页位置一致
+    width: 380px;                          // 与导航页宽度一致
+    height: calc(100vh - 116px);            // 与导航页高度一致,固定高度
+    z-index: 1000;                         // 与导航页层级一致
+    transition: transform 0.3s ease;       // 与导航页动画一致
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+    background: #fff;
+    border-radius: 8px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+
+    &.is-hidden {
+      transform: translateX(calc(100% + 16px)); // 与导航页隐藏方式一致
+    }
+  }
+
+  // 编辑页收起按钮样式(参考导航页)
+  .edit-panel-close-btn {
+    position: absolute;
+    top: 8px;
+    right: 8px;
+    z-index: 10;
+    padding: 4px;
+    font-size: 14px;
+    color: #909399;
+    
+    &:hover {
+      color: #409eff;
+      background: rgba(64, 158, 255, 0.1);
+    }
+  }
+
+  // 编辑页展开拉手现在使用与导航页相同的内联样式和事件处理
+
+  .panel-content {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    overflow: hidden;
+  }
+
+  // Tab内容区域滚动优化(复用标定页样式)
+  ::v-deep .el-tabs {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+  }
+
+  ::v-deep .el-tabs__header {
+    margin: 0;
+    padding: 0 16px;
+    background: #fff;
+    border-bottom: none; // 移除边框,与导航页保持一致
+  }
+  
+  ::v-deep .el-tabs__content {
+    flex: 1;
+    overflow: hidden;
+    padding: 0;
+  }
+  
+  ::v-deep .el-tab-pane {
+    height: 100%;
+    overflow-y: auto;
+    
+    // 自定义滚动条
+    &::-webkit-scrollbar {
+      width: 6px;
+    }
+    
+    &::-webkit-scrollbar-track {
+      background: #f1f1f1;
+      border-radius: 3px;
+    }
+    
+    &::-webkit-scrollbar-thumb {
+      background: #c1c1c1;
+      border-radius: 3px;
+      
+      &:hover {
+        background: #a8a8a8;
+      }
+    }
+  }
+  
+  // 编辑模式实时信息样式 - 复用导航页样式
+  .nav-tab-content {
+    height: 100%;
+    padding: var(--spacing-4);   // 添加左右留白,与导航页保持一致
+    overflow-y: auto;
+  }
+  
+  // 实时信息卡片 - 仿照标定页样式
+  .nav-info-grid {
+    @include info-card-base;
+    margin-bottom: 0;
+    
+    .nav-info-header {
+      padding: var(--spacing-3) var(--spacing-4);
+      border-bottom: 1px solid var(--color-border-secondary);
+      background: var(--color-bg-secondary);
+      
+      .nav-info-title {
+        margin: 0;
+        font-size: var(--font-size-base);
+        font-weight: var(--font-weight-semibold);
+        color: var(--color-text-primary);
+      }
+    }
+    
+    .nav-info-content {
+      padding: 0;
+    }
+  }
+  
+  .nav-info-item {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    height: 36px;
+    padding: 0 var(--spacing-4);
+    border-bottom: 1px solid var(--color-border-tertiary);
+    
+    &:last-child {
+      border-bottom: none;
+    }
+    
+    .label {
+      font-size: var(--font-size-sm);
+      color: var(--color-text-secondary);
+      font-weight: var(--font-weight-normal);
+    }
+    
+    .value {
+      font-size: var(--font-size-sm);
+      color: var(--color-text-primary);
+      text-align: right;
+      font-weight: var(--font-weight-medium);
+      font-family: var(--font-family-mono);
+    }
+  }
+  
+  // 元素管理内容
+  .edit-elements-content {
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    
+    .element-filter {
+      margin-bottom: var(--spacing-3);
+      
+      :deep(.el-tabs__header) {
+        margin: 0;
+        background: var(--color-bg-secondary);
+        border-bottom: none; // 移除多余的边框,避免与主标签页边框重复
+      }
+    }
+    
+    .element-search {
+      margin-bottom: var(--spacing-3);
+      padding: 0 var(--spacing-4);
+    }
+    
+    .element-list {
+      flex: 1;
+      overflow-y: auto;
+      padding: 0 var(--spacing-4);
+      @include custom-scrollbar;
+      
+      .element-item {
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        padding: var(--spacing-3);
+        border: 1px solid var(--color-border-tertiary);
+        border-radius: var(--radius-md);
+        margin-bottom: var(--spacing-2);
+        cursor: pointer;
+        transition: all 0.2s ease;
+        
+        &:hover {
+          border-color: var(--color-primary);
+          background: var(--color-bg-secondary);
+        }
+        
+        &.selected {
+          border-color: var(--color-primary);
+          background: var(--color-primary-light);
+        }
+        
+        .element-info {
+          flex: 1;
+          
+          .element-id {
+            font-weight: var(--font-weight-semibold);
+            color: var(--color-text-primary);
+            font-size: var(--font-size-sm);
+            margin-bottom: var(--spacing-1);
+          }
+          
+          .element-name {
+            color: var(--color-text-secondary);
+            font-size: var(--font-size-xs);
+          }
+        }
+        
+        .element-actions {
+          display: flex;
+          gap: var(--spacing-1);
+          
+          .el-button {
+            padding: 2px 4px;
+            font-size: var(--font-size-xs);
+            height: 24px;
+            line-height: 20px;
+            
+            &.el-button--text {
+              color: var(--color-primary);
+              
+              &:hover {
+                color: var(--color-primary-light);
+                background: transparent;
+              }
+            }
+          }
+        }
+      }
+      
+      .empty-state {
+        display: flex;
+        flex-direction: column;
+        align-items: center;
+        justify-content: center;
+        padding: var(--spacing-8) var(--spacing-4);
+        color: var(--color-text-quaternary);
+        
+        i {
+          font-size: var(--font-size-2xl);
+          margin-bottom: var(--spacing-2);
+          opacity: 0.5;
+        }
+        
+        p {
+          margin: 0;
+          font-size: var(--font-size-sm);
+          opacity: 0.8;
+        }
+      }
+    }
+  }
+  
+  // 路网操作内容
+  .edit-network-content {
+    height: 100%;
+    overflow-y: auto;
+    padding: var(--spacing-4);
+    @include custom-scrollbar;
+    
+    .network-operations {
+      .operation-section {
+        margin-bottom: var(--spacing-6);
+        
+        &:last-child {
+          margin-bottom: 0;
+        }
+        
+        .section-title {
+          margin: 0 0 var(--spacing-3) 0;
+          font-size: var(--font-size-base);
+          font-weight: var(--font-weight-semibold);
+          color: var(--color-text-primary);
+        }
+        
+        .import-buttons {
+          display: flex;
+          flex-direction: column;
+          gap: var(--spacing-2);
+        }
+        
+        .operation-tips {
+          background: var(--color-bg-secondary);
+          padding: var(--spacing-4);
+          border-radius: var(--radius-md);
+          
+          .tip-item {
+            margin: 0 0 var(--spacing-3) 0;
+            font-size: var(--font-size-sm);
+            line-height: 1.6;
+            color: var(--color-text-secondary);
+            
+            &:last-child {
+              margin-bottom: 0;
+            }
+            
+            strong {
+              color: var(--color-text-primary);
+              font-weight: var(--font-weight-semibold);
+              margin-right: var(--spacing-1);
+            }
+          }
+        }
+        
+        .operation-buttons {
+          display: flex;
+          flex-direction: column;
+          gap: var(--spacing-2);
+          
+          .el-button {
+            margin: 0;
+            transition: all 0.2s ease;
+            
+            &.is-block {
+              width: 100%;
+            }
+            
+            // 确保按钮失去焦点后恢复正常状态
+            &:not(:hover):not(:active) {
+              background-color: var(--color-bg-button, #ffffff);
+              border-color: var(--color-border-primary, #dcdfe6);
+              color: var(--color-text-primary, #606266);
+              
+              &.el-button--primary {
+                background-color: var(--color-primary, #409eff);
+                border-color: var(--color-primary, #409eff);
+                color: #ffffff;
+              }
+            }
+            
+            // 移除焦点时的outline
+            &:focus {
+              outline: none;
+            }
+            
+            // 点击后立即失去焦点的效果
+            &:active {
+              transform: translateY(1px);
+            }
+          }
+        }
+      }
+    }
+  }
+}
 </style>
 

+ 1298 - 1217
src/views/map/maplist/edit.vue

@@ -1,1218 +1,1299 @@
-<template>
-	<div class="main">
-		<el-row>
-			<el-col :span="1">
-				<div class="map-edit-conent_button">
-					<!-- <transition name="el-zoom-in-top"> -->
-					<div class="map-element-browsing" v-show="eleBrowsingShow">
-						<div class="element-title" @click="eleBrowsingShow = false"><i class="el-icon-error"></i></div>
-						<div class="element-data">
-							<el-tabs type="border-card" v-model="elementActiveName">
-								<el-tab-pane label="点" name="point">
-									<el-table :data="pointData" style="width: 100%" :max-height="'330px'" border>
-										<el-table-column prop="id" label="id" width="65">
-										</el-table-column>
-										<el-table-column prop="name" label="名称" show-overflow-tooltip width="80">
-										</el-table-column>
-										<el-table-column label="操作" align="center" width="110">
-											<template slot-scope="scope">
-												<el-button size="mini" type="text" icon="el-icon-location-outline"
-													@click="watchEle(scope.row.id)">查看</el-button>
-												<el-popconfirm title="删除选中的元素?" @confirm="removeElement(scope.row.id)">
-													<el-button size="mini" type="text" icon="el-icon-delete" slot="reference"
-														style="margin-left: 5px;">删除</el-button>
-												</el-popconfirm>
-											</template>
-										</el-table-column>
-									</el-table>
-								</el-tab-pane>
-								<el-tab-pane label="线" name="line">
-									<el-table :data="lineData" style="width: 100%" :max-height="'330px'" border>
-										<el-table-column prop="id" label="id" width="65">
-										</el-table-column>
-										<el-table-column prop="name" label="名称" show-overflow-tooltip width="80">
-										</el-table-column>
-										<el-table-column label="操作" align="center" width="110">
-											<template slot-scope="scope">
-												<el-button size="mini" type="text" icon="el-icon-location-outline"
-													@click="watchEle(scope.row.id)">查看</el-button>
-												<el-popconfirm title="删除选中的元素?" @confirm="removeElement(scope.row.id)">
-													<el-button size="mini" type="text" icon="el-icon-delete" slot="reference"
-														style="margin-left: 5px;">删除</el-button>
-												</el-popconfirm>
-											</template>
-										</el-table-column>
-									</el-table>
-								</el-tab-pane>
-								<el-tab-pane label="弧" name="arc">
-									<el-table :data="bowData" style="width: 100%" :max-height="'330px'" border>
-										<el-table-column prop="id" label="id" width="65">
-										</el-table-column>
-										<el-table-column prop="name" label="名称" show-overflow-tooltip width="80">
-										</el-table-column>
-										<el-table-column label="操作" align="center" width="110">
-											<template slot-scope="scope">
-												<el-button size="mini" type="text" icon="el-icon-location-outline"
-													@click="watchEle(scope.row.id)">查看</el-button>
-												<el-popconfirm title="删除选中的元素?" @confirm="removeElement(scope.row.id)">
-													<el-button size="mini" type="text" icon="el-icon-delete" slot="reference"
-														style="margin-left: 5px;">删除</el-button>
-												</el-popconfirm>
-											</template>
-										</el-table-column>
-									</el-table>
-								</el-tab-pane>
-								<el-tab-pane label="面" name="surface">
-									<el-table :data="shapeData" style="width: 100%" :max-height="'330px'" border>
-										<el-table-column prop="id" label="id" width="65">
-										</el-table-column>
-										<el-table-column prop="name" label="名称" show-overflow-tooltip width="80">
-										</el-table-column>
-										<el-table-column label="操作" align="center" width="110">
-											<template slot-scope="scope">
-												<el-button size="mini" type="text" icon="el-icon-location-outline"
-													@click="watchEle(scope.row.id)">查看</el-button>
-												<el-popconfirm title="删除选中的元素?" @confirm="removeElement(scope.row.id)">
-													<el-button size="mini" type="text" icon="el-icon-delete" slot="reference"
-														style="margin-left: 5px;">删除</el-button>
-												</el-popconfirm>
-											</template>
-										</el-table-column>
-									</el-table>
-								</el-tab-pane>
-							</el-tabs>
-						</div>
-					</div>
-					<!-- </transition> -->
-					<div class="button-item">
-						<el-tooltip class="item" effect="dark" content="元素选择" placement="right" :hide-after="1000">
-							<el-button class="button-item_button"><img src="@/assets/icons/img/hand.png"
-									:class="['notification__image', { 'element_select_button': isChoose == 'select' }]"
-									style="opacity: 0.6;" width="25px" height="25px" @click="elementSelect" /></el-button>
-						</el-tooltip>
-					</div>
-					<div class="button-item">
-						<el-tooltip class="item" effect="dark" content="地图编辑" placement="right" :hide-after="400">
-							<el-popover placement="right" title="选择元素" width="170" trigger="hover" ref="editPopover">
-								<div class="close-edit">
-									<el-button type="danger" size="mini" style="padding: 3px 7px;border-radius: 0;"
-										v-show="!pointDraSelectEnabled" @click="closeEditModle">退出编辑</el-button>
-								</div>
-								<el-row :gutter="10">
-									<el-col :span="12">
-										<div class="button-item_grid-content" ref="drPoint" @click="drawModle('drPoint')"><el-button
-												class="button-item_button-dra"><img src="@/assets/icons/img/point.png"
-													class="notification__image" style="opacity: 0.6;" width="30px" height="30px" /></el-button>
-											<div><span>绘点</span></div>
-										</div>
-									</el-col>
-									<el-col :span="12">
-										<div class="button-item_grid-content" ref="drLine" @click="drawModle('drLine')"><el-button
-												class="button-item_button-dra"><img src="@/assets/icons/img/line.png"
-													class="notification__image" style="opacity: 0.6;" width="30px" height="30px" /></el-button>
-											<div><span>绘线</span></div>
-										</div>
-									</el-col>
-								</el-row>
-								<div style="width: 100%;height: 20px;"></div>
-								<el-row :gutter="10">
-									<el-col :span="12">
-										<div class="button-item_grid-content" ref="drCurve" @click="drawModle('drCurve')"><el-button
-												class="button-item_button-dra"><img src="@/assets/icons/img/curve.png"
-													class="notification__image" width="30px" height="30px" /></el-button>
-											<div><span>曲线</span></div>
-										</div>
-									</el-col>
-									<el-col :span="12">
-										<div class="button-item_grid-content" ref="drGraphics" @click="drawModle('drGraphics')"><el-button
-												class="button-item_button-dra"><img src="@/assets/icons/img/region.png"
-													class="notification__image" width="30px" height="30px" /></el-button>
-											<div><span>区域</span></div>
-										</div>
-									</el-col>
-								</el-row>
-								<el-button class="button-item_button" slot="reference" @click="eleBrowsingShow = false"><img
-										src="@/assets/icons/img/draw.png"
-										:class="['notification__image', { 'element_select_button': isChoose == 'edit' }]"
-										style="opacity: 0.6;" width="25px" height="25px" /></el-button>
-							</el-popover>
-						</el-tooltip>
-					</div>
-					<div class="button-item" @click="eleBrowsingShow = !eleBrowsingShow">
-						<el-tooltip class="item" effect="dark" content="元素预览" placement="right" :hide-after="500">
-							<el-button class="button-item_button"><img src="@/assets/icons/img/element.png"
-									class="notification__image" style="opacity: 0.6;" width="25px" height="25px" /></el-button>
-						</el-tooltip>
-					</div>
-					<div class="button-item">
-						<el-tooltip class="item" effect="dark" content="路网操作" placement="right" :hide-after="500">
-							<el-popover placement="bottom-start" width="160" trigger="manual" v-model="visible"
-								transition="el-zoom-in-top">
-								<div class="roadnetword-content">
-									<el-button class="roadnetword-content_button">导出路网</el-button>
-									<el-button class="roadnetword-content_button">导入路网</el-button>
-									<el-button class="roadnetword-content_button">合并导入路网</el-button>
-									<el-button class="roadnetword-content_button">增量导入路网</el-button>
-									<el-button class="roadnetword-content_button">覆盖导入路网</el-button>
-								</div>
-								<el-button class="button-item_button" slot="reference" @click="visible = !visible"><img
-										class="notification__image" src="@/assets/icons/img/roadnetwork.png" style="opacity: 0.6;"
-										width="25px" height="25px" /></el-button>
-							</el-popover>
-						</el-tooltip>
-					</div>
-					<div class="save-button">
-						<el-button type="primary" size="mini" style="padding: 5px 10px;" @click="saveRoute">保存</el-button>
-					</div>
-				</div>
-			</el-col>
-			<el-col :span="23">
-				<div class="map-edit-conent">
-					<!-- 地图内容 -->
-					<OlMap ref="olmap" :width="olWidth + 'px'" :height="olHeight + 'px'" backgroundColor="#F5F5F5"
-						:pointDraSelectEnabled="pointDraSelectEnabled" @elementRoadInitEnd="elementRoadInitEnd"
-						@elementRoadDrawEnd="elementRoadDrawEnd" @removeElementResult="removeElementResult"
-						@selectShowEleResult="selectShowEleResult":mapName="mapName"></OlMap>
-				</div>
-			</el-col>
-		</el-row>
-		<div class="info-dra-class-all">
-			<el-drawer :visible.sync="infoDrawer" :direction="'rtl'" :modal="false" :size="'100%'" :wrapperClosable="false"
-				custom-class="navigation-info-dra-class" :modal-append-to-body="false" :withHeader="false">
-				<div class="info-dra-class_content">
-					<div style="margin-bottom: 20px;position: relative;">
-						<div class="info-dra_title">
-							<div
-								style="height: 20px;width: 5px;background-color: #6565FC;margin-right: 5px;border-radius: 4px 4px 0 0;">
-							</div><span>当前地图</span>
-							<i class="el-icon-close" style="position: absolute;cursor: pointer;right: 0;top: 0;"
-								@click="infoDrawer = false"></i>
-						</div>
-						<span class="info-dra_content">shanghai</span>
-					</div>
-					<div style="margin-bottom: 20px;">
-						<div class="info-dra_title">
-							<div
-								style="height: 20px;width: 5px;background-color: #6565FC;margin-right: 5px;border-radius: 4px 4px 0 0;">
-							</div><span>实时信息</span>
-						</div>
-						<span class="info-dra_content info-dra_content_title">速度: <span
-								class="info-dra_content_other">0.35m/s</span></span>
-						<span class="info-dra_content info-dra_content_title">速度指令: <span
-								class="info-dra_content_other">0.22m/s</span></span>
-						<span class="info-dra_content info-dra_content_title">坐标: <span class="info-dra_content_other">(1.813,
-								-63.931,
-								0.000)</span></span>
-						<span class="info-dra_content info-dra_content_title">航向: <span
-								class="info-dra_content_other">-79.6°</span></span>
-						<span class="info-dra_content info-dra_content_title">累计里程: <span
-								class="info-dra_content_other">5965352.00m</span></span>
-						<span class="info-dra_content info-dra_content_title">配准误差: <span
-								class="info-dra_content_other">10.000</span></span>
-						<span class="info-dra_content info-dra_content_title">电量: <span
-								class="info-dra_content_other">67%</span></span>
-					</div>
-					<div style="margin-bottom: 20px;">
-						<div class="info-dra_title" style="border-bottom: none;">
-							<div
-								style="height: 20px;width: 5px;background-color: #6565FC;margin-right: 5px;border-radius: 4px 4px 0 0;">
-							</div><span>实时位姿</span>
-							<div style="position: absolute;right: 0;"></div>
-						</div>
-						<span class="info-dra_content info-dra_content_title">x坐标: <span
-								class="info-dra_content_other">0.35m/s</span></span>
-						<span class="info-dra_content info-dra_content_title">y坐标: <span
-								class="info-dra_content_other">0.22m/s</span></span>
-						<span class="info-dra_content info-dra_content_title">z坐标: <span
-								class="info-dra_content_other">0.22m/s</span></span>
-						<el-button plain size="mini" icon="el-icon-circle-plus-outline" @click="addCurrentMapShow = true"
-							style="color: #6565FC;">添加当前点</el-button>
-					</div>
-
-					<div class="element-pram-box" v-if="currentFeature.id">
-						<div class="info-dra_title" style="border-bottom: none;margin-bottom: 0;">
-							<span>当前元素参数</span>
-							<div style="position: absolute;right: 0;"><el-button type="text"><i
-										:class="elementPramShow ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
-										@click="elementPramShow = !elementPramShow"></i></el-button></div>
-						</div>
-						<div v-show="elementPramShow" style="margin-top: 10px;">
-							<!-- 点位类型 -->
-							<template v-if="currentFeature.typeEle == 'Point'">
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">id:</span>
-									<el-input v-model="currentFeature.id" size="mini" style="margin-bottom: 5px;width: 40%;"
-										:disabled="true"></el-input>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">名称:</span>
-									<el-tooltip class="item" effect="dark" content="输入完毕点击一次右侧√即可" placement="top">
-										<el-input v-model="currentFeature.name" size="mini" style="margin-bottom: 5px;width: 50%;"
-											@change="setProperties">
-											<template slot="append"><el-button type="success" size="mini"><i
-														class="el-icon-check"></i></el-button></template>
-										</el-input>
-									</el-tooltip>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">x坐标(m):</span>
-									<el-input-number v-model="currentFeature.position[0]" controls-position="right" size="mini"
-										style="margin-bottom: 5px;width: 50%;" @change="positionEdit()" :step="0.5"></el-input-number>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">y坐标(m):</span>
-									<el-input-number v-model="currentFeature.position[1]" controls-position="right" size="mini"
-										@change="positionEdit()" :step="0.5" style="margin-bottom: 5px;width: 50%;"></el-input-number>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">z坐标(m):</span>
-									<el-input-number v-model="currentFeature.position[2]" controls-position="right" size="mini"
-										@change="positionEdit()" :step="0.5" style="margin-bottom: 5px;width: 50%;"></el-input-number>
-								</div>
-							</template>
-							<!-- 线类型 -->
-							<template v-if="currentFeature.typeEle == 'LineString'">
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">id:</span>
-									<el-input v-model="currentFeature.id" size="mini" style="margin-bottom: 5px;width: 40%;"
-										:disabled="true"></el-input>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">名称:</span>
-									<el-tooltip class="item" effect="dark" content="输入完毕点击一次右侧√即可" placement="top">
-										<el-input v-model="currentFeature.name" size="mini" style="margin-bottom: 5px;width: 50%;"
-											@change="setProperties">
-											<template slot="append"><el-button type="success" size="mini"><i
-														class="el-icon-check"></i></el-button></template>
-										</el-input>
-									</el-tooltip>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">起点id:</span>
-									<el-input v-model="currentFeature.startid" size="mini" style="margin-bottom: 5px;width: 40%;"
-										:disabled="true"></el-input>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">终点id:</span>
-									<el-input v-model="currentFeature.endid" size="mini" style="margin-bottom: 5px;width: 40%;"
-										:disabled="true"></el-input>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<el-tooltip class="item" effect="dark"
-										content="车辆在线路上的运动模式,解释: 1.前进-允许车辆正开从起点运动到终点 2.倒行-允许车辆倒开从起点运动到终点" placement="top">
-										<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-											class="info-dra_content_title">起点->终点<i class="el-icon-question"></i></span>
-									</el-tooltip>
-									<div style="margin-top: 10px;">
-										<el-checkbox v-model="currentFeature.directList[0]" @change="setProperties">前行</el-checkbox>
-										<el-checkbox v-model="currentFeature.directList[1]" @change="setProperties">倒行</el-checkbox>
-									</div>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<el-tooltip class="item" effect="dark"
-										content="车辆在线路上的运动模式,解释: 1.前进-允许车辆正开从终点运动到起点 2.倒行-允许车辆倒开从终点运动到起点" placement="top">
-										<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-											class="info-dra_content_title">终点->起点<i class="el-icon-question"></i></span>
-									</el-tooltip>
-									<div style="margin-top: 10px;">
-										<el-checkbox v-model="currentFeature.directList[2]" @change="setProperties">前行</el-checkbox>
-										<el-checkbox v-model="currentFeature.directList[3]" @change="setProperties">倒行</el-checkbox>
-									</div>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">最大限速(m/s):</span>
-									<el-input-number v-model="currentFeature.maxspeed" controls-position="right" size="mini"
-										style="margin-bottom: 5px;width: 50%;" @change="setProperties"></el-input-number>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">最小限速(m/s):</span>
-									<el-input-number v-model="currentFeature.minspeed" controls-position="right" size="mini"
-										style="margin-bottom: 5px;width: 50%;" @change="setProperties"></el-input-number>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">车道宽度(m):</span>
-									<el-input-number v-model="currentFeature.lanewidth" controls-position="right" size="mini"
-										style="margin-bottom: 5px;width: 50%;" @change="setProperties"></el-input-number>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">左车道数:</span>
-									<el-input-number v-model="currentFeature.leftlanenum" controls-position="right" size="mini"
-										style="margin-bottom: 5px;width: 50%;" @change="setProperties"></el-input-number>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">右车道处:</span>
-									<el-input-number v-model="currentFeature.rightlanenum" controls-position="right" size="mini"
-										style="margin-bottom: 5px;width: 50%;" @change="setProperties"></el-input-number>
-								</div>
-							</template>
-							<!-- 面类型 -->
-							<template v-if="currentFeature.typeEle == 'Polygon'">
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">id:</span>
-									<el-input v-model="currentFeature.id" size="mini" style="margin-bottom: 5px;width: 40%;"
-										:disabled="true"></el-input>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">名称:</span>
-									<el-tooltip class="item" effect="dark" content="输入完毕点击一次右侧√即可" placement="top">
-										<el-input v-model="currentFeature.name" size="mini" style="margin-bottom: 5px;width: 50%;"
-											@change="setProperties">
-											<template slot="append"><el-button type="success" size="mini"><i
-														class="el-icon-check"></i></el-button></template>
-										</el-input>
-									</el-tooltip>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">类型:</span>
-									<el-select v-model="currentFeature.type" placeholder="请选择" size="mini"
-										style="margin-bottom: 5px;width: 50%;" @change="setProperties">
-										<el-option v-for="item in snapeTypeRange" :key="item.value" :label="item.label" :value="item.value">
-										</el-option>
-									</el-select>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">颜色:</span>
-									<el-color-picker v-model="currentFeature.color" size="mini" @change="setProperties"></el-color-picker>
-								</div>
-								<div style="margin-bottom: 5px;" class="ele-pram-input">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">透明度:</span>
-									<el-slider v-model="currentFeature.transparent" style="margin-bottom: 5px;width: 50%;" :max="255"
-										:step="5" @change="setProperties"></el-slider>
-								</div>
-							</template>
-						</div>
-					</div>
-					<div class="element-pram-box" style="margin-bottom: 0;" v-if="currentFeature.id">
-						<div class="info-dra_title" style="border-bottom: none;margin-bottom: 0;">
-							<span>当前元素参数高级设置</span>
-							<div style="position: absolute;right: 0;"><el-button type="text"><i
-										:class="elementPramMoreShow ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"
-										@click="elementPramMoreShow = !elementPramMoreShow"></i></el-button></div>
-						</div>
-						<div v-show="elementPramMoreShow" style="margin-top: 15px;">
-							<template v-if="currentFeature.typeEle == 'Point'">
-								<div class="ele-data-more">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">航偏角对准使能</span>
-									<div class="ele-data-more-title-right">
-										<el-switch v-model="currentFeature.isyawfix" active-color="#13ce66" inactive-color="#9D9D9D"
-											@change="setProperties">
-										</el-switch>
-									</div>
-								</div>
-								<div class="ele-data-more">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">航偏角(rad)</span>
-									<div class="ele-data-more-title-right">
-										<el-input-number v-model="currentFeature.yaw" controls-position="right" size="mini"
-											@change="setProperties" style="width: 100%;"></el-input-number>
-									</div>
-								</div>
-							</template>
-							<template v-if="currentFeature.typeEle == 'LineString'">
-								<!-- 线 -->
-								<div class="ele-data-more">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">避障方式</span>
-									<div class="ele-data-more-title-right">
-										<el-select v-model="currentFeature.obstype" placeholder="请选择" size="mini" @change="setProperties">
-											<el-option v-for="item in obstypeRange" :key="item.value" :label="item.label" :value="item.value">
-											</el-option>
-										</el-select>
-									</div>
-								</div>
-								<div class="ele-data-more">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">正向前移(m)</span>
-									<div class="ele-data-more-title-right">
-										<el-input-number v-model="currentFeature.s2eforward" controls-position="right" size="mini"
-											style="width: 100%;" @change="setProperties"></el-input-number>
-									</div>
-								</div>
-								<div class="ele-data-more">
-									<span style="margin-right: 16px;font-size: 13px;color: #585858;"
-										class="info-dra_content_title">逆向前移(m)</span>
-									<div class="ele-data-more-title-right">
-										<el-input-number v-model="currentFeature.e2sforward" controls-position="right" size="mini"
-											style="width: 100%;" @change="setProperties"></el-input-number>
-									</div>
-								</div>
-							</template>
-						</div>
-					</div>
-				</div>
-			</el-drawer>
-		</div>
-		<!-- 展开抽屉 -->
-		<div class="fixed-right-center" @click="showInfoDra" v-if="!infoDrawer">
-			<i class="el-icon-arrow-left"></i>
-		</div>
-		<!-- 添加当前点位到地图 -->
-		<el-dialog title="绘制当前实时位姿到点" :visible.sync="addCurrentMapShow" width="650px" @open="openCurrentToMapDia">
-			<el-form ref="form" :model="currentRobotRecord" label-width="110px">
-				<el-row>
-					<el-col :span="12"><el-form-item label="x坐标(m)">
-							<el-input-number v-model="currentRobotRecord.x" controls-position="right"></el-input-number>
-						</el-form-item></el-col>
-					<el-col :span="12"><el-form-item label="y坐标(m)">
-							<el-input-number v-model="currentRobotRecord.y" controls-position="right"></el-input-number>
-						</el-form-item></el-col>
-				</el-row>
-				<el-row>
-					<el-col :span="12">
-						<el-form-item label="航偏角">
-							<el-input-number v-model="currentRobotRecord.angle" controls-position="right"
-								:disabled="true"></el-input-number>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="航偏角使能">
-							<el-switch v-model="currentRobotRecord.angleEnable"></el-switch>
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
-			<span slot="footer" class="dialog-footer">
-				<el-button @click="addCurrentMapShow = false">取 消</el-button>
-				<el-button type="primary" @click="addCurrentToMap">确 定</el-button>
-			</span>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import OlMap from "@/components/OlMap";
-
-export default {
-	name: "EditPage",
-	components: {
-		OlMap
-	},
-	data() {
-		return {
-			// 弹出层标题
-			title: "",
-			eleBrowsingShow: false,
-			visible: false,
-			poseValue: false,
-			elementPramShow: true,
-			elementPramMoreShow: false,
-			pointData: [],
-			lineData: [],
-			bowData: [],
-			shapeData: [],
-			elementActiveName: 'point',
-			infoDrawer: true,
-			olWidth: 0,  // 用于存储宽度的变量
-			olHeight: 0,
-			// 当前启用的按钮名
-			isChoose: '',
-			// 元素选择模式是否启用
-			pointDraSelectEnabled: true,
-			// 当前所有元素原始对象Feature,包含新绘的
-			resourcesFeature: [],
-			// 无人车坐标数据
-			robotPoseData: {
-				x: 0,
-				y: 0,
-				angle: 0
-			},
-			// 当前选择的feature
-			currentFeature: {},
-			// 实时位置绘制点位的dialog
-			addCurrentMapShow: false,
-			currentRobotRecord: {},
-			// 避障方式
-			obstypeRange: [
-				{ label: '停车等待', value: 0 },
-				{ label: '车道绕障', value: 1 },
-				{ label: '路网绕障', value: 2 }
-			],
-			// 区域类型
-			snapeTypeRange: [
-				{ label: '隔离区域', value: 0 },
-				{ label: '装饰区域', value: 1 },
-				{ label: '禁行区域', value: 2 },
-				{ label: '会车管制区', value: 3 },
-				{ label: '道闸管控区', value: 4 },
-				{ label: 'GPS定位区', value: 11 },
-				{ label: '动态禁行区', value: 22 },
-			],
-			haveDraw: false, // 是否操作过页面元素(新增修改或者删除元素) (在切换页面或者刷新时进行拦截,避免误操作退出页面)
-			mapName:this.$route.params.mapName
-		};
-	},
-	created() {
-
-	},
-	beforeDestroy() {
-		window.removeEventListener("beforeunload", this.handleBeforeUnload);
-	},
-	beforeRouteLeave(to, from, next) {
-		// 如果页面有修改或者新增删除,则拦截*前端路由跳转*, 二次确认
-		this.$confirm('当前页面的地图修改还未保存,确定要跳转或退出?', '提示', {
-			confirmButtonText: '确定',
-			cancelButtonText: '取消',
-			type: 'warning'
-		}).then(() => {
-			next();
-		}).catch(() => {
-			next(false);
-		});
-	},
-	mounted() {
-		const mapId = this.$route.params.mapId;
-		this.updateOlCss();
-		this.elementSelect();
-		window.addEventListener('resize', this.updateOlCss);
-		window.addEventListener("beforeunload", this.handleBeforeUnload);
-	},
-	methods: {
-		updateOlCss() {
-			const element = this.$el.querySelector('.map-edit-conent');
-			this.olWidth = element.offsetWidth;
-			this.olHeight = element.offsetHeight;
-		},
-		handleBeforeUnload(event) {
-			// 如果页面有修改或者新增删除,则强制拦截*浏览器主体*的刷新,和关闭操作, 二次确认
-			if (this.haveDraw) {
-				event.preventDefault();
-				event.returnValue = "";
-			}
-		},
-		// 展开右侧实时信息
-		showInfoDra() {
-			this.infoDrawer = true;
-		},
-		// 启用地图绘制模式 modle(绘制模式)点线面
-		drawModle(modle) {
-			// 将当前编辑按钮标记为选中状态
-			this.isChoose = 'edit';
-			this.pointDraSelectEnabled = false;
-			this.addSelectedBrage(modle);
-			this.$notify({
-				title: '地图编辑',
-				message: '已开启地图绘制模式',
-				type: 'success',
-				duration: 1500
-			});
-			// this.$refs.editPopover.doClose(); // 进入编辑模式是否关闭编辑元素选择画板, 可以选择启停
-			switch (modle) {
-				case 'drPoint':
-					// 绘制点模式
-					this.$refs.olmap.drawPoint();
-					break;
-				case 'drLine':
-					// 绘制线模式
-					this.$refs.olmap.drawLine();
-					break;
-				case 'drCurve':
-					// 绘制曲线模式
-					this.$refs.olmap.drawCurve();
-					break;
-				case 'drGraphics':
-					this.$refs.olmap.drawPolygon();
-					// 绘制图形模式
-					break;
-			}
-		},
-		// 关闭绘图模式
-		closeEditModle() {
-			this.$refs.editPopover.doClose();
-			this.isChoose = '';
-			// 去除所有选择的绘图元素
-			this.addSelectedBrage('');
-			this.$notify({
-				title: '地图编辑',
-				message: '已关闭地图绘制模式',
-				type: 'info',
-				duration: 1000
-			});
-			this.$refs.olmap.clearDraw();
-		},
-		// 为绘图元素添加选中效果
-		addSelectedBrage(modle) {
-			let refs = ['drPoint', 'drLine', 'drCurve', 'drGraphics'];
-			// 遍历 refs 数组
-			refs.forEach(ref => {
-				const element = this.$refs[ref];
-				if (element) {
-					// 如果当前元素是 modle,就添加 class
-					if (ref == modle) {
-						element.classList.add('element_select_button');
-					} else {
-						// 否则移除 class
-						element.classList.remove('element_select_button');
-					}
-				}
-			});
-		},
-		// 开启元素选择模式
-		elementSelect() {
-			if (this.isChoose != 'select') {
-				this.closeEditModle();
-				this.$notify({
-					title: '地图编辑',
-					message: '已开启元素选择模式',
-					type: 'success',
-					duration: 1000
-				});
-				// 开启模式
-				this.pointDraSelectEnabled = true;
-				this.isChoose = 'select';
-				const mapElement = document.querySelector('.map-edit-conent'); // 地图视图的 DOM 元素
-				mapElement.style.cursor = 'pointer'; // 设置鼠标样式为手
-			}
-		},
-		// 地图元素加载完毕的回执
-		elementRoadInitEnd(features) {
-			features.forEach(item => {
-				// 检查 item.values_ 和 item.values_.id 是否存在
-				if (item.values_ && item.values_.id) {
-					const id = item.values_.id;
-					const data = {
-						id: id,
-						name: item.values_.name,
-						type: item.getGeometry().getType()
-					};
-					// 根据 id 前缀分类存入对应数组
-					if (id.startsWith('p')) {
-						this.pointData.push(data);
-					} else if (id.startsWith('l')) {
-						this.lineData.push(data);
-					} else if (id.startsWith('b')) {
-						this.bowData.push(data);
-					} else if (id.startsWith('s')) {
-						this.shapeData.push(data);
-					}
-				}
-			});
-			// 对分类后的数组按数字部分排序
-			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;
-		},
-		// 某个元素绘制完成的回调
-		elementRoadDrawEnd(feature) {
-			const id = feature.values_.id || feature.getId();
-			const data = {
-				id: id,
-				name: feature.values_.name,
-				type: feature.getGeometry().getType()
-			};
-			if (id.startsWith('p')) {
-				this.pointData.push(data);
-				// 按照类型为feature初始化基础参数和高级参数(点)
-				this.initElelmentParamsPonint(feature);
-			} else if (id.startsWith('l')) {
-				this.lineData.push(data);
-				// 按照类型为feature初始化基础参数和高级参数(线)
-				this.initElelmentParamsBowOrLine(feature);
-			} else if (id.startsWith('b')) {
-				this.bowData.push(data);
-				// 按照类型为feature初始化基础参数和高级参数(曲线)
-				this.initElelmentParamsBowOrLine(feature);
-			} else if (id.startsWith('s')) {
-				this.shapeData.push(data);
-				// 按照类型为feature初始化基础参数和高级参数(面)
-				this.initElelmentParamsSnape(feature);
-			}
-			// 标记当前存在绘制的操作,拦截使用
-			this.haveDraw = true; // 是否操作过页面元素(新增修改或者删除元素) (在切换页面或者刷新时进行拦截,避免误操作退出页面)
-		},
-		// 查看某个元素
-		watchEle(id) {
-			// 调用地图组件显示元素
-			this.$refs.olmap.selectShowEle(id);
-		},
-		// 查看某个元素的子组件回调
-		selectShowEleResult(feature) {
-			const geometry = feature.getGeometry();
-			console.log(geometry.getCoordinates());
-			
-			if (geometry.getType() == 'Point') {
-				// 如果是点位则临时保存xyz和类型到参数用于渲染(保存时会移除)  注:保存元素坐标为参数用于对应表单数据,否则需要用方法获取,无法双向绑定
-				feature.set('position', geometry.getCoordinates())
-			} else if (geometry.getType() == "LineString") {
-				// 解析线段类型的direct参数用于渲染和修改(保存时需要渲染回原始数据模式,切记)
-				const value = feature.values_.direct - 100 || 0;
-				const binary = value.toString(2).padStart(4, '0');
-				const result = [
-					binary[0] === '1',  // bit3
-					binary[1] === '1',  // bit2
-					binary[2] === '1',  // bit1
-					binary[3] === '1'   // bit0
-				];
-				// 临时参数渲染使用, 保存时会删除
-				feature.set('directList', result);
-			}
-			// 临时保存类型到参数用于渲染(保存时回移除)
-			feature.set('typeEle', geometry.getType())
-			this.currentFeature = feature.getProperties();
-		},
-		// 移除某个元素
-		removeElement(id) {
-			// 调用地图组件移除元素
-			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);
-				}
-			}
-			if (id.startsWith('s')) {
-				const index = this.shapeData.findIndex(item => item.id === id);
-				if (index !== -1) {
-					this.shapeData.splice(index, 1);
-				}
-			}
-			// 判断当前选择元素是否被删除,如果是则删除
-			if (this.currentFeature && this.currentFeature.id == id) {
-				this.currentFeature = {};
-			}
-			// 删除源features中的此元素
-			const index = this.resourcesFeature.findIndex(feature => feature.getId() == id);
-			// 如果找到了该元素,则删除
-			if (index !== -1) {
-				this.resourcesFeature.splice(index, 1);
-			}
-			this.haveDraw = true;
-		},
-		// 初始化点位元素基础和高级参数
-		initElelmentParamsPonint(feature) {
-			feature.set('name', '');
-			feature.set('yaw', 0);	// 弧度
-			// 如果是通过实时位姿则需要判断是否有值,有则设置
-			feature.set('isyawfix', feature.get('isyawfix') ? true : false); // 偏航使能
-			feature.set('taskid', 0);
-			feature.set('offset', 0);
-			feature.set('pitch', 0)
-			feature.set('roll', 0)
-			this.resourcesFeature.push(feature);
-		},
-		// 初始化执行元素基础和高级参数
-		initElelmentParamsBowOrLine(feature) {
-			feature.set('name', '');
-			// direct中的值首先需要减去100,结果的后4位bit位分别代表如下含义:
-			// bit3:为1时,表示允许车辆从起点行驶到终点,且机器人以正常前行的方式行驶;
-			// bit2:为1时,表示允许车辆从起点行驶到终点,且机器人以倒车的方式行驶;
-			// bit1:为1时,表示允许车辆从终点行驶到起点,且机器人以正常前行的方式行驶;
-			// bit0:为1时,表示允许车辆从终点行驶到起点,且机器人以倒车的方式行驶。
-			// 举例: direct=105(bit2和bit0为1),表示为双向车道,但是不论机器人从起点前往终点,还是从终点前往起点,都会以倒车的方式行驶。
-			feature.set('direct', 110); // 常用 110:起点至终点 + 终点至起点(双方向) 正向行驶    108:起点至终点 正向行驶
-			feature.set('maxspeed', 8) // 最大限速  m/s
-			feature.set('minspeed', 0) // 最小限速  m/s
-			feature.set('lanewidth', 1) // 车道宽度
-			feature.set('leftlanenum', 0) //前进方向左侧车道数
-			feature.set('rightlanenum', 0) //前进方向右侧车道数
-			feature.set('obstype', 0) // 避障方式,0:停车等待,1:车道绕障,2:路网绕障,默认为0
-			feature.set('obsvalue', 200) // 障碍物参数(例如:权重值或影响范围)
-			feature.set('followdis', 0.4) // 跟随距离
-			feature.set('runtype', 0) // 运行类型
-			feature.set('selftheta', 0.5) // 自身角度
-			feature.set('xytolerance', 0.2) // 平面坐标容差
-			feature.set('s2eforward', 0) // 机器人沿当前线段从起点到达终点时多行驶(负数则为少行驶)的距离,单位为米,默认为0
-			feature.set('e2sforward', 0) // 机器人沿当前线段从终点到达起点时多行驶的距离,单位为米,默认为0
-			feature.set('thtolerance', 0) // 角度容差
-			feature.set('mintheta', 0) // 最小转向角
-			feature.set('maxtheta', 1) // 最大转向角
-
-			this.resourcesFeature.push(feature);
-		},
-		// 初始化面元素基础和高级参数
-		initElelmentParamsSnape(feature) {
-			feature.set('name', '');
-			feature.set('typeEle', feature.getGeometry().getType()); // 保存元素类型为参数用于动态绑定对应的元素修改表单,否则需要用方法获取
-			feature.set('color', "#7EFFFA");
-			//区域类型,说明: 0:隔离区域,限定机器人只能在该区域活动;1:装饰区域,仅人机交互用,不影响导航;
-			// 2:禁行区域,不允许机器人进入该区域;3:会车管制区,特定场景使用;
-			// 4:道闸管控区,特定场景使用;11:GPS定位区,进入该区域后,从激光定位切换到GPS定位;
-			// 22:动态禁行区,特定场景使用。
-			feature.set('type', 1);
-			feature.set('transparent', 200);
-			this.resourcesFeature.push(feature);
-		},
-		/**
-		 * 点位坐标修改
-		 */
-		positionEdit() {
-			this.haveDraw = true;
-			this.$refs.olmap.pointPositionUpdate(this.currentFeature.id, this.currentFeature.position);
-		},
-		// 自定义参数修改提交
-		setProperties() {
-			if (!this.currentFeature) return;
-			this.resourcesFeature.forEach(item => {
-				if (item.getId() == this.currentFeature.id) {
-					// 额外参数处理 线
-					if (this.currentFeature.id.startsWith('l') || this.currentFeature.id.startsWith('b')) {
-						// direct还原
-						let directList = this.currentFeature.directList;
-						// 将布尔集合转成4位bit集合, 再转成10进制, 加100还原成原始报文
-						const binaryString = directList.map(bit => (bit ? '1' : '0')).join('');
-						const value = parseInt(binaryString, 2);
-						const direct = value + 100;
-						this.currentFeature.direct = direct;
-					}
-					// 处理完毕后先刷新对象到画布
-					item.setProperties(this.currentFeature)
-					// 刷新完毕后用自定义参数处理其他需要修改完毕能看到的元素效果(例如修改name或者图形颜色等)
-					if (this.currentFeature.id.startsWith('s')) {
-						// 额外处理多边形的效果
-						// 1. 修改图形的颜色,文本
-						this.$refs.olmap.editSnapeColor(this.currentFeature.id);
-					}
-				}
-			})
-			this.haveDraw = true; // 是否操作过页面元素(新增修改或者删除元素) (在切换页面或者刷新时进行拦截,避免误操作退出页面)
-		},
-		// 保存路网
-		saveRoute() {
-			// 保存时清理当前所有对象的额外渲染参数
-			this.resourcesFeature.forEach(feature => {
-				const values = feature.getProperties(); // 获取feature的values_自定义属性
-				// 检查并删除特定的属性(如果存在)
-				if (values) {
-					if (values.hasOwnProperty('typeEle')) {
-						delete values.typeEle;  // 删除 typeEle 属性
-					}
-					if (values.hasOwnProperty('position')) {
-						delete values.position;  // 删除 position 属性
-					}
-					if (values.hasOwnProperty('directList')) {
-						delete values.directList;  // 删除 directList 属性
-					}
-				}
-			});
-		},
-		// 以当前车辆实时位姿为点位绘制到地图
-		addCurrentToMap() {
-			this.addCurrentMapShow = false;
-			// 使用组件的根据坐标绘制点方法
-			let data = [{ yaw: this.currentRobotRecord.angle }, { isyawfix: this.currentRobotRecord.angleEnable }]
-			this.$refs.olmap.createPointAtCoordinate([this.currentRobotRecord.x, this.currentRobotRecord.y, 0], '', data);
-		},
-		openCurrentToMapDia() {
-			this.currentRobotRecord = this.robotPoseData;
-			this.currentRobotRecord.isyawfix = false;
-		}
-	}
-};
-</script>
-
-<style scoped>
-.main{
-	position: relative;
-	min-width: 1200px;
-	overflow-x: auto;
-}
-
-.element-title {
-	position: absolute;
-	z-index: 1001;
-	right: 8px;
-	top: 8px;
-	color: #717171;
-	cursor: pointer
-}
-
-.explore-unit {
-	margin-left: 8px;
-}
-
-::v-deep .el-table__header-wrapper {
-	border-radius: 10px 10px 0 0;
-}
-
-::v-deep .el-table .el-table__header-wrapper th,
-.el-table .el-table__fixed-header-wrapper th {
-	background-color: #f6f6f6;
-}
-
-::v-deep .el-table--medium .el-table__cell {
-	padding: 8px 0;
-}
-
-::v-deep .el-dialog__body {
-	padding: 20px 20px 0 20px;
-}
-
-::v-deep .download-map .el-dialog__body {
-	padding: 10px 20px 0 20px !important;
-}
-
-.map-edit-conent {
-	width: 100%;
-	min-height: calc(100vh - 84px);
-}
-
-.map-edit-conent_button {
-	display: flex;
-	flex-direction: column;
-	/* 垂直排列子元素 */
-	/* 在容器内均匀分布元素 */
-	align-items: center;
-	/* 水平居中 */
-	position: relative;
-	width: 100%;
-	min-height: calc(100vh - 84px);
-}
-
-.button-item {
-	margin: 8% 2%;
-}
-
-::v-deep .button-item_button {
-	padding: 10px 12px;
-	border: none;
-	background-color: transparent;
-}
-
-::v-deep .button-item_button-dra {
-	padding: 5px 12px;
-	border: none;
-	background-color: transparent;
-}
-
-
-.save-button {
-	position: absolute;
-	bottom: 10px;
-}
-
-.button-item_grid-content {
-	text-align: center;
-}
-
-.map-element-browsing {
-	height: 450px;
-	width: 290px;
-	position: absolute;
-	left: 100%;
-	border-radius: 0 8px 8px 8px;
-}
-
-.element-data {
-	z-index: 1000;
-	position: relative;
-}
-
-::v-deep .element-data .el-tabs__header {
-	margin: 0 0 5px;
-}
-
-::v-deep .element-data .el-tabs--border-card>.el-tabs__content {
-	padding: 5px 15px 15px 15px;
-}
-
-::v-deep .element-data .el-tabs--border-card {
-	box-shadow: none;
-}
-
-.roadnetword-content {
-	display: flex;
-	flex-direction: column;
-	align-items: center;
-	justify-content: center;
-}
-
-.roadnetword-content .roadnetword-content_button {
-	margin-left: 0;
-	width: 100%;
-	margin-bottom: 8px;
-	border: none;
-	background-color: #F7F7F7;
-	border: 1px solid #f0f0f0;
-}
-
-.info-dra-class_content {
-	padding: 20px 20px 20px 20px;
-}
-
-.info-dra_title {
-	display: flex;
-	/* 启用 Flexbox 布局 */
-	justify-content: flex-start;
-	/* 水平从左排列 */
-	align-items: center;
-	color: #5e5e5e;
-	font-size: 16px;
-	font-weight: bold;
-	border-bottom: 2px solid #B9B9FF;
-	margin-bottom: 10px;
-	width: 100%;
-	position: relative;
-}
-
-.info-dra_content {
-	color: #5A5A5A;
-	font-size: 14px;
-	margin-left: 8px;
-	display: block;
-	margin-bottom: 12px;
-}
-
-.info-dra_content_title {
-	font-weight: bold;
-}
-
-.info-dra_content_other {
-	font-weight: 400;
-}
-
-::v-deep .info-dra-class-all .el-drawer__wrapper {
-	width: 15%;
-	left: 85%;
-}
-
-@media (max-width: 1599px) {
-	::v-deep .info-dra-class-all .el-drawer__wrapper {
-		width: 18%;
-		left: 82%;
-	}
-}
-
-@media (max-width: 1269px) {
-	::v-deep .info-dra-class-all .el-drawer__wrapper {
-		width: 21%;
-		left: 79%;
-	}
-}
-
-@media (max-width: 1000px) {
-	::v-deep .info-dra-class-all .el-drawer__wrapper {
-		width: 24%;
-		left: 76%;
-	}
-}
-
-.fixed-right-center {
-	position: fixed;
-	top: 50%;
-	right: 0;
-	transform: translateY(-50%);
-	background-color: rgba(0, 0, 255, 0.6);
-	padding: 4px;
-	color: white;
-	border-radius: 6px 0 0 6px;
-	cursor: pointer;
-}
-
-.element-pram-box {
-	margin-bottom: 20px;
-	background-color: #f9f9f9;
-	padding: 10px;
-	border-radius: 8px;
-	border: 1px solid #e9e9e9;
-}
-
-.ele-data-more {
-	margin-top: 15px;
-	margin-bottom: 15px;
-	position: relative;
-	display: flex;
-	justify-content: space-between;
-	/* 左右分布,一个靠左,一个靠右 */
-	align-items: center;
-	/* 垂直居中 */
-	height: 100%;
-	/* 确保父容器有高度 */
-}
-
-.ele-data-more-title-right {
-	width: 40%;
-}
-
-::v-deep .ele-pram-input .el-input__inner {
-	border: none;
-	background-color: #e9e9e9;
-}
-
-::v-deep .el-table .el-table__header-wrapper th,
-.el-table .el-table__fixed-header-wrapper th {
-	background-color: #f6f6f6;
-}
-
-.ele-pram-input {
-	display: flex;
-	/* 设置为Flexbox布局 */
-	align-items: center;
-	/* 垂直居中对齐 */
-	justify-content: space-between;
-}
-
-.button-item_grid-content:hover {
-	text-align: center;
-	background-color: #ffc1b9;
-	border-radius: 5px;
-}
-
-.element_select_button {
-	background-color: #ffc1b9;
-	border-radius: 5px;
-}
-
-.close-edit {
-	position: absolute;
-	top: 0;
-	right: 0;
-}
-
-::v-deep .element-pram-box .el-input.is-disabled .el-input__inner {
-	background-color: #f1f1f1;
-	color: #5A5A5A;
-}
-</style>
-
-<style>
-.navigation-info-dra-class {
-	background-color: #ffffff;
-	box-shadow: none;
-	border-radius: 5px 0 0 5px;
-}
-
-.navigation-info-dra-class .el-drawer__header {
-	margin-bottom: 20px;
-}
+<template>
+  <div class="edit-page">
+    <!-- 地图容器 -->
+    <div class="map-container">
+      <!-- 左侧工具栏 -->
+      <div class="toolbar-container">
+        <MapToolbar
+          preset="edit"
+          :selected-key="currentMode"
+          @mode-change="handleModeChange"
+          @zoom-in="handleZoomIn"
+          @zoom-out="handleZoomOut"
+          @center-robot="handleCenterRobot"
+          @toggle-fullscreen="handleToggleFullscreen"
+          @save="handleSaveMap"
+        />
+						</div>
+
+      <!-- 地图组件(作为主内容区域) -->
+      <div id="map-stage" class="page-content" ref="mapContent">
+        <OlMap 
+          ref="olmap" 
+          :width="olWidth + 'px'" 
+          :height="olHeight + 'px'" 
+          backgroundColor="#F5F5F5" 
+          :mapName="mapName"
+          :pointDraSelectEnabled="pointDraSelectEnabled"
+          :showDefaultControls="false"
+          @elementRoadInitEnd="elementRoadInitEnd"
+          @elementRoadDrawEnd="elementRoadDrawEnd"
+          @removeElementResult="removeElementResult"
+          @selectShowEleResult="selectShowEleResult"
+        />
+        
+        <!-- 底部Inspector面板 -->
+        <BottomInspector
+          ref="bottomInspector"
+          :visible="inspector.visible"
+          :mode="inspector.mode"
+          :data="inspector.data"
+          :loading="inspector.loading"
+          @edit="handleInspectorEdit"
+          @save="handleInspectorSave"
+          @cancel="handleInspectorCancel"
+          @close="handleInspectorClose"
+          @locate="handleInspectorLocate"
+          @height-change="handleInspectorHeightChange"
+        />
+					</div>
+
+      <!-- 右侧面板 -->
+      <div class="panel-container">
+        <RightPanel
+          mode="edit"
+          panelType="edit"
+          v-model="rightPanelVisible"
+          :overlay="true"
+          :tabs="rightPanelTabs"
+          :initial-tab="'info'"
+          :realtime-info="realtimeInfo"
+          :element-list="allElementList"
+          :selected-element="currentFeature"
+          :element-types="elementTypeCounts"
+          @add-current-point="handleAddCurrentPoint"
+          @element-select="handleElementSelect"
+          @element-edit="handleElementEdit"
+          @element-locate="handleElementLocate"
+          @element-remove="handleElementRemove"
+          @network-export="handleNetworkExport"
+          @network-import="handleNetworkImport"
+        />
+					</div>
+					</div>
+
+    <!-- 添加当前点对话框 -->
+    <el-dialog 
+      title="添加当前实时位姿点" 
+      :visible.sync="addCurrentMapShow" 
+      width="480px" 
+      @open="openCurrentToMapDialog"
+      class="add-pose-dialog"
+      :close-on-click-modal="false"
+      :modal="true"
+      :modal-append-to-body="true"
+      :append-to-body="true"
+      center
+      custom-class="centered-dialog"
+    >
+      <div class="dialog-content">
+        <el-form ref="currentPointForm" :model="currentRobotRecord" label-width="90px" size="small" class="pose-form">
+          <div class="form-section">
+            <h4 class="section-title">
+              <i class="el-icon-location-outline"></i>
+              位置信息
+            </h4>
+            <el-form-item label="X坐标(m)" prop="x">
+              <el-input-number 
+                v-model="currentRobotRecord.x" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                placeholder="请输入X坐标值"
+                class="coordinate-input"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="Y坐标(m)" prop="y">
+              <el-input-number 
+                v-model="currentRobotRecord.y" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                placeholder="请输入Y坐标值"
+                class="coordinate-input"
+                style="width: 100%;"
+              />
+            </el-form-item>
+            <el-form-item label="Z坐标(m)" prop="z">
+              <el-input-number 
+                v-model="currentRobotRecord.z" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                placeholder="请输入Z坐标值"
+                class="coordinate-input"
+                style="width: 100%;"
+              />
+            </el-form-item>
+						</div>
+
+          <div class="form-section">
+            <h4 class="section-title">
+              <i class="el-icon-guide"></i>
+              方向配置
+            </h4>
+            <el-form-item label="航偏角(rad)" prop="angle">
+              <el-input-number 
+                v-model="currentRobotRecord.angle" 
+                :precision="3"
+                :step="0.1"
+                controls-position="right"
+                placeholder="请输入航偏角值"
+                style="width: 100%;"
+              />
+						</el-form-item>
+            <el-form-item label="航偏角使能" prop="angleEnable">
+              <el-switch v-model="currentRobotRecord.angleEnable" />
+						</el-form-item>
+          </div>
+			</el-form>
+      </div>
+			<span slot="footer" class="dialog-footer">
+        <el-button @click="addCurrentMapShow = false" size="medium">取 消</el-button>
+        <el-button type="primary" @click="addCurrentToMap" size="medium">
+          <i class="el-icon-check"></i> 确 定
+        </el-button>
+			</span>
+		</el-dialog>
+	</div>
+</template>
+
+<script>
+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"
+
+export default {
+	name: "EditPage",
+	components: {
+		OlMap,
+		MapToolbar,
+		RightPanel,
+		BottomInspector
+	},
+	data() {
+		return {
+			// 地图相关
+			olWidth: 0,
+			olHeight: 0,
+			mapName: 'demo',
+			
+			// 编辑模式相关
+			currentMode: 'select', // 当前编辑模式
+			pointDraSelectEnabled: true, // 元素选择模式是否启用
+			
+			// 面板显示控制
+			rightPanelVisible: true,
+			
+			// Inspector状态管理
+			inspector: {
+			visible: false,
+				mode: 'view', // 'view' | 'edit'
+				data: null,
+				loading: false
+			},
+			
+			// 右侧面板配置
+			rightPanelTabs: ['info', 'elements', 'network'],
+			
+			// 实时信息数据
+			realtimeInfo: {
+				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%'
+			},
+			
+			// 元素数据
+			pointData: [],
+			lineData: [],
+			bowData: [],
+			shapeData: [],
+			resourcesFeature: [], // 所有元素的原始Feature对象
+			currentFeature: {}, // 当前选中的元素
+			
+			// 添加当前点对话框
+			addCurrentMapShow: false,
+			currentRobotRecord: {
+				x: 0,
+				y: 0,
+				z: 0,
+				angle: 0,
+				angleEnable: false
+			},
+			
+			// 机器人位姿数据
+			robotPoseData: {
+				x: 0,
+				y: 0,
+				angle: 0
+			},
+			
+			// 页面状态
+			haveDraw: false, // 是否有未保存的更改
+		};
+	},
+	computed: {
+		// 所有元素列表(用于右侧面板)
+		allElementList() {
+			return [
+				...this.pointData,
+				...this.lineData,
+				...this.bowData,
+				...this.shapeData
+			];
+		},
+		
+		// 元素类型统计
+		elementTypeCounts() {
+			return [
+				{ key: 'point', label: '点', count: this.pointData.length },
+				{ key: 'line', label: '线', count: this.lineData.length },
+				{ key: 'curve', label: '弧', count: this.bowData.length },
+				{ key: 'polygon', label: '面', count: this.shapeData.length }
+			];
+		}
+	},
+	watch: {
+		// 监听右侧面板显隐变化
+		rightPanelVisible(visible) {
+			this.updateRightWidth(visible ? 380 : 0);
+		}
+	},
+	created() {
+		// 获取地图ID
+		this.mapName = this.$route.params.mapId || 'demo';
+	},
+	beforeDestroy() {
+		window.removeEventListener("beforeunload", this.handleBeforeUnload);
+		window.removeEventListener('resize', this.updateMapSize);
+	},
+	beforeRouteLeave(to, from, next) {
+		// 检查Inspector是否有未保存更改
+		const inspectorHasChanges = this.inspector.visible && this.inspector.mode === 'edit' && this.$refs.bottomInspector?.hasUnsavedChanges;
+		
+		// 如果页面有修改或者新增删除,则拦截*前端路由跳转*, 二次确认
+		if (this.haveDraw || inspectorHasChanges) {
+			const message = inspectorHasChanges ? 
+				'Inspector中有未保存的元素更改,确定要跳转或退出?' : 
+				'当前页面的地图修改还未保存,确定要跳转或退出?';
+				
+			this.$confirm(message, '提示', {
+			confirmButtonText: '确定',
+			cancelButtonText: '取消',
+			type: 'warning'
+		}).then(() => {
+				// 关闭Inspector
+				if (this.inspector.visible) {
+					this.inspector.visible = false;
+				}
+			next();
+		}).catch(() => {
+			next(false);
+		});
+		} else {
+			next();
+		}
+	},
+	mounted() {
+		this.updateMapSize();
+		this.initEditMode();
+		this.updateRightWidth(this.rightPanelVisible ? 380 : 0);
+		window.addEventListener('resize', this.updateMapSize);
+		window.addEventListener("beforeunload", this.handleBeforeUnload);
+	},
+	methods: {
+		// === 页面初始化相关 ===
+		
+		// 更新地图尺寸
+		updateMapSize() {
+			const mapContent = this.$refs.mapContent;
+			if (mapContent) {
+				this.olWidth = mapContent.offsetWidth;
+				this.olHeight = mapContent.offsetHeight;
+			}
+		},
+		
+		// 初始化编辑模式
+		initEditMode() {
+			this.currentMode = 'select';
+			this.pointDraSelectEnabled = true;
+		},
+		
+		// 处理页面刷新/关闭前的确认
+		handleBeforeUnload(event) {
+			if (this.haveDraw) {
+				event.preventDefault();
+				event.returnValue = "";
+			}
+		},
+		
+		// 更新右侧面板宽度变量
+		updateRightWidth(width) {
+			const host = this.$el || document.documentElement;
+			host.style.setProperty('--right-panel-width', `${width}px`);
+		},
+		
+		// === 工具栏事件处理 ===
+		
+		// 处理模式切换
+		handleModeChange(mode) {
+			this.currentMode = mode;
+			
+			// 根据模式切换地图交互状态
+			switch (mode) {
+				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('已切换到绘点模式');
+					break;
+				case 'draw-line':
+					this.pointDraSelectEnabled = false;
+					this.$refs.olmap && this.$refs.olmap.drawLine && this.$refs.olmap.drawLine();
+					this.$message.success('已切换到绘线模式');
+					break;
+				case 'draw-curve':
+					this.pointDraSelectEnabled = false;
+					this.$refs.olmap && this.$refs.olmap.drawCurve && this.$refs.olmap.drawCurve();
+					this.$message.success('已切换到绘曲线模式');
+					break;
+				case 'draw-polygon':
+					this.pointDraSelectEnabled = false;
+					this.$refs.olmap && this.$refs.olmap.drawPolygon && this.$refs.olmap.drawPolygon();
+					this.$message.success('已切换到绘区域模式');
+					break;
+			}
+		},
+		
+		// 工具栏基础操作
+		handleZoomIn() {
+			// TODO: 实现地图放大
+			this.$message.info('地图放大');
+		},
+		
+		handleZoomOut() {
+			// TODO: 实现地图缩小
+			this.$message.info('地图缩小');
+		},
+		
+		handleCenterRobot() {
+			// TODO: 实现居中到机器人
+			this.$message.info('居中到机器人');
+		},
+		
+		handleToggleFullscreen() {
+			// TODO: 实现全屏切换
+			this.$message.info('全屏切换');
+		},
+		
+		handleSaveMap() {
+			this.saveRoute();
+		},
+		
+		// === 右侧面板事件处理 ===
+		
+		// 添加当前点
+		handleAddCurrentPoint() {
+			this.addCurrentMapShow = true;
+		},
+		
+		// 元素选择
+		handleElementSelect(element) {
+			this.showInspector(element, 'edit');
+			// 在地图上选中该元素
+			this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(element.id);
+		},
+		
+		
+		// 编辑元素
+		handleElementEdit(element) {
+			this.showInspector(element, 'edit');
+		},
+		
+		// 定位元素
+		handleElementLocate(element) {
+			this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(element.id);
+			this.$message.success(`已定位到元素 ${element.id}`);
+		},
+		
+		// 删除元素
+		handleElementRemove(element) {
+			this.$confirm(`确定要删除元素 ${element.id} 吗?`, '确认删除', {
+				confirmButtonText: '确定',
+				cancelButtonText: '取消',
+				type: 'warning'
+			}).then(() => {
+				this.$refs.olmap && this.$refs.olmap.removeElement && this.$refs.olmap.removeElement(element.id);
+			});
+		},
+		
+		// 路网导出
+		handleNetworkExport() {
+			// 实际的导出逻辑应该在这里实现
+			setTimeout(() => {
+				try {
+					// TODO: 实现实际的路网导出逻辑
+					// 这里可以调用后端API或者生成文件下载
+					console.log('导出路网数据...');
+					// 成功后不需要再显示消息,因为RightPanel已经处理了
+				} catch (error) {
+					this.$message.error('路网导出失败:' + error.message);
+				}
+			}, 200);
+		},
+		
+		// 路网导入
+		handleNetworkImport(type) {
+			// 实际的导入逻辑应该在这里实现
+			setTimeout(() => {
+				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已经处理了
+				} catch (error) {
+					this.$message.error(`路网${typeMap[type] || '导入'}失败:` + error.message);
+				}
+			}, 200);
+		},
+		
+		// === Inspector事件处理 ===
+		
+		// 显示Inspector
+		showInspector(element, mode = 'edit') {
+			this.inspector.loading = true;
+			this.inspector.visible = true;
+			this.inspector.mode = mode;
+			
+			// 模拟数据加载延迟
+			setTimeout(() => {
+				// 准备元素数据
+				const elementData = this.prepareElementData(element);
+				this.inspector.data = elementData;
+				this.inspector.loading = false;
+				this.currentFeature = element;
+			}, 100);
+		},
+		
+		// 准备元素数据
+		prepareElementData(element) {
+			// 从resourcesFeature中获取完整的元素数据
+			const feature = this.resourcesFeature.find(f => f.getId() === element.id);
+			if (feature) {
+				const properties = feature.getProperties();
+				const geometry = feature.getGeometry();
+				
+				// 合并基础信息和属性
+				return {
+					...element,
+					...properties,
+					type: geometry.getType(),
+					position: geometry.getType() === 'Point' ? geometry.getCoordinates() : undefined
+				};
+			}
+			return element;
+		},
+		
+		// Inspector编辑模式
+		handleInspectorEdit() {
+			this.inspector.mode = 'edit';
+		},
+		
+		// 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);
+					}
+				}
+			});
+			
+			// 更新当前特征数据
+			this.currentFeature = { ...elementData };
+			this.haveDraw = true;
+			
+			// 刷新右侧列表中的元素信息
+			this.updateElementInList(elementData);
+			
+			// 关闭Inspector
+			this.inspector.visible = false;
+			
+			// 在地图上保持选中状态
+			setTimeout(() => {
+				this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(elementData.id);
+			}, 100);
+		},
+		
+		// Inspector取消
+		handleInspectorCancel() {
+			this.inspector.visible = false;
+			this.inspector.mode = 'view';
+		},
+		
+		// Inspector关闭
+		handleInspectorClose() {
+			this.inspector.visible = false;
+			this.inspector.mode = 'view';
+			this.inspector.data = null;
+		},
+		
+		// Inspector定位
+		handleInspectorLocate(element) {
+			this.$refs.olmap && this.$refs.olmap.selectShowEle && this.$refs.olmap.selectShowEle(element.id);
+			// 可以添加地图缩放和居中逻辑
+		},
+		
+		
+		// Inspector高度变化
+		handleInspectorHeightChange(height) {
+			// 可以在这里处理高度变化后的布局调整
+			console.log('Inspector高度变化为:', height);
+		},
+		
+		// 更新元素列表中的元素信息
+		updateElementInList(elementData) {
+			const updateElementInArray = (array) => {
+				const index = array.findIndex(item => item.id === elementData.id);
+				if (index !== -1) {
+					array[index] = { ...array[index], ...elementData };
+				}
+			};
+			
+			// 根据ID前缀更新对应数组
+			if (elementData.id.startsWith('p')) {
+				updateElementInArray(this.pointData);
+			} else if (elementData.id.startsWith('l')) {
+				updateElementInArray(this.lineData);
+			} else if (elementData.id.startsWith('b')) {
+				updateElementInArray(this.bowData);
+			} else if (elementData.id.startsWith('s')) {
+				updateElementInArray(this.shapeData);
+			}
+		},
+		
+		// === 地图事件处理(保留原有逻辑)===
+		
+		// 地图元素加载完毕的回执
+		elementRoadInitEnd(features) {
+			features.forEach(item => {
+				// 检查 item.values_ 和 item.values_.id 是否存在
+				if (item.values_ && item.values_.id) {
+					const id = item.values_.id;
+					const data = {
+						id: id,
+						name: item.values_.name,
+						type: item.getGeometry().getType()
+					};
+					// 根据 id 前缀分类存入对应数组
+					if (id.startsWith('p')) {
+						this.pointData.push(data);
+					} else if (id.startsWith('l')) {
+						this.lineData.push(data);
+					} else if (id.startsWith('b')) {
+						this.bowData.push(data);
+					} else if (id.startsWith('s')) {
+						this.shapeData.push(data);
+					}
+				}
+			});
+			// 对分类后的数组按数字部分排序
+			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;
+		},
+		// 某个元素绘制完成的回调
+		elementRoadDrawEnd(feature) {
+			const id = feature.values_.id || feature.getId();
+			const data = {
+				id: id,
+				name: feature.values_.name,
+				type: feature.getGeometry().getType()
+			};
+			if (id.startsWith('p')) {
+				this.pointData.push(data);
+				// 按照类型为feature初始化基础参数和高级参数(点)
+				this.initElelmentParamsPonint(feature);
+			} else if (id.startsWith('l')) {
+				this.lineData.push(data);
+				// 按照类型为feature初始化基础参数和高级参数(线)
+				this.initElelmentParamsBowOrLine(feature);
+			} else if (id.startsWith('b')) {
+				this.bowData.push(data);
+				// 按照类型为feature初始化基础参数和高级参数(曲线)
+				this.initElelmentParamsBowOrLine(feature);
+			} else if (id.startsWith('s')) {
+				this.shapeData.push(data);
+				// 按照类型为feature初始化基础参数和高级参数(面)
+				this.initElelmentParamsSnape(feature);
+			}
+			// 标记当前存在绘制的操作,拦截使用
+			this.haveDraw = true; // 是否操作过页面元素(新增修改或者删除元素) (在切换页面或者刷新时进行拦截,避免误操作退出页面)
+		},
+		// 查看某个元素
+		watchEle(id) {
+			// 调用地图组件显示元素
+			this.$refs.olmap.selectShowEle(id);
+		},
+		// 查看某个元素的子组件回调
+		selectShowEleResult(feature) {
+			const geometry = feature.getGeometry();
+			console.log(geometry.getCoordinates());
+			
+			if (geometry.getType() == 'Point') {
+				// 如果是点位则临时保存xyz和类型到参数用于渲染(保存时会移除)  注:保存元素坐标为参数用于对应表单数据,否则需要用方法获取,无法双向绑定
+				feature.set('position', geometry.getCoordinates())
+			} else if (geometry.getType() == "LineString") {
+				// 解析线段类型的direct参数用于渲染和修改(保存时需要渲染回原始数据模式,切记)
+				const value = feature.values_.direct - 100 || 0;
+				const binary = value.toString(2).padStart(4, '0');
+				const result = [
+					binary[0] === '1',  // bit3
+					binary[1] === '1',  // bit2
+					binary[2] === '1',  // bit1
+					binary[3] === '1'   // bit0
+				];
+				// 临时参数渲染使用, 保存时会删除
+				feature.set('directList', result);
+			}
+			// 临时保存类型到参数用于渲染(保存时回移除)
+			feature.set('typeEle', geometry.getType())
+			
+			// 准备元素数据并显示Inspector
+			const elementData = {
+				id: feature.getId(),
+				name: feature.get('name') || '',
+				type: geometry.getType(),
+				...feature.getProperties()
+			};
+			
+			this.showInspector(elementData, 'edit');
+		},
+		// 移除某个元素
+		removeElement(id) {
+			// 调用地图组件移除元素
+			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);
+				}
+			}
+			if (id.startsWith('s')) {
+				const index = this.shapeData.findIndex(item => item.id === id);
+				if (index !== -1) {
+					this.shapeData.splice(index, 1);
+				}
+			}
+			// 判断当前选择元素是否被删除,如果是则删除
+			if (this.currentFeature && this.currentFeature.id == id) {
+				this.currentFeature = {};
+			}
+			// 删除源features中的此元素
+			const index = this.resourcesFeature.findIndex(feature => feature.getId() == id);
+			// 如果找到了该元素,则删除
+			if (index !== -1) {
+				this.resourcesFeature.splice(index, 1);
+			}
+			this.haveDraw = true;
+		},
+		// 初始化点位元素基础和高级参数
+		initElelmentParamsPonint(feature) {
+			feature.set('name', '');
+			feature.set('yaw', 0);	// 弧度
+			// 如果是通过实时位姿则需要判断是否有值,有则设置
+			feature.set('isyawfix', feature.get('isyawfix') ? true : false); // 偏航使能
+			feature.set('taskid', 0);
+			feature.set('offset', 0);
+			feature.set('pitch', 0)
+			feature.set('roll', 0)
+			this.resourcesFeature.push(feature);
+		},
+		// 初始化执行元素基础和高级参数
+		initElelmentParamsBowOrLine(feature) {
+			feature.set('name', '');
+			// direct中的值首先需要减去100,结果的后4位bit位分别代表如下含义:
+			// bit3:为1时,表示允许车辆从起点行驶到终点,且机器人以正常前行的方式行驶;
+			// bit2:为1时,表示允许车辆从起点行驶到终点,且机器人以倒车的方式行驶;
+			// bit1:为1时,表示允许车辆从终点行驶到起点,且机器人以正常前行的方式行驶;
+			// bit0:为1时,表示允许车辆从终点行驶到起点,且机器人以倒车的方式行驶。
+			// 举例: direct=105(bit2和bit0为1),表示为双向车道,但是不论机器人从起点前往终点,还是从终点前往起点,都会以倒车的方式行驶。
+			feature.set('direct', 110); // 常用 110:起点至终点 + 终点至起点(双方向) 正向行驶    108:起点至终点 正向行驶
+			feature.set('maxspeed', 8) // 最大限速  m/s
+			feature.set('minspeed', 0) // 最小限速  m/s
+			feature.set('lanewidth', 1) // 车道宽度
+			feature.set('leftlanenum', 0) //前进方向左侧车道数
+			feature.set('rightlanenum', 0) //前进方向右侧车道数
+			feature.set('obstype', 0) // 避障方式,0:停车等待,1:车道绕障,2:路网绕障,默认为0
+			feature.set('obsvalue', 200) // 障碍物参数(例如:权重值或影响范围)
+			feature.set('followdis', 0.4) // 跟随距离
+			feature.set('runtype', 0) // 运行类型
+			feature.set('selftheta', 0.5) // 自身角度
+			feature.set('xytolerance', 0.2) // 平面坐标容差
+			feature.set('s2eforward', 0) // 机器人沿当前线段从起点到达终点时多行驶(负数则为少行驶)的距离,单位为米,默认为0
+			feature.set('e2sforward', 0) // 机器人沿当前线段从终点到达起点时多行驶的距离,单位为米,默认为0
+			feature.set('thtolerance', 0) // 角度容差
+			feature.set('mintheta', 0) // 最小转向角
+			feature.set('maxtheta', 1) // 最大转向角
+
+			this.resourcesFeature.push(feature);
+		},
+		// 初始化面元素基础和高级参数
+		initElelmentParamsSnape(feature) {
+			feature.set('name', '');
+			feature.set('typeEle', feature.getGeometry().getType()); // 保存元素类型为参数用于动态绑定对应的元素修改表单,否则需要用方法获取
+			feature.set('color', "#7EFFFA");
+			//区域类型,说明: 0:隔离区域,限定机器人只能在该区域活动;1:装饰区域,仅人机交互用,不影响导航;
+			// 2:禁行区域,不允许机器人进入该区域;3:会车管制区,特定场景使用;
+			// 4:道闸管控区,特定场景使用;11:GPS定位区,进入该区域后,从激光定位切换到GPS定位;
+			// 22:动态禁行区,特定场景使用。
+			feature.set('type', 1);
+			feature.set('transparent', 200);
+			this.resourcesFeature.push(feature);
+		},
+		/**
+		 * 点位坐标修改
+		 */
+		positionEdit() {
+			this.haveDraw = true;
+			this.$refs.olmap.pointPositionUpdate(this.currentFeature.id, this.currentFeature.position);
+		},
+		// 自定义参数修改提交
+		setProperties() {
+			if (!this.currentFeature) return;
+			this.resourcesFeature.forEach(item => {
+				if (item.getId() == this.currentFeature.id) {
+					// 额外参数处理 线
+					if (this.currentFeature.id.startsWith('l') || this.currentFeature.id.startsWith('b')) {
+						// direct还原
+						let directList = this.currentFeature.directList;
+						// 将布尔集合转成4位bit集合, 再转成10进制, 加100还原成原始报文
+						const binaryString = directList.map(bit => (bit ? '1' : '0')).join('');
+						const value = parseInt(binaryString, 2);
+						const direct = value + 100;
+						this.currentFeature.direct = direct;
+					}
+					// 处理完毕后先刷新对象到画布
+					item.setProperties(this.currentFeature)
+					// 刷新完毕后用自定义参数处理其他需要修改完毕能看到的元素效果(例如修改name或者图形颜色等)
+					if (this.currentFeature.id.startsWith('s')) {
+						// 额外处理多边形的效果
+						// 1. 修改图形的颜色,文本
+						this.$refs.olmap.editSnapeColor(this.currentFeature.id);
+					}
+				}
+			})
+			this.haveDraw = true; // 是否操作过页面元素(新增修改或者删除元素) (在切换页面或者刷新时进行拦截,避免误操作退出页面)
+		},
+		// 保存路网
+		saveRoute() {
+			// 保存时清理当前所有对象的额外渲染参数
+			this.resourcesFeature.forEach(feature => {
+				const values = feature.getProperties(); // 获取feature的values_自定义属性
+				// 检查并删除特定的属性(如果存在)
+				if (values) {
+					if (values.hasOwnProperty('typeEle')) {
+						delete values.typeEle;  // 删除 typeEle 属性
+					}
+					if (values.hasOwnProperty('position')) {
+						delete values.position;  // 删除 position 属性
+					}
+					if (values.hasOwnProperty('directList')) {
+						delete values.directList;  // 删除 directList 属性
+					}
+				}
+			});
+		},
+		// === 添加当前点对话框处理 ===
+		
+		// 打开添加当前点对话框
+		openCurrentToMapDialog() {
+			this.currentRobotRecord = {
+				x: this.robotPoseData.x || 0,
+				y: this.robotPoseData.y || 0,
+				z: 0,
+				angle: this.robotPoseData.angle || 0,
+				angleEnable: false
+			};
+		},
+		
+		// 添加当前点到地图
+		addCurrentToMap() {
+			this.$refs.currentPointForm.validate((valid) => {
+				if (valid) {
+			this.addCurrentMapShow = false;
+			// 使用组件的根据坐标绘制点方法
+					const data = [
+						{ yaw: this.currentRobotRecord.angle }, 
+						{ isyawfix: this.currentRobotRecord.angleEnable }
+					];
+					this.$refs.olmap && this.$refs.olmap.createPointAtCoordinate && 
+					this.$refs.olmap.createPointAtCoordinate(
+						[this.currentRobotRecord.x, this.currentRobotRecord.y, this.currentRobotRecord.z || 0], 
+						'', 
+						data
+					);
+					this.$message.success('已添加当前位姿点到地图');
+				}
+			});
+		}
+	}
+};
+</script>
+
+<style lang="scss" scoped>
+@import './components/shared/_map-shared.scss';
+
+.edit-page {
+  height: 100vh;
+  display: flex;
+  flex-direction: column;
+  background: #f5f7fa;
+	position: relative;
+  overflow: hidden;
+  
+  /* CSS变量:定义安全区域 */
+  --left-safe: 88px;                    /* 左边工具栏+内边距的安全距离 */
+  --right-panel-width: 380px;           /* 右侧面板宽度,由JS动态更新 */
+  --right-gutter: 32px;                 /* 右侧间隙 */
+  --right-safe: calc(var(--right-panel-width) + var(--right-gutter));
+}
+
+.map-container {
+  flex: 1;
+  display: flex;
+  position: relative;
+  min-height: 0;
+}
+
+.toolbar-container {
+  position: absolute;
+  top: 16px;
+  left: 16px;
+  z-index: $map-z-index-toolbar;
+  pointer-events: none;
+  
+  > * {
+    pointer-events: auto;
+  }
+}
+
+#map-stage {
+  flex: 1;
+  position: relative;  /* 让子元素 absolute 以此为参照 */
+  background: #f5f5f5;
+  overflow: hidden;    /* 防止子元素视觉溢出 */
+}
+
+.panel-container {
+  // 编辑页面板现在使用 fixed 定位,容器不需要特殊样式
+  pointer-events: none;
+  
+  > * {
+    pointer-events: auto;
+  }
+}
+
+// 对话框样式优化
+:deep(.el-dialog) {
+  border-radius: 12px;
+  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
+  
+  .el-dialog__header {
+    padding: 20px 24px 10px;
+    border-bottom: 1px solid #e4e7ed;
+    
+    .el-dialog__title {
+      font-size: 18px;
+      font-weight: 600;
+      color: #303133;
+    }
+  }
+  
+  .el-dialog__body {
+    padding: 20px 24px;
+  }
+  
+  .el-dialog__footer {
+    padding: 10px 24px 20px;
+    text-align: right;
+    
+    .el-button {
+      margin-left: 12px;
+      
+      &:first-child {
+        margin-left: 0;
+      }
+    }
+  }
+}
+
+// 表单样式优化
+:deep(.el-form) {
+  .el-form-item {
+    margin-bottom: 18px;
+    
+    .el-form-item__label {
+      font-weight: 500;
+      color: #374151;
+      line-height: 1.6;
+    }
+    
+    .el-form-item__content {
+      line-height: 1.6;
+    }
+  }
+  
+  .el-input {
+    .el-input__inner {
+      border-radius: 8px;
+      border: 1px solid #d1d5db;
+      transition: all 0.2s ease;
+      
+      &:focus {
+        border-color: #3b82f6;
+        box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+      }
+    }
+  }
+  
+  .el-input-number {
+	width: 100%;
+    
+    .el-input__inner {
+      border-radius: 8px;
+      border: 1px solid #d1d5db;
+      text-align: left;
+      
+      &:focus {
+        border-color: #3b82f6;
+        box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
+      }
+    }
+  }
+  
+  .el-switch {
+    .el-switch__core {
+      border-radius: 12px;
+    }
+  }
+}
+
+// 响应式适配
+@media (max-width: 1200px) {
+  .panel-container {
+    right: 8px;
+    top: 8px;
+  }
+  
+  .toolbar-container {
+    left: 8px;
+    top: 8px;
+  }
+}
+
+@media (max-width: 768px) {
+  .edit-page {
+    .map-container {
+	flex-direction: column;
+    }
+    
+    .panel-container {
+      position: relative;
+      top: auto;
+      right: auto;
+	width: 100%;
+      z-index: auto;
+    }
+    
+    .toolbar-container {
+	position: relative;
+      top: auto;
+      left: auto;
+      z-index: auto;
+	margin-bottom: 8px;
+    }
+  }
+}
+
+// 暗色主题适配
+html.dark {
+  .edit-page {
+    background: #1f2937;
+    
+    .map-content {
+      background: #374151;
+    }
+  }
+}
+
+// 动画效果
+@keyframes fadeInUp {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.edit-page {
+  animation: fadeInUp 0.3s ease-out;
+}
+
+/* === 添加当前点弹框样式(参考导航页目标点编辑弹框) === */
+/* 全局弹框居中样式 */
+.centered-dialog {
+  display: flex !important;
+  align-items: center !important;
+  justify-content: center !important;
+}
+
+.add-pose-dialog {
+  ::v-deep .el-dialog {
+    border-radius: 12px;
+    overflow: hidden;
+    box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
+    margin: 0 auto;
+    position: relative;
+    top: 50%;
+    transform: translateY(-50%);
+    max-height: 90vh;
+    overflow-y: auto;
+  }
+
+  /* 确保弹框在页面中居中 */
+  ::v-deep .el-dialog__wrapper {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    min-height: 100vh;
+  }
+  
+  /* 移除默认的margin-top */
+  ::v-deep .el-dialog {
+    margin-top: 0 !important;
+    margin-bottom: 0 !important;
+  }
+
+  ::v-deep .el-dialog__header {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    padding: 20px 24px;
+    margin: 0;
+    border-radius: 12px 12px 0 0;
+
+    .el-dialog__title {
+      color: white;
+      font-size: 18px;
+      font-weight: 600;
+    }
+    
+    .el-dialog__close {
+      color: white;
+      font-size: 18px;
+      
+      &:hover {
+        color: #f0f0f0;
+      }
+    }
+  }
+  
+  ::v-deep .el-dialog__body {
+    padding: 24px;
+    background: #f8fafc;
+    margin: 0;
+    max-height: calc(90vh - 120px);
+    overflow-y: auto;
+  }
+  
+  ::v-deep .el-dialog__footer {
+    padding: 16px 24px 24px;
+    background: #f8fafc;
+    border-top: 1px solid #e2e8f0;
+    border-radius: 0 0 12px 12px;
+    margin: 0;
+  }
+}
+
+.pose-form {
+  .el-form-item {
+    margin-bottom: 14px;
+	display: flex;
+	align-items: center;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    .el-form-item__content {
+      flex: 1;
+      margin-left: 0;
+    }
+  }
+  
+  .coordinate-input {
+    margin-bottom: 4px;
+  }
+  
+  .el-form-item__label {
+    font-weight: 600;
+    color: #2d3748;
+    padding-right: 12px;
+    min-width: 100px;
+    font-size: 14px;
+    line-height: 44px;
+    height: 44px;
+	display: flex;
+	align-items: center;
+    margin-bottom: 0;
+  }
+  
+  .coordinate-input {
+    ::v-deep .el-input__inner {
+      border-radius: 8px;
+      border: 1px solid #e2e8f0;
+      height: 44px;
+      font-size: 15px;
+      padding: 0 16px;
+      background: #ffffff;
+      transition: all 0.2s ease;
+      
+      &:focus {
+        border-color: #667eea;
+        box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
+        background: #fafbff;
+      }
+      
+      &:hover {
+        border-color: #cbd5e0;
+      }
+    }
+  }
+  
+  ::v-deep .el-input-number {
+    width: 100%;
+    
+    .el-input__inner {
+      border-radius: 6px;
+      border: 1px solid #e2e8f0;
+      height: 40px;
+      font-size: 14px;
+      padding: 0 12px;
+      
+      &:focus {
+        border-color: #667eea;
+        box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.1);
+      }
+    }
+  }
+}
+
+.dialog-content {
+  .form-section {
+    background: white;
+    border-radius: 10px;
+    padding: 20px;
+    margin-bottom: 16px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    border: 1px solid #f1f5f9;
+    
+    &:last-child {
+      margin-bottom: 0;
+    }
+    
+    .section-title {
+      display: flex;
+      align-items: center;
+      margin: 0 0 16px 0;
+      font-size: 15px;
+      font-weight: 600;
+      color: #2d3748;
+      border-bottom: 2px solid #e2e8f0;
+      padding-bottom: 8px;
+      
+      i {
+        margin-right: 10px;
+        color: #667eea;
+        font-size: 18px;
+      }
+    }
+  }
+}
 </style>