diff --git a/ruoyi-ui/src/assets/icons/svg/chongtu.svg b/ruoyi-ui/src/assets/icons/svg/chongtu.svg
new file mode 100644
index 0000000..cc5c41a
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/chongtu.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/circle.svg b/ruoyi-ui/src/assets/icons/svg/circle.svg
new file mode 100644
index 0000000..833bdf7
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/circle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/cj.svg b/ruoyi-ui/src/assets/icons/svg/cj.svg
new file mode 100644
index 0000000..1a23e44
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/cj.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/cursor.svg b/ruoyi-ui/src/assets/icons/svg/cursor.svg
new file mode 100644
index 0000000..d159790
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/cursor.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/dt.svg b/ruoyi-ui/src/assets/icons/svg/dt.svg
new file mode 100644
index 0000000..09fb981
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/dt.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/jx.svg b/ruoyi-ui/src/assets/icons/svg/jx.svg
new file mode 100644
index 0000000..f9441ad
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/jx.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/ky.svg b/ruoyi-ui/src/assets/icons/svg/ky.svg
new file mode 100644
index 0000000..0255ab7
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/ky.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/plan.svg b/ruoyi-ui/src/assets/icons/svg/plan.svg
new file mode 100644
index 0000000..608c2aa
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/plan.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/assets/icons/svg/sx.svg b/ruoyi-ui/src/assets/icons/svg/sx.svg
new file mode 100644
index 0000000..95ca792
--- /dev/null
+++ b/ruoyi-ui/src/assets/icons/svg/sx.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue
index de6a0f0..58d3b46 100644
--- a/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue
+++ b/ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue
@@ -9,7 +9,8 @@
@click="handleItemClick(item)"
:title="item.name"
>
-
+
+
@@ -42,9 +43,9 @@ export default {
allToolbarItems: [
{ id: 'mouse', name: '鼠标', icon: 'el-icon-position' },
{ id: 'polygon', name: '面', icon: 'el-icon-house' },
- { id: 'rectangle', name: '矩形', icon: 'el-icon-crop' },
- { id: 'circle', name: '圆形', icon: 'el-icon-circle-plus-outline' },
- { id: 'sector', name: '扇形', icon: 'el-icon-pie-chart' },
+ { id: 'rectangle', name: '矩形', icon: 'jx' },
+ { id: 'circle', name: '圆形', icon: 'circle' },
+ { id: 'sector', name: '扇形', icon: 'sx' },
{ id: 'arrow', name: '箭头', icon: 'el-icon-right' },
{ id: 'text', name: '文本', icon: 'el-icon-document' },
{ id: 'image', name: '图片', icon: 'el-icon-picture-outline' },
@@ -55,7 +56,7 @@ export default {
],
// 测距模式工具列表
rangingToolbarItems: [
- { id: 'mouse', name: '鼠标', icon: 'el-icon-position' },
+ { id: 'mouse', name: '鼠标', icon: 'cursor' },
{ id: 'point', name: '点', icon: 'el-icon-location' },
{ id: 'line', name: '线', icon: 'el-icon-edit-outline' },
{ id: 'clear', name: '清除', icon: 'el-icon-delete' }
@@ -72,6 +73,11 @@ export default {
}
},
methods: {
+ /** 判断是否为本地 SVG 图标(非 Element 的 el-icon-* 类名) */
+ isSvgIcon(icon) {
+ return icon && typeof icon === 'string' && !icon.startsWith('el-icon-')
+ },
+
handleItemClick(item) {
if (item.id === 'clear') {
this.$emit('clear-all')
@@ -148,6 +154,12 @@ export default {
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.3);
}
+.toolbar-item .toolbar-svg-icon {
+ width: 1em;
+ height: 1em;
+ font-size: 16px;
+}
+
.toolbar-item:disabled {
opacity: 0.5;
cursor: not-allowed;
diff --git a/ruoyi-ui/src/views/cesiumMap/index.vue b/ruoyi-ui/src/views/cesiumMap/index.vue
index aec8372..6781269 100644
--- a/ruoyi-ui/src/views/cesiumMap/index.vue
+++ b/ruoyi-ui/src/views/cesiumMap/index.vue
@@ -428,11 +428,17 @@ export default {
}
});
});
- // 在起点渲染平台图标(当航线有关联平台且平台有图标时)
+ // 在起点渲染平台图标(当航线有关联平台且平台有图标时),默认朝向为航线方向
const iconUrl = (platform && platform.imageUrl) || (platform && platform.iconUrl);
if (iconUrl && originalPositions.length > 0) {
const platformBillboardId = `route-platform-${routeId}`;
const fullUrl = this.formatPlatformIconUrl(iconUrl);
+ let initialRotation;
+ const pathData = this.getRoutePathWithSegmentIndices(waypoints);
+ if (pathData.path && pathData.path.length >= 2) {
+ const heading = this.computeHeadingFromPositions(pathData.path[0], pathData.path[1]);
+ if (heading !== undefined) initialRotation = Math.PI / 2 - heading;
+ }
this.viewer.entities.add({
id: platformBillboardId,
name: (platform && platform.name) || '平台',
@@ -445,7 +451,8 @@ export default {
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
scaleByDistance: new Cesium.NearFarScalar(500, 2.0, 200000, 0.4),
- translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6)
+ translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6),
+ ...(initialRotation !== undefined && { rotation: initialRotation })
}
});
}
@@ -551,6 +558,44 @@ export default {
}
return arc;
},
+
+ /**
+ * 获取与地图绘制一致的带转弯弧的路径(用于推演时图标沿弧线运动)。
+ * @param {Array} waypoints - 航点列表,需含 lng, lat, alt, speed, turnAngle
+ * @returns {{ path: Array<{lng,lat,alt}>, segmentEndIndices: number[] }} path 为路径点;segmentEndIndices[i] 为第 i 段(航点 i -> i+1)在 path 中的结束下标
+ */
+ getRoutePathWithSegmentIndices(waypoints) {
+ if (!waypoints || waypoints.length === 0) return { path: [], segmentEndIndices: [] };
+ const ellipsoid = this.viewer.scene.globe.ellipsoid;
+ const toLngLatAlt = (cartesian) => {
+ const carto = Cesium.Cartographic.fromCartesian(cartesian, ellipsoid);
+ return {
+ lng: Cesium.Math.toDegrees(carto.longitude),
+ lat: Cesium.Math.toDegrees(carto.latitude),
+ alt: carto.height
+ };
+ };
+ const originalPositions = waypoints.map(wp =>
+ Cesium.Cartesian3.fromDegrees(parseFloat(wp.lng), parseFloat(wp.lat), Number(wp.alt) || 0)
+ );
+ const path = [];
+ const segmentEndIndices = [];
+ for (let i = 0; i < waypoints.length; i++) {
+ const currPos = originalPositions[i];
+ const radius = this.getWaypointRadius(waypoints[i]);
+ if (i === 0 || i === waypoints.length - 1 || radius <= 0) {
+ path.push(toLngLatAlt(currPos));
+ } else {
+ const prevPos = originalPositions[i - 1];
+ const nextPos = originalPositions[i + 1];
+ const arcPoints = this.computeArcPositions(prevPos, currPos, nextPos, radius);
+ arcPoints.forEach(p => path.push(toLngLatAlt(p)));
+ }
+ if (i >= 1) segmentEndIndices[i - 1] = path.length - 1;
+ }
+ return { path, segmentEndIndices };
+ },
+
removeRouteById(routeId) {
// 从地图上移除所有属于该 routeId 的实体
const entityList = this.viewer.entities.values;
@@ -568,6 +613,52 @@ export default {
}
this.allEntities = this.allEntities.filter(item => item.id !== routeId && item.id !== `route-platform-${routeId}`);
},
+ /**
+ * 根据当前点与另一点计算航向角(弧度),用于飞机图标朝向。
+ * 航向:北为 0,顺时针为正。Cesium billboard 的 rotation 为自上而下看逆时针,故设置 rotation = -heading。
+ */
+ computeHeadingFromPositions(current, other) {
+ if (!current || !other) return undefined;
+ const cartesian1 = current.x !== undefined && current.y !== undefined && current.z !== undefined
+ ? current
+ : Cesium.Cartesian3.fromDegrees(Number(current.lng), Number(current.lat), Number(current.alt) || 0);
+ const cartesian2 = other.x !== undefined && other.y !== undefined && other.z !== undefined
+ ? other
+ : Cesium.Cartesian3.fromDegrees(Number(other.lng), Number(other.lat), Number(other.alt) || 0);
+ const enu = Cesium.Transforms.eastNorthUpToFixedFrame(cartesian1);
+ const east = Cesium.Matrix4.getColumn(enu, 0, new Cesium.Cartesian3());
+ const north = Cesium.Matrix4.getColumn(enu, 1, new Cesium.Cartesian3());
+ const toOther = Cesium.Cartesian3.subtract(cartesian2, cartesian1, new Cesium.Cartesian3());
+ const e = Cesium.Cartesian3.dot(toOther, east);
+ const n = Cesium.Cartesian3.dot(toOther, north);
+ if (Math.abs(e) < 1e-10 && Math.abs(n) < 1e-10) return undefined;
+ const heading = Math.atan2(e, n);
+ return heading;
+ },
+
+ /** 动态推演:更新某条航线的平台图标位置与朝向(position: { lng, lat, alt } 或 Cesium.Cartesian3;directionPoint 为用于计算机头朝向的另一点,如下一位置或上一位置) */
+ updatePlatformPosition(routeId, position, directionPoint) {
+ if (!this.viewer) return;
+ const entity = this.viewer.entities.getById(`route-platform-${routeId}`);
+ if (!entity || !entity.position) return;
+ let cartesian;
+ if (position && position.x !== undefined && position.y !== undefined && position.z !== undefined) {
+ cartesian = position;
+ } else if (position && position.lng != null && position.lat != null) {
+ const alt = position.alt != null ? Number(position.alt) : 0;
+ cartesian = Cesium.Cartesian3.fromDegrees(Number(position.lng), Number(position.lat), alt);
+ } else {
+ return;
+ }
+ entity.position = cartesian;
+ if (entity.billboard && directionPoint) {
+ const heading = this.computeHeadingFromPositions(position, directionPoint);
+ if (heading !== undefined) {
+ // 图标默认朝右(东),要让机头指向运动方向需逆时针转 90° 使“右”对齐北,故 rotation = π/2 - heading
+ entity.billboard.rotation = Math.PI / 2 - heading;
+ }
+ }
+ },
checkCesiumLoaded() {
if (typeof Cesium === 'undefined') {
console.error('Cesium未加载,请检查CDN链接');
diff --git a/ruoyi-ui/src/views/childRoom/LeftMenu.vue b/ruoyi-ui/src/views/childRoom/LeftMenu.vue
index 64c3896..64b0af7 100644
--- a/ruoyi-ui/src/views/childRoom/LeftMenu.vue
+++ b/ruoyi-ui/src/views/childRoom/LeftMenu.vue
@@ -30,24 +30,25 @@
@contextmenu.prevent="handleRightClick(item)"
:title="item.name"
>
-
+
+
-
+
-
+
-
+
{{ $t('leftMenu.delete') }}
-
+
\ No newline at end of file
+
diff --git a/ruoyi-ui/src/views/childRoom/RightPanel.vue b/ruoyi-ui/src/views/childRoom/RightPanel.vue
index d972568..3187dc1 100644
--- a/ruoyi-ui/src/views/childRoom/RightPanel.vue
+++ b/ruoyi-ui/src/views/childRoom/RightPanel.vue
@@ -85,7 +85,7 @@
{{ point.name }}
-
高度: {{ point.alt }}m | 速度: {{ point.speed }}
+
高度: {{ point.alt }}m | 速度: {{ point.speed }} | 相对K: {{ formatWaypointKTime(point.startTime) }}
@@ -334,6 +334,17 @@ 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)
diff --git a/ruoyi-ui/src/views/childRoom/TopHeader.vue b/ruoyi-ui/src/views/childRoom/TopHeader.vue
index b3e3b34..b04d5d2 100644
--- a/ruoyi-ui/src/views/childRoom/TopHeader.vue
+++ b/ruoyi-ui/src/views/childRoom/TopHeader.vue
@@ -233,11 +233,18 @@
-
+
{{ $t('topHeader.info.combatTime') }}
-
{{ combatTime }}
+
+ {{ combatTime }}
+
+
@@ -312,6 +319,14 @@ export default {
type: String,
default: ''
},
+ roomDetail: {
+ type: Object,
+ default: null
+ },
+ canSetKTime: {
+ type: Boolean,
+ default: false
+ },
userAvatar: {
type: String,
default: 'https://cube.elemecdn.com/0/88dd03f9bf287d08f58fbcf58fddbf4a8c6/avatar.png'
@@ -389,9 +404,7 @@ export default {
this.$emit('import-layer')
},
- importRoute() {
- this.$emit('import-route')
- },
+
exportPlan() {
this.$emit('export-plan')
@@ -856,6 +869,17 @@ export default {
font-weight: 600;
}
+.info-box.clickable {
+ cursor: pointer;
+}
+
+.info-box .set-k-hint {
+ margin-left: 4px;
+ font-size: 12px;
+ color: #008aff;
+ vertical-align: middle;
+}
+
.info-icon {
font-size: 20px;
color: #008aff;
diff --git a/ruoyi-ui/src/views/childRoom/index.vue b/ruoyi-ui/src/views/childRoom/index.vue
index 26cdb87..66eff38 100644
--- a/ruoyi-ui/src/views/childRoom/index.vue
+++ b/ruoyi-ui/src/views/childRoom/index.vue
@@ -36,6 +36,26 @@
确 定
+
+
+
+
+
+
+
+ 航线的任务时间将以此 K 时为基准进行加减;航点表时间为相对 K 的分钟数。房主/管理员可随时再次点击「作战时间」修改 K 时。
+
+
+