|
|
@@ -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;
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|