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 = "高度 (米)") @Excel(name = "高度 (米)")
private Long alt; private Double alt;
/** 速度 (km/h) */ /** 速度 (km/h) */
@Excel(name = "速度 (km/h)") @Excel(name = "速度 (km/h)")
private Long speed; private Double speed;
/** 起始时间 (如: K+00:40:00) */ /** 起始时间 (如: K+00:40:00) */
@Excel(name = "起始时间 (如: K+00:40:00)") @Excel(name = "起始时间 (如: K+00:40:00)")
@ -53,7 +53,7 @@ public class RouteWaypoints extends BaseEntity
/** 转弯角度 (用于计算转弯半径) */ /** 转弯角度 (用于计算转弯半径) */
@Excel(name = "转弯角度 (用于计算转弯半径)") @Excel(name = "转弯角度 (用于计算转弯半径)")
private Long turnAngle; private Double turnAngle;
public void setId(Long id) public void setId(Long id)
{ {
@ -115,22 +115,22 @@ public class RouteWaypoints extends BaseEntity
return lng; return lng;
} }
public void setAlt(Long alt) public void setAlt(Double alt)
{ {
this.alt = alt; this.alt = alt;
} }
public Long getAlt() public Double getAlt()
{ {
return alt; return alt;
} }
public void setSpeed(Long speed) public void setSpeed(Double speed)
{ {
this.speed = speed; this.speed = speed;
} }
public Long getSpeed() public Double getSpeed()
{ {
return speed; return speed;
} }
@ -145,12 +145,12 @@ public class RouteWaypoints extends BaseEntity
return startTime; return startTime;
} }
public void setTurnAngle(Long turnAngle) public void setTurnAngle(Double turnAngle)
{ {
this.turnAngle = turnAngle; this.turnAngle = turnAngle;
} }
public Long getTurnAngle() public Double getTurnAngle()
{ {
return turnAngle; return turnAngle;
} }
@ -183,7 +183,7 @@ public class RouteWaypoints extends BaseEntity
// 单位换算:速度从 km/h 转为 m/s // 单位换算:速度从 km/h 转为 m/s
double v_mps = this.speed / 3.6; double v_mps = this.speed / 3.6;
// 单位换算:角度从 度(Degree) 转为 弧度(Radians) // 单位换算:角度从 度(Degree) 转为 弧度(Radians)
double radians = Math.toRadians(this.turnAngle.doubleValue()); double radians = Math.toRadians(this.turnAngle);
// 重力加速度 g // 重力加速度 g
double g = 9.8; 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 @Override
public Routes selectRoutesById(Long id) 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="platformId" column="platform_id" />
<result property="callSign" column="call_sign" /> <result property="callSign" column="call_sign" />
<result property="attributes" column="attributes" /> <result property="attributes" column="attributes" />
<collection property="waypoints" javaType="java.util.List" resultMap="com.ruoyi.system.mapper.RouteWaypointsMapper.RouteWaypointsResult" />
</resultMap> </resultMap>
<sql id="selectRoutesVo"> <sql id="selectRoutesVo">

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

@ -100,6 +100,204 @@ export default {
}, },
methods: { 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() { checkCesiumLoaded() {
if (typeof Cesium === 'undefined') { if (typeof Cesium === 'undefined') {
console.error('Cesium未加载,请检查CDN链接'); console.error('Cesium未加载,请检查CDN链接');
@ -155,6 +353,23 @@ export default {
this.initScaleBar() this.initScaleBar()
console.log('Cesium离线二维地图已加载') 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) { } catch (error) {
console.error('地图错误:', error) console.error('地图错误:', error)
@ -625,6 +840,7 @@ export default {
this.drawingHandler.setInputAction(() => { this.drawingHandler.setInputAction(() => {
this.cancelDrawing(); this.cancelDrawing();
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
}, },
finishRectangleDrawing() { finishRectangleDrawing() {
@ -1060,19 +1276,6 @@ export default {
return area 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() { clearAll() {
// 1. // 1.
if (this.allEntities && this.allEntities.length > 0) { if (this.allEntities && this.allEntities.length > 0) {

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

@ -32,8 +32,8 @@
v-for="route in routes" v-for="route in routes"
:key="route.id" :key="route.id"
class="route-item" class="route-item"
:class="{ selected: selectedRouteId === route.id }" :class="{ 'active': activeRouteIds.includes(route.id) }"
@click="handleSelectRoute(route)" @click="$emit('select-route', route)"
> >
<i class="el-icon-map-location"></i> <i class="el-icon-map-location"></i>
<div class="route-info"> <div class="route-info">
@ -222,9 +222,9 @@ export default {
type: Array, type: Array,
default: () => [] default: () => []
}, },
selectedRouteId: { activeRouteIds: {
type: [String, Number], type: Array,
default: null default: () => []
}, },
selectedRouteDetails: { selectedRouteDetails: {
type: Object, type: Object,
@ -261,11 +261,7 @@ export default {
this.$emit('hide') this.$emit('hide')
}, },
handleSelectRoute(route) { // 线
this.$emit('select-route', route)
},
// 线
handleCreateRoute() { handleCreateRoute() {
this.$emit('create-route') this.$emit('create-route')
}, },
@ -432,7 +428,7 @@ export default {
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15); box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15);
} }
.route-item.selected { .route-item.active {
background: rgba(0, 138, 255, 0.15); background: rgba(0, 138, 255, 0.15);
border-color: rgba(0, 138, 255, 0.3); border-color: rgba(0, 138, 255, 0.3);
box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25); 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="header-left">
<div class="system-title"> <div class="system-title">
<!-- 按照实际路径引入logo.jpg --> <!-- 按照实际路径引入logo.jpg -->
<img <img
src="@/views/childRoom/logo.png" src="@/views/childRoom/logo.png"
class="logo-icon mr-2" class="logo-icon mr-2"
alt="系统logo" 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> <span class="title-text blue-title">网络化任务规划系统</span>
</div> </div>
<!-- 顶部导航菜单 --> <!-- 顶部导航菜单 -->
<div class="top-nav-menu"> <div class="top-nav-menu">
<div <div
v-for="item in topNavItems" v-for="item in topNavItems"
:key="item.id" :key="item.id"
class="top-nav-item" class="top-nav-item"
:class="{ active: activeTopNav === item.id }" :class="{ active: activeTopNav === item.id }"
@ -23,11 +23,11 @@
> >
<i :class="item.icon" class="nav-icon"></i> <i :class="item.icon" class="nav-icon"></i>
<span class="nav-text">{{ item.name }}</span> <span class="nav-text">{{ item.name }}</span>
<!-- 文件下拉菜单 --> <!-- 文件下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'file'" v-if="item.id === 'file'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -38,12 +38,12 @@
<el-dropdown-item @click.native="newPlan">新建计划</el-dropdown-item> <el-dropdown-item @click.native="newPlan">新建计划</el-dropdown-item>
<el-dropdown-item @click.native="openPlan">打开</el-dropdown-item> <el-dropdown-item @click.native="openPlan">打开</el-dropdown-item>
<el-dropdown-item @click.native="savePlan">保存</el-dropdown-item> <el-dropdown-item @click.native="savePlan">保存</el-dropdown-item>
<!-- 导入二级菜单 --> <!-- 导入二级菜单 -->
<el-dropdown-item class="submenu-item"> <el-dropdown-item class="submenu-item">
<span>导入</span> <span>导入</span>
<el-dropdown <el-dropdown
trigger="hover" trigger="hover"
placement="right-start" placement="right-start"
class="submenu-dropdown" class="submenu-dropdown"
> >
@ -57,15 +57,15 @@
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item @click.native="exportPlan">导出</el-dropdown-item> <el-dropdown-item @click.native="exportPlan">导出</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 编辑下拉菜单 --> <!-- 编辑下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'edit'" v-if="item.id === 'edit'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -76,12 +76,12 @@
<el-dropdown-item @click.native="militaryMarking">军事标绘</el-dropdown-item> <el-dropdown-item @click.native="militaryMarking">军事标绘</el-dropdown-item>
<el-dropdown-item @click.native="iconEdit">图标编辑</el-dropdown-item> <el-dropdown-item @click.native="iconEdit">图标编辑</el-dropdown-item>
<el-dropdown-item @click.native="attributeEdit">属性修改</el-dropdown-item> <el-dropdown-item @click.native="attributeEdit">属性修改</el-dropdown-item>
<!-- 推演编辑二级菜单 --> <!-- 推演编辑二级菜单 -->
<el-dropdown-item class="submenu-item"> <el-dropdown-item class="submenu-item">
<span>推演编辑</span> <span>推演编辑</span>
<el-dropdown <el-dropdown
trigger="hover" trigger="hover"
placement="right-start" placement="right-start"
class="submenu-dropdown" class="submenu-dropdown"
> >
@ -96,11 +96,11 @@
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 视图下拉菜单 --> <!-- 视图下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'view'" v-if="item.id === 'view'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -113,11 +113,11 @@
<el-dropdown-item @click.native="toggleScale">比例尺</el-dropdown-item> <el-dropdown-item @click.native="toggleScale">比例尺</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 地图下拉菜单 --> <!-- 地图下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'map'" v-if="item.id === 'map'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -129,11 +129,11 @@
<el-dropdown-item @click.native="loadAeroChart">航空图</el-dropdown-item> <el-dropdown-item @click.native="loadAeroChart">航空图</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 空域下拉菜单 --> <!-- 空域下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'airspace'" v-if="item.id === 'airspace'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -144,11 +144,11 @@
<el-dropdown-item @click.native="threatZone">威胁区</el-dropdown-item> <el-dropdown-item @click.native="threatZone">威胁区</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 工具下拉菜单 --> <!-- 工具下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'tools'" v-if="item.id === 'tools'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -161,11 +161,11 @@
<el-dropdown-item @click.native="coordinateConversion">坐标换算</el-dropdown-item> <el-dropdown-item @click.native="coordinateConversion">坐标换算</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 选项下拉菜单 --> <!-- 选项下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'options'" v-if="item.id === 'options'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -175,8 +175,8 @@
<!-- 设置二级菜单 --> <!-- 设置二级菜单 -->
<el-dropdown-item class="submenu-item"> <el-dropdown-item class="submenu-item">
<span>设置</span> <span>设置</span>
<el-dropdown <el-dropdown
trigger="hover" trigger="hover"
placement="right-start" placement="right-start"
class="submenu-dropdown" class="submenu-dropdown"
> >
@ -194,11 +194,11 @@
<el-dropdown-item @click.native="systemDescription">系统说明</el-dropdown-item> <el-dropdown-item @click.native="systemDescription">系统说明</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<!-- 收藏下拉菜单 --> <!-- 收藏下拉菜单 -->
<el-dropdown <el-dropdown
v-if="item.id === 'favorites'" v-if="item.id === 'favorites'"
trigger="click" trigger="click"
placement="bottom-start" placement="bottom-start"
:hide-on-click="false" :hide-on-click="false"
class="file-dropdown" class="file-dropdown"
@ -224,7 +224,7 @@
<div class="info-value">{{ roomCode }}</div> <div class="info-value">{{ roomCode }}</div>
</div> </div>
</div> </div>
<!-- 在线人数 --> <!-- 在线人数 -->
<div class="info-box" @click="showOnlineMembersDialog"> <div class="info-box" @click="showOnlineMembersDialog">
<i class="el-icon-user info-icon"></i> <i class="el-icon-user info-icon"></i>
@ -233,7 +233,7 @@
<div class="info-value">{{ onlineCount }}</div> <div class="info-value">{{ onlineCount }}</div>
</div> </div>
</div> </div>
<!-- 作战时间 --> <!-- 作战时间 -->
<div class="info-box"> <div class="info-box">
<i class="el-icon-timer info-icon"></i> <i class="el-icon-timer info-icon"></i>
@ -242,7 +242,7 @@
<div class="info-value">{{ combatTime }}</div> <div class="info-value">{{ combatTime }}</div>
</div> </div>
</div> </div>
<!-- 天文时间 --> <!-- 天文时间 -->
<div class="info-box"> <div class="info-box">
<i class="el-icon-sunny info-icon"></i> <i class="el-icon-sunny info-icon"></i>
@ -252,31 +252,31 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 用户状态区域 --> <!-- 用户状态区域 -->
<div class="user-status-area"> <div class="user-status-area">
<!-- 用户头像 --> <!-- 用户头像 -->
<el-avatar :size="32" :src="userAvatar" class="user-avatar" /> <el-avatar :size="32" :src="userAvatar" class="user-avatar" />
</div> </div>
</div> </div>
<!-- 威力区弹窗 --> <!-- 威力区弹窗 -->
<power-zone-dialog <power-zone-dialog
v-model="powerZoneDialogVisible" v-model="powerZoneDialogVisible"
:power-zone="currentPowerZone" :power-zone="currentPowerZone"
@save="savePowerZone" @save="savePowerZone"
/> />
<!-- 比例尺弹窗 --> <!-- 比例尺弹窗 -->
<scale-dialog <scale-dialog
v-model="scaleDialogVisible" v-model="scaleDialogVisible"
:scale="currentScale" :scale="currentScale"
@save="saveScale" @save="saveScale"
/> />
<!-- 外部参数弹窗 --> <!-- 外部参数弹窗 -->
<external-params-dialog <external-params-dialog
v-model="externalParamsDialogVisible" v-model="externalParamsDialogVisible"
:external-params="currentExternalParams" :external-params="currentExternalParams"
@save="saveExternalParams" @save="saveExternalParams"
@import-airport="importAirport" @import-airport="importAirport"
@ -342,10 +342,10 @@ export default {
} }
}, },
methods: { methods: {
selectTopNav(item) { /*selectTopNav(item) {
this.$emit('select-nav', item) this.$emit('select-nav', item)
}, },*/
// //
newPlan() { newPlan() {
this.$emit('new-plan') this.$emit('new-plan')
@ -353,193 +353,193 @@ export default {
openPlan() { openPlan() {
this.$emit('open-plan') this.$emit('open-plan')
}, },
// //
savePlan() { savePlan() {
this.$emit('save-plan') this.$emit('save-plan')
}, },
importPlanFile() { importPlanFile() {
this.$emit('import-plan-file') this.$emit('import-plan-file')
}, },
importACD() { importACD() {
this.$emit('import-acd') this.$emit('import-acd')
}, },
importATO() { importATO() {
this.$emit('import-ato') this.$emit('import-ato')
}, },
importLayer() { importLayer() {
this.$emit('import-layer') this.$emit('import-layer')
}, },
importRoute() { importRoute() {
this.$emit('import-route') this.$emit('import-route')
}, },
exportPlan() { exportPlan() {
this.$emit('export-plan') this.$emit('export-plan')
}, },
// //
routeEdit() { routeEdit() {
this.$emit('route-edit') this.$emit('route-edit')
}, },
militaryMarking() { militaryMarking() {
this.$emit('military-marking') this.$emit('military-marking')
}, },
iconEdit() { iconEdit() {
this.$emit('icon-edit') this.$emit('icon-edit')
}, },
attributeEdit() { attributeEdit() {
this.$emit('attribute-edit') this.$emit('attribute-edit')
}, },
timeSettings() { timeSettings() {
this.$emit('time-settings') this.$emit('time-settings')
}, },
aircraftSettings() { aircraftSettings() {
this.$emit('aircraft-settings') this.$emit('aircraft-settings')
}, },
keyEventEdit() { keyEventEdit() {
this.$emit('key-event-edit') this.$emit('key-event-edit')
}, },
missileLaunch() { missileLaunch() {
this.$emit('missile-launch') this.$emit('missile-launch')
}, },
// //
toggle2D3D() { toggle2D3D() {
this.$emit('toggle-2d-3d') this.$emit('toggle-2d-3d')
}, },
toggleRuler() { toggleRuler() {
this.$emit('toggle-ruler') this.$emit('toggle-ruler')
}, },
toggleGrid() { toggleGrid() {
this.$emit('toggle-grid') this.$emit('toggle-grid')
}, },
toggleScale() { toggleScale() {
this.scaleDialogVisible = true this.scaleDialogVisible = true
this.currentScale = {} this.currentScale = {}
}, },
// //
loadTerrain() { loadTerrain() {
this.$emit('load-terrain') this.$emit('load-terrain')
}, },
changeProjection() { changeProjection() {
this.$emit('change-projection') this.$emit('change-projection')
}, },
loadAeroChart() { loadAeroChart() {
this.$emit('load-aero-chart') this.$emit('load-aero-chart')
}, },
// //
powerZone() { powerZone() {
this.powerZoneDialogVisible = true this.powerZoneDialogVisible = true
this.currentPowerZone = {} this.currentPowerZone = {}
}, },
threatZone() { threatZone() {
this.$emit('threat-zone') this.$emit('threat-zone')
}, },
// //
routeCalculation() { routeCalculation() {
this.$emit('route-calculation') this.$emit('route-calculation')
}, },
conflictDisplay() { conflictDisplay() {
this.$emit('conflict-display') this.$emit('conflict-display')
}, },
dataMaterials() { dataMaterials() {
this.$emit('data-materials') this.$emit('data-materials')
}, },
coordinateConversion() { coordinateConversion() {
this.$emit('coordinate-conversion') this.$emit('coordinate-conversion')
}, },
// //
pageLayout() { pageLayout() {
this.$emit('page-layout') this.$emit('page-layout')
}, },
dataStoragePath() { dataStoragePath() {
this.$emit('data-storage-path') this.$emit('data-storage-path')
}, },
externalParams() { externalParams() {
this.externalParamsDialogVisible = true this.externalParamsDialogVisible = true
this.currentExternalParams = {} this.currentExternalParams = {}
}, },
toggleAirport() { toggleAirport() {
this.$emit('toggle-airport') this.$emit('toggle-airport')
}, },
toggleLandmark() { toggleLandmark() {
this.$emit('toggle-landmark') this.$emit('toggle-landmark')
}, },
toggleRoute() { toggleRoute() {
this.$emit('toggle-route') this.$emit('toggle-route')
}, },
systemDescription() { systemDescription() {
this.$emit('system-description') this.$emit('system-description')
}, },
// //
layerFavorites() { layerFavorites() {
this.$emit('layer-favorites') this.$emit('layer-favorites')
}, },
routeFavorites() { routeFavorites() {
this.$emit('route-favorites') this.$emit('route-favorites')
}, },
showOnlineMembersDialog() { showOnlineMembersDialog() {
this.$emit('show-online-members') this.$emit('show-online-members')
}, },
// //
savePowerZone(powerZone) { savePowerZone(powerZone) {
this.$emit('save-power-zone', powerZone) this.$emit('save-power-zone', powerZone)
}, },
// //
saveScale(scale) { saveScale(scale) {
this.$emit('save-scale', scale) this.$emit('save-scale', scale)
}, },
// //
saveExternalParams(externalParams) { saveExternalParams(externalParams) {
this.$emit('save-external-params', externalParams) this.$emit('save-external-params', externalParams)
}, },
importAirport(path) { importAirport(path) {
this.$emit('import-airport', path) this.$emit('import-airport', path)
}, },
importRoute(path) { importRoute(path) {
this.$emit('import-route-data', path) this.$emit('import-route-data', path)
}, },
importLandmark(path) { importLandmark(path) {
this.$emit('import-landmark', path) this.$emit('import-landmark', path)
} }
@ -862,4 +862,4 @@ export default {
transform: scale(1.1); transform: scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.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"> <div id="gis-map-background" class="map-background">
<!-- cesiummap组件 --> <!-- cesiummap组件 -->
<cesiumMap :drawDomClick="drawDom"/> <cesiumMap ref="cesiumMap" :drawDomClick="drawDom" @draw-complete="handleMapDrawComplete"
@open-waypoint-dialog="handleOpenWaypointEdit" />
<div class="map-overlay-text"> <div class="map-overlay-text">
<i class="el-icon-location-outline text-3xl mb-2 block"></i> <i class="el-icon-location-outline text-3xl mb-2 block"></i>
<p>二维GIS地图区域</p> <p>二维GIS地图区域</p>
@ -21,6 +22,17 @@
<div class="red-dot"></div> <div class="red-dot"></div>
<i class="el-icon-s-unfold icon-inside"></i> <i class="el-icon-s-unfold icon-inside"></i>
</div> </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> </div>
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
@ -30,7 +42,6 @@
:combat-time="combatTime" :combat-time="combatTime"
:astro-time="astroTime" :astro-time="astroTime"
:user-avatar="userAvatar" :user-avatar="userAvatar"
@select-nav="selectTopNav"
@save-plan="savePlan" @save-plan="savePlan"
@import-plan-file="importPlanFile" @import-plan-file="importPlanFile"
@import-acd="importACD" @import-acd="importACD"
@ -88,7 +99,7 @@
:is-hidden="isRightPanelHidden" :is-hidden="isRightPanelHidden"
:active-tab="activeRightTab" :active-tab="activeRightTab"
:routes="routes" :routes="routes"
:selected-route-id="selectedRouteId" :active-route-ids="activeRouteIds"
:selected-route-details="selectedRouteDetails" :selected-route-details="selectedRouteDetails"
:conflicts="conflicts" :conflicts="conflicts"
:conflict-count="conflictCount" :conflict-count="conflictCount"
@ -208,6 +219,8 @@ 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 { listRoutes, getRoutes, addRoutes } from "@/api/system/routes";
import { updateWaypoints } from "@/api/system/waypoints";
export default { export default {
name: 'MissionPlanningView', name: 'MissionPlanningView',
components: { components: {
@ -233,6 +246,9 @@ export default {
selectedRoute: null, selectedRoute: null,
showWaypointDialog: false, showWaypointDialog: false,
selectedWaypoint: null, selectedWaypoint: null,
showNameDialog: false,
newRouteName: '',
tempMapPoints: [],
// //
roomCode: 'JTF-7-ALPHA', roomCode: 'JTF-7-ALPHA',
@ -270,7 +286,7 @@ export default {
// //
activeRightTab: 'plan', activeRightTab: 'plan',
selectedRouteId: 101, activeRouteIds: [], // 线ID
selectedRouteDetails: null, selectedRouteDetails: null,
// //
@ -314,11 +330,7 @@ export default {
], ],
// 线 // 线
routes: [ routes: [],
{ id: 101, name: 'Alpha进场航线', points: 8, conflict: true },
{ id: 102, name: 'Beta巡逻航线', points: 6, conflict: false },
{ id: 103, name: '侦察覆盖区', points: 4, conflict: false },
],
// //
timeProgress: 45, timeProgress: 45,
@ -332,11 +344,11 @@ export default {
}; };
}, },
mounted() { mounted() {
this.getList();
// //
this.isMenuHidden = true; this.isMenuHidden = true;
// //
this.isRightPanelHidden = true; this.isRightPanelHidden = true;
// //
this.updateTime(); this.updateTime();
setInterval(this.updateTime, 1000); setInterval(this.updateTime, 1000);
@ -351,6 +363,25 @@ export default {
} }
}, },
methods: { 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() { showOnlineMembersDialog() {
this.showOnlineMembers = true; this.showOnlineMembers = true;
@ -385,41 +416,114 @@ export default {
this.$message.success('航线名称更新成功'); this.$message.success('航线名称更新成功');
} }
}, },
// 线 // 线
createRoute() { 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) { openWaypointDialog(waypoint) {
this.selectedWaypoint = waypoint; this.selectedWaypoint = waypoint;
this.showWaypointDialog = true; this.showWaypointDialog = true;
}, },
updateWaypoint(updatedWaypoint) { /** 航点编辑保存:更新数据库并同步地图显示 */
// 1. 线 async updateWaypoint(updatedWaypoint) {
if (this.selectedRouteDetails && this.selectedRouteDetails.waypoints) { // 线
if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) return;
// 2. try {
const index = this.selectedRouteDetails.waypoints.indexOf(this.selectedWaypoint); const response = await updateWaypoints(updatedWaypoint);
if (response.code === 200) {
if (index !== -1) { //
// 3. 使 splice const index = this.selectedRouteDetails.waypoints.findIndex(p => p.id === updatedWaypoint.id);
this.selectedRouteDetails.waypoints.splice(index, 1, updatedWaypoint); if (index !== -1) {
// Entity
// 4. 便 const oldName = this.selectedRouteDetails.waypoints[index].name;
this.selectedWaypoint = updatedWaypoint; const newName = updatedWaypoint.name;
// 使 splice Vue UI
this.$message.success('航点更新成功'); this.selectedRouteDetails.waypoints.splice(index, 1, { ...updatedWaypoint });
} else { // 5.
// indexOf if (this.$refs.cesiumMap) {
const nameIndex = this.selectedRouteDetails.waypoints.findIndex(p => p.name === this.selectedWaypoint.name); this.$refs.cesiumMap.updateWaypointGraphic(oldName, newName);
if (nameIndex !== -1) { }
this.selectedRouteDetails.waypoints.splice(nameIndex, 1, updatedWaypoint);
this.selectedWaypoint = updatedWaypoint; this.showWaypointDialog = false;
this.$message.success('航点更新成功'); this.$message.success('航点信息已持久化至数据库');
} else {
this.$message.error('更新失败:未找到对应航点');
} }
} }
} catch (error) {
console.error("更新航点失败:", error);
this.$message.error('数据库更新失败,请重试');
} }
}, },
updateTime() { updateTime() {
@ -677,9 +781,9 @@ export default {
this.isRightPanelHidden = false; this.isRightPanelHidden = false;
} }
} else if(item.id === 'modify'){ } else if(item.id === 'modify'){
this.drawDom = !this.drawDom this.drawDom = !this.drawDom
console.log(this.drawDom,999999) console.log(this.drawDom,999999)
} }
if (item.id === 'deduction') { if (item.id === 'deduction') {
// /K // /K
this.showKTimePopup = !this.showKTimePopup; this.showKTimePopup = !this.showKTimePopup;
@ -774,23 +878,64 @@ export default {
const minutes = (val % 4) * 15; const minutes = (val % 4) * 15;
return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`; 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) { if (index > -1) {
this.selectedRouteId = route.id; this.activeRouteIds.splice(index, 1);
// if (this.$refs.cesiumMap) {
this.selectedRouteDetails = { this.$refs.cesiumMap.removeRouteById(route.id);
name: route.name, }
waypoints: [ if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
{ name: 'WP1', altitude: 5000, speed: '800km/h', eta: 'K+00:40:00' }, if (this.activeRouteIds.length > 0) {
{ name: 'WP2', altitude: 6000, speed: '850km/h', eta: 'K+00:55:00' }, const lastId = this.activeRouteIds[this.activeRouteIds.length - 1];
{ name: 'WP3', altitude: 5500, speed: '820km/h', eta: 'K+01:10:00' }, try {
{ name: 'WP4', altitude: 5800, speed: '830km/h', eta: 'K+01:25:00' }, const res = await getRoutes(lastId);
] if (res.code === 200 && res.data) {
}; this.selectedRouteDetails = {
// this.openRouteDialog(route); 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() { addWaypoint() {
if (this.selectedRouteDetails) { if (this.selectedRouteDetails) {
const count = this.selectedRouteDetails.waypoints.length + 1; const count = this.selectedRouteDetails.waypoints.length + 1;
@ -805,9 +950,15 @@ export default {
}, },
cancelRoute() { cancelRoute() {
this.selectedRouteId = null; // 线
if (this.$refs.cesiumMap) {
this.activeRouteIds.forEach(id => {
this.$refs.cesiumMap.removeRouteById(id);
});
}
this.activeRouteIds = [];
this.selectedRouteDetails = null; this.selectedRouteDetails = null;
this.$message.info('已取消选中'); this.$message.info('已清空所有选中航线');
}, },
// //
@ -857,7 +1008,7 @@ export default {
height: 100%; height: 100%;
background: linear-gradient(135deg, #1a2f4b 0%, #2c3e50 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-size: cover;
background-position: center; background-position: center;
z-index: 1; 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 CopyWebpackPlugin = require('copy-webpack-plugin')
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题 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 // 端口 const port = process.env.port || process.env.npm_config_port || 80 // 端口
// 定义 Cesium 源码路径 // 定义 Cesium 源码路径

Loading…
Cancel
Save