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