Browse Source

新增航线的展示、可多选、查询航线的逻辑变动

master
menghao 2 months ago
parent
commit
9f1f028bee
  1. 20
      ruoyi-system/src/main/java/com/ruoyi/system/domain/RouteWaypoints.java
  2. 13
      ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java
  3. 1
      ruoyi-system/src/main/resources/mapper/system/RoutesMapper.xml
  4. 230
      ruoyi-ui/src/views/cesiumMap/index.vue
  5. 18
      ruoyi-ui/src/views/childRoom/RightPanel.vue
  6. 218
      ruoyi-ui/src/views/childRoom/TopHeader.vue
  7. 265
      ruoyi-ui/src/views/childRoom/index.vue
  8. 2
      ruoyi-ui/vue.config.js

20
ruoyi-system/src/main/java/com/ruoyi/system/domain/RouteWaypoints.java

@ -41,11 +41,11 @@ public class RouteWaypoints extends BaseEntity
/** 高度 (米) */
@Excel(name = "高度 (米)")
private Long alt;
private Double alt;
/** 速度 (km/h) */
@Excel(name = "速度 (km/h)")
private Long speed;
private Double speed;
/** 起始时间 (如: K+00:40:00) */
@Excel(name = "起始时间 (如: K+00:40:00)")
@ -53,7 +53,7 @@ public class RouteWaypoints extends BaseEntity
/** 转弯角度 (用于计算转弯半径) */
@Excel(name = "转弯角度 (用于计算转弯半径)")
private Long turnAngle;
private Double turnAngle;
public void setId(Long id)
{
@ -115,22 +115,22 @@ public class RouteWaypoints extends BaseEntity
return lng;
}
public void setAlt(Long alt)
public void setAlt(Double alt)
{
this.alt = alt;
}
public Long getAlt()
public Double getAlt()
{
return alt;
}
public void setSpeed(Long speed)
public void setSpeed(Double speed)
{
this.speed = speed;
}
public Long getSpeed()
public Double getSpeed()
{
return speed;
}
@ -145,12 +145,12 @@ public class RouteWaypoints extends BaseEntity
return startTime;
}
public void setTurnAngle(Long turnAngle)
public void setTurnAngle(Double turnAngle)
{
this.turnAngle = turnAngle;
}
public Long getTurnAngle()
public Double getTurnAngle()
{
return turnAngle;
}
@ -183,7 +183,7 @@ public class RouteWaypoints extends BaseEntity
// 单位换算:速度从 km/h 转为 m/s
double v_mps = this.speed / 3.6;
// 单位换算:角度从 度(Degree) 转为 弧度(Radians)
double radians = Math.toRadians(this.turnAngle.doubleValue());
double radians = Math.toRadians(this.turnAngle);
// 重力加速度 g
double g = 9.8;
// 计算半径

13
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/RoutesServiceImpl.java

@ -35,7 +35,18 @@ public class RoutesServiceImpl implements IRoutesService
@Override
public Routes selectRoutesById(Long id)
{
return routesMapper.selectRoutesById(id);
// 查出航线基本信息
Routes routes = routesMapper.selectRoutesById(id);
// 如果查到了航线,就再去查属于它的航点
if (routes != null) {
RouteWaypoints queryWp = new RouteWaypoints();
queryWp.setRouteId(id); // 根据航线ID查询
List<RouteWaypoints> wpList = routeWaypointsService.selectRouteWaypointsList(queryWp);
// 把查出来的航点列表塞进 routes 对象的 waypoints 属性里
routes.setWaypoints(wpList);
}
return routes;
}
/**

1
ruoyi-system/src/main/resources/mapper/system/RoutesMapper.xml

@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="platformId" column="platform_id" />
<result property="callSign" column="call_sign" />
<result property="attributes" column="attributes" />
<collection property="waypoints" javaType="java.util.List" resultMap="com.ruoyi.system.mapper.RouteWaypointsMapper.RouteWaypointsResult" />
</resultMap>
<sql id="selectRoutesVo">

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

@ -100,6 +100,204 @@ export default {
},
methods: {
preventContextMenu(e) {
e.preventDefault();
},
clearRoute() {
// 1. allEntities
this.allEntities.forEach(item => {
this.viewer.entities.remove(item.entity);
});
this.allEntities = [];
// 2.
const entities = this.viewer.entities.values;
for (let i = entities.length - 1; i >= 0; i--) {
const entity = entities[i];
if (entity.properties && entity.properties.isMissionWaypoint) {
this.viewer.entities.remove(entity);
}
}
},
updateWaypointGraphic(oldName, newName) {
//
const entities = this.viewer.entities.values;
const target = entities.find(e => e.name === oldName && e.properties.isMissionWaypoint);
if (target) {
target.name = newName; //
target.label.text = newName; //
}
},
// 线
startMissionRouteDrawing() {
this.stopDrawing(); //
this.drawingPoints = [];
let activeCursorPosition = null;
this.isDrawing = true;
this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
window.addEventListener('contextmenu', this.preventContextMenu, true);
//
this.drawingHandler.setInputAction((movement) => {
const newPosition = this.getClickPosition(movement.endPosition);
if (newPosition) {
activeCursorPosition = newPosition;
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
//
this.drawingHandler.setInputAction((click) => {
const position = this.getClickPosition(click.position);
if (!position) return;
this.drawingPoints.push(position);
const wpIndex = this.drawingPoints.length;
//
this.viewer.entities.add({
name: `WP${wpIndex}`,
position: position,
properties: {
isMissionWaypoint: true, //
originalIndex: wpIndex //
},
point: {
pixelSize: 10,
color: Cesium.Color.WHITE,
outlineColor: Cesium.Color.fromCssColorString('#0078FF'),
outlineWidth: 3,
disableDepthTestDistance: Number.POSITIVE_INFINITY //
},
label: {
text: `WP${wpIndex}`,
font: '12px MicroSoft YaHei',
pixelOffset: new Cesium.Cartesian2(0, -20),
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE
}
});
// 线
if (this.drawingPoints.length === 1) {
this.tempPreviewEntity = this.viewer.entities.add({
polyline: {
positions: new Cesium.CallbackProperty(() => {
if (this.drawingPoints.length > 0 && activeCursorPosition) {
return [...this.drawingPoints, activeCursorPosition];
}
return this.drawingPoints;
}, false),
width: 4,
//
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.WHITE, //
gapColor: Cesium.Color.BLACK, //
dashLength: 20.0 //
}),
clampToGround: true
}
});
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//
this.drawingHandler.setInputAction(() => {
if (this.drawingPoints.length > 1) {
// childRoom/index.vue
const latLngPoints = this.drawingPoints.map((p, index) => {
const coords = this.cartesianToLatLng(p);
return {
id: index + 1,
name: `WP${index + 1}`,
lat: coords.lat,
lng: coords.lng,
alt: 500, //
speed: 600 //
};
});
this.$emit('draw-complete', latLngPoints);
} else {
this.$message.info('点数不足,航线已取消');
}
//
this.stopDrawing();
setTimeout(() => {
window.removeEventListener('contextmenu', this.preventContextMenu, true);
}, 200);
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
},
renderRouteWaypoints(waypoints, routeId = 'default') {
if (!waypoints || waypoints.length < 1) return;
const positions = [];
// 1.
waypoints.forEach((wp, index) => {
const lon = parseFloat(wp.lng);
const lat = parseFloat(wp.lat);
const pos = Cesium.Cartesian3.fromDegrees(lon, lat, parseFloat(wp.alt || 500));
positions.push(pos);
this.viewer.entities.add({
name: wp.name || `WP${index + 1}`,
position: pos,
properties: {
isMissionWaypoint: true,
routeId: routeId
},
point: {
pixelSize: 10,
color: Cesium.Color.WHITE,
outlineColor: Cesium.Color.fromCssColorString('#0078FF'),
outlineWidth: 3,
disableDepthTestDistance: Number.POSITIVE_INFINITY
},
label: {
text: wp.name || `WP${index + 1}`,
font: '12px MicroSoft YaHei',
pixelOffset: new Cesium.Cartesian2(0, -20),
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE
}
});
});
// 2. 线 > 1
if (positions.length > 1) {
const routeEntity = this.viewer.entities.add({
id: `route-line-${routeId}`,
polyline: {
positions: positions,
width: 4,
material: new Cesium.PolylineDashMaterialProperty({
color: Cesium.Color.WHITE,
gapColor: Cesium.Color.BLACK,
dashLength: 20.0
}),
clampToGround: true
},
properties: { isMissionRouteLine: true, routeId: routeId }
});
this.allEntities.push({ id: `route-line-${routeId}`, entity: routeEntity, type: 'line' });
}
},
removeRouteById(routeId) {
// routeId
const entityList = this.viewer.entities.values;
for (let i = entityList.length - 1; i >= 0; i--) {
const entity = entityList[i];
// entity routeId
if (entity.properties && entity.properties.routeId) {
// Cesium getValue()
const id = entity.properties.routeId.getValue();
if (id === routeId) {
this.viewer.entities.remove(entity);
}
}
}
// allEntities
this.allEntities = this.allEntities.filter(item => item.id !== routeId);
},
checkCesiumLoaded() {
if (typeof Cesium === 'undefined') {
console.error('Cesium未加载,请检查CDN链接');
@ -155,6 +353,23 @@ export default {
this.initScaleBar()
console.log('Cesium离线二维地图已加载')
// 1.
this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas);
this.handler.setInputAction((click) => {
// 线
if (this.isDrawing) return;
// 2.
const pickedObject = this.viewer.scene.pick(click.position);
if (Cesium.defined(pickedObject) && pickedObject.id) {
const entity = pickedObject.id;
// 3.
if (entity.properties && entity.properties.isMissionWaypoint) {
// name ID
this.$emit('open-waypoint-dialog', entity.name);
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
} catch (error) {
console.error('地图错误:', error)
@ -625,6 +840,7 @@ export default {
this.drawingHandler.setInputAction(() => {
this.cancelDrawing();
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
},
finishRectangleDrawing() {
@ -1060,19 +1276,6 @@ export default {
return area
},
calculateRectangleArea(coordinates) {
const rect = coordinates.getValue ? coordinates.getValue() : coordinates
const width = Cesium.Cartesian3.distance(
Cesium.Cartesian3.fromRadians(rect.west, rect.north),
Cesium.Cartesian3.fromRadians(rect.east, rect.north)
)
const height = Cesium.Cartesian3.distance(
Cesium.Cartesian3.fromRadians(rect.west, rect.north),
Cesium.Cartesian3.fromRadians(rect.west, rect.south)
)
return width * height
},
// ================== ==================
@ -1167,7 +1370,6 @@ export default {
}
}
},
clearAll() {
// 1.
if (this.allEntities && this.allEntities.length > 0) {

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

@ -32,8 +32,8 @@
v-for="route in routes"
:key="route.id"
class="route-item"
:class="{ selected: selectedRouteId === route.id }"
@click="handleSelectRoute(route)"
:class="{ 'active': activeRouteIds.includes(route.id) }"
@click="$emit('select-route', route)"
>
<i class="el-icon-map-location"></i>
<div class="route-info">
@ -222,9 +222,9 @@ export default {
type: Array,
default: () => []
},
selectedRouteId: {
type: [String, Number],
default: null
activeRouteIds: {
type: Array,
default: () => []
},
selectedRouteDetails: {
type: Object,
@ -261,11 +261,7 @@ export default {
this.$emit('hide')
},
handleSelectRoute(route) {
this.$emit('select-route', route)
},
// 线
// 线
handleCreateRoute() {
this.$emit('create-route')
},
@ -432,7 +428,7 @@ export default {
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15);
}
.route-item.selected {
.route-item.active {
background: rgba(0, 138, 255, 0.15);
border-color: rgba(0, 138, 255, 0.3);
box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25);

218
ruoyi-ui/src/views/childRoom/TopHeader.vue

@ -3,19 +3,19 @@
<div class="header-left">
<div class="system-title">
<!-- 按照实际路径引入logo.jpg -->
<img
src="@/views/childRoom/logo.png"
class="logo-icon mr-2"
<img
src="@/views/childRoom/logo.png"
class="logo-icon mr-2"
alt="系统logo"
style="width:24px; height:24px; object-fit:contain;"
style="width:24px; height:24px; object-fit:contain;"
>
<span class="title-text blue-title">网络化任务规划系统</span>
</div>
<!-- 顶部导航菜单 -->
<div class="top-nav-menu">
<div
v-for="item in topNavItems"
<div
v-for="item in topNavItems"
:key="item.id"
class="top-nav-item"
:class="{ active: activeTopNav === item.id }"
@ -23,11 +23,11 @@
>
<i :class="item.icon" class="nav-icon"></i>
<span class="nav-text">{{ item.name }}</span>
<!-- 文件下拉菜单 -->
<el-dropdown
v-if="item.id === 'file'"
trigger="click"
<el-dropdown
v-if="item.id === 'file'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -38,12 +38,12 @@
<el-dropdown-item @click.native="newPlan">新建计划</el-dropdown-item>
<el-dropdown-item @click.native="openPlan">打开</el-dropdown-item>
<el-dropdown-item @click.native="savePlan">保存</el-dropdown-item>
<!-- 导入二级菜单 -->
<el-dropdown-item class="submenu-item">
<span>导入</span>
<el-dropdown
trigger="hover"
<el-dropdown
trigger="hover"
placement="right-start"
class="submenu-dropdown"
>
@ -57,15 +57,15 @@
</el-dropdown-menu>
</el-dropdown>
</el-dropdown-item>
<el-dropdown-item @click.native="exportPlan">导出</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 编辑下拉菜单 -->
<el-dropdown
v-if="item.id === 'edit'"
trigger="click"
<el-dropdown
v-if="item.id === 'edit'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -76,12 +76,12 @@
<el-dropdown-item @click.native="militaryMarking">军事标绘</el-dropdown-item>
<el-dropdown-item @click.native="iconEdit">图标编辑</el-dropdown-item>
<el-dropdown-item @click.native="attributeEdit">属性修改</el-dropdown-item>
<!-- 推演编辑二级菜单 -->
<el-dropdown-item class="submenu-item">
<span>推演编辑</span>
<el-dropdown
trigger="hover"
<el-dropdown
trigger="hover"
placement="right-start"
class="submenu-dropdown"
>
@ -96,11 +96,11 @@
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 视图下拉菜单 -->
<el-dropdown
v-if="item.id === 'view'"
trigger="click"
<el-dropdown
v-if="item.id === 'view'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -113,11 +113,11 @@
<el-dropdown-item @click.native="toggleScale">比例尺</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 地图下拉菜单 -->
<el-dropdown
v-if="item.id === 'map'"
trigger="click"
<el-dropdown
v-if="item.id === 'map'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -129,11 +129,11 @@
<el-dropdown-item @click.native="loadAeroChart">航空图</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 空域下拉菜单 -->
<el-dropdown
v-if="item.id === 'airspace'"
trigger="click"
<el-dropdown
v-if="item.id === 'airspace'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -144,11 +144,11 @@
<el-dropdown-item @click.native="threatZone">威胁区</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 工具下拉菜单 -->
<el-dropdown
v-if="item.id === 'tools'"
trigger="click"
<el-dropdown
v-if="item.id === 'tools'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -161,11 +161,11 @@
<el-dropdown-item @click.native="coordinateConversion">坐标换算</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 选项下拉菜单 -->
<el-dropdown
v-if="item.id === 'options'"
trigger="click"
<el-dropdown
v-if="item.id === 'options'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -175,8 +175,8 @@
<!-- 设置二级菜单 -->
<el-dropdown-item class="submenu-item">
<span>设置</span>
<el-dropdown
trigger="hover"
<el-dropdown
trigger="hover"
placement="right-start"
class="submenu-dropdown"
>
@ -194,11 +194,11 @@
<el-dropdown-item @click.native="systemDescription">系统说明</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<!-- 收藏下拉菜单 -->
<el-dropdown
v-if="item.id === 'favorites'"
trigger="click"
<el-dropdown
v-if="item.id === 'favorites'"
trigger="click"
placement="bottom-start"
:hide-on-click="false"
class="file-dropdown"
@ -224,7 +224,7 @@
<div class="info-value">{{ roomCode }}</div>
</div>
</div>
<!-- 在线人数 -->
<div class="info-box" @click="showOnlineMembersDialog">
<i class="el-icon-user info-icon"></i>
@ -233,7 +233,7 @@
<div class="info-value">{{ onlineCount }}</div>
</div>
</div>
<!-- 作战时间 -->
<div class="info-box">
<i class="el-icon-timer info-icon"></i>
@ -242,7 +242,7 @@
<div class="info-value">{{ combatTime }}</div>
</div>
</div>
<!-- 天文时间 -->
<div class="info-box">
<i class="el-icon-sunny info-icon"></i>
@ -252,31 +252,31 @@
</div>
</div>
</div>
<!-- 用户状态区域 -->
<div class="user-status-area">
<!-- 用户头像 -->
<el-avatar :size="32" :src="userAvatar" class="user-avatar" />
</div>
</div>
<!-- 威力区弹窗 -->
<power-zone-dialog
v-model="powerZoneDialogVisible"
<power-zone-dialog
v-model="powerZoneDialogVisible"
:power-zone="currentPowerZone"
@save="savePowerZone"
/>
<!-- 比例尺弹窗 -->
<scale-dialog
v-model="scaleDialogVisible"
<scale-dialog
v-model="scaleDialogVisible"
:scale="currentScale"
@save="saveScale"
/>
<!-- 外部参数弹窗 -->
<external-params-dialog
v-model="externalParamsDialogVisible"
<external-params-dialog
v-model="externalParamsDialogVisible"
:external-params="currentExternalParams"
@save="saveExternalParams"
@import-airport="importAirport"
@ -342,10 +342,10 @@ export default {
}
},
methods: {
selectTopNav(item) {
/*selectTopNav(item) {
this.$emit('select-nav', item)
},
},*/
//
newPlan() {
this.$emit('new-plan')
@ -353,193 +353,193 @@ export default {
openPlan() {
this.$emit('open-plan')
},
//
savePlan() {
this.$emit('save-plan')
},
importPlanFile() {
this.$emit('import-plan-file')
},
importACD() {
this.$emit('import-acd')
},
importATO() {
this.$emit('import-ato')
},
importLayer() {
this.$emit('import-layer')
},
importRoute() {
this.$emit('import-route')
},
exportPlan() {
this.$emit('export-plan')
},
//
routeEdit() {
this.$emit('route-edit')
},
militaryMarking() {
this.$emit('military-marking')
},
iconEdit() {
this.$emit('icon-edit')
},
attributeEdit() {
this.$emit('attribute-edit')
},
timeSettings() {
this.$emit('time-settings')
},
aircraftSettings() {
this.$emit('aircraft-settings')
},
keyEventEdit() {
this.$emit('key-event-edit')
},
missileLaunch() {
this.$emit('missile-launch')
},
//
toggle2D3D() {
this.$emit('toggle-2d-3d')
},
toggleRuler() {
this.$emit('toggle-ruler')
},
toggleGrid() {
this.$emit('toggle-grid')
},
toggleScale() {
this.scaleDialogVisible = true
this.currentScale = {}
},
//
loadTerrain() {
this.$emit('load-terrain')
},
changeProjection() {
this.$emit('change-projection')
},
loadAeroChart() {
this.$emit('load-aero-chart')
},
//
powerZone() {
this.powerZoneDialogVisible = true
this.currentPowerZone = {}
},
threatZone() {
this.$emit('threat-zone')
},
//
routeCalculation() {
this.$emit('route-calculation')
},
conflictDisplay() {
this.$emit('conflict-display')
},
dataMaterials() {
this.$emit('data-materials')
},
coordinateConversion() {
this.$emit('coordinate-conversion')
},
//
pageLayout() {
this.$emit('page-layout')
},
dataStoragePath() {
this.$emit('data-storage-path')
},
externalParams() {
this.externalParamsDialogVisible = true
this.currentExternalParams = {}
},
toggleAirport() {
this.$emit('toggle-airport')
},
toggleLandmark() {
this.$emit('toggle-landmark')
},
toggleRoute() {
this.$emit('toggle-route')
},
systemDescription() {
this.$emit('system-description')
},
//
layerFavorites() {
this.$emit('layer-favorites')
},
routeFavorites() {
this.$emit('route-favorites')
},
showOnlineMembersDialog() {
this.$emit('show-online-members')
},
//
savePowerZone(powerZone) {
this.$emit('save-power-zone', powerZone)
},
//
saveScale(scale) {
this.$emit('save-scale', scale)
},
//
saveExternalParams(externalParams) {
this.$emit('save-external-params', externalParams)
},
importAirport(path) {
this.$emit('import-airport', path)
},
importRoute(path) {
this.$emit('import-route-data', path)
},
importLandmark(path) {
this.$emit('import-landmark', path)
}
@ -862,4 +862,4 @@ export default {
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style>
</style>

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

@ -4,7 +4,8 @@
<!-- 地图背景 -->
<div id="gis-map-background" class="map-background">
<!-- cesiummap组件 -->
<cesiumMap :drawDomClick="drawDom"/>
<cesiumMap ref="cesiumMap" :drawDomClick="drawDom" @draw-complete="handleMapDrawComplete"
@open-waypoint-dialog="handleOpenWaypointEdit" />
<div class="map-overlay-text">
<i class="el-icon-location-outline text-3xl mb-2 block"></i>
<p>二维GIS地图区域</p>
@ -21,6 +22,17 @@
<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">
<el-form label-width="80px">
<el-form-item label="航线名称">
<el-input v-model="newRouteName" placeholder="例如:航线一"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="showNameDialog = false"> </el-button>
<el-button type="primary" @click="confirmSaveNewRoute"> </el-button>
</div>
</el-dialog>
</div>
<!-- 顶部导航栏 -->
@ -30,7 +42,6 @@
:combat-time="combatTime"
:astro-time="astroTime"
:user-avatar="userAvatar"
@select-nav="selectTopNav"
@save-plan="savePlan"
@import-plan-file="importPlanFile"
@import-acd="importACD"
@ -88,7 +99,7 @@
:is-hidden="isRightPanelHidden"
:active-tab="activeRightTab"
:routes="routes"
:selected-route-id="selectedRouteId"
:active-route-ids="activeRouteIds"
:selected-route-details="selectedRouteDetails"
:conflicts="conflicts"
:conflict-count="conflictCount"
@ -208,6 +219,8 @@ import LeftMenu from './LeftMenu'
import RightPanel from './RightPanel'
import BottomLeftPanel from './BottomLeftPanel'
import TopHeader from './TopHeader'
import { listRoutes, getRoutes, addRoutes } from "@/api/system/routes";
import { updateWaypoints } from "@/api/system/waypoints";
export default {
name: 'MissionPlanningView',
components: {
@ -233,6 +246,9 @@ export default {
selectedRoute: null,
showWaypointDialog: false,
selectedWaypoint: null,
showNameDialog: false,
newRouteName: '',
tempMapPoints: [],
//
roomCode: 'JTF-7-ALPHA',
@ -270,7 +286,7 @@ export default {
//
activeRightTab: 'plan',
selectedRouteId: 101,
activeRouteIds: [], // 线ID
selectedRouteDetails: null,
//
@ -314,11 +330,7 @@ export default {
],
// 线
routes: [
{ id: 101, name: 'Alpha进场航线', points: 8, conflict: true },
{ id: 102, name: 'Beta巡逻航线', points: 6, conflict: false },
{ id: 103, name: '侦察覆盖区', points: 4, conflict: false },
],
routes: [],
//
timeProgress: 45,
@ -332,11 +344,11 @@ export default {
};
},
mounted() {
this.getList();
//
this.isMenuHidden = true;
//
this.isRightPanelHidden = true;
//
this.updateTime();
setInterval(this.updateTime, 1000);
@ -351,6 +363,25 @@ export default {
}
},
methods: {
//
handleOpenWaypointEdit(wpName) {
// 1. 线
if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) {
this.$message.warning('请先在右侧列表选择一条航线');
return;
}
// 2.
const wpData = this.selectedRouteDetails.waypoints.find(item => item.name === wpName);
if (wpData) {
// 3.
this.selectedWaypoint = JSON.parse(JSON.stringify(wpData));
// 4.
this.showWaypointDialog = true;
}
else {
this.$message.info('未找到该航点的业务数据');
}
},
// 线
showOnlineMembersDialog() {
this.showOnlineMembers = true;
@ -385,41 +416,114 @@ export default {
this.$message.success('航线名称更新成功');
}
},
// 线
// 线
createRoute() {
this.$message.info('新建航线功能开发中...');
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.startMissionRouteDrawing();
this.$message.success('进入航线规划模式');
}
},
/** 从数据库拉取最新的航线列表数据 */
async getList() {
const query = { scenarioId: 1 };
try {
const response = await listRoutes(query);
if (response.code === 200) {
this.routes = response.rows.map(item => ({
id: item.id,
name: item.callSign,
points: item.waypoints ? item.waypoints.length : 0,
conflict: false
}));
}
} catch (error) {
this.$message.error("获取航线列表失败");
}
},
handleMapDrawComplete(points) {
if (!points || points.length < 2) {
this.$message.error('航点太少,无法生成航线');
this.drawDom = false;
return;
}
this.tempMapPoints = points; //
this.showNameDialog = true; //
},
/** 弹窗点击“确定”:正式将数据保存到后端数据库 */
async confirmSaveNewRoute() {
//
if (!this.newRouteName || this.newRouteName.trim() === '') {
this.$message.error('新增航线未命名,请输入名称后保存!');
return;
}
// Routes
const routeData = {
callSign: this.newRouteName,
scenarioId: 1, // ID
platformId: 1,
attributes: "{}",
waypoints: this.tempMapPoints.map((p, index) => ({
name: `WP${index + 1}`,
lat: p.lat,
lng: p.lng,
alt: 5000.0,
speed: 800.0,
startTime: 'K+00:00:00',
turnAngle: 0.0
}))
};
try {
// API
const response = await addRoutes(routeData);
if (response.code === 200) {
this.$message.success('航线及其航点已成功保存至数据库');
// UI
this.showNameDialog = false;
this.drawDom = false;
this.newRouteName = '';
this.tempMapPoints = [];
// 线
await this.getList();
}
} catch (error) {
console.error("保存航线失败:", error);
this.$message.error('保存失败,请检查后端服务');
}
},
//
openWaypointDialog(waypoint) {
this.selectedWaypoint = waypoint;
this.showWaypointDialog = true;
},
updateWaypoint(updatedWaypoint) {
// 1. 线
if (this.selectedRouteDetails && this.selectedRouteDetails.waypoints) {
// 2.
const index = this.selectedRouteDetails.waypoints.indexOf(this.selectedWaypoint);
if (index !== -1) {
// 3. 使 splice
this.selectedRouteDetails.waypoints.splice(index, 1, updatedWaypoint);
// 4. 便
this.selectedWaypoint = updatedWaypoint;
this.$message.success('航点更新成功');
} else {
// indexOf
const nameIndex = this.selectedRouteDetails.waypoints.findIndex(p => p.name === this.selectedWaypoint.name);
if (nameIndex !== -1) {
this.selectedRouteDetails.waypoints.splice(nameIndex, 1, updatedWaypoint);
this.selectedWaypoint = updatedWaypoint;
this.$message.success('航点更新成功');
} else {
this.$message.error('更新失败:未找到对应航点');
/** 航点编辑保存:更新数据库并同步地图显示 */
async updateWaypoint(updatedWaypoint) {
// 线
if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) return;
try {
const response = await updateWaypoints(updatedWaypoint);
if (response.code === 200) {
//
const index = this.selectedRouteDetails.waypoints.findIndex(p => p.id === updatedWaypoint.id);
if (index !== -1) {
// Entity
const oldName = this.selectedRouteDetails.waypoints[index].name;
const newName = updatedWaypoint.name;
// 使 splice Vue UI
this.selectedRouteDetails.waypoints.splice(index, 1, { ...updatedWaypoint });
// 5.
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.updateWaypointGraphic(oldName, newName);
}
this.showWaypointDialog = false;
this.$message.success('航点信息已持久化至数据库');
}
}
} catch (error) {
console.error("更新航点失败:", error);
this.$message.error('数据库更新失败,请重试');
}
},
updateTime() {
@ -677,9 +781,9 @@ export default {
this.isRightPanelHidden = false;
}
} else if(item.id === 'modify'){
this.drawDom = !this.drawDom
console.log(this.drawDom,999999)
}
this.drawDom = !this.drawDom
console.log(this.drawDom,999999)
}
if (item.id === 'deduction') {
// /K
this.showKTimePopup = !this.showKTimePopup;
@ -774,23 +878,64 @@ export default {
const minutes = (val % 4) * 15;
return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`;
},
/** 切换航线:实现多选/开关逻辑 */
async selectRoute(route) {
const index = this.activeRouteIds.indexOf(route.id);
// 线
selectRoute(route) {
this.selectedRouteId = route.id;
//
this.selectedRouteDetails = {
name: route.name,
waypoints: [
{ name: 'WP1', altitude: 5000, speed: '800km/h', eta: 'K+00:40:00' },
{ name: 'WP2', altitude: 6000, speed: '850km/h', eta: 'K+00:55:00' },
{ name: 'WP3', altitude: 5500, speed: '820km/h', eta: 'K+01:10:00' },
{ name: 'WP4', altitude: 5800, speed: '830km/h', eta: 'K+01:25:00' },
]
};
// this.openRouteDialog(route);
// 线
if (index > -1) {
this.activeRouteIds.splice(index, 1);
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.removeRouteById(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.selectedRouteDetails = {
id: res.data.id,
name: res.data.callSign,
waypoints: res.data.waypoints || []
};
}
} catch (e) {
console.error("回显剩余航线失败", e);
}
} else {
this.selectedRouteDetails = null;
}
}
this.$message.info(`已移除航线: ${route.name}`);
return;
}
// 线
try {
const response = await getRoutes(route.id);
if (response.code === 200 && response.data) {
const fullRouteData = response.data;
const waypoints = fullRouteData.waypoints || [];
this.activeRouteIds.push(route.id);
this.selectedRouteDetails = {
id: fullRouteData.id,
name: fullRouteData.callSign,
waypoints: waypoints
};
if (waypoints.length > 0) {
//
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.renderRouteWaypoints(waypoints, route.id);
}
} else {
this.$message.warning('该航线暂无坐标数据,无法在地图展示');
}
}
} catch (error) {
console.error("获取航线详情失败:", error);
this.$message.error('无法加载该航线的详细航点数据');
}
},
addWaypoint() {
if (this.selectedRouteDetails) {
const count = this.selectedRouteDetails.waypoints.length + 1;
@ -805,9 +950,15 @@ export default {
},
cancelRoute() {
this.selectedRouteId = null;
// 线
if (this.$refs.cesiumMap) {
this.activeRouteIds.forEach(id => {
this.$refs.cesiumMap.removeRouteById(id);
});
}
this.activeRouteIds = [];
this.selectedRouteDetails = null;
this.$message.info('已取消选中');
this.$message.info('已清空所有选中航线');
},
//
@ -857,7 +1008,7 @@ export default {
height: 100%;
background: linear-gradient(135deg, #1a2f4b 0%, #2c3e50 100%);
/* 正确的写法,直接复制这行替换 */
background: url('~@/assets/map-background.png');
background: url('~@/assets/map-background.png');
background-size: cover;
background-position: center;
z-index: 1;

2
ruoyi-ui/vue.config.js

@ -10,7 +10,7 @@ const CompressionPlugin = require('compression-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
const baseUrl = 'http://192.168.50.30:8080' // 后端接口
const baseUrl = 'http://127.0.0.1:8080' // 后端接口
const port = process.env.port || process.env.npm_config_port || 80 // 端口
// 定义 Cesium 源码路径

Loading…
Cancel
Save