Compare commits

...

7 Commits

  1. 80
      ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
  2. 122
      ruoyi-ui/src/views/cesiumMap/MapScreenDomLabels.vue
  3. 2077
      ruoyi-ui/src/views/cesiumMap/index.vue
  4. 9
      ruoyi-ui/src/views/childRoom/LeftMenu.vue
  5. 89
      ruoyi-ui/src/views/childRoom/RightPanel.vue
  6. 202
      ruoyi-ui/src/views/childRoom/index.vue
  7. 73
      ruoyi-ui/src/views/dialogs/RouteEditDialog.vue

80
ruoyi-ui/src/views/cesiumMap/ContextMenu.vue

@ -7,7 +7,17 @@
<span>切换选择 ({{ (pickIndex || 0) + 1 }}/{{ pickList.length }})</span>
</div>
</div>
<div class="menu-section" v-if="!entityData || (entityData.type !== 'routePlatform' && entityData.type !== 'route')">
<!-- 框选多平台仅批量删除 -->
<div class="menu-section" v-if="entityData && entityData.type === 'platformBoxSelection'">
<div class="menu-title">框选平台{{ entityData.count }} </div>
<div class="menu-item" @click="handleDeleteBoxSelection">
<span class="menu-icon">🗑</span>
<span>删除全部框选平台</span>
</div>
</div>
<div class="menu-section" v-if="(!entityData || (entityData.type !== 'routePlatform' && entityData.type !== 'route')) && (!entityData || entityData.type !== 'platformBoxSelection')">
<div class="menu-item" @click="handleDelete">
<span class="menu-icon">🗑</span>
<span>删除</span>
@ -131,6 +141,14 @@
<span class="menu-icon">📋</span>
<span>复制</span>
</div>
<div v-if="!isRouteLocked" class="menu-item" @click="handleRouteSegmentSplit">
<span class="menu-icon"></span>
<span>拆分航段</span>
</div>
<div v-if="!isRouteLocked" class="menu-item" @click="handleRouteSegmentCopy">
<span class="menu-icon">📄</span>
<span>拆分复制</span>
</div>
<div class="menu-item" @click="handleSingleRouteDeduction">
<span class="menu-icon"></span>
<span>单条航线推演</span>
@ -224,6 +242,21 @@
<span>磁方位</span>
</div>
</div>
<template v-if="toolMode === 'ranging'">
<div class="menu-item" @click="toggleRangingUnitMenu">
<span class="menu-icon">📏</span>
<span>距离单位</span>
<span class="menu-value">{{ rangingDistanceUnit === 'nm' ? '海里' : '公里' }}</span>
</div>
<div class="sub-menu" v-if="showRangingUnitMenu">
<div class="sub-menu-item" @click="selectRangingUnit('km')" :class="{ active: rangingDistanceUnit === 'km' }">
<span>公里km</span>
</div>
<div class="sub-menu-item" @click="selectRangingUnit('nm')" :class="{ active: rangingDistanceUnit === 'nm' }">
<span>海里1 海里 = 1852 </span>
</div>
</div>
</template>
</div>
<!-- 点特有选项 -->
@ -598,6 +631,14 @@ export default {
powerZoneVisible: {
type: Boolean,
default: true
},
toolMode: {
type: String,
default: 'airspace'
},
rangingDistanceUnit: {
type: String,
default: 'km'
}
},
data() {
@ -615,6 +656,7 @@ export default {
showOpacityPicker: false,
showFontSizePicker: false,
showBearingTypeMenu: false,
showRangingUnitMenu: false,
presetColors: [
'#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#FF00FF', '#00FFFF',
'#FF6600', '#663399', '#999999', '#000000', '#FFFFFF', '#FF99CC',
@ -671,6 +713,10 @@ export default {
this.$emit('delete')
},
handleDeleteBoxSelection() {
this.$emit('delete-box-selected-platforms')
},
handleAdjustPosition() {
this.$emit('adjust-airspace-position')
},
@ -729,6 +775,14 @@ export default {
this.$emit('copy-route')
},
handleRouteSegmentSplit() {
this.$emit('route-segment-split')
},
handleRouteSegmentCopy() {
this.$emit('route-segment-copy')
},
handleSingleRouteDeduction() {
this.$emit('single-route-deduction', this.entityData.routeId)
},
@ -842,6 +896,8 @@ export default {
this.showSizePicker = false
this.showOpacityPicker = false
this.showFontSizePicker = false
this.showBearingTypeMenu = false
this.showRangingUnitMenu = false
this.showColorPickerFor = property
}
},
@ -860,6 +916,8 @@ export default {
this.showSizePicker = false
this.showOpacityPicker = false
this.showFontSizePicker = false
this.showBearingTypeMenu = false
this.showRangingUnitMenu = false
this.showWidthPicker = true
}
},
@ -954,6 +1012,7 @@ export default {
this.showSizePicker = false
this.showOpacityPicker = false
this.showFontSizePicker = false
this.showRangingUnitMenu = false
}
},
@ -961,6 +1020,25 @@ export default {
//
this.$emit('update-property', 'bearingType', bearingType)
this.showBearingTypeMenu = false
},
toggleRangingUnitMenu() {
this.showRangingUnitMenu = !this.showRangingUnitMenu
if (this.showRangingUnitMenu) {
this.showColorPickerFor = null
this.showWidthPicker = false
this.showSizePicker = false
this.showOpacityPicker = false
this.showFontSizePicker = false
this.showBearingTypeMenu = false
}
},
selectRangingUnit(unit) {
if (unit === 'km' || unit === 'nm') {
this.$emit('ranging-distance-unit', unit)
}
this.showRangingUnitMenu = false
}
}
}

122
ruoyi-ui/src/views/cesiumMap/MapScreenDomLabels.vue

@ -0,0 +1,122 @@
<template>
<div class="map-screen-dom-labels-layer">
<div
v-for="item in items"
v-show="item.visible"
:key="item.id"
class="map-screen-dom-label"
:class="item.themeClass"
:style="item.wrapperStyle"
>
<template v-if="item.kind === 'platform'">
<div
class="map-screen-dom-label__platform-name"
:style="{ fontSize: item.titleSizePx + 'px' }"
>
{{ item.name }}
</div>
<div
class="map-screen-dom-label__platform-stats"
:style="{ fontSize: item.statSizePx + 'px' }"
>
h: {{ item.altitude }}m &nbsp; v: {{ item.speed }}km/h &nbsp; s: {{ item.heading }}°
</div>
</template>
<template v-else-if="item.kind === 'waypoint' || item.kind === 'mapText'">
<span class="map-screen-dom-label__transparent-text" :style="item.transparentTextStyle">{{ item.text }}</span>
</template>
<template v-else>
<span
class="map-screen-dom-label__plain"
:style="{ whiteSpace: item.multiline ? 'pre-line' : 'nowrap' }"
>{{ item.text }}</span>
</template>
</div>
</div>
</template>
<script>
export default {
name: 'MapScreenDomLabels',
props: {
items: {
type: Array,
default: () => []
}
}
}
</script>
<style scoped>
.map-screen-dom-labels-layer {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 9998;
overflow: hidden;
}
.map-screen-dom-label {
position: absolute;
pointer-events: none;
white-space: nowrap;
}
/* 与 HoverTooltip 一致的深色提示条 */
.map-screen-dom-label--tooltip {
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
line-height: 1.35;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
}
/* 飞机平台标牌:白底黑字、机名蓝色,尺寸紧凑 */
.map-screen-dom-label--platform-card {
background: rgba(255, 255, 255, 0.96);
color: #1a1a1a;
padding: 4px 8px;
border-radius: 4px;
border: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
line-height: 1.25;
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
}
.map-screen-dom-label--platform {
min-width: 0;
text-align: center;
}
.map-screen-dom-label__platform-name {
font-weight: 600;
line-height: 1.2;
margin-bottom: 2px;
color: #0078ff;
}
.map-screen-dom-label__platform-stats {
font-weight: 400;
color: #1a1a1a;
}
/* 航点 / 插入文字 / 空域威力区命名:透明底,描边式字 */
.map-screen-dom-label--maptext {
background: transparent;
padding: 0 2px;
border-radius: 0;
box-shadow: none;
font-family: 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', sans-serif;
line-height: 1.2;
}
.map-screen-dom-label__transparent-text {
display: inline-block;
}
</style>

2077
ruoyi-ui/src/views/cesiumMap/index.vue

File diff suppressed because it is too large

9
ruoyi-ui/src/views/childRoom/LeftMenu.vue

@ -29,7 +29,7 @@
v-for="item in localMenuItems"
:key="item.id"
class="menu-item"
:class="{ active: activeMenu === item.id, 'dragging': isDragging, 'edit-mode': isEditMode }"
:class="{ active: isItemActive(item), 'dragging': isDragging, 'edit-mode': isEditMode }"
@click="handleSelectMenu(item)"
@contextmenu.prevent="handleRightClick(item)"
:title="item.name"
@ -143,6 +143,13 @@ export default {
return icon && typeof icon === 'string' && !icon.startsWith('el-icon-')
},
/** 框选等项通过图标编辑添加时 id 为随机字符串,activeMenu 固定为 platformBoxSelect,需按 action 匹配高亮 */
isItemActive(item) {
if (this.activeMenu === item.id) return true
if (this.activeMenu === 'platformBoxSelect' && item.action === 'platformBoxSelect') return true
return false
},
handleHide() {
this.$emit('hide')
},

89
ruoyi-ui/src/views/childRoom/RightPanel.vue

@ -55,7 +55,7 @@
:class="getRouteClasses(route.id)"
>
<div class="tree-item-header" @click="toggleRoute(route.id)">
<i :class="expandedRoutes.includes(route.id) ? 'el-icon-map-location' : 'el-icon-map-location'" class="tree-icon"></i>
<i class="el-icon-map-location tree-icon"></i>
<div class="tree-item-info">
<div class="tree-item-name">{{ route.name }}</div>
<div class="tree-item-meta">{{ route.points }}{{ $t('rightPanel.points') }}</div>
@ -98,25 +98,6 @@
></i>
</div>
</div>
<!-- 航点列表 -->
<div v-if="expandedRoutes.includes(route.id)" class="tree-children waypoint-children">
<div
v-for="(point,index) in (expandedRoutes.includes(route.id) && route.waypoints ? route.waypoints : [])"
:key="point.name"
class="tree-item waypoint-item"
>
<div class="tree-item-header">
<i class="el-icon-location tree-icon"></i>
<div class="tree-item-info">
<div class="tree-item-name">{{ point.name }}</div>
<div class="tree-item-meta">高度: {{ point.alt }}m | 速度: {{ point.speed }}<template v-if="point.startTime"> | 相对K: {{ formatWaypointKTime(point.startTime) }}</template></div>
</div>
<div class="tree-item-actions">
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenWaypointDialog(point, index, route.waypoints.length)"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -289,7 +270,7 @@ export default {
type: Object,
default: null
},
/** 父组件要求展开的航线 ID 列表(如冲突定位时),会展开对应方案与航线 */
/** 父组件要求展开的方案树(如冲突定位时),会展开对应方案以便看到航线行 */
expandRouteIds: {
type: Array,
default: () => []
@ -311,7 +292,6 @@ export default {
return {
activePlatformTab: 'air',
expandedPlans: [], //
expandedRoutes: [], // 线
platformJustDragged: false //
}
},
@ -323,9 +303,6 @@ export default {
if (r && r.scenarioId != null && !this.expandedPlans.includes(r.scenarioId)) {
this.expandedPlans.push(r.scenarioId);
}
if (routeId != null && !this.expandedRoutes.includes(routeId)) {
this.expandedRoutes.push(routeId);
}
});
}
},
@ -346,17 +323,6 @@ export default {
}
},
methods: {
/** 航点 startTime(如 K+00:40:00)格式化为简短显示:K+40 或 K-15 */
formatWaypointKTime(startTime) {
if (!startTime || typeof startTime !== 'string') return '—';
const m = startTime.match(/K([+-])(\d{2}):(\d{2})/);
if (!m) return startTime;
const sign = m[1];
const h = parseInt(m[2], 10);
const min = parseInt(m[3], 10);
const totalMin = h * 60 + min;
return totalMin === 0 ? 'K+0' : `K${sign}${totalMin}`;
},
// /
togglePlan(planId) {
const index = this.expandedPlans.indexOf(planId)
@ -372,7 +338,6 @@ export default {
} else {
// 线线
this.expandedPlans = [];
this.expandedRoutes = [];
this.expandedPlans.push(planId)
const plan = this.plans.find(p => p.id === planId)
if (plan) {
@ -381,34 +346,19 @@ export default {
}
},
// 线/
// 线 selectRoute 线
toggleRoute(routeId) {
const route = this.routes.find(r => r.id === routeId)
if (!route) return
if (!route.waypoints) {
this.$set(route, 'waypoints', []); // 使 $set
this.$set(route, 'waypoints', [])
}
const isRouteSelected = this.activeRouteIds.includes(routeId)
const isRouteExpanded = this.expandedRoutes.includes(routeId)
if (isRouteSelected) {
if (isRouteExpanded) {
const index = this.expandedRoutes.indexOf(routeId)
this.expandedRoutes.splice(index, 1)
} else {
this.expandedRoutes.push(routeId)
this.handleSelectRoute(route)
this.$nextTick(() => {
if (route.scenarioId != null && !this.expandedPlans.includes(route.scenarioId)) {
this.expandedPlans.push(route.scenarioId)
}
} else {
this.handleSelectRoute(route)
this.$nextTick(() => {
// 线
if (!this.expandedPlans.includes(route.scenarioId)) {
this.expandedPlans.push(route.scenarioId);
}
if (!this.expandedRoutes.includes(routeId)) {
this.expandedRoutes.push(routeId)
}
})
}
})
},
//
@ -473,11 +423,6 @@ export default {
handleToggleRouteVisibility(route) {
this.$emit('toggle-route-visibility', route)
// 线
const routeIndex = this.expandedRoutes.indexOf(route.id)
if (routeIndex > -1) {
this.expandedRoutes.splice(routeIndex, 1)
}
},
getRouteClasses(routeId) {
@ -498,14 +443,6 @@ export default {
const e = this.routeLockedBy[routeId]
return (e && (e.nickName || e.userName)) || ''
},
handleOpenWaypointDialog(point,index,total) {
this.$emit('open-waypoint-dialog', {
...point,
currentIndex: index,
totalPoints: total
});
},
handleOpenPlatformDialog(platform) {
this.$emit('open-platform-dialog', platform)
},
@ -685,10 +622,6 @@ export default {
background: rgba(255, 255, 255, 0.8) !important;
}
.tree-item.waypoint-item .tree-item-header {
background: rgba(224, 238, 255, 0.8);
}
.tree-item.active .tree-item-header {
background: rgba(0, 138, 255, 0.15) !important;
border-color: rgba(0, 138, 255, 0.3);
@ -785,10 +718,6 @@ export default {
margin-left: 25px;
}
.waypoint-children {
margin-left: 50px;
}
.action-buttons {
display: flex;
gap: 10px;

202
ruoyi-ui/src/views/childRoom/index.vue

@ -22,6 +22,8 @@
:route-locked-by-other-ids="routeLockedByOtherRouteIds"
:deduction-time-minutes="deductionMinutesFromK"
:room-id="currentRoomId"
:platform-box-select-mode="platformBoxSelectMode"
@request-exit-platform-box-select="onRequestExitPlatformBoxSelect"
@draw-complete="handleMapDrawComplete"
@route-lock-changed="handleRouteLockChanged"
@drawing-points-update="missionDrawingPointsCount = $event"
@ -30,6 +32,8 @@
@open-waypoint-dialog="handleOpenWaypointEdit"
@open-route-dialog="handleOpenRouteEdit"
@copy-route="handleCopyRoute"
@route-segment-pick="handleRouteSegmentPick"
@route-segment-placed="handleRouteSegmentPlaced"
@single-route-deduction="handleSingleRouteDeduction"
@route-copy-placed="handleRouteCopyPlaced"
@add-waypoint-at="handleAddWaypointAt"
@ -40,6 +44,7 @@
@missile-deleted="handleMissileDeleted"
@scale-click="handleScaleClick"
@platform-icon-updated="onPlatformIconUpdated"
@platform-icons-batch-updated="onPlatformIconsBatchUpdated"
@platform-icon-removed="onPlatformIconRemoved"
@viewer-ready="onViewerReady"
@drawing-entities-changed="onDrawingEntitiesChanged"
@ -69,7 +74,7 @@
<div class="red-dot"></div>
<i class="el-icon-s-unfold icon-inside"></i>
</div>
<el-dialog title="保存新航线" :visible.sync="showNameDialog" width="30%" :append-to-body="true" @open="onSaveRouteDialogOpen" @close="tempMapPlatform = null; saveDialogScenarioId = null">
<el-dialog title="保存新航线" :visible.sync="showNameDialog" width="30%" :append-to-body="true" @open="onSaveRouteDialogOpen" @close="onSaveNewRouteDialogClose">
<el-form label-width="80px">
<el-form-item label="航线名称">
<el-input v-model="newRouteName" placeholder="例如:航线一"></el-input>
@ -560,6 +565,8 @@ export default {
return {
drawDom:false,
airspaceDrawDom:false,
/** 左侧菜单「框选平台」:多选房间平台图标并整体平移 */
platformBoxSelectMode: false,
/** 是否允许地图拖动(由顶部小手图标切换,默认关闭) */
mapDragEnabled: false,
// 线
@ -590,6 +597,8 @@ export default {
showNameDialog: false,
newRouteName: '',
tempMapPoints: [],
/** 拆分航段:保存新航线成功后从原航线删除这些航点 id */
pendingSegmentSplitAfterNewRoute: null,
/** 从平台右键「在此之前/在此之后插入航线」完成时传入,保存航线时用作 platformId */
tempMapPlatform: null,
/** 保存新航线弹窗内选择的方案 ID(弹窗内可直接选方案,无需先展开侧边) */
@ -644,6 +653,7 @@ export default {
{ id: '4t', name: '4T', icon: 'T' },
{ id: 'start', name: '冲突', icon: 'chongtu' },
{ id: 'insert', name: '平台', icon: 'el-icon-s-platform' },
{ id: 'platformBoxSelect', name: '框选平台', icon: 'el-icon-crop', action: 'platformBoxSelect' },
{ id: 'pattern', name: '空域', icon: 'ky' },
{ id: 'deduction', name: '推演', icon: 'el-icon-video-play' },
{ id: 'modify', name: '测距', icon: 'cj' },
@ -671,6 +681,7 @@ export default {
},
// -
topNavItems: [
{ id: 'platformBoxSelect', name: '框选平台', icon: 'el-icon-crop' },
{ id: 'routeEdit', name: '航线编辑', icon: 'el-icon-edit-outline' },
{ id: 'militaryMarking', name: '军事标绘', icon: 'el-icon-crop' },
{ id: 'attributeEdit', name: '属性修改', icon: 'el-icon-setting' },
@ -1681,11 +1692,58 @@ export default {
/** 复制航线已放置:用当前偏移后的航点打开「保存新航线」弹窗 */
handleRouteCopyPlaced(points) {
this.pendingSegmentSplitAfterNewRoute = null;
this.tempMapPoints = points || [];
this.tempMapPlatform = null;
this.showNameDialog = true;
},
/** 右键拆分航段/拆分复制:拉取航点后进入地图两点选范围 */
async handleRouteSegmentPick({ routeId, mode }) {
if (routeId == null) return;
if (this.routeLocked[routeId]) {
this.$message.info('该航线已上锁,请先解锁后再操作');
return;
}
if (this.isRouteLockedByOther(routeId)) {
this.$message.warning('该航线正被其他成员编辑,请稍后再试');
return;
}
try {
const res = await getRoutes(routeId);
if (res.code !== 200 || !res.data) {
this.$message.error('获取航线数据失败');
return;
}
const waypoints = res.data.waypoints || [];
if (waypoints.length < 2) {
this.$message.warning('航线航点不足,无法选取航段');
return;
}
if (this.$refs.cesiumMap && typeof this.$refs.cesiumMap.startRouteSegmentPickMode === 'function') {
const routeStyle = this.parseRouteStyle(res.data.attributes);
this.$refs.cesiumMap.startRouteSegmentPickMode(routeId, waypoints, mode, routeStyle);
}
} catch (e) {
this.$message.error('获取航线数据失败');
console.error(e);
}
},
/** 航段预览已放置:打开保存弹窗;拆分航段时记下待从原航线删除的航点 id */
handleRouteSegmentPlaced(payload) {
const { mode, sourceRouteId, removedWaypointIds, points } = payload || {};
if (!points || points.length < 2) return;
this.tempMapPoints = points;
this.tempMapPlatform = null;
if (mode === 'split' && sourceRouteId != null && removedWaypointIds && removedWaypointIds.length) {
this.pendingSegmentSplitAfterNewRoute = { sourceRouteId, waypointIds: removedWaypointIds };
} else {
this.pendingSegmentSplitAfterNewRoute = null;
}
this.showNameDialog = true;
},
/** 地图上拖拽航点结束:将新位置写回数据库并刷新显示 */
async handleWaypointPositionChanged({ dbId, routeId, lat, lng, alt }) {
if (this.isRouteLockedByOther(routeId)) {
@ -2658,6 +2716,7 @@ export default {
this.drawDom = false;
return;
}
this.pendingSegmentSplitAfterNewRoute = null;
this.tempMapPoints = points;
this.tempMapPlatform = (options && (options.platformId != null || options.platform)) ? options : null;
this.showNameDialog = true;
@ -2667,6 +2726,12 @@ export default {
this.saveDialogScenarioId = this.selectedPlanId || (this.plans[0] && this.plans[0].id) || null;
},
onSaveNewRouteDialogClose() {
this.tempMapPlatform = null;
this.saveDialogScenarioId = null;
this.pendingSegmentSplitAfterNewRoute = null;
},
openAddHoldDuringDrawing() {
this.addHoldContext = { mode: 'drawing' };
this.addHoldForm = { holdType: 'hold_circle', edgeLengthKm: 20, clockwise: true, startTime: '', startTimeMinutes: 60 };
@ -2741,6 +2806,21 @@ export default {
return;
}
const pendingSplit = this.pendingSegmentSplitAfterNewRoute;
if (pendingSplit && pendingSplit.sourceRouteId != null && Array.isArray(pendingSplit.waypointIds) && pendingSplit.waypointIds.length > 0) {
const roomIdParam = this.currentRoomId != null ? { roomId: this.currentRoomId } : {};
try {
for (const wid of pendingSplit.waypointIds) {
await delWaypoints(wid, roomIdParam);
}
this.wsConnection?.sendSyncWaypoints?.(pendingSplit.sourceRouteId);
} catch (splitErr) {
console.error(splitErr);
this.$message.warning('新航线已保存,但从原航线切除所选航段失败,请手动删除原航线上的对应航点');
}
}
this.pendingSegmentSplitAfterNewRoute = null;
// 1.
this.selectedRouteId = newRouteId;
this.selectedRouteDetails = JSON.parse(JSON.stringify(savedRoute));
@ -3456,7 +3536,8 @@ export default {
'toggleRoute': () => this.toggleRoute(),
'layerFavorites': () => this.layerFavorites(),
'routeFavorites': () => this.routeFavorites(),
'refresh': () => this.captureMapScreenshot()
'refresh': () => this.captureMapScreenshot(),
'platformBoxSelect': () => this.togglePlatformBoxSelectMenu()
}
if (actionMap[actionId]) {
@ -3464,6 +3545,27 @@ export default {
}
},
/** 左侧「框选平台」:与菜单项 id 无关,统一用 action: platformBoxSelect 触发(含图标编辑添加的随机 id 项) */
togglePlatformBoxSelectMenu() {
this.platformBoxSelectMode = !this.platformBoxSelectMode
this.activeMenu = this.platformBoxSelectMode ? 'platformBoxSelect' : ''
if (!this.platformBoxSelectMode) {
if (this.$refs.cesiumMap && typeof this.$refs.cesiumMap.exitPlatformBoxSelectMode === 'function') {
this.$refs.cesiumMap.exitPlatformBoxSelectMode()
}
} else {
this.drawDom = false
this.airspaceDrawDom = false
this.isRightPanelHidden = true
}
},
/** 地图内右键退出框选(子组件 emit) */
onRequestExitPlatformBoxSelect() {
this.platformBoxSelectMode = false
this.activeMenu = ''
},
/** 截图:隐藏上下左右菜单只保留地图,用 postRender + readPixels 避免 WebGL 缓冲被清空导致黑屏 */
async captureMapScreenshot() {
const cm = this.$refs.cesiumMap
@ -4159,6 +4261,27 @@ export default {
}).catch(() => {})
}, 500)
},
/** 框选整体拖拽结束:批量写库(避免多次 emit 被防抖合并为只保存最后一个) */
onPlatformIconsBatchUpdated(list) {
if (!list || !list.length) return
const payloads = list.filter(e => e && e.serverId)
if (!payloads.length) return
Promise.all(
payloads.map(ed =>
updateRoomPlatformIcon({
id: ed.serverId,
lng: ed.lng,
lat: ed.lat,
heading: ed.heading != null ? ed.heading : 0,
iconScale: ed.iconScale != null ? ed.iconScale : 1
})
)
)
.then(() => {
this.wsConnection?.sendSyncPlatformIcons?.()
})
.catch(() => {})
},
/** 平台图标从地图删除时同步删除服务端记录 */
onPlatformIconRemoved({ serverId }) {
if (!serverId) return
@ -4509,6 +4632,21 @@ export default {
},
selectMenu(item) {
// action handleMenuAction handleMenuAction
if (item.action === 'platformBoxSelect') {
return
}
// id action
if (item.id === 'platformBoxSelect') {
this.togglePlatformBoxSelectMenu()
return
}
if (this.platformBoxSelectMode) {
this.platformBoxSelectMode = false
if (this.$refs.cesiumMap && typeof this.$refs.cesiumMap.exitPlatformBoxSelectMode === 'function') {
this.$refs.cesiumMap.exitPlatformBoxSelectMode()
}
}
this.activeMenu = item.id;
if (item.action) {
this.handleMenuAction(item.action)
@ -5674,45 +5812,37 @@ export default {
/** 切换航线:实现多选/开关逻辑 */
async selectRoute(route) {
const index = this.activeRouteIds.indexOf(route.id);
const isRouteExpanded = this.$refs.rightPanel ? this.$refs.rightPanel.expandedRoutes.includes(route.id) : false;
// 线
// 线线
if (index > -1) {
if (isRouteExpanded) {
return;
} else {
// 线
this.activeRouteIds.splice(index, 1);
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.removeRouteById(route.id);
// 线
this.$refs.cesiumMap.removeDetectionZoneByRouteId(route.id);
this.$refs.cesiumMap.removePowerZoneByRouteId(route.id);
}
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
if (this.activeRouteIds.length > 0) {
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1];
try {
const res = await getRoutes(lastId);
if (res.code === 200 && res.data) {
this.selectedRouteId = res.data.id;
this.selectedRouteDetails = {
id: res.data.id,
name: res.data.callSign,
waypoints: res.data.waypoints || []
};
}
} catch (e) {
console.error("回显剩余航线失败", e);
this.activeRouteIds.splice(index, 1);
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.removeRouteById(route.id);
this.$refs.cesiumMap.removeDetectionZoneByRouteId(route.id);
this.$refs.cesiumMap.removePowerZoneByRouteId(route.id);
}
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
if (this.activeRouteIds.length > 0) {
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1];
try {
const res = await getRoutes(lastId);
if (res.code === 200 && res.data) {
this.selectedRouteId = res.data.id;
this.selectedRouteDetails = {
id: res.data.id,
name: res.data.callSign,
waypoints: res.data.waypoints || []
};
}
} else {
this.selectedRouteId = null;
this.selectedRouteDetails = null;
} catch (e) {
console.error("回显剩余航线失败", e);
}
} else {
this.selectedRouteId = null;
this.selectedRouteDetails = null;
}
// 线
this.$message.info(`已取消航线: ${route.name}`);
return;
}
this.$message.info(`已取消航线: ${route.name}`);
return;
}
// 线线

73
ruoyi-ui/src/views/dialogs/RouteEditDialog.vue

@ -8,9 +8,26 @@
</div>
<div class="dialog-body-inner">
<div class="route-edit-tab-bar">
<button type="button" class="tab-bar-item" :class="{ active: activeTab === 'basic' }" @click="activeTab = 'basic'">基础</button>
<button type="button" class="tab-bar-item" :class="{ active: activeTab === 'platform' }" @click="activeTab = 'platform'">平台</button>
<button type="button" class="tab-bar-item" :class="{ active: activeTab === 'waypoints' }" @click="activeTab = 'waypoints'">航点</button>
<div class="tab-bar-tabs">
<button type="button" class="tab-bar-item" :class="{ active: activeTab === 'basic' }" @click="activeTab = 'basic'">基础</button>
<button type="button" class="tab-bar-item" :class="{ active: activeTab === 'platform' }" @click="activeTab = 'platform'">平台</button>
<button type="button" class="tab-bar-item" :class="{ active: activeTab === 'waypoints' }" @click="activeTab = 'waypoints'">航点</button>
</div>
<div class="tab-bar-actions">
<template v-if="activeTab === 'basic' || activeTab === 'platform'">
<el-button size="mini" @click="visible = false"> </el-button>
<el-button type="primary" size="mini" class="blue-btn" @click="handleSave"> </el-button>
</template>
<template v-else-if="activeTab === 'waypoints' && waypointsTableData.length">
<template v-if="!waypointsEditMode">
<el-button type="primary" size="mini" class="blue-btn" @click="waypointsEditMode = true"> </el-button>
</template>
<template v-else>
<el-button size="mini" @click="cancelWaypointsEdit"> </el-button>
<el-button type="primary" size="mini" class="blue-btn" @click="confirmWaypointsEdit"> </el-button>
</template>
</template>
</div>
</div>
<div v-show="activeTab === 'basic'" class="tab-pane-wrap">
<div class="tab-pane-body basic-tab-content">
@ -195,15 +212,6 @@
<div class="tab-pane-body waypoints-tab-content">
<div v-if="!waypointsTableData.length" class="empty-tip">该航线暂无航点数据</div>
<template v-else>
<div class="waypoints-table-actions">
<template v-if="!waypointsEditMode">
<el-button type="primary" size="mini" class="blue-btn" @click="waypointsEditMode = true"> </el-button>
</template>
<template v-else>
<el-button type="primary" size="mini" class="blue-btn" @click="confirmWaypointsEdit"> </el-button>
<el-button size="mini" @click="cancelWaypointsEdit"> </el-button>
</template>
</div>
<div class="waypoints-table-wrap">
<el-table :data="waypointsTableData" border size="small" max-height="340" class="waypoints-table">
<el-table-column type="index" label="序号" width="52" align="center" />
@ -311,11 +319,6 @@
</template>
</div>
</div>
<div class="dialog-footer">
<el-button size="mini" @click="visible = false"> </el-button>
<el-button type="primary" size="mini" class="blue-btn" @click="handleSave"> </el-button>
</div>
</div>
<div class="resize-handle" @mousedown="onResizeStart" title="拖动调整大小"></div>
</div>
@ -445,6 +448,7 @@ export default {
activeTab(val) {
if (val === 'platform' && this.visible) this.loadPlatforms()
if (val === 'waypoints' && this.panelWidth < 920) this.panelWidth = 920
if (val !== 'waypoints') this.waypointsEditMode = false
}
},
methods: {
@ -707,7 +711,7 @@ export default {
},
confirmWaypointsEdit() {
this.waypointsEditMode = false
this.$message.success('表格已保存,点击下方「确定」提交航线与航点')
this.$message.success('航点表格已保存,请切换到「基础」或「平台」后点击「确定」提交航线')
},
cancelWaypointsEdit() {
this.syncWaypointsTableData(this.route.waypoints || [])
@ -863,11 +867,6 @@ export default {
flex-direction: column;
min-height: 0;
}
.route-edit-dialog-wrap .dialog-footer {
padding: 10px 0 0;
margin-top: 8px;
border-top: 1px solid #ebeef5;
}
.route-edit-dialog-wrap .resize-handle {
position: absolute;
right: 0;
@ -886,11 +885,31 @@ export default {
<style scoped>
.route-edit-tab-bar {
display: flex;
gap: 24px;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
margin-bottom: 0;
padding-bottom: 12px;
border-bottom: 1px solid #e4e7ed;
}
.tab-bar-tabs {
display: flex;
gap: 24px;
align-items: flex-end;
flex: 1;
min-width: 0;
}
.tab-bar-actions {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
padding-bottom: 2px;
}
/* 标签栏操作区:仅胶囊形圆角,不改颜色与其它样式 */
.tab-bar-actions ::v-deep .el-button {
border-radius: 999px;
}
.tab-bar-item {
padding: 0;
border: none;
@ -1162,12 +1181,6 @@ export default {
padding: 0 16px;
background: #fff;
}
.waypoints-table-actions {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.waypoints-table-wrap {
background: #fff;
border-radius: 8px;

Loading…
Cancel
Save