|
|
@ -13,6 +13,7 @@ |
|
|
:tool-mode="drawDom ? 'ranging' : (airspaceDrawDom ? 'airspace' : 'airspace')" |
|
|
:tool-mode="drawDom ? 'ranging' : (airspaceDrawDom ? 'airspace' : 'airspace')" |
|
|
:scaleConfig="scaleConfig" |
|
|
:scaleConfig="scaleConfig" |
|
|
:coordinateFormat="coordinateFormat" |
|
|
:coordinateFormat="coordinateFormat" |
|
|
|
|
|
:bottomPanelVisible="bottomPanelVisible" |
|
|
:route-locked="routeLocked" |
|
|
:route-locked="routeLocked" |
|
|
:deduction-time-minutes="deductionMinutesFromK" |
|
|
:deduction-time-minutes="deductionMinutesFromK" |
|
|
:room-id="currentRoomId" |
|
|
:room-id="currentRoomId" |
|
|
@ -25,6 +26,9 @@ |
|
|
@open-route-dialog="handleOpenRouteEdit" |
|
|
@open-route-dialog="handleOpenRouteEdit" |
|
|
@copy-route="handleCopyRoute" |
|
|
@copy-route="handleCopyRoute" |
|
|
@route-copy-placed="handleRouteCopyPlaced" |
|
|
@route-copy-placed="handleRouteCopyPlaced" |
|
|
|
|
|
@add-waypoint-at="handleAddWaypointAt" |
|
|
|
|
|
@add-waypoint-placed="handleAddWaypointPlaced" |
|
|
|
|
|
@toggle-waypoint-hold="handleToggleWaypointHold" |
|
|
@waypoint-position-changed="handleWaypointPositionChanged" |
|
|
@waypoint-position-changed="handleWaypointPositionChanged" |
|
|
@scale-click="handleScaleClick" |
|
|
@scale-click="handleScaleClick" |
|
|
@platform-icon-updated="onPlatformIconUpdated" |
|
|
@platform-icon-updated="onPlatformIconUpdated" |
|
|
@ -143,6 +147,7 @@ |
|
|
@toggle-airport="toggleAirport" |
|
|
@toggle-airport="toggleAirport" |
|
|
@toggle-landmark="toggleLandmark" |
|
|
@toggle-landmark="toggleLandmark" |
|
|
@toggle-route="toggleRoute" |
|
|
@toggle-route="toggleRoute" |
|
|
|
|
|
@generate-gantt-chart="generateGanttChart" |
|
|
@system-description="systemDescription" |
|
|
@system-description="systemDescription" |
|
|
@layer-favorites="layerFavorites" |
|
|
@layer-favorites="layerFavorites" |
|
|
@route-favorites="routeFavorites" |
|
|
@route-favorites="routeFavorites" |
|
|
@ -209,7 +214,7 @@ |
|
|
@open-import-dialog="showImportDialog = true" |
|
|
@open-import-dialog="showImportDialog = true" |
|
|
/> |
|
|
/> |
|
|
<!-- 左下角工具面板 --> |
|
|
<!-- 左下角工具面板 --> |
|
|
<bottom-left-panel v-show="!screenshotMode" /> |
|
|
<bottom-left-panel v-show="!screenshotMode" @bottom-panel-visible="handleBottomPanelVisible" :room-id="currentRoomId" /> |
|
|
<!-- 底部时间轴(最初版本的样式)- 蓝色主题 --> |
|
|
<!-- 底部时间轴(最初版本的样式)- 蓝色主题 --> |
|
|
<div |
|
|
<div |
|
|
v-show="!screenshotMode" |
|
|
v-show="!screenshotMode" |
|
|
@ -220,7 +225,6 @@ |
|
|
<div class="popup-hide-btn" @click="hideKTimePopup" title="隐藏K时"> |
|
|
<div class="popup-hide-btn" @click="hideKTimePopup" title="隐藏K时"> |
|
|
<i class="el-icon-arrow-down"></i> |
|
|
<i class="el-icon-arrow-down"></i> |
|
|
</div> |
|
|
</div> |
|
|
<p class="deduction-hint">仅推演当前展示的航线;K 时可随时由房主/管理员在右上角「作战时间」处修改。</p> |
|
|
|
|
|
<div class="timeline-controls"> |
|
|
<div class="timeline-controls"> |
|
|
<div class="current-time blue-time"> |
|
|
<div class="current-time blue-time"> |
|
|
<i class="el-icon-time"></i> |
|
|
<i class="el-icon-time"></i> |
|
|
@ -263,14 +267,6 @@ |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div v-if="deductionWarnings.length > 0 || hasEarlyArrivalLegs" class="deduction-warnings"> |
|
|
|
|
|
<i class="el-icon-warning-outline"></i> |
|
|
|
|
|
<span>{{ deductionWarnings[0] || '存在航段将提前到达下一航点。' }}</span> |
|
|
|
|
|
<el-tooltip v-if="deductionWarnings.length > 1" :content="deductionWarnings.join(';')" placement="top"> |
|
|
|
|
|
<span class="warnings-more">等 {{ deductionWarnings.length }} 条</span> |
|
|
|
|
|
</el-tooltip> |
|
|
|
|
|
<el-button v-if="hasEarlyArrivalLegs" type="text" size="mini" @click="openAddHoldFromFirstEarly">在此添加盘旋</el-button> |
|
|
|
|
|
</div> |
|
|
|
|
|
<el-dialog :title="addHoldDialogTitle" :visible.sync="showAddHoldDialog" width="420px" append-to-body> |
|
|
<el-dialog :title="addHoldDialogTitle" :visible.sync="showAddHoldDialog" width="420px" append-to-body> |
|
|
<div v-if="addHoldContext" class="add-hold-tip">{{ addHoldDialogTip }}</div> |
|
|
<div v-if="addHoldContext" class="add-hold-tip">{{ addHoldDialogTip }}</div> |
|
|
<el-form :model="addHoldForm" label-width="100px" size="small"> |
|
|
<el-form :model="addHoldForm" label-width="100px" size="small"> |
|
|
@ -373,6 +369,13 @@ |
|
|
@confirm="handleImportConfirm" |
|
|
@confirm="handleImportConfirm" |
|
|
/> |
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 4T悬浮窗(THREAT/TASK/TARGET/TACTIC)- 仅点击方案或4T时渲染 --> |
|
|
|
|
|
<four-t-panel |
|
|
|
|
|
v-if="show4TPanel && !screenshotMode" |
|
|
|
|
|
:visible.sync="show4TPanel" |
|
|
|
|
|
:room-id="currentRoomId" |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<el-dialog |
|
|
<el-dialog |
|
|
title="新建方案" |
|
|
title="新建方案" |
|
|
:visible.sync="showPlanNameDialog" |
|
|
:visible.sync="showPlanNameDialog" |
|
|
@ -430,8 +433,9 @@ import LeftMenu from './LeftMenu' |
|
|
import RightPanel from './RightPanel' |
|
|
import RightPanel from './RightPanel' |
|
|
import BottomLeftPanel from './BottomLeftPanel' |
|
|
import BottomLeftPanel from './BottomLeftPanel' |
|
|
import TopHeader from './TopHeader' |
|
|
import TopHeader from './TopHeader' |
|
|
|
|
|
import FourTPanel from './FourTPanel' |
|
|
import { listScenario, addScenario, delScenario } from "@/api/system/scenario"; |
|
|
import { listScenario, addScenario, delScenario } from "@/api/system/scenario"; |
|
|
import { listRoutes, getRoutes, addRoutes, updateRoutes, delRoutes } from "@/api/system/routes"; |
|
|
import { listRoutes, getRoutes, addRoutes, updateRoutes, delRoutes, getPlatformStyle } from "@/api/system/routes"; |
|
|
import { updateWaypoints, addWaypoints, delWaypoints } from "@/api/system/waypoints"; |
|
|
import { updateWaypoints, addWaypoints, delWaypoints } from "@/api/system/waypoints"; |
|
|
import { listLib,addLib,delLib} from "@/api/system/lib"; |
|
|
import { listLib,addLib,delLib} from "@/api/system/lib"; |
|
|
import { getRooms, updateRooms } from "@/api/system/rooms"; |
|
|
import { getRooms, updateRooms } from "@/api/system/rooms"; |
|
|
@ -453,7 +457,8 @@ export default { |
|
|
LeftMenu, |
|
|
LeftMenu, |
|
|
RightPanel, |
|
|
RightPanel, |
|
|
BottomLeftPanel, |
|
|
BottomLeftPanel, |
|
|
TopHeader |
|
|
TopHeader, |
|
|
|
|
|
FourTPanel |
|
|
}, |
|
|
}, |
|
|
data() { |
|
|
data() { |
|
|
return { |
|
|
return { |
|
|
@ -494,6 +499,8 @@ export default { |
|
|
platformIconSaveTimer: null, |
|
|
platformIconSaveTimer: null, |
|
|
//导入平台弹窗 |
|
|
//导入平台弹窗 |
|
|
showImportDialog: false, |
|
|
showImportDialog: false, |
|
|
|
|
|
// 底部面板可见状态(时间轴/六步法) |
|
|
|
|
|
bottomPanelVisible: false, |
|
|
|
|
|
|
|
|
// 地图截图 |
|
|
// 地图截图 |
|
|
screenshotMode: false, |
|
|
screenshotMode: false, |
|
|
@ -519,6 +526,7 @@ export default { |
|
|
// 默认菜单项配置 |
|
|
// 默认菜单项配置 |
|
|
defaultMenuItems: [ |
|
|
defaultMenuItems: [ |
|
|
{ id: 'file', name: '方案', icon: 'plan' }, |
|
|
{ id: 'file', name: '方案', icon: 'plan' }, |
|
|
|
|
|
{ id: '4t', name: '4T', icon: 'T' }, |
|
|
{ id: 'start', name: '冲突', icon: 'chongtu' }, |
|
|
{ id: 'start', name: '冲突', icon: 'chongtu' }, |
|
|
{ id: 'insert', name: '平台', icon: 'el-icon-s-platform' }, |
|
|
{ id: 'insert', name: '平台', icon: 'el-icon-s-platform' }, |
|
|
{ id: 'pattern', name: '空域', icon: 'ky' }, |
|
|
{ id: 'pattern', name: '空域', icon: 'ky' }, |
|
|
@ -582,6 +590,8 @@ export default { |
|
|
isRightPanelHidden: true, // 是否完全隐藏右侧面板 |
|
|
isRightPanelHidden: true, // 是否完全隐藏右侧面板 |
|
|
// K时弹出框控制 |
|
|
// K时弹出框控制 |
|
|
showKTimePopup: false, |
|
|
showKTimePopup: false, |
|
|
|
|
|
// 4T悬浮窗显示控制(点击方案或4T时显示) |
|
|
|
|
|
show4TPanel: false, |
|
|
|
|
|
|
|
|
// 显示/隐藏控制 |
|
|
// 显示/隐藏控制 |
|
|
showAirport: true, |
|
|
showAirport: true, |
|
|
@ -600,26 +610,9 @@ export default { |
|
|
activeRouteIds: [], // 存储当前所有选中的航线ID |
|
|
activeRouteIds: [], // 存储当前所有选中的航线ID |
|
|
/** 航线上锁状态:routeId -> true 上锁,与地图右键及右侧列表锁图标同步 */ |
|
|
/** 航线上锁状态:routeId -> true 上锁,与地图右键及右侧列表锁图标同步 */ |
|
|
routeLocked: {}, |
|
|
routeLocked: {}, |
|
|
// 冲突数据 |
|
|
// 冲突数据(由 runConflictCheck 根据当前航线与时间轴计算真实问题) |
|
|
conflictCount: 2, |
|
|
conflictCount: 0, |
|
|
conflicts: [ |
|
|
conflicts: [], |
|
|
{ |
|
|
|
|
|
id: 1, |
|
|
|
|
|
title: '航线空间冲突', |
|
|
|
|
|
routes: ['Alpha进场航线', 'Beta巡逻航线'], |
|
|
|
|
|
time: 'K+01:20:00', |
|
|
|
|
|
position: 'E116° N39°', |
|
|
|
|
|
severity: 'high' |
|
|
|
|
|
}, |
|
|
|
|
|
{ |
|
|
|
|
|
id: 2, |
|
|
|
|
|
title: '时间窗口重叠', |
|
|
|
|
|
routes: ['侦察覆盖区', '无人机巡逻'], |
|
|
|
|
|
time: 'K+02:15:00', |
|
|
|
|
|
position: 'E117° N38°', |
|
|
|
|
|
severity: 'medium' |
|
|
|
|
|
} |
|
|
|
|
|
], |
|
|
|
|
|
|
|
|
|
|
|
// 平台数据 |
|
|
// 平台数据 |
|
|
activePlatformTab: 'air', |
|
|
activePlatformTab: 'air', |
|
|
@ -635,7 +628,7 @@ export default { |
|
|
deductionEarlyArrivalByRoute: {}, // routeId -> earlyArrivalLegs |
|
|
deductionEarlyArrivalByRoute: {}, // routeId -> earlyArrivalLegs |
|
|
showAddHoldDialog: false, |
|
|
showAddHoldDialog: false, |
|
|
addHoldContext: null, // { routeId, routeName, legIndex, fromName, toName } |
|
|
addHoldContext: null, // { routeId, routeName, legIndex, fromName, toName } |
|
|
addHoldForm: { holdType: 'hold_circle', radius: 500, semiMajor: 500, semiMinor: 300, headingDeg: 0, clockwise: true, startTime: '', startTimeMinutes: null }, |
|
|
addHoldForm: { holdType: 'hold_circle', radius: 15000, semiMajor: 500, semiMinor: 300, headingDeg: 0, clockwise: true, startTime: '', startTimeMinutes: null }, |
|
|
missionDrawingActive: false, |
|
|
missionDrawingActive: false, |
|
|
missionDrawingPointsCount: 0, |
|
|
missionDrawingPointsCount: 0, |
|
|
isPlaying: false, |
|
|
isPlaying: false, |
|
|
@ -737,8 +730,24 @@ export default { |
|
|
if (this.currentRoomId) this.getRoomDetail(); |
|
|
if (this.currentRoomId) this.getRoomDetail(); |
|
|
}, |
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
// 处理从地图点击传来的编辑请求 |
|
|
handleBottomPanelVisible(visible) { |
|
|
async handleOpenWaypointEdit(wpId, routeId) { |
|
|
this.bottomPanelVisible = visible |
|
|
|
|
|
}, |
|
|
|
|
|
// 处理从地图点击传来的编辑请求(支持 wpId 或 waypointIndex,如右键盘旋弧时仅有 waypointIndex) |
|
|
|
|
|
async handleOpenWaypointEdit(wpId, routeId, waypointIndex) { |
|
|
|
|
|
if (waypointIndex != null && (wpId == null || wpId === undefined)) { |
|
|
|
|
|
try { |
|
|
|
|
|
const response = await getRoutes(routeId); |
|
|
|
|
|
if (response.code === 200 && response.data && response.data.waypoints) { |
|
|
|
|
|
const wp = response.data.waypoints[waypointIndex]; |
|
|
|
|
|
if (wp) wpId = wp.id; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) {} |
|
|
|
|
|
} |
|
|
|
|
|
if (wpId == null || wpId === undefined) { |
|
|
|
|
|
this.$message.info('未找到该航点'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
console.log(`>>> [父组件接收] 航点 ID: ${wpId}, 所属航线 ID: ${routeId}`); |
|
|
console.log(`>>> [父组件接收] 航点 ID: ${wpId}, 所属航线 ID: ${routeId}`); |
|
|
// 如果点击的点不属于当前选中的航线 |
|
|
// 如果点击的点不属于当前选中的航线 |
|
|
if (this.selectedRouteId != routeId) { |
|
|
if (this.selectedRouteId != routeId) { |
|
|
@ -747,12 +756,16 @@ export default { |
|
|
const response = await getRoutes(routeId); |
|
|
const response = await getRoutes(routeId); |
|
|
if (response.code === 200 && response.data) { |
|
|
if (response.code === 200 && response.data) { |
|
|
const fullRouteData = response.data; |
|
|
const fullRouteData = response.data; |
|
|
// 同步更新父组件状态,保持和 selectRoute 方法一致的结构 |
|
|
const fromList = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
// 同步更新父组件状态,合并 list 中的 platform 以便拖拽后重绘平台不丢失 |
|
|
this.selectedRouteId = fullRouteData.id; |
|
|
this.selectedRouteId = fullRouteData.id; |
|
|
this.selectedRouteDetails = { |
|
|
this.selectedRouteDetails = { |
|
|
id: fullRouteData.id, |
|
|
id: fullRouteData.id, |
|
|
name: fullRouteData.callSign, |
|
|
name: fullRouteData.callSign, |
|
|
waypoints: fullRouteData.waypoints || [] |
|
|
waypoints: fullRouteData.waypoints || [], |
|
|
|
|
|
platformId: fromList?.platformId, |
|
|
|
|
|
platform: fromList?.platform, |
|
|
|
|
|
attributes: fromList?.attributes |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
@ -831,6 +844,231 @@ export default { |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 右键航点“向前/向后增加航点”:进入放置模式,传入 waypoints 给地图预览 */ |
|
|
|
|
|
handleAddWaypointAt({ routeId, waypointIndex, mode }) { |
|
|
|
|
|
if (this.routeLocked[routeId]) { |
|
|
|
|
|
this.$message.info('该航线已上锁,请先解锁'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const route = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (!route || !route.waypoints || route.waypoints.length === 0) { |
|
|
|
|
|
this.$message.warning('航线无航点数据'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
if (!this.$refs.cesiumMap || typeof this.$refs.cesiumMap.startAddWaypointAt !== 'function') return; |
|
|
|
|
|
this.$refs.cesiumMap.startAddWaypointAt(routeId, waypointIndex, mode, route.waypoints); |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 地图放置新航点后:调用 addWaypoints 插入,再按插入位置重排 seq 并重绘。向后添加时:当前第 K 个,新航点为第 K+1 个,原第 K+1 个及以后依次后移。 */ |
|
|
|
|
|
async handleAddWaypointPlaced({ routeId, waypointIndex, mode, position }) { |
|
|
|
|
|
const route = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (!route || !route.waypoints) { |
|
|
|
|
|
this.$message.warning('航线不存在或无航点'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const waypoints = route.waypoints; |
|
|
|
|
|
const refWp = waypoints[waypointIndex]; |
|
|
|
|
|
// 向后添加:insertIndex = waypointIndex+1,新航点成为第 (waypointIndex+2) 个,原第 waypointIndex+2 个变为第 waypointIndex+3 个 |
|
|
|
|
|
const insertIndex = mode === 'before' ? waypointIndex : waypointIndex + 1; |
|
|
|
|
|
const prevWp = insertIndex > 0 ? waypoints[insertIndex - 1] : null; |
|
|
|
|
|
const startTime = prevWp && prevWp.startTime ? prevWp.startTime : 'K+00:00:00'; |
|
|
|
|
|
const count = waypoints.length + 1; |
|
|
|
|
|
const newName = `WP${insertIndex + 1}`; |
|
|
|
|
|
try { |
|
|
|
|
|
const addRes = await addWaypoints({ |
|
|
|
|
|
routeId, |
|
|
|
|
|
name: newName, |
|
|
|
|
|
seq: insertIndex + 1, |
|
|
|
|
|
lat: position.lat, |
|
|
|
|
|
lng: position.lng, |
|
|
|
|
|
alt: position.alt, |
|
|
|
|
|
speed: (refWp && refWp.speed != null) ? refWp.speed : 800, |
|
|
|
|
|
startTime, |
|
|
|
|
|
turnAngle: (refWp && refWp.turnAngle != null) ? refWp.turnAngle : (insertIndex === 0 || insertIndex === count - 1 ? 0 : 45) |
|
|
|
|
|
}); |
|
|
|
|
|
await this.getList(); |
|
|
|
|
|
let updated = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (!updated || !updated.waypoints || updated.waypoints.length !== count) { |
|
|
|
|
|
this.$message.warning('添加航点后数据未刷新'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const list = updated.waypoints; |
|
|
|
|
|
const prevIds = new Set(waypoints.map(w => w.id)); |
|
|
|
|
|
const newWp = (addRes && addRes.data && addRes.data.id != null && list.find(w => w.id === addRes.data.id)) |
|
|
|
|
|
? list.find(w => w.id === addRes.data.id) |
|
|
|
|
|
: list.find(w => !prevIds.has(w.id)) || list[list.length - 1]; |
|
|
|
|
|
if (!newWp) { |
|
|
|
|
|
this.$message.warning('未找到新插入的航点'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const others = list.filter(w => w.id !== newWp.id); |
|
|
|
|
|
others.sort((a, b) => (Number(a.seq) || 0) - (Number(b.seq) || 0)); |
|
|
|
|
|
const reordered = [...others.slice(0, insertIndex), newWp, ...others.slice(insertIndex)]; |
|
|
|
|
|
const routeInListFirst = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (routeInListFirst) routeInListFirst.waypoints = reordered; |
|
|
|
|
|
if (this.selectedRouteId === routeId) this.selectedRouteDetails = { ...this.selectedRouteDetails, waypoints: reordered }; |
|
|
|
|
|
if (this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(reordered, routeId, routeInListFirst?.platformId, routeInListFirst?.platform, this.parseRouteStyle(routeInListFirst?.attributes || route?.attributes)); |
|
|
|
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
|
|
|
} |
|
|
|
|
|
const isHold = (w) => (w.pointType || w.point_type) === 'hold_circle' || (w.pointType || w.point_type) === 'hold_ellipse'; |
|
|
|
|
|
for (let i = 0; i < reordered.length; i++) { |
|
|
|
|
|
const wp = reordered[i]; |
|
|
|
|
|
const newSeq = i + 1; |
|
|
|
|
|
const isNewlyInserted = wp.id === newWp.id; |
|
|
|
|
|
const nameToUse = isNewlyInserted ? (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`) : (wp.name || (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`)); |
|
|
|
|
|
await updateWaypoints({ |
|
|
|
|
|
id: wp.id, |
|
|
|
|
|
routeId, |
|
|
|
|
|
name: nameToUse, |
|
|
|
|
|
seq: newSeq, |
|
|
|
|
|
lat: wp.lat, |
|
|
|
|
|
lng: wp.lng, |
|
|
|
|
|
alt: wp.alt, |
|
|
|
|
|
speed: wp.speed, |
|
|
|
|
|
startTime: wp.startTime, |
|
|
|
|
|
turnAngle: wp.turnAngle, |
|
|
|
|
|
...(wp.pointType != null && { pointType: wp.pointType }), |
|
|
|
|
|
...(wp.holdParams != null && { holdParams: wp.holdParams }), |
|
|
|
|
|
...(wp.labelFontSize != null && { labelFontSize: wp.labelFontSize }), |
|
|
|
|
|
...(wp.labelColor != null && { labelColor: wp.labelColor }) |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
await this.getList(); |
|
|
|
|
|
updated = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (!updated || !updated.waypoints) { |
|
|
|
|
|
this.$message.warning('刷新后未拿到航线航点'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const sortedWaypoints = updated.waypoints.slice().sort((a, b) => (Number(a.seq) || 0) - (Number(b.seq) || 0)); |
|
|
|
|
|
updated.waypoints = sortedWaypoints; |
|
|
|
|
|
const routeInList = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (routeInList) routeInList.waypoints = sortedWaypoints; |
|
|
|
|
|
if (this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
if (roomId && updated.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId, platformId: updated.platformId }); |
|
|
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(sortedWaypoints, routeId, updated.platformId, updated.platform, this.parseRouteStyle(updated.attributes)); |
|
|
|
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
|
|
|
} |
|
|
|
|
|
if (this.selectedRouteId === routeId) { |
|
|
|
|
|
this.selectedRouteDetails = { ...this.selectedRouteDetails, waypoints: sortedWaypoints }; |
|
|
|
|
|
} |
|
|
|
|
|
this.$message.success('已添加航点'); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
this.$message.error(e.msg || e.message || '添加航点失败'); |
|
|
|
|
|
console.error(e); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 右键航点“切换盘旋航点”:普通航点设为盘旋(圆形默认),盘旋航点设为普通;支持首尾航点 */ |
|
|
|
|
|
async handleToggleWaypointHold({ routeId, dbId, waypointIndex }) { |
|
|
|
|
|
if (this.routeLocked[routeId]) { |
|
|
|
|
|
this.$message.info('该航线已上锁,请先解锁'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
let route = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
let waypoints = route && route.waypoints; |
|
|
|
|
|
if (!waypoints || waypoints.length === 0) { |
|
|
|
|
|
try { |
|
|
|
|
|
const res = await getRoutes(routeId); |
|
|
|
|
|
if (res.code === 200 && res.data && res.data.waypoints) { |
|
|
|
|
|
waypoints = res.data.waypoints; |
|
|
|
|
|
route = { ...route, waypoints }; |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
this.$message.error('获取航线失败'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (!waypoints || waypoints.length === 0) { |
|
|
|
|
|
this.$message.warning('航线无航点'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const wp = dbId != null ? waypoints.find(w => w.id === dbId) : waypoints[waypointIndex]; |
|
|
|
|
|
if (!wp) { |
|
|
|
|
|
this.$message.warning('未找到该航点'); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
const index = waypoints.indexOf(wp); |
|
|
|
|
|
const total = waypoints.length; |
|
|
|
|
|
const isFirstOrLast = index === 0 || index === total - 1; |
|
|
|
|
|
const isHold = this.isHoldWaypoint(wp); |
|
|
|
|
|
let pointType; |
|
|
|
|
|
let holdParams; |
|
|
|
|
|
let turnAngle; |
|
|
|
|
|
if (isHold) { |
|
|
|
|
|
pointType = 'normal'; |
|
|
|
|
|
holdParams = null; |
|
|
|
|
|
turnAngle = isFirstOrLast ? 0 : (Number(wp.turnAngle) || 45); |
|
|
|
|
|
} else { |
|
|
|
|
|
pointType = 'hold_circle'; |
|
|
|
|
|
holdParams = JSON.stringify({ radius: 15000, clockwise: true }); |
|
|
|
|
|
turnAngle = 0; |
|
|
|
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const payload = { |
|
|
|
|
|
id: wp.id, |
|
|
|
|
|
routeId, |
|
|
|
|
|
name: wp.name, |
|
|
|
|
|
seq: wp.seq, |
|
|
|
|
|
lat: wp.lat, |
|
|
|
|
|
lng: wp.lng, |
|
|
|
|
|
alt: wp.alt, |
|
|
|
|
|
speed: wp.speed, |
|
|
|
|
|
startTime: wp.startTime != null && wp.startTime !== '' ? wp.startTime : 'K+00:00:00', |
|
|
|
|
|
turnAngle, |
|
|
|
|
|
pointType |
|
|
|
|
|
}; |
|
|
|
|
|
if (holdParams != null) payload.holdParams = holdParams; |
|
|
|
|
|
else payload.holdParams = ''; |
|
|
|
|
|
if (wp.labelFontSize != null) payload.labelFontSize = wp.labelFontSize; |
|
|
|
|
|
if (wp.labelColor != null) payload.labelColor = wp.labelColor; |
|
|
|
|
|
if (turnAngle > 0 && this.$refs.cesiumMap) { |
|
|
|
|
|
payload.turnRadius = this.$refs.cesiumMap.getWaypointRadius(payload); |
|
|
|
|
|
} else { |
|
|
|
|
|
payload.turnRadius = 0; |
|
|
|
|
|
} |
|
|
|
|
|
const response = await updateWaypoints(payload); |
|
|
|
|
|
if (response.code !== 200) throw new Error(response.msg || '更新失败'); |
|
|
|
|
|
const merged = { ...wp, ...payload }; |
|
|
|
|
|
const routeInList = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (routeInList && routeInList.waypoints) { |
|
|
|
|
|
const idx = routeInList.waypoints.findIndex(p => p.id === wp.id); |
|
|
|
|
|
if (idx !== -1) routeInList.waypoints.splice(idx, 1, merged); |
|
|
|
|
|
} |
|
|
|
|
|
if (this.selectedRouteId === routeId && this.selectedRouteDetails && this.selectedRouteDetails.waypoints) { |
|
|
|
|
|
const idxS = this.selectedRouteDetails.waypoints.findIndex(p => p.id === wp.id); |
|
|
|
|
|
if (idxS !== -1) this.selectedRouteDetails.waypoints.splice(idxS, 1, merged); |
|
|
|
|
|
} |
|
|
|
|
|
if (this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
|
|
const r = this.routes.find(rr => rr.id === routeId); |
|
|
|
|
|
if (r && r.waypoints) { |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
if (roomId && r.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId, platformId: r.platformId }); |
|
|
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
|
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
|
|
|
this.$refs.cesiumMap.renderRouteWaypoints(r.waypoints, routeId, r.platformId, r.platform, this.parseRouteStyle(r.attributes)); |
|
|
|
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
this.$message.success(isHold ? '已设为普通航点' : '已设为盘旋航点'); |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
this.$message.error(e.msg || e.message || '切换失败'); |
|
|
|
|
|
console.error(e); |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
/** 右键「复制航线」:拉取航点后进入复制预览,左键放置后弹窗保存 */ |
|
|
/** 右键「复制航线」:拉取航点后进入复制预览,左键放置后弹窗保存 */ |
|
|
async handleCopyRoute(routeId) { |
|
|
async handleCopyRoute(routeId) { |
|
|
try { |
|
|
try { |
|
|
@ -912,11 +1150,19 @@ export default { |
|
|
if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); |
|
|
if (i !== -1) this.selectedRouteDetails.waypoints.splice(i, 1, merged); |
|
|
} |
|
|
} |
|
|
if (this.$refs.cesiumMap) { |
|
|
if (this.$refs.cesiumMap) { |
|
|
|
|
|
const routeForPlatform = this.routes.find(r => r.id === routeId) || route; |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
if (roomId && routeForPlatform.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId, platformId: routeForPlatform.platformId }); |
|
|
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
this.$refs.cesiumMap.renderRouteWaypoints( |
|
|
this.$refs.cesiumMap.renderRouteWaypoints( |
|
|
waypoints, |
|
|
waypoints, |
|
|
routeId, |
|
|
routeId, |
|
|
route.platformId, |
|
|
routeForPlatform.platformId, |
|
|
route.platform, |
|
|
routeForPlatform.platform, |
|
|
this.parseRouteStyle(route.attributes) |
|
|
this.parseRouteStyle(route.attributes) |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
@ -1004,11 +1250,11 @@ export default { |
|
|
// 关闭弹窗 |
|
|
// 关闭弹窗 |
|
|
this.showPlatformDialog = false; |
|
|
this.showPlatformDialog = false; |
|
|
}, |
|
|
}, |
|
|
/** 新建航线时写入数据库的默认样式(与地图默认显示一致:紫色实线线宽3) */ |
|
|
/** 新建航线时写入数据库的默认样式(与地图默认显示一致:墨绿色实线线宽3) */ |
|
|
getDefaultRouteAttributes() { |
|
|
getDefaultRouteAttributes() { |
|
|
const defaultAttrs = { |
|
|
const defaultAttrs = { |
|
|
waypointStyle: { pixelSize: 7, color: '#ffffff', outlineColor: '#0078FF', outlineWidth: 2 }, |
|
|
waypointStyle: { pixelSize: 7, color: '#ffffff', outlineColor: '#0078FF', outlineWidth: 2 }, |
|
|
lineStyle: { style: 'solid', width: 3, color: '#800080', gapColor: '#000000', dashLength: 20 } |
|
|
lineStyle: { style: 'solid', width: 3, color: '#2E5C3E', gapColor: '#000000', dashLength: 20 } |
|
|
}; |
|
|
}; |
|
|
return JSON.stringify(defaultAttrs); |
|
|
return JSON.stringify(defaultAttrs); |
|
|
}, |
|
|
}, |
|
|
@ -1044,7 +1290,7 @@ export default { |
|
|
this.selectedRoute = route; |
|
|
this.selectedRoute = route; |
|
|
this.showRouteDialog = true; |
|
|
this.showRouteDialog = true; |
|
|
}, |
|
|
}, |
|
|
// 更新航线数据 |
|
|
// 更新航线数据(含航点表编辑后的批量持久化) |
|
|
async updateRoute(updatedRoute) { |
|
|
async updateRoute(updatedRoute) { |
|
|
const index = this.routes.findIndex(r => r.id === updatedRoute.id); |
|
|
const index = this.routes.findIndex(r => r.id === updatedRoute.id); |
|
|
if (index === -1) return; |
|
|
if (index === -1) return; |
|
|
@ -1064,6 +1310,41 @@ export default { |
|
|
platform: updatedRoute.platform, |
|
|
platform: updatedRoute.platform, |
|
|
attributes: updatedRoute.attributes |
|
|
attributes: updatedRoute.attributes |
|
|
}; |
|
|
}; |
|
|
|
|
|
// 若编辑航线弹窗中提交了航点表数据,逐条持久化到数据库并合并到本地 |
|
|
|
|
|
if (updatedRoute.waypoints && updatedRoute.waypoints.length > 0) { |
|
|
|
|
|
for (const wp of updatedRoute.waypoints) { |
|
|
|
|
|
const payload = { |
|
|
|
|
|
id: wp.id, |
|
|
|
|
|
routeId: wp.routeId, |
|
|
|
|
|
name: wp.name, |
|
|
|
|
|
seq: wp.seq, |
|
|
|
|
|
lat: wp.lat, |
|
|
|
|
|
lng: wp.lng, |
|
|
|
|
|
alt: wp.alt, |
|
|
|
|
|
speed: wp.speed, |
|
|
|
|
|
startTime: wp.startTime != null && wp.startTime !== '' ? wp.startTime : 'K+00:00:00', |
|
|
|
|
|
turnAngle: wp.turnAngle |
|
|
|
|
|
}; |
|
|
|
|
|
if (wp.pointType != null) payload.pointType = wp.pointType; |
|
|
|
|
|
if (wp.holdParams != null) payload.holdParams = wp.holdParams; |
|
|
|
|
|
if (wp.labelFontSize != null) payload.labelFontSize = wp.labelFontSize; |
|
|
|
|
|
if (wp.labelColor != null) payload.labelColor = wp.labelColor; |
|
|
|
|
|
if (payload.turnAngle > 0 && this.$refs.cesiumMap) { |
|
|
|
|
|
payload.turnRadius = this.$refs.cesiumMap.getWaypointRadius(payload); |
|
|
|
|
|
} else { |
|
|
|
|
|
payload.turnRadius = 0; |
|
|
|
|
|
} |
|
|
|
|
|
await updateWaypoints(payload); |
|
|
|
|
|
} |
|
|
|
|
|
const mergedWaypoints = (newRouteData.waypoints || []).map((oldWp) => { |
|
|
|
|
|
const fromDialog = updatedRoute.waypoints.find((w) => w.id === oldWp.id); |
|
|
|
|
|
return fromDialog ? { ...oldWp, ...fromDialog } : oldWp; |
|
|
|
|
|
}); |
|
|
|
|
|
newRouteData.waypoints = mergedWaypoints; |
|
|
|
|
|
if (this.selectedRouteDetails && this.selectedRouteId === updatedRoute.id) { |
|
|
|
|
|
this.selectedRouteDetails.waypoints = mergedWaypoints; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
this.routes.splice(index, 1, newRouteData); |
|
|
this.routes.splice(index, 1, newRouteData); |
|
|
if (this.selectedRouteDetails && this.selectedRouteId === updatedRoute.id) { |
|
|
if (this.selectedRouteDetails && this.selectedRouteId === updatedRoute.id) { |
|
|
this.selectedRouteDetails.name = updatedRoute.name; |
|
|
this.selectedRouteDetails.name = updatedRoute.name; |
|
|
@ -1071,14 +1352,20 @@ export default { |
|
|
this.selectedRouteDetails.platform = updatedRoute.platform; |
|
|
this.selectedRouteDetails.platform = updatedRoute.platform; |
|
|
this.selectedRouteDetails.attributes = updatedRoute.attributes; |
|
|
this.selectedRouteDetails.attributes = updatedRoute.attributes; |
|
|
} |
|
|
} |
|
|
this.$message.success('航线更新成功'); |
|
|
this.$message.success(updatedRoute.waypoints && updatedRoute.waypoints.length > 0 ? '航线与航点已保存' : '航线更新成功'); |
|
|
const routeStyle = updatedRoute.routeStyle || this.parseRouteStyle(updatedRoute.attributes); |
|
|
const routeStyle = updatedRoute.routeStyle || this.parseRouteStyle(updatedRoute.attributes); |
|
|
if (this.$refs.cesiumMap && this.activeRouteIds.includes(updatedRoute.id)) { |
|
|
if (this.$refs.cesiumMap && this.activeRouteIds.includes(updatedRoute.id)) { |
|
|
const route = this.routes.find(r => r.id === updatedRoute.id); |
|
|
const route = this.routes.find(r => r.id === updatedRoute.id); |
|
|
if (route && route.waypoints && route.waypoints.length > 0) { |
|
|
if (route && route.waypoints && route.waypoints.length > 0) { |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
if (roomId && route.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId: updatedRoute.id, platformId: route.platformId }); |
|
|
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(updatedRoute.id, styleRes.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
this.$refs.cesiumMap.removeRouteById(updatedRoute.id); |
|
|
this.$refs.cesiumMap.removeRouteById(updatedRoute.id); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(route.waypoints, updatedRoute.id, route.platformId, route.platform, routeStyle); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(route.waypoints, updatedRoute.id, route.platformId, route.platform, routeStyle); |
|
|
// 切换平台后按当前推演时间把图标更新到对应位置,避免图标回到起点 |
|
|
|
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
@ -1122,7 +1409,8 @@ export default { |
|
|
// 同步地图:如果该航线正在显示,立即清除 |
|
|
// 同步地图:如果该航线正在显示,立即清除 |
|
|
if (this.$refs.cesiumMap) { |
|
|
if (this.$refs.cesiumMap) { |
|
|
this.$refs.cesiumMap.removeRouteById(route.id); |
|
|
this.$refs.cesiumMap.removeRouteById(route.id); |
|
|
// 同时清除该航线的威力区 |
|
|
// 同时清除该航线的探测区、威力区(removeRouteById 已会移除,此处显式调用保持一致) |
|
|
|
|
|
this.$refs.cesiumMap.removeDetectionZoneByRouteId(route.id); |
|
|
this.$refs.cesiumMap.removePowerZoneByRouteId(route.id); |
|
|
this.$refs.cesiumMap.removePowerZoneByRouteId(route.id); |
|
|
} |
|
|
} |
|
|
// 同步状态:从选中列表中移除该 ID |
|
|
// 同步状态:从选中列表中移除该 ID |
|
|
@ -1176,18 +1464,26 @@ export default { |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
this.routes = allRoutes; |
|
|
this.routes = allRoutes; |
|
|
// 直接根据 activeRouteIds 同步地图即可 |
|
|
// 先预取所有展示中航线的平台样式,再渲染,避免平台图标先黑后变色 |
|
|
if (this.activeRouteIds.length > 0) { |
|
|
if (this.activeRouteIds.length > 0 && this.$refs.cesiumMap) { |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
await Promise.all(this.activeRouteIds.map(async (id) => { |
|
|
|
|
|
const route = this.routes.find(r => r.id === id); |
|
|
|
|
|
if (!route || !route.waypoints || route.waypoints.length === 0) return; |
|
|
|
|
|
if (roomId && route.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const res = await getPlatformStyle({ roomId, routeId: id, platformId: route.platformId }); |
|
|
|
|
|
if (res.data) this.$refs.cesiumMap.setPlatformStyle(id, res.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
|
|
|
})); |
|
|
this.$nextTick(() => { |
|
|
this.$nextTick(() => { |
|
|
this.activeRouteIds.forEach(id => { |
|
|
this.activeRouteIds.forEach(id => { |
|
|
const route = this.routes.find(r => r.id === id); |
|
|
const route = this.routes.find(r => r.id === id); |
|
|
// 只要数据里有点位,直接指挥地图画线 |
|
|
if (route && route.waypoints && route.waypoints.length > 0 && this.$refs.cesiumMap) { |
|
|
if (route && route.waypoints && route.waypoints.length > 0) { |
|
|
|
|
|
if (this.$refs.cesiumMap) { |
|
|
|
|
|
this.$refs.cesiumMap.removeRouteById(id); |
|
|
this.$refs.cesiumMap.removeRouteById(id); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(route.waypoints, id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(route.waypoints, id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
}); |
|
|
}); |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
@ -1224,7 +1520,7 @@ export default { |
|
|
|
|
|
|
|
|
openAddHoldDuringDrawing() { |
|
|
openAddHoldDuringDrawing() { |
|
|
this.addHoldContext = { mode: 'drawing' }; |
|
|
this.addHoldContext = { mode: 'drawing' }; |
|
|
this.addHoldForm = { holdType: 'hold_circle', radius: 500, semiMajor: 500, semiMinor: 300, headingDeg: 0, clockwise: true, startTime: '', startTimeMinutes: 60 }; |
|
|
this.addHoldForm = { holdType: 'hold_circle', radius: 15000, semiMajor: 500, semiMinor: 300, headingDeg: 0, clockwise: true, startTime: '', startTimeMinutes: 60 }; |
|
|
this.showAddHoldDialog = true; |
|
|
this.showAddHoldDialog = true; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
@ -1242,9 +1538,24 @@ export default { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 2. 构造数据(含盘旋航点的 pointType、holdParams;地图标签默认字号 14、颜色 #333333) |
|
|
// 2. 构造数据(含盘旋航点的 pointType、holdParams;地图标签默认字号 14、颜色 #333333) |
|
|
// 新建航线时:首尾航点转弯坡度固定为 0,中间可编辑航点默认 45° |
|
|
// 默认相对 K 时:按“路程÷默认速度”累加;用向上取整避免因整数分钟舍去导致冲突检测误报“无法按时到达” |
|
|
const wpCount = this.tempMapPoints.length; |
|
|
const wpCount = this.tempMapPoints.length; |
|
|
const finalWaypoints = this.tempMapPoints.map((p, index) => { |
|
|
let cumulativeMinutes = 0; |
|
|
|
|
|
const pointsWithStartTime = this.tempMapPoints.map((p, index) => { |
|
|
|
|
|
const startTime = this.minutesToStartTime(index === 0 ? 0 : Math.ceil(cumulativeMinutes)); |
|
|
|
|
|
if (index < wpCount - 1) { |
|
|
|
|
|
const next = this.tempMapPoints[index + 1]; |
|
|
|
|
|
const dist = this.segmentDistance( |
|
|
|
|
|
{ lat: p.lat, lng: p.lng, alt: p.alt != null ? p.alt : 5000 }, |
|
|
|
|
|
{ lat: next.lat, lng: next.lng, alt: next.alt != null ? next.alt : 5000 } |
|
|
|
|
|
); |
|
|
|
|
|
const speedKmh = Number(p.speed) || 800; |
|
|
|
|
|
cumulativeMinutes += (dist / 1000) * (60 / speedKmh); |
|
|
|
|
|
} |
|
|
|
|
|
return { ...p, startTime }; |
|
|
|
|
|
}); |
|
|
|
|
|
// 新建航线时:首尾航点转弯坡度固定为 0,中间可编辑航点默认 45° |
|
|
|
|
|
const finalWaypoints = pointsWithStartTime.map((p, index) => { |
|
|
const isFirstOrLast = index === 0 || index === wpCount - 1; |
|
|
const isFirstOrLast = index === 0 || index === wpCount - 1; |
|
|
const defaultTurnAngle = isFirstOrLast ? 0.0 : 45.0; |
|
|
const defaultTurnAngle = isFirstOrLast ? 0.0 : 45.0; |
|
|
return { |
|
|
return { |
|
|
@ -1253,7 +1564,7 @@ export default { |
|
|
lng: p.lng, |
|
|
lng: p.lng, |
|
|
alt: p.alt != null ? p.alt : 5000.0, |
|
|
alt: p.alt != null ? p.alt : 5000.0, |
|
|
speed: p.speed != null ? p.speed : 800.0, |
|
|
speed: p.speed != null ? p.speed : 800.0, |
|
|
startTime: p.startTime || 'K+00:00:00', |
|
|
startTime: p.startTime, |
|
|
turnAngle: p.turnAngle != null ? p.turnAngle : defaultTurnAngle, |
|
|
turnAngle: p.turnAngle != null ? p.turnAngle : defaultTurnAngle, |
|
|
labelFontSize: p.labelFontSize != null ? p.labelFontSize : 14, |
|
|
labelFontSize: p.labelFontSize != null ? p.labelFontSize : 14, |
|
|
labelColor: p.labelColor || '#333333', |
|
|
labelColor: p.labelColor || '#333333', |
|
|
@ -1383,13 +1694,21 @@ export default { |
|
|
if (idxInList !== -1) routeInList.waypoints.splice(idxInList, 1, merged); |
|
|
if (idxInList !== -1) routeInList.waypoints.splice(idxInList, 1, merged); |
|
|
} |
|
|
} |
|
|
if (this.$refs.cesiumMap) { |
|
|
if (this.$refs.cesiumMap) { |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
const sd = this.selectedRouteDetails; |
|
|
|
|
|
if (roomId && sd.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId: sd.id, platformId: sd.platformId }); |
|
|
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(sd.id, styleRes.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
this.$refs.cesiumMap.updateWaypointGraphicById(updatedWaypoint.id, updatedWaypoint.name); |
|
|
this.$refs.cesiumMap.updateWaypointGraphicById(updatedWaypoint.id, updatedWaypoint.name); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints( |
|
|
this.$refs.cesiumMap.renderRouteWaypoints( |
|
|
this.selectedRouteDetails.waypoints, |
|
|
sd.waypoints, |
|
|
this.selectedRouteDetails.id, |
|
|
sd.id, |
|
|
this.selectedRouteDetails.platformId, |
|
|
sd.platformId, |
|
|
this.selectedRouteDetails.platform, |
|
|
sd.platform, |
|
|
this.parseRouteStyle(this.selectedRouteDetails.attributes) |
|
|
this.parseRouteStyle(sd.attributes) |
|
|
); |
|
|
); |
|
|
} |
|
|
} |
|
|
this.showWaypointDialog = false; |
|
|
this.showWaypointDialog = false; |
|
|
@ -1805,6 +2124,18 @@ export default { |
|
|
} catch (e) { /* 解析失败保留默认 */ } |
|
|
} catch (e) { /* 解析失败保留默认 */ } |
|
|
if (Array.isArray(arr) && arr.length > 0) { |
|
|
if (Array.isArray(arr) && arr.length > 0) { |
|
|
const defaultMap = (this.defaultMenuItems || []).reduce((m, it) => { m[it.id] = it; return m }, {}) |
|
|
const defaultMap = (this.defaultMenuItems || []).reduce((m, it) => { m[it.id] = it; return m }, {}) |
|
|
|
|
|
const savedIds = new Set(arr.map(i => i.id)) |
|
|
|
|
|
// 合并缺失的默认项(如新增的4T),按 defaultMenuItems 顺序插入 |
|
|
|
|
|
const defaultOrder = (this.defaultMenuItems || []).map(d => d.id) |
|
|
|
|
|
defaultOrder.forEach(defId => { |
|
|
|
|
|
if (!savedIds.has(defId) && defaultMap[defId]) { |
|
|
|
|
|
const insertAfterId = defaultOrder[defaultOrder.indexOf(defId) - 1] |
|
|
|
|
|
const refIdx = insertAfterId ? arr.findIndex(i => i.id === insertAfterId) : -1 |
|
|
|
|
|
const insertIdx = refIdx >= 0 ? refIdx + 1 : 0 |
|
|
|
|
|
arr.splice(insertIdx, 0, { ...defaultMap[defId] }) |
|
|
|
|
|
savedIds.add(defId) |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
this.menuItems = arr.map(item => { |
|
|
this.menuItems = arr.map(item => { |
|
|
const def = defaultMap[item.id] |
|
|
const def = defaultMap[item.id] |
|
|
if (def) return { ...item, name: def.name, icon: def.icon, action: def.action } |
|
|
if (def) return { ...item, name: def.name, icon: def.icon, action: def.action } |
|
|
@ -2064,6 +2395,11 @@ export default { |
|
|
this.$message.success(this.showRoute ? '显示航线' : '隐藏航线'); |
|
|
this.$message.success(this.showRoute ? '显示航线' : '隐藏航线'); |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
generateGanttChart() { |
|
|
|
|
|
const url = this.$router.resolve('/ganttChart').href |
|
|
|
|
|
window.open(url, '_blank') |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
systemDescription() { |
|
|
systemDescription() { |
|
|
this.$message.success('系统说明'); |
|
|
this.$message.success('系统说明'); |
|
|
// 这里可以添加系统说明的逻辑 |
|
|
// 这里可以添加系统说明的逻辑 |
|
|
@ -2091,21 +2427,25 @@ export default { |
|
|
this.handleMenuAction(item.action) |
|
|
this.handleMenuAction(item.action) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 点击方案、平台、冲突等菜单项时,停止地图绘制状态 |
|
|
// 点击方案、平台、冲突、4T等菜单项时,停止地图绘制状态 |
|
|
if (item.id === 'file' || item.id === 'start' || item.id === 'insert') { |
|
|
if (item.id === 'file' || item.id === 'start' || item.id === 'insert' || item.id === '4t') { |
|
|
this.drawDom = false; |
|
|
this.drawDom = false; |
|
|
this.airspaceDrawDom = false; |
|
|
this.airspaceDrawDom = false; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 点击左侧的方案、冲突、平台时,切换右侧面板内容 |
|
|
// 点击左侧的方案、冲突、平台时,切换右侧面板内容 |
|
|
if (item.id === 'file') { |
|
|
if (item.id === 'file') { |
|
|
// 如果当前已经是方案标签页,则关闭右侧面板 |
|
|
// 方案:切换右侧面板,并显示4T悬浮窗 |
|
|
|
|
|
this.show4TPanel = true; |
|
|
if (this.activeRightTab === 'plan' && !this.isRightPanelHidden) { |
|
|
if (this.activeRightTab === 'plan' && !this.isRightPanelHidden) { |
|
|
this.isRightPanelHidden = true; |
|
|
this.isRightPanelHidden = true; |
|
|
} else { |
|
|
} else { |
|
|
this.activeRightTab = 'plan'; |
|
|
this.activeRightTab = 'plan'; |
|
|
this.isRightPanelHidden = false; |
|
|
this.isRightPanelHidden = false; |
|
|
} |
|
|
} |
|
|
|
|
|
} else if (item.id === '4t') { |
|
|
|
|
|
// 4T:切换4T悬浮窗显示 |
|
|
|
|
|
this.show4TPanel = !this.show4TPanel; |
|
|
} else if (item.id === 'start') { |
|
|
} else if (item.id === 'start') { |
|
|
// 如果当前已经是冲突标签页,则关闭右侧面板 |
|
|
// 如果当前已经是冲突标签页,则关闭右侧面板 |
|
|
if (this.activeRightTab === 'conflict' && !this.isRightPanelHidden) { |
|
|
if (this.activeRightTab === 'conflict' && !this.isRightPanelHidden) { |
|
|
@ -2377,11 +2717,14 @@ export default { |
|
|
const pos = { lng: p.lng, lat: p.lat, alt: p.alt }; |
|
|
const pos = { lng: p.lng, lat: p.lat, alt: p.alt }; |
|
|
return { |
|
|
return { |
|
|
segments: [{ startTime: globalMin, endTime: globalMax, startPos: pos, endPos: pos, type: 'wait' }], |
|
|
segments: [{ startTime: globalMin, endTime: globalMax, startPos: pos, endPos: pos, type: 'wait' }], |
|
|
warnings |
|
|
warnings, |
|
|
|
|
|
earlyArrivalLegs: [], |
|
|
|
|
|
lateArrivalLegs: [] |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
const effectiveTime = [points[0].minutes]; |
|
|
const effectiveTime = [points[0].minutes]; |
|
|
const segments = []; |
|
|
const segments = []; |
|
|
|
|
|
const lateArrivalLegs = []; // 无法按时到达的航段,供冲突检测用 |
|
|
const path = pathData && pathData.path; |
|
|
const path = pathData && pathData.path; |
|
|
const segmentEndIndices = pathData && pathData.segmentEndIndices; |
|
|
const segmentEndIndices = pathData && pathData.segmentEndIndices; |
|
|
const holdArcRanges = pathData && pathData.holdArcRanges || {}; |
|
|
const holdArcRanges = pathData && pathData.holdArcRanges || {}; |
|
|
@ -2448,6 +2791,13 @@ export default { |
|
|
warnings.push( |
|
|
warnings.push( |
|
|
`某航段:距离约 ${(dist / 1000).toFixed(1)}km,计划 ${(scheduled - points[i].minutes).toFixed(0)} 分钟,当前速度 ${speedKmh}km/h 无法按时到达,约需 ≥${Math.ceil(requiredSpeedKmh)}km/h,请调整相对K时或速度。` |
|
|
`某航段:距离约 ${(dist / 1000).toFixed(1)}km,计划 ${(scheduled - points[i].minutes).toFixed(0)} 分钟,当前速度 ${speedKmh}km/h 无法按时到达,约需 ≥${Math.ceil(requiredSpeedKmh)}km/h,请调整相对K时或速度。` |
|
|
); |
|
|
); |
|
|
|
|
|
lateArrivalLegs.push({ |
|
|
|
|
|
legIndex: i, |
|
|
|
|
|
fromName: waypoints[i].name, |
|
|
|
|
|
toName: waypoints[i + 1].name, |
|
|
|
|
|
requiredSpeedKmh: Math.ceil(requiredSpeedKmh), |
|
|
|
|
|
speedKmh |
|
|
|
|
|
}); |
|
|
} else if (actualArrival < scheduled - 0.5) { |
|
|
} else if (actualArrival < scheduled - 0.5) { |
|
|
warnings.push('存在航段将提前到达下一航点,平台将在该点等待至计划时间再飞往下一段。'); |
|
|
warnings.push('存在航段将提前到达下一航点,平台将在该点等待至计划时间再飞往下一段。'); |
|
|
} |
|
|
} |
|
|
@ -2472,7 +2822,7 @@ export default { |
|
|
earlyArrivalLegs.push({ legIndex: i, scheduled, actualArrival, fromName: waypoints[i].name, toName: waypoints[i + 1].name }); |
|
|
earlyArrivalLegs.push({ legIndex: i, scheduled, actualArrival, fromName: waypoints[i].name, toName: waypoints[i + 1].name }); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return { segments, warnings, earlyArrivalLegs }; |
|
|
return { segments, warnings, earlyArrivalLegs, lateArrivalLegs }; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
/** 从路径片段中按比例 t(0~1)取点:按弧长比例插值 */ |
|
|
/** 从路径片段中按比例 t(0~1)取点:按弧长比例插值 */ |
|
|
@ -2717,6 +3067,13 @@ export default { |
|
|
await this.getList(); |
|
|
await this.getList(); |
|
|
const updated = this.routes.find(r => r.id === routeId); |
|
|
const updated = this.routes.find(r => r.id === routeId); |
|
|
if (updated && updated.waypoints && this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
if (updated && updated.waypoints && this.activeRouteIds.includes(routeId) && this.$refs.cesiumMap) { |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
if (roomId && updated.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId, platformId: updated.platformId }); |
|
|
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(routeId, styleRes.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
this.$refs.cesiumMap.removeRouteById(routeId); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(updated.waypoints, routeId, updated.platformId, updated.platform, this.parseRouteStyle(updated.attributes)); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(updated.waypoints, routeId, updated.platformId, updated.platform, this.parseRouteStyle(updated.attributes)); |
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
this.$nextTick(() => this.updateDeductionPositions()); |
|
|
@ -2786,7 +3143,8 @@ export default { |
|
|
this.activeRouteIds.splice(index, 1); |
|
|
this.activeRouteIds.splice(index, 1); |
|
|
if (this.$refs.cesiumMap) { |
|
|
if (this.$refs.cesiumMap) { |
|
|
this.$refs.cesiumMap.removeRouteById(route.id); |
|
|
this.$refs.cesiumMap.removeRouteById(route.id); |
|
|
// 隐藏航线时,同时移除关联的威力区 |
|
|
// 隐藏航线时,同时移除关联的探测区、威力区 |
|
|
|
|
|
this.$refs.cesiumMap.removeDetectionZoneByRouteId(route.id); |
|
|
this.$refs.cesiumMap.removePowerZoneByRouteId(route.id); |
|
|
this.$refs.cesiumMap.removePowerZoneByRouteId(route.id); |
|
|
} |
|
|
} |
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { |
|
|
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) { |
|
|
@ -2823,10 +3181,14 @@ export default { |
|
|
const waypoints = fullRouteData.waypoints || []; |
|
|
const waypoints = fullRouteData.waypoints || []; |
|
|
this.activeRouteIds.push(route.id); |
|
|
this.activeRouteIds.push(route.id); |
|
|
this.selectedRouteId = fullRouteData.id; |
|
|
this.selectedRouteId = fullRouteData.id; |
|
|
|
|
|
// 合并 list 中的 platformId/platform,以便拖拽航点后重绘时平台图标不丢失 |
|
|
this.selectedRouteDetails = { |
|
|
this.selectedRouteDetails = { |
|
|
id: fullRouteData.id, |
|
|
id: fullRouteData.id, |
|
|
name: fullRouteData.callSign, |
|
|
name: fullRouteData.callSign, |
|
|
waypoints: waypoints |
|
|
waypoints: waypoints, |
|
|
|
|
|
platformId: route.platformId, |
|
|
|
|
|
platform: route.platform, |
|
|
|
|
|
attributes: route.attributes |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
// 更新 routes 数组中对应航线的 waypoints 字段 |
|
|
// 更新 routes 数组中对应航线的 waypoints 字段 |
|
|
@ -2839,8 +3201,14 @@ export default { |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (waypoints.length > 0) { |
|
|
if (waypoints.length > 0) { |
|
|
// 通知地图渲染 |
|
|
|
|
|
if (this.$refs.cesiumMap) { |
|
|
if (this.$refs.cesiumMap) { |
|
|
|
|
|
const roomId = this.currentRoomId; |
|
|
|
|
|
if (roomId && route.platformId) { |
|
|
|
|
|
try { |
|
|
|
|
|
const styleRes = await getPlatformStyle({ roomId, routeId: route.id, platformId: route.platformId }); |
|
|
|
|
|
if (styleRes.data) this.$refs.cesiumMap.setPlatformStyle(route.id, styleRes.data); |
|
|
|
|
|
} catch (_) {} |
|
|
|
|
|
} |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(waypoints, route.id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); |
|
|
this.$refs.cesiumMap.renderRouteWaypoints(waypoints, route.id, route.platformId, route.platform, this.parseRouteStyle(route.attributes)); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
} else { |
|
|
@ -2975,11 +3343,15 @@ export default { |
|
|
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; |
|
|
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1]; |
|
|
getRoutes(lastId).then(res => { |
|
|
getRoutes(lastId).then(res => { |
|
|
if (res.code === 200 && res.data) { |
|
|
if (res.code === 200 && res.data) { |
|
|
|
|
|
const fromList = this.routes.find(r => r.id === lastId); |
|
|
this.selectedRouteId = res.data.id; |
|
|
this.selectedRouteId = res.data.id; |
|
|
this.selectedRouteDetails = { |
|
|
this.selectedRouteDetails = { |
|
|
id: res.data.id, |
|
|
id: res.data.id, |
|
|
name: res.data.callSign, |
|
|
name: res.data.callSign, |
|
|
waypoints: res.data.waypoints || [] |
|
|
waypoints: res.data.waypoints || [], |
|
|
|
|
|
platformId: fromList?.platformId, |
|
|
|
|
|
platform: fromList?.platform, |
|
|
|
|
|
attributes: fromList?.attributes |
|
|
}; |
|
|
}; |
|
|
} |
|
|
} |
|
|
}).catch(e => { |
|
|
}).catch(e => { |
|
|
@ -2996,10 +3368,58 @@ export default { |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
// 冲突操作 |
|
|
// 冲突操作:根据当前展示的航线与时间轴计算真实问题(提前到达、无法按时到达) |
|
|
runConflictCheck() { |
|
|
runConflictCheck() { |
|
|
this.conflictCount = 2; |
|
|
const list = []; |
|
|
this.$message.warning('检测到2处航线冲突'); |
|
|
let id = 1; |
|
|
|
|
|
const routeIds = this.activeRouteIds && this.activeRouteIds.length > 0 ? this.activeRouteIds : this.routes.map(r => r.id); |
|
|
|
|
|
const { minMinutes, maxMinutes } = this.getDeductionTimeRange(); |
|
|
|
|
|
|
|
|
|
|
|
routeIds.forEach(routeId => { |
|
|
|
|
|
const route = this.routes.find(r => r.id === routeId); |
|
|
|
|
|
if (!route || !route.waypoints || route.waypoints.length < 2) return; |
|
|
|
|
|
let pathData = null; |
|
|
|
|
|
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) { |
|
|
|
|
|
const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(route.waypoints); |
|
|
|
|
|
if (ret.path && ret.path.length > 0 && ret.segmentEndIndices) { |
|
|
|
|
|
pathData = { path: ret.path, segmentEndIndices: ret.segmentEndIndices, holdArcRanges: ret.holdArcRanges || {} }; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
const { earlyArrivalLegs, lateArrivalLegs } = this.buildRouteTimeline(route.waypoints, minMinutes, maxMinutes, pathData); |
|
|
|
|
|
|
|
|
|
|
|
const routeName = route.name || `航线${route.id}`; |
|
|
|
|
|
(earlyArrivalLegs || []).forEach(leg => { |
|
|
|
|
|
list.push({ |
|
|
|
|
|
id: id++, |
|
|
|
|
|
title: '提前到达', |
|
|
|
|
|
routeName, |
|
|
|
|
|
fromWaypoint: leg.fromName, |
|
|
|
|
|
toWaypoint: leg.toName, |
|
|
|
|
|
time: this.minutesToStartTime(leg.actualArrival), |
|
|
|
|
|
suggestion: '该航段将提前到达下一航点,建议在此段加入盘旋或延后下一航点计划时间。', |
|
|
|
|
|
severity: 'high' |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
(lateArrivalLegs || []).forEach(leg => { |
|
|
|
|
|
list.push({ |
|
|
|
|
|
id: id++, |
|
|
|
|
|
title: '无法按时到达', |
|
|
|
|
|
routeName, |
|
|
|
|
|
fromWaypoint: leg.fromName, |
|
|
|
|
|
toWaypoint: leg.toName, |
|
|
|
|
|
suggestion: `当前速度不足,建议将本段速度提升至 ≥${leg.requiredSpeedKmh} km/h,或延后下一航点计划时间。`, |
|
|
|
|
|
severity: 'high' |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
this.conflicts = list; |
|
|
|
|
|
this.conflictCount = list.length; |
|
|
|
|
|
if (list.length > 0) { |
|
|
|
|
|
this.$message.warning(`检测到 ${list.length} 处航线时间问题`); |
|
|
|
|
|
} else { |
|
|
|
|
|
this.$message.success('未发现航线时间冲突'); |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
viewConflict(conflict) { |
|
|
viewConflict(conflict) { |
|
|
@ -3034,7 +3454,7 @@ export default { |
|
|
overflow: hidden; |
|
|
overflow: hidden; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/* 地图背景 - 保持不变 */ |
|
|
/* 地图背景:使用相对路径便于 IDE 与构建解析;若需图片请将 map-background.png 放到 src/assets/ */ |
|
|
.map-background { |
|
|
.map-background { |
|
|
position: absolute; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
top: 0; |
|
|
@ -3042,8 +3462,7 @@ export default { |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
height: 100%; |
|
|
background: linear-gradient(135deg, #1a2f4b 0%, #2c3e50 100%); |
|
|
background: linear-gradient(135deg, #1a2f4b 0%, #2c3e50 100%); |
|
|
/* 正确的写法,直接复制这行替换 */ |
|
|
/* 若已存在 src/assets/map-background.png,可改为:background: url('../../assets/map-background.png'); 并注释掉上一行 */ |
|
|
background: url('~@/assets/map-background.png'); |
|
|
|
|
|
background-size: cover; |
|
|
background-size: cover; |
|
|
background-position: center; |
|
|
background-position: center; |
|
|
z-index: 1; |
|
|
z-index: 1; |
|
|
@ -3267,40 +3686,6 @@ export default { |
|
|
line-height: 1.5; |
|
|
line-height: 1.5; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.deduction-hint { |
|
|
|
|
|
margin: 0 0 8px 0; |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
color: #909399; |
|
|
|
|
|
line-height: 1.4; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.deduction-warnings { |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
gap: 6px; |
|
|
|
|
|
margin-top: 8px; |
|
|
|
|
|
padding: 6px 10px; |
|
|
|
|
|
background: rgba(230, 162, 60, 0.15); |
|
|
|
|
|
border: 1px solid rgba(230, 162, 60, 0.5); |
|
|
|
|
|
border-radius: 6px; |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
color: #b88230; |
|
|
|
|
|
} |
|
|
|
|
|
.deduction-warnings i { |
|
|
|
|
|
flex-shrink: 0; |
|
|
|
|
|
} |
|
|
|
|
|
.deduction-warnings span { |
|
|
|
|
|
flex: 1; |
|
|
|
|
|
overflow: hidden; |
|
|
|
|
|
text-overflow: ellipsis; |
|
|
|
|
|
white-space: nowrap; |
|
|
|
|
|
} |
|
|
|
|
|
.deduction-warnings .warnings-more { |
|
|
|
|
|
flex-shrink: 0; |
|
|
|
|
|
color: #008aff; |
|
|
|
|
|
cursor: help; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
.popup-hide-btn { |
|
|
.popup-hide-btn { |
|
|
position: absolute; |
|
|
position: absolute; |
|
|
top: -28px; |
|
|
top: -28px; |
|
|
|