Преглед на файлове

优化地图列表页面

yawuga преди 8 месеца
родител
ревизия
20db594b22
променени са 3 файла, в които са добавени 783 реда и са изтрити 195 реда
  1. 134 42
      src/components/XtMapCard/index.vue
  2. 40 0
      src/utils/route-helpers.js
  3. 609 153
      src/views/map/maplist/index.vue

+ 134 - 42
src/components/XtMapCard/index.vue

@@ -59,25 +59,27 @@
 
     <!-- 操作按钮区 -->
     <div class="card-actions" :class="{ 'show-on-mobile': true }">
-      <el-button 
-        type="primary" 
-        size="mini" 
-        icon="el-icon-position"
-        @click="handleNavigation"
-        :aria-label="`导航到${item.name}`"
-      >
-        导航
-      </el-button>
+      <router-link :to="buildNavTo(this, item)" class="card-op-link">
+        <el-button 
+          type="primary" 
+          size="mini" 
+          icon="el-icon-position"
+          :aria-label="`导航到${item.name}`"
+        >
+          导航
+        </el-button>
+      </router-link>
       
-      <el-button 
-        type="default" 
-        size="mini" 
-        icon="el-icon-edit-outline"
-        @click="handleEdit"
-        :aria-label="`编辑${item.name}`"
-      >
-        编辑
-      </el-button>
+      <router-link :to="buildEditTo(this, item)" class="card-op-link">
+        <el-button 
+          type="default" 
+          size="mini" 
+          icon="el-icon-edit-outline"
+          :aria-label="`编辑${item.name}`"
+        >
+          编辑
+        </el-button>
+      </router-link>
       
       <el-dropdown 
         @command="handleMoreAction"
@@ -90,19 +92,12 @@
           icon="el-icon-more"
           :aria-label="`更多操作${item.name}`"
         />
-        <el-dropdown-menu slot="dropdown">
-          <el-dropdown-item command="publish" icon="el-icon-upload2">
-            发布地图
-          </el-dropdown-item>
-          <el-dropdown-item command="copy" icon="el-icon-document-copy">
-            复制地图
-          </el-dropdown-item>
-          <el-dropdown-item command="download" icon="el-icon-download">
-            下载地图
-          </el-dropdown-item>
-          <el-dropdown-item command="delete" icon="el-icon-delete" class="danger-item">
-            删除地图
-          </el-dropdown-item>
+        <el-dropdown-menu slot="dropdown" class="xt-more">
+          <el-dropdown-item command="rename"><i class="el-icon-edit"></i> 重命名</el-dropdown-item>
+          <el-dropdown-item command="download"><i class="el-icon-download"></i> 下载地图</el-dropdown-item>
+          <el-dropdown-item command="build"><i class="el-icon-cpu"></i> 构建地图</el-dropdown-item>
+          <el-dropdown-item divided command="delete" class="is-danger"><i class="el-icon-delete"></i> 删除地图</el-dropdown-item>
+          <el-dropdown-item divided command="calibrate"><i class="el-icon-position"></i> 坐标系标定</el-dropdown-item>
         </el-dropdown-menu>
       </el-dropdown>
     </div>
@@ -111,6 +106,7 @@
 
 <script>
 import { formatDateTimeCompat, pickUpdatedAt } from '@/utils/datefmt'
+import { buildNavTo, buildEditTo, buildCalibrateTo, buildConstructTo, pickId } from '@/utils/route-helpers'
 
 export default {
   name: 'XtMapCard',
@@ -174,6 +170,13 @@ export default {
     // 格式化时间
     formatDateTimeCompat,
     pickUpdatedAt,
+
+    // 路由辅助函数
+    buildNavTo,
+    buildEditTo,
+    buildCalibrateTo,
+    buildConstructTo,
+    pickId,
     
     // 处理选择变化
     handleSelectChange(checked) {
@@ -193,8 +196,75 @@ export default {
     },
 
     // 更多操作
-    handleMoreAction(action) {
-      this.$emit('clickMore', this.item.id, action)
+    async handleMoreAction(cmd) {
+      const row = this.item;
+      const id = pickId(row);
+      
+      switch(cmd) {
+        case 'rename':
+          // 优先:已有重命名弹框/方法
+          if (typeof this.openRenameDialog === 'function') return this.openRenameDialog(row);
+          if (this.renameDialog) { this.renameDialog.visible = true; this.renameDialog.row = row; return; }
+          // 兜底:Element prompt
+          this.$prompt('请输入新的地图名称', '重命名', {
+            inputValue: row.mapName || row.name || '',
+            inputPattern: /\S+/,
+            inputErrorMessage: '名称不能为空'
+          }).then(({ value }) => {
+            // 若有旧方法:this.renameMap(id, value)
+            if (typeof this.renameMap === 'function') return this.renameMap(id, value);
+            row.mapName = value; 
+            row.name = value;
+            this.$message.success('已重命名'); // mock 刷新
+          }).catch(()=>{});
+          break;
+
+        case 'download':
+          // 触发父组件的下载对话框
+          this.$emit('clickMore', this.item.id, 'download');
+          break;
+
+        case 'build':
+          // 优先:旧路由/方法
+          if (typeof this.openBuildDialog === 'function') return this.openBuildDialog(row);
+          if (typeof this.constructOpen === 'boolean') {
+            this.$parent.constructOpen = true;
+            this.$parent.title = `构建地图 - ${row.name || row.mapName || ''}`;
+            this.$parent.currentRow = row;
+            return;
+          }
+          if (typeof this.toBuildPage === 'function') return this.toBuildPage(row);
+          return this.$router.push(buildConstructTo(row));
+
+        case 'delete':
+          // 优先:旧删除方法
+          if (typeof this.handleDelete === 'function') {
+            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
+              .then(() => this.handleDelete([id]))
+              .catch(() => {});
+          }
+          if (typeof this.deleteMap === 'function') {
+            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
+              .then(() => this.deleteMap(id))
+              .catch(() => {});
+          }
+          this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
+            .then(() => { 
+              this.$emit && this.$emit('remove', row); 
+              this.$message.success('已删除(mock)'); 
+            })
+            .catch(() => {});
+          break;
+
+        case 'calibrate':
+          // 优先:旧路由/方法
+          if (typeof this.openCalibration === 'function') return this.openCalibration(row);
+          return this.$router.push(buildCalibrateTo(row));
+          
+        default:
+          // 兼容原有的操作
+          this.$emit('clickMore', this.item.id, cmd);
+      }
     },
 
     // 图片加载失败
@@ -379,23 +449,37 @@ export default {
       }
     }
 
-    .el-button {
+    .card-op-link {
+      display: inline-flex;
+      align-items: center;
+      text-decoration: none;
       flex: 1;
-      border-radius: var(--radius-base);
-      font-size: var(--font-size-xs);
-      padding: 6px 12px;
 
-      &:last-child {
-        flex: 0 0 auto;
-        min-width: 32px;
-        padding: 6px 8px;
+      &:hover {
+        text-decoration: none;
       }
+
+      .el-button {
+        width: 100%;
+        border-radius: var(--radius-base);
+        font-size: var(--font-size-xs);
+        padding: 6px 12px;
+      }
+    }
+
+    .el-button {
+      flex: 0 0 auto;
+      min-width: 32px;
+      padding: 6px 8px;
+      border-radius: var(--radius-base);
+      font-size: var(--font-size-xs);
     }
   }
 }
 
 // 下拉菜单危险项样式
-::v-deep .danger-item {
+::v-deep .danger-item,
+::v-deep .is-danger {
   color: var(--color-danger);
 
   &:hover {
@@ -403,6 +487,14 @@ export default {
   }
 }
 
+// 更多菜单样式
+::v-deep .xt-more .is-danger { 
+  color: #e11d48; 
+}
+::v-deep .xt-more .is-danger i { 
+  color: #e11d48; 
+}
+
 // 暗色主题适配
 html.dark {
   .map-card {

+ 40 - 0
src/utils/route-helpers.js

@@ -0,0 +1,40 @@
+// 读取记录的主键,兼容多种 ID 字段名
+export function pickId(row) {
+  return row?.id ?? row?.mapId ?? row?.map_id ?? row?.ID ?? row?.MapId;
+}
+
+// === 根据真实路由配置构造跳转对象 ===
+
+// Nav route: name: 'Navigation', path: '/map/maplist/navigation/index/:mapId(\\d+)', params: { mapId }
+export function buildNavTo(row) {
+  const id = pickId(row);
+  return { 
+    name: 'Navigation', 
+    params: { mapId: id } 
+  };
+}
+
+// Edit route: name: 'Edit', path: '/map/maplist/edit/index/:mapId(\\d+)', params: { mapId }
+export function buildEditTo(row) {
+  const id = pickId(row);
+  return { 
+    name: 'Edit', 
+    params: { mapId: id } 
+  };
+}
+
+// Calibration route: name: 'Calibration', path: '/map/maplist/calibration/index/:mapId(\\d+)', params: { mapId }
+export function buildCalibrateTo(row) {
+  const id = pickId(row);
+  return { 
+    name: 'Calibration', 
+    params: { mapId: id } 
+  };
+}
+
+// Build route (fallback implementation as no specific route was found)
+export function buildConstructTo(row) {
+  const id = pickId(row);
+  // 使用通用路径,因为未找到特定的构建地图路由
+  return { path: '/map/build', query: { id } };
+}

+ 609 - 153
src/views/map/maplist/index.vue

@@ -58,16 +58,6 @@
           新建地图
         </el-button>
         
-        <el-button
-          v-if="selectedMaps.length > 0"
-          type="danger"
-          icon="el-icon-delete"
-          size="small"
-          @click="handleBatchDelete"
-        >
-          批量删除
-        </el-button>
-        
         <el-button
           class="xt-btn"
           :disabled="selectedMaps.length !== 1"
@@ -148,8 +138,8 @@
         <!-- 地图卡片列表 -->
         <div v-else class="card-grid" :class="{ compact: isCompactMode }">
           <XtMapCard
-            v-for="item in mapList"
-            :key="item.id"
+            v-for="item in displayedList"
+            :key="item.id || item.mapId || item.map_id"
             :item="item"
             :selectable="selectedMaps.length > 0"
             :selected="selectedMaps.includes(item.id)"
@@ -168,7 +158,7 @@
           <el-table 
             ref="mapTable"
             v-loading="loading" 
-            :data="mapList" 
+            :data="displayedList" 
             @selection-change="handleTableSelectionChange" 
             class="map-table"
             :fit="true"
@@ -230,28 +220,30 @@
     </template>
             </el-table-column>
             
-            <el-table-column label="操作" width="220" header-align="center" align="right">
+                        <el-table-column label="操作" width="220" header-align="center" align="right">
               <template slot-scope="{ row }">
                 <div class="op-cell">
-                  <el-button type="text" size="mini" @click="goNav(row)">
+                  <router-link :to="buildNavTo(this, row)" class="op-link">
                     <i class="el-icon-position"></i><span class="op-text">导航</span>
-                  </el-button>
-                  <el-button type="text" size="mini" @click="goEdit(row)">
+                  </router-link>
+                  <router-link :to="buildEditTo(this, row)" class="op-link">
                     <i class="el-icon-edit"></i><span class="op-text">编辑</span>
-                  </el-button>
+                  </router-link>
                   <el-dropdown @command="cmd=>onMore(row,cmd)">
                     <span class="el-dropdown-link">
                       <i class="el-icon-more"></i><span class="op-text">更多</span>
                     </span>
-              <el-dropdown-menu slot="dropdown">
-                      <el-dropdown-item command="publish">发布</el-dropdown-item>
-                      <el-dropdown-item command="delete">删除</el-dropdown-item>
-                      <el-dropdown-item command="download">下载</el-dropdown-item>
-              </el-dropdown-menu>
-            </el-dropdown>
+                    <el-dropdown-menu slot="dropdown" class="xt-more">
+                      <el-dropdown-item command="rename"><i class="el-icon-edit"></i> 重命名</el-dropdown-item>
+                      <el-dropdown-item command="download"><i class="el-icon-download"></i> 下载地图</el-dropdown-item>
+                      <el-dropdown-item command="build"><i class="el-icon-cpu"></i> 构建地图</el-dropdown-item>
+                      <el-dropdown-item divided command="delete" class="is-danger"><i class="el-icon-delete"></i> 删除地图</el-dropdown-item>
+                      <el-dropdown-item divided command="calibrate"><i class="el-icon-position"></i> 坐标系标定</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </el-dropdown>
                 </div>
-          </template>
-        </el-table-column>
+              </template>
+            </el-table-column>
       </el-table>
       </div>
     </template>
@@ -281,59 +273,77 @@
         :current-page="currentPage"
         :page-sizes="[12, 24, 48, 96]"
         :page-size="pageSize"
-        :total="total"
+        :total="totalCount"
         layout="total, sizes, prev, pager, next, jumper"
         @size-change="handleSizeChange"
         @current-change="handlePageChange"
       />
               </div>
-    <!-- 探索规则配置对话框 -->
-    <el-dialog :visible.sync="exploreOpen" width="560px" append-to-body :close-on-click-modal="false" class="xt-explore-dialog">
+        <!-- 自主探索配置对话框 -->
+    <el-dialog :visible.sync="autoExploreOpen" width="560px" append-to-body :close-on-click-modal="false" class="xt-explore-dialog">
       <span slot="title" class="xt-title">
         自主探索
         <small class="xt-subtitle">为所选地图配置探索规则 · {{ selectedMapName }}</small>
-                    </span>
+      </span>
       <el-form ref="form" :model="exploreParams" :rules="rules" label-width="120px" class="xt-form">
         <el-form-item label="最大探索时间" prop="maxTime">
           <div class="xt-field">
-            <el-input-number v-model="exploreParams.maxTime" :min="1" :step="1" controls-position="right" />
+            <el-input-number 
+              v-model="exploreParams.maxTime" 
+              :min="1" 
+              :step="1" 
+              controls-position="right" 
+              placeholder="请输入时间"
+            />
             <span class="xt-unit">分钟</span>
-            <span class="xt-hint">建议 1–10</span>
-                </div>
-          <div class="xt-presets">
-            <el-tag size="mini" @click.native="exploreParams.maxTime=1">1</el-tag>
-            <el-tag size="mini" @click.native="exploreParams.maxTime=3">3</el-tag>
-            <el-tag size="mini" @click.native="exploreParams.maxTime=5">5</el-tag>
-              </div>
+            <span class="xt-hint">建议 1-10 分钟</span>
+          </div>
         </el-form-item>
         <el-form-item label="最远探索距离" prop="maxDistance">
           <div class="xt-field">
-            <el-input-number v-model="exploreParams.maxDistance" :min="1" :step="1" controls-position="right" />
+            <el-input-number 
+              v-model="exploreParams.maxDistance" 
+              :min="1" 
+              :step="1" 
+              controls-position="right" 
+              placeholder="请输入距离"
+            />
             <span class="xt-unit">米</span>
-            <span class="xt-hint">建议 ≤ 500</span>
-      </div>
-          <div class="xt-presets">
-            <el-tag size="mini" @click.native="exploreParams.maxDistance=50">50</el-tag>
-            <el-tag size="mini" @click.native="exploreParams.maxDistance=100">100</el-tag>
-            <el-tag size="mini" @click.native="exploreParams.maxDistance=200">200</el-tag>
+            <span class="xt-hint">建议不超过 500 米</span>
           </div>
         </el-form-item>
         <el-form-item label="最大探索范围" prop="maxRange">
           <div class="xt-field">
-            <el-input-number v-model="exploreParams.maxRange" :min="1" :step="1" controls-position="right" />
+            <el-input-number 
+              v-model="exploreParams.maxRange" 
+              :min="1" 
+              :step="1" 
+              controls-position="right" 
+              placeholder="请输入范围"
+            />
             <span class="xt-unit">平方米</span>
-            <span class="xt-hint">建议 100–5000</span>
-          </div>
-          <div class="xt-presets">
-            <el-tag size="mini" @click.native="exploreParams.maxRange=100">100</el-tag>
-            <el-tag size="mini" @click.native="exploreParams.maxRange=500">500</el-tag>
-            <el-tag size="mini" @click.native="exploreParams.maxRange=1000">1000</el-tag>
+            <span class="xt-hint">建议 100-5000 平方米</span>
           </div>
         </el-form-item>
       </el-form>
       <span slot="footer" class="dialog-footer xt-footer">
-        <el-button plain @click="exploreCancel">取 消</el-button>
-        <el-button type="primary" @click="submitExplore">开 始</el-button>
+        <el-button plain @click="autoExploreCancel">取 消</el-button>
+        <el-button type="primary" @click="submitAutoExplore">开 始</el-button>
+      </span>
+    </el-dialog>
+
+    <!-- 遥控探索确认对话框 -->
+    <el-dialog :visible.sync="remoteExploreOpen" width="480px" append-to-body :close-on-click-modal="false" class="xt-confirm-dialog">
+      <span slot="title" class="confirm-title">
+        <i class="el-icon-warning" style="color: #E6A23C; margin-right: 8px;"></i>
+        提示
+      </span>
+      <div class="confirm-content">
+        <p>将对编号为「{{ selectedMapCode }}-{{ selectedMapName }}」的地图执行遥控探索,是否继续?</p>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="remoteExploreCancel">取 消</el-button>
+        <el-button type="primary" @click="submitRemoteExplore">确 定</el-button>
       </span>
     </el-dialog>
     <!--  下载地图dia -->
@@ -382,6 +392,16 @@
 import XtMapCard from '@/components/XtMapCard'
 // 导入日期格式化工具
 import { formatDateTimeCompat, pickUpdatedAt } from '@/utils/datefmt'
+// 导入路由辅助函数
+import { buildNavTo, buildEditTo, buildCalibrateTo, buildConstructTo, pickId } from '@/utils/route-helpers'
+
+// 安全获取数组函数
+function pickArray(...arrs){ 
+  for(const a of arrs){ 
+    if(Array.isArray(a) && a.length>=0) return a 
+  } 
+  return [] 
+}
 
 // 尝试导入真实 API,失败则使用 Mock
 let mapApi
@@ -424,6 +444,7 @@ export default {
       mockMapList: [
         {
           id: 1,
+          code: 'SH001',
           name: '上海办公楼一层',
           mapName: '上海办公楼一层',
           showImg: 'https://placehold.co/320x180/4f46e5/white?text=Office+Floor+1',
@@ -436,6 +457,7 @@ export default {
         },
         {
           id: 2,
+          code: 'FC001',
           name: '工厂车间A区',
           mapName: '工厂车间A区',
           showImg: 'https://placehold.co/320x180/dc2626/white?text=Factory+A',
@@ -448,6 +470,7 @@ export default {
         },
         {
           id: 3,
+          code: 'WH001',
           name: '仓库主区域',
           mapName: '仓库主区域',
           showImg: 'https://placehold.co/320x180/f59e0b/white?text=Warehouse',
@@ -460,6 +483,7 @@ export default {
         },
         {
           id: 4,
+          code: 'RD002',
           name: '研发中心二楼',
           mapName: '研发中心二楼',
           showImg: 'https://placehold.co/320x180/059669/white?text=R%26D+Floor+2',
@@ -472,6 +496,7 @@ export default {
         },
         {
           id: 5,
+          code: 'PK001',
           name: '停车场地下一层',
           mapName: '停车场地下一层',
           showImg: 'https://placehold.co/320x180/7c3aed/white?text=Parking+B1',
@@ -484,6 +509,7 @@ export default {
         },
         {
           id: 6,
+          code: 'CF001',
           name: '食堂餐厅区域',
           mapName: '食堂餐厅区域',
           showImg: 'https://placehold.co/320x180/ea580c/white?text=Cafeteria',
@@ -496,6 +522,7 @@ export default {
         },
         {
           id: 7,
+          code: 'OD001',
           name: '户外园区',
           mapName: '户外园区',
           showImg: 'https://placehold.co/320x180/16a34a/white?text=Outdoor+Park',
@@ -508,6 +535,7 @@ export default {
         },
         {
           id: 8,
+          code: 'MC001',
           name: '会议中心',
           mapName: '会议中心',
           showImg: 'https://placehold.co/320x180/0891b2/white?text=Conference',
@@ -539,15 +567,15 @@ export default {
       
       // 弹窗状态(保留原有对话框)
       title: "",
-      exploreOpen: false,
-      isRemoteExplore: false, // 区分自主/遥控探索
+      autoExploreOpen: false,   // 自主探索弹框
+      remoteExploreOpen: false, // 遥控探索确认弹框
       downloadOpen: false,
       constructOpen: false,
       constructModle: 'hand',
       exploreParams: {
-        maxTime: '',
-        maxDistance: '',
-        maxRange: ''
+        maxTime: null,
+        maxDistance: null,
+        maxRange: null
       },
       downLoadTypes: [],
       constructTypes: [],
@@ -566,6 +594,102 @@ export default {
   },
 
   computed: {
+    // 原始数据:兼容不同变量名
+    rawList(){
+      // 按出现频率降序尝试
+      return pickArray(this.tableData, this.mapList, this.list, this.dataList, this.maps)
+    },
+    
+    // 供表格/卡片使用的数据(先不做筛选,只做分页裁切;如果你已有筛选,请把你现有的筛选结果放到最前面)
+    displayedList(){
+      // 如果已有筛选后的数据,优先使用
+      if (this.searchKeyword.trim() || this.statusFilter !== 'all') {
+        // 使用现有的筛选逻辑
+        let filteredData = [...this.mockMapList]
+        
+        // 搜索过滤
+        if (this.searchKeyword.trim()) {
+          const keyword = this.searchKeyword.toLowerCase()
+          filteredData = filteredData.filter(item => 
+            item.name.toLowerCase().includes(keyword) ||
+            item.remark.toLowerCase().includes(keyword)
+          )
+        }
+        
+        // 状态筛选
+        if (this.statusFilter !== 'all') {
+          filteredData = filteredData.filter(item => item.status === this.statusFilter)
+        }
+        
+        // 排序
+        filteredData.sort((a, b) => {
+          let aVal, bVal
+          
+          switch (this.sortField) {
+            case 'name':
+              aVal = a.name.toLowerCase()
+              bVal = b.name.toLowerCase()
+              break
+            case 'status':
+              // 状态优先级:ok > scanning > down
+              const statusOrder = { ok: 2, scanning: 1, down: 0 }
+              aVal = statusOrder[a.status] || 0
+              bVal = statusOrder[b.status] || 0
+              break
+            case 'updated':
+            default:
+              aVal = new Date(a.updatedAt).getTime()
+              bVal = new Date(b.updatedAt).getTime()
+              break
+          }
+
+          if (this.sortOrder === 'asc') {
+            return aVal > bVal ? 1 : aVal < bVal ? -1 : 0
+          } else {
+            return aVal < bVal ? 1 : aVal > bVal ? -1 : 0
+          }
+        })
+        
+        // 分页
+        const start = (this.currentPage - 1) * this.pageSize
+        const end = start + this.pageSize
+        return filteredData.slice(start, end)
+      }
+      
+      // 否则使用通用分页逻辑
+      const src = this.rawList || []
+      const page = this.pagination?.page || this.pageNum || this.currentPage || 1
+      const size = this.pagination?.pageSize || this.pageSize || 10
+      const start = (page-1)*size
+      return src.slice(start, start+size)
+    },
+    
+    totalCount(){
+      if (this.searchKeyword.trim() || this.statusFilter !== 'all') {
+        // 使用现有的筛选逻辑计算总数
+        let filteredData = [...this.mockMapList]
+        
+        // 搜索过滤
+        if (this.searchKeyword.trim()) {
+          const keyword = this.searchKeyword.toLowerCase()
+          filteredData = filteredData.filter(item => 
+            item.name.toLowerCase().includes(keyword) ||
+            item.remark.toLowerCase().includes(keyword)
+          )
+        }
+        
+        // 状态筛选
+        if (this.statusFilter !== 'all') {
+          filteredData = filteredData.filter(item => item.status === this.statusFilter)
+        }
+        
+        return filteredData.length
+      }
+      
+      const src = this.rawList || []
+      return src.length
+    },
+
     // 是否有搜索或筛选条件
     hasSearchOrFilter() {
       return this.searchKeyword.trim() || this.statusFilter !== 'all'
@@ -574,10 +698,21 @@ export default {
     // 获取选中的地图名称
     selectedMapName() {
       if (this.selectedMaps.length === 1) {
-        const selectedMap = this.mapList.find(map => map.id === this.selectedMaps[0])
+        const selectedMap = this.displayedList.find(map => map.id === this.selectedMaps[0]) || 
+                           this.rawList.find(map => map.id === this.selectedMaps[0])
         return selectedMap ? selectedMap.name : ''
       }
       return ''
+    },
+
+    // 获取选中的地图编号
+    selectedMapCode() {
+      if (this.selectedMaps.length === 1) {
+        const selectedMap = this.displayedList.find(map => map.id === this.selectedMaps[0]) || 
+                           this.rawList.find(map => map.id === this.selectedMaps[0])
+        return selectedMap ? (selectedMap.code || selectedMap.id) : ''
+      }
+      return ''
     }
   },
 
@@ -585,6 +720,12 @@ export default {
     this.loadViewMode()
     this.calculateTableHeight()
   },
+  
+  watch: {
+    displayedList() {
+      this.$nextTick(() => this.$refs.mapTable && this.$refs.mapTable.doLayout())
+    }
+  },
 
   mounted() {
     this.getList()
@@ -592,6 +733,11 @@ export default {
     window.addEventListener('resize', this.onResize)
     // 添加键盘监听
     this.addKeyboardListeners()
+    
+    // 表格渲染后重排一次,防止列宽缓存
+    this.$nextTick(() => { 
+      this.$refs.mapTable && this.$refs.mapTable.doLayout() 
+    })
   },
 
   beforeDestroy() {
@@ -626,7 +772,9 @@ export default {
         }
         */
         
-        // 使用 Mock 数据
+        // 使用 Mock 数据 - 确保 mapList 被设置
+        this.mapList = [...this.mockMapList]
+        
         let filteredData = [...this.mockMapList]
         
         // 搜索过滤
@@ -678,6 +826,7 @@ export default {
         const end = start + this.pageSize
         const pagedData = filteredData.slice(start, end)
         
+        // 处理后的数据仍然赋值给 mapList,但原始数据已保存
         this.mapList = this.processMapList(pagedData)
         
       } catch (error) {
@@ -814,8 +963,75 @@ export default {
       this.handleEdit(row.id)
     },
 
-    onMore(row, command) {
-      this.handleCardAction(row.id, command)
+    async onMore(row, cmd) {
+      const id = pickId(row);
+      switch(cmd) {
+        case 'rename':
+          // 优先:已有重命名弹框/方法
+          if (typeof this.openRenameDialog === 'function') return this.openRenameDialog(row);
+          if (this.renameDialog) { this.renameDialog.visible = true; this.renameDialog.row = row; return; }
+          // 兜底:Element prompt
+          this.$prompt('请输入新的地图名称', '重命名', {
+            inputValue: row.mapName || row.name || '',
+            inputPattern: /\S+/,
+            inputErrorMessage: '名称不能为空'
+          }).then(({ value }) => {
+            // 若有旧方法:this.renameMap(id, value)
+            if (typeof this.renameMap === 'function') return this.renameMap(id, value);
+            row.mapName = value; 
+            row.name = value;
+            this.$message.success('已重命名'); // mock 刷新
+          }).catch(()=>{});
+          break;
+
+        case 'download':
+          // 打开下载对话框
+          this.title = `下载地图 - ${row.name || row.mapName || ''}`;
+          this.currentRow = row;
+          this.downloadOpen = true;
+          break;
+
+        case 'build':
+          // 优先:旧路由/方法
+          if (typeof this.openBuildDialog === 'function') return this.openBuildDialog(row);
+          if (typeof this.constructOpen === 'boolean') {
+            this.constructOpen = true;
+            this.title = `构建地图 - ${row.name || row.mapName || ''}`;
+            this.currentRow = row;
+            return;
+          }
+          if (typeof this.toBuildPage === 'function') return this.toBuildPage(row);
+          return this.$router.push(buildConstructTo(row));
+
+        case 'delete':
+          // 优先:旧删除方法
+          if (typeof this.handleDelete === 'function') {
+            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
+              .then(() => this.handleDelete([id]))
+              .catch(() => {});
+          }
+          if (typeof this.deleteMap === 'function') {
+            return this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
+              .then(() => this.deleteMap(id))
+              .catch(() => {});
+          }
+          this.$confirm('确认删除该地图?此操作不可恢复', '删除地图', {type: 'warning'})
+            .then(() => { 
+              this.$emit && this.$emit('remove', row); 
+              this.$message.success('已删除(mock)'); 
+            })
+            .catch(() => {});
+          break;
+
+        case 'calibrate':
+          // 优先:旧路由/方法
+          if (typeof this.openCalibration === 'function') return this.openCalibration(row);
+          return this.$router.push(buildCalibrateTo(row));
+          
+        default:
+          // 兼容原有的操作
+          return this.handleCardAction(row.id, cmd);
+      }
     },
 
     // 序号计算方法
@@ -972,6 +1188,15 @@ export default {
     formatDateTimeCompat,
     pickUpdatedAt,
 
+    // 路由辅助函数
+    buildNavTo(vm, row){ 
+      return buildNavTo(row);
+    },
+    buildEditTo(vm, row){ 
+      return buildEditTo(row);
+    },
+    pickId,
+
     // 本地存储相关
     loadViewMode() {
       try {
@@ -992,35 +1217,42 @@ export default {
       }
     },
 
-    // 打开探索弹框的方法
+    // 打开自主探索弹框
     openSelfExplore() {
       if (this.selectedMaps.length !== 1) {
         this.$message.warning('请选择一个地图进行自主探索')
         return
       }
-      this.isRemoteExplore = false
-      this.exploreOpen = true
+      this.autoExploreOpen = true
     },
 
+    // 打开遥控探索确认弹框
     openRemoteExplore() {
       if (this.selectedMaps.length !== 1) {
         this.$message.warning('请选择一个地图进行遥控探索')
         return
       }
-      this.isRemoteExplore = true
-      this.exploreOpen = true
+      this.remoteExploreOpen = true
     },
 
     // 键盘监听方法
     addKeyboardListeners() {
       this.handleKeyboard = (event) => {
-        if (this.exploreOpen) {
+        if (this.autoExploreOpen) {
+          if (event.key === 'Enter') {
+            event.preventDefault()
+            this.submitAutoExplore()
+          } else if (event.key === 'Escape') {
+            event.preventDefault()
+            this.autoExploreCancel()
+          }
+        } else if (this.remoteExploreOpen) {
           if (event.key === 'Enter') {
             event.preventDefault()
-            this.submitExplore()
+            this.submitRemoteExplore()
           } else if (event.key === 'Escape') {
             event.preventDefault()
-            this.exploreCancel()
+            this.remoteExploreCancel()
           }
         }
       }
@@ -1033,21 +1265,30 @@ export default {
       }
     },
 
-    // 保留的原有对话框相关方法
-    exploreCancel() {
-      this.exploreOpen = false
+    // 自主探索相关方法
+    autoExploreCancel() {
+      this.autoExploreOpen = false
     },
 
-    submitExplore() {
+    submitAutoExplore() {
       this.$refs["form"].validate(valid => {
         if (valid) {
-          const exploreType = this.isRemoteExplore ? '遥控探索' : '自主探索'
-          this.$message.success(`已开始${exploreType}...`)
-          this.exploreOpen = false
+          this.$message.success('已开始自主探索...')
+          this.autoExploreOpen = false
         }
       })
     },
 
+    // 遥控探索相关方法
+    remoteExploreCancel() {
+      this.remoteExploreOpen = false
+    },
+
+    submitRemoteExplore() {
+      this.$message.success('已开始遥控探索...')
+      this.remoteExploreOpen = false
+    },
+
     submitDownload() {
       console.log('Download types:', this.downLoadTypes)
       this.downloadOpen = false
@@ -1305,28 +1546,44 @@ export default {
         justify-content: flex-end;
         align-items: center;
         gap: 8px;
+      }
 
-        .el-button {
-          display: inline-flex;
-          align-items: center;
+      .op-link {
+        display: inline-flex;
+        align-items: center;
+        color: var(--color-primary, #1d4ed8);
+        text-decoration: none;
+        font-size: var(--font-size-sm);
+        transition: color 0.2s ease;
+
+        &:hover {
+          color: var(--color-primary-light);
+          text-decoration: none;
+        }
 }
 
 .el-dropdown-link {
-          color: var(--color-primary);
+        color: var(--color-primary);
   cursor: pointer;
-          display: inline-flex;
-          align-items: center;
-          font-size: var(--font-size-sm);
+        display: inline-flex;
+        align-items: center;
+        font-size: var(--font-size-sm);
 
-          &:hover {
-            color: var(--color-primary-light);
-          }
+        &:hover {
+          color: var(--color-primary-light);
         }
       }
 
       .op-text {
         margin-left: 4px;
       }
+      
+      .xt-more .is-danger { 
+        color: #e11d48; 
+      }
+      .xt-more .is-danger i { 
+        color: #e11d48; 
+      }
     }
 
     // 空态
@@ -1459,81 +1716,191 @@ export default {
     margin-top: 4px;
   }
 
-  .xt-form .el-form-item {
-    margin-bottom: 14px;
+  .xt-form {
+    background: white;
+    border-radius: 12px;
+    padding: 24px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+
+    .el-form-item {
+      margin-bottom: 24px;
+
+      &:last-child {
+        margin-bottom: 0;
+      }
+
+      .el-form-item__label {
+        color: #333;
+        font-size: 14px;
+        font-weight: 500;
+        line-height: 1.4;
+      }
+    }
   }
 
   .xt-field {
     display: flex;
     align-items: center;
-    gap: 8px;
+    gap: 12px;
   }
 
   .xt-unit {
-    color: var(--text-secondary, #6b7280);
-    margin-left: 4px;
+    color: #666;
+    font-size: 14px;
+    font-weight: 500;
+    white-space: nowrap;
   }
 
   .xt-hint {
-    margin-left: auto;
+    color: #888;
     font-size: 12px;
-    color: var(--text-secondary, #6b7280);
+    margin-left: auto;
+    white-space: nowrap;
   }
 
-  .xt-presets {
-    margin-top: 6px;
+  .xt-footer {
     display: flex;
-    gap: 6px;
-
-    .el-tag {
-      cursor: pointer;
+    justify-content: flex-end;
+    gap: 12px;
+    padding: 16px 0;
+
+    .el-button {
+      padding: 10px 24px;
+      border-radius: 8px;
+      font-weight: 500;
       transition: all 0.2s ease;
+      border: none;
+
+      &.el-button--default {
+        background: #f1f5f9;
+        color: #64748b;
+        
+        &:hover {
+          background: #e2e8f0;
+          color: #475569;
+          transform: translateY(-1px);
+          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+        }
+      }
 
-      &:hover {
-        background: var(--color-primary, #409eff);
+      &.el-button--primary {
+        background: linear-gradient(135deg, #10b981, #059669);
         color: white;
+        
+        &:hover {
+          background: linear-gradient(135deg, #059669, #047857);
+          transform: translateY(-1px);
+          box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
+        }
       }
     }
   }
 
-  .xt-footer {
-    padding: 8px 0 16px;
-  }
+    /* 输入框外层容器样式 */
+  .xt-explore-dialog ::v-deep .el-input-number {
+    width: 160px;
+    border-radius: 24px;
+    overflow: hidden;
+    border: 1.5px solid #d1d5db;
+    transition: all 0.2s ease;
+    background: white;
+
+    &:hover {
+      border-color: #9ca3af;
+    }
 
-  /* 统一输入高度 & 去除绿色"断裂"边框 */
-  .xt-explore-dialog ::v-deep .el-input__inner {
-    height: 36px;
-    line-height: 36px;
-    border-radius: 10px;
-  }
+    &:focus-within,
+    &.is-focus {
+      border-color: #08C28E;
+      box-shadow: 0 0 0 2px rgba(8, 194, 142, 0.2);
+    }
 
-  .xt-explore-dialog ::v-deep .el-input-number {
-    line-height: 36px;
-    border-radius: 10px;
+    .el-input__inner {
+      height: 40px;
+      line-height: 40px;
+      border: none;
+      border-radius: 0;
+      box-shadow: none;
+      padding-right: 40px;
+      font-size: 14px;
+      background: transparent;
+
+      &:focus {
+        outline: none;
+        border: none;
+        box-shadow: none;
+      }
+
+      &::placeholder {
+        color: #9ca3af;
+        font-size: 13px;
+      }
+    }
   }
 
-  /* 右侧加减按钮与输入框圆角衔接 */
+  /* 右侧加减按钮 */
   .xt-explore-dialog ::v-deep .el-input-number.is-controls-right .el-input-number__increase,
   .xt-explore-dialog ::v-deep .el-input-number.is-controls-right .el-input-number__decrease {
   right: 0;
-    border-left: 1px solid var(--border-color, #e5e7eb);
-    width: 32px;
-    height: 18px;
-    border-top-right-radius: 10px;
-    border-bottom-right-radius: 10px;
+    border: none;
+    border-left: 1.5px solid #d1d5db;
+    width: 35px;
+    height: 20px;
+    border-radius: 0;
+    background: #f9fafb;
+    color: #6b7280;
+    transition: all 0.2s ease;
+
+    &:hover {
+      background: #f3f4f6;
+      color: #374151;
+    }
   }
 
-  .xt-explore-dialog ::v-deep .el-input-number.is-controls-right .el-input__inner {
-    padding-right: 36px;
-    border-right-color: transparent;
-  }
+  // 遥控探索确认弹框样式
+  .xt-confirm-dialog {
+    ::v-deep .el-dialog {
+      border-radius: 12px;
+    }
 
-  /* 聚焦时只使用外层主题描边,禁止默认 outline/多重阴影 */
-  .xt-explore-dialog ::v-deep .el-input.is-active .el-input__inner,
-  .xt-explore-dialog ::v-deep .el-input__inner:focus {
-    outline: none;
-    box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.12);
-    border-color: var(--primary, #16a34a);
+    .confirm-title {
+      display: flex;
+      align-items: center;
+      font-size: 16px;
+      font-weight: 600;
+      color: #333;
+    }
+
+    .confirm-content {
+      padding: 20px 0;
+      
+      p {
+        margin: 0;
+        font-size: 14px;
+        line-height: 1.6;
+        color: #666;
+      }
+    }
+
+    ::v-deep .el-dialog__footer {
+      padding: 16px 24px 24px;
+      
+      .el-button {
+        padding: 8px 20px;
+        border-radius: 6px;
+        font-weight: 500;
+        
+        &.el-button--primary {
+          background: linear-gradient(135deg, #10b981, #059669);
+          border-color: #10b981;
+          
+          &:hover {
+            background: linear-gradient(135deg, #059669, #047857);
+            border-color: #059669;
+          }
+        }
+      }
+    }
   }
 
   // 保留的对话框样式
@@ -1594,49 +1961,138 @@ html.dark {
 
     // 暗色主题下的探索弹框样式
     .xt-explore-dialog {
+      ::v-deep .el-dialog {
+        background: #1e293b;
+        box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
+      }
+
+      ::v-deep .el-dialog__header {
+        background: linear-gradient(135deg, #334155, #475569);
+        border-bottom: 1px solid #475569;
+      }
+
+      ::v-deep .el-dialog__body,
+      ::v-deep .el-dialog__footer {
+        background: #1e293b;
+      }
+
+      ::v-deep .el-dialog__footer {
+        border-top: 1px solid #475569;
+      }
+
       .xt-title {
-        color: var(--color-text-primary);
+        color: #f1f5f9;
       }
 
       .xt-subtitle {
-        color: var(--color-text-secondary);
+        color: #94a3b8;
+      }
+
+      .xt-form {
+        background: #0f172a;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
+
+        .el-form-item__label {
+          color: #e2e8f0 !important;
+        }
+      }
+
+      .xt-unit {
+        color: #cbd5e1;
       }
 
-      .xt-unit,
       .xt-hint {
-        color: var(--color-text-secondary);
+        color: #94a3b8;
       }
 
-      .xt-presets .el-tag {
-        background: var(--color-bg-tertiary);
-        color: var(--color-text-primary);
-        border-color: var(--color-border-secondary);
+      ::v-deep .el-input-number {
+        background: #0f172a;
+        border-color: #475569;
 
         &:hover {
-          background: var(--color-primary);
-          color: white;
+          border-color: #64748b;
         }
-      }
 
-      ::v-deep .el-input__inner {
-        background: var(--color-bg-card);
-        border-color: var(--color-border-secondary);
-        color: var(--color-text-primary);
+        &:focus-within,
+        &.is-focus {
+          border-color: #08C28E;
+          box-shadow: 0 0 0 2px rgba(8, 194, 142, 0.3);
+        }
+
+        .el-input__inner {
+          background: transparent;
+          color: #e2e8f0;
 
-        &:focus {
-          border-color: var(--color-primary);
-          box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.2);
+          &::placeholder {
+            color: #64748b;
+          }
         }
       }
 
       ::v-deep .el-input-number__increase,
       ::v-deep .el-input-number__decrease {
-        background: var(--color-bg-tertiary);
-        border-color: var(--color-border-secondary);
-        color: var(--color-text-primary);
+        background: #334155;
+        border-color: #475569;
+        color: #94a3b8;
 
         &:hover {
-          background: var(--color-bg-quaternary);
+          background: #475569;
+          color: #e2e8f0;
+        }
+      }
+
+      .xt-footer {
+        .el-button {
+          &.el-button--default {
+            background: #334155;
+            color: #94a3b8;
+            
+            &:hover {
+              background: #475569;
+              color: #e2e8f0;
+            }
+          }
+
+          &.el-button--primary {
+            background: linear-gradient(135deg, #10b981, #059669);
+            color: white;
+            
+            &:hover {
+              background: linear-gradient(135deg, #059669, #047857);
+            }
+          }
+        }
+      }
+    }
+
+    // 暗色主题下的确认弹框样式
+    .xt-confirm-dialog {
+      ::v-deep .el-dialog {
+        background: #1e293b;
+      }
+
+      .confirm-title {
+        color: #f1f5f9;
+      }
+
+      .confirm-content p {
+        color: #cbd5e1;
+      }
+
+      ::v-deep .el-dialog__footer {
+        background: #1e293b;
+        
+        .el-button {
+          &:not(.el-button--primary) {
+            background: #334155;
+            color: #94a3b8;
+            border-color: #475569;
+            
+            &:hover {
+              background: #475569;
+              color: #e2e8f0;
+            }
+          }
         }
       }
     }