menghao 3 weeks ago
parent
commit
bb80bd9bce
  1. 154
      ruoyi-ui/src/views/cesiumMap/ContextMenu.vue
  2. 79
      ruoyi-ui/src/views/cesiumMap/index.vue
  3. 233
      ruoyi-ui/src/views/childRoom/index.vue
  4. 47
      ruoyi-ui/src/views/dialogs/RouteEditDialog.vue
  5. 47
      ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue

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

@ -7,20 +7,38 @@
</div>
</div>
<!-- 航点编辑向前/向后增加航点 -->
<!-- 航点编辑向前/向后增加航点可选默认/定时定速点仅能通过航点编辑切换 -->
<div class="menu-section" v-if="entityData && entityData.type === 'routeWaypoint'">
<div class="menu-title">航点</div>
<div class="menu-item" @click="handleEditWaypoint">
<span class="menu-icon">📝</span>
<span>编辑航点</span>
</div>
<div class="menu-item" @click="handleAddWaypointBefore">
<div class="menu-item" @click.stop="toggleAddWaypointExpand('before')">
<span class="menu-icon"></span>
<span>向前增加航点</span>
<span class="menu-expand-icon">{{ expandedAddWaypoint === 'before' ? '▼' : '▶' }}</span>
</div>
<div class="menu-item" @click="handleAddWaypointAfter">
<div v-if="expandedAddWaypoint === 'before'" class="menu-sub-group">
<div class="menu-item menu-item-sub" @click="handleAddWaypointWithMode('before', null)">
<span class="menu-icon-sub">·</span><span>默认航点</span>
</div>
<div class="menu-item menu-item-sub" @click="handleAddWaypointWithMode('before', 'fixed_time')">
<span class="menu-icon-sub">·</span><span>定时点</span>
</div>
</div>
<div class="menu-item" @click.stop="toggleAddWaypointExpand('after')">
<span class="menu-icon"></span>
<span>向后增加航点</span>
<span class="menu-expand-icon">{{ expandedAddWaypoint === 'after' ? '▼' : '▶' }}</span>
</div>
<div v-if="expandedAddWaypoint === 'after'" class="menu-sub-group">
<div class="menu-item menu-item-sub" @click="handleAddWaypointWithMode('after', null)">
<span class="menu-icon-sub">·</span><span>默认航点</span>
</div>
<div class="menu-item menu-item-sub" @click="handleAddWaypointWithMode('after', 'fixed_time')">
<span class="menu-icon-sub">·</span><span>定时点</span>
</div>
</div>
<div class="menu-item" @click="handleToggleWaypointHold">
<span class="menu-icon">🔄</span>
@ -28,6 +46,43 @@
</div>
</div>
<!-- 定时/定速点参数弹窗选择定时或定速后填写固定时间或速度 -->
<el-dialog
:title="addWaypointDialogTitle"
:visible.sync="showAddWaypointDialog"
width="360px"
append-to-body
:close-on-click-modal="false"
>
<el-form :model="addWaypointForm" label-width="120px" size="small">
<el-form-item v-if="addWaypointDialogSegmentMode === 'fixed_time'" label="距上一航点(分)">
<el-input-number
v-model="addWaypointForm.segmentTargetMinutes"
:min="0.1"
:max="9999"
:precision="1"
placeholder="从上一航点飞至此点的飞行时间"
style="width:100%"
/>
<div class="form-tip-small">飞行时间如10表示飞10分钟到达相对K时=上一航点+10速度将根据距离自动计算</div>
</el-form-item>
<el-form-item v-else-if="addWaypointDialogSegmentMode === 'fixed_speed'" label="定速(km/h)">
<el-input-number
v-model="addWaypointForm.segmentTargetSpeed"
:min="0"
:precision="1"
placeholder="本航段使用的固定速度"
style="width:100%"
/>
<div class="form-tip-small">上一航点到新航点之间使用的固定速度</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="showAddWaypointDialog = false">取消</el-button>
<el-button type="primary" @click="confirmAddWaypointParams">确定</el-button>
</span>
</el-dialog>
<!-- 航线上锁/解锁复制航点右键时也显示 routeId -->
<div class="menu-section" v-if="entityData && (entityData.type === 'route' || entityData.type === 'routeWaypoint')">
<div class="menu-title">航线编辑</div>
@ -480,6 +535,11 @@ export default {
},
data() {
return {
expandedAddWaypoint: null,
showAddWaypointDialog: false,
addWaypointDialogMode: null,
addWaypointDialogSegmentMode: null,
addWaypointForm: { segmentTargetMinutes: 10, segmentTargetSpeed: 800 },
showColorPickerFor: null,
showWidthPicker: false,
showSizePicker: false,
@ -522,6 +582,12 @@ export default {
isRouteLocked() {
if (!this.entityData || this.entityData.routeId == null) return false
return !!this.routeLocked[this.entityData.routeId]
},
addWaypointDialogTitle() {
if (!this.addWaypointDialogMode) return '设置参数'
const dir = this.addWaypointDialogMode === 'before' ? '向前' : '向后'
const type = this.addWaypointDialogSegmentMode === 'fixed_time' ? '定时点' : '定速点'
return `${dir}增加${type} - 设置参数`
}
},
methods: {
@ -591,12 +657,59 @@ export default {
this.$emit('open-waypoint-dialog', this.entityData.dbId, this.entityData.routeId, this.entityData.waypointIndex)
},
handleAddWaypointBefore() {
this.$emit('add-waypoint-at', { routeId: this.entityData.routeId, waypointIndex: this.entityData.waypointIndex, mode: 'before' })
toggleAddWaypointExpand(which) {
this.expandedAddWaypoint = this.expandedAddWaypoint === which ? null : which
},
handleAddWaypointAfter() {
this.$emit('add-waypoint-at', { routeId: this.entityData.routeId, waypointIndex: this.entityData.waypointIndex, mode: 'after' })
handleAddWaypointWithMode(mode, segmentMode) {
this.expandedAddWaypoint = null
if (segmentMode === 'fixed_time' || segmentMode === 'fixed_speed') {
this.addWaypointDialogMode = mode
this.addWaypointDialogSegmentMode = segmentMode
this.addWaypointForm = {
segmentTargetMinutes: 10,
segmentTargetSpeed: 800
}
this.showAddWaypointDialog = true
} else {
this.emitAddWaypointAt(mode, segmentMode, null, null)
}
},
confirmAddWaypointParams() {
const segmentMode = this.addWaypointDialogSegmentMode
if (segmentMode === 'fixed_time') {
const v = this.addWaypointForm.segmentTargetMinutes
if (v == null || v === '' || Number(v) <= 0) {
this.$message && this.$message.warning('请填写距上一航点的飞行时间(分),且必须大于0')
return
}
} else if (segmentMode === 'fixed_speed') {
const v = this.addWaypointForm.segmentTargetSpeed
if (v == null || v === '' || Number(v) <= 0) {
this.$message && this.$message.warning('请填写定速(km/h),且必须大于0')
return
}
}
this.showAddWaypointDialog = false
const mode = this.addWaypointDialogMode
const segmentTargetMinutes = segmentMode === 'fixed_time' ? this.addWaypointForm.segmentTargetMinutes : null
const segmentTargetSpeed = segmentMode === 'fixed_speed' ? Number(this.addWaypointForm.segmentTargetSpeed) : null
this.emitAddWaypointAt(mode, segmentMode, segmentTargetMinutes, segmentTargetSpeed)
this.addWaypointDialogMode = null
this.addWaypointDialogSegmentMode = null
},
emitAddWaypointAt(mode, segmentMode, segmentTargetMinutes, segmentTargetSpeed) {
const payload = {
routeId: this.entityData.routeId,
waypointIndex: this.entityData.waypointIndex,
mode,
segmentMode
}
if (segmentTargetMinutes != null) payload.segmentTargetMinutes = segmentTargetMinutes
if (segmentTargetSpeed != null) payload.segmentTargetSpeed = segmentTargetSpeed
this.$emit('add-waypoint-at', payload)
},
handleToggleWaypointHold() {
@ -818,6 +931,26 @@ export default {
flex: 1;
}
.menu-expand-icon {
margin-left: auto;
font-size: 10px;
color: #999;
flex: none;
}
.menu-sub-group {
padding-left: 0;
background-color: #fafafa;
border-left: 2px solid #e0e0e0;
margin-left: 8px;
}
.menu-icon-sub {
margin-right: 6px;
font-size: 12px;
color: #999;
}
.menu-item-sub {
padding: 6px 16px 6px 24px;
font-size: 12px;
@ -829,6 +962,13 @@ export default {
color: #333;
}
.form-tip-small {
font-size: 11px;
color: #999;
margin-top: 4px;
line-height: 1.3;
}
/* 颜色选择器样式 */
.color-picker-container {
padding: 8px 16px;

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

@ -1116,7 +1116,7 @@ export default {
lng: coords.lng,
alt: 5000,
speed: 800,
...(meta && { pointType: meta.radius != null ? 'hold_circle' : 'hold_ellipse', holdParams: JSON.stringify(meta) })
...(meta && { pointType: (meta.radius != null || meta.type === 'hold_circle') ? 'hold_circle' : 'hold_ellipse', holdParams: JSON.stringify(meta) })
});
insertIdx++;
if (this.missionPendingHold && this.missionPendingHold.beforeIndex === i) {
@ -1128,7 +1128,7 @@ export default {
lng: holdCoords.lng,
alt: 5000,
speed: 800,
pointType: this.missionPendingHold.params.radius != null ? 'hold_circle' : 'hold_ellipse',
pointType: (this.missionPendingHold.params.radius != null || this.missionPendingHold.params.type === 'hold_circle') ? 'hold_circle' : 'hold_ellipse',
holdParams: JSON.stringify(this.missionPendingHold.params)
});
insertIdx++;
@ -1390,7 +1390,7 @@ export default {
alt: 5000,
speed: 800,
...(isHold && {
pointType: this.missionPendingHold.params.radius != null ? 'hold_circle' : 'hold_ellipse',
pointType: (this.missionPendingHold.params.radius != null || this.missionPendingHold.params.type === 'hold_circle') ? 'hold_circle' : 'hold_ellipse',
holdParams: JSON.stringify(this.missionPendingHold.params)
})
});
@ -1425,9 +1425,9 @@ export default {
}
const center = this.missionPendingHold.center;
const p = holdParams;
const isCircle = p.radius != null;
const isCircle = p.radius != null || p.type === 'hold_circle';
const isRaceTrack = !isCircle && p.edgeLength != null;
const radius = isCircle ? (p.radius || 500) : 500;
const radius = isCircle ? (this.getWaypointRadius({ speed: p.speed ?? 800, turnAngle: p.turnAngle ?? 45 }) || 500) : 500;
const semiMajor = !isCircle ? (p.semiMajor ?? p.semiMajorAxis ?? 500) : radius;
const semiMinor = !isCircle ? (p.semiMinor ?? p.semiMinorAxis ?? 300) : radius;
const headingRad = !isCircle ? ((p.headingDeg ?? 0) * Math.PI) / 180 : 0;
@ -2113,7 +2113,8 @@ export default {
if (i < 1 || i >= waypoints.length - 1) return false;
const wp = waypoints[i];
const effectiveAngle = this.getEffectiveTurnAngle(wp, i, waypoints.length);
if (this.getWaypointRadius({ ...wp, turnAngle: effectiveAngle }) <= 0) return false;
const prevWp = waypoints[i - 1];
if (this.getTurnRadiusFromPrevSpeed(prevWp, effectiveAngle) <= 0) return false;
const nextPos = originalPositions[i + 1];
return !!nextPos;
};
@ -2409,7 +2410,8 @@ export default {
const pt = wp.pointType || wp.point_type;
const useCircle = pt === 'hold_circle';
const effectiveTurnAngle = this.getEffectiveTurnAngle(wp, i, waypoints.length);
const turnRadius = this.getWaypointRadius({ ...wp, turnAngle: effectiveTurnAngle }) || 500;
const prevWpForHold = i > 0 ? waypoints[i - 1] : null;
const turnRadius = this.getHoldRadiusFromPrevSpeed(wp, prevWpForHold, effectiveTurnAngle) || 500;
// +
const radius = turnRadius;
const turnRadiusForHold = turnRadius;
@ -2494,7 +2496,8 @@ export default {
lastPos = holdPositions[holdPositions.length - 1];
} else {
const effectiveAngle = this.getEffectiveTurnAngle(wp, i, waypoints.length);
const radius = this.getWaypointRadius({ ...wp, turnAngle: effectiveAngle });
const prevWpForTurn = waypoints[i - 1];
const radius = this.getTurnRadiusFromPrevSpeed(prevWpForTurn, effectiveAngle);
let nextLogical = nextPos;
if (nextPos && i + 1 < waypoints.length && this.isHoldWaypoint(waypoints[i + 1])) {
const nextWp = waypoints[i + 1];
@ -2502,7 +2505,8 @@ export default {
const nextHoldIsCircle = (nextWp.pointType || nextWp.point_type) === 'hold_circle';
if (nextHoldIsCircle) {
const holdPos = originalPositions[i + 1];
const computedNextR = this.getWaypointRadius({ ...nextWp, turnAngle: this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length) });
const nextHoldTurnAngle = this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length);
const computedNextR = this.getHoldRadiusFromPrevSpeed(nextWp, wp, nextHoldTurnAngle);
const r = (computedNextR > 0) ? computedNextR : 500;
const clock = holdParams && holdParams.clockwise !== false;
const center = this.getHoldCenterFromPrevNext(currPos, holdPos, r, clock);
@ -2511,7 +2515,8 @@ export default {
const nextNext = i + 2 < waypoints.length ? originalPositions[i + 2] : null;
const nextDir = this.getRaceTrackDirectionRad(originalPositions[i + 1], currPos, nextNext, holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0);
const nextEdge = Math.max(1000, holdParams && holdParams.edgeLength != null ? holdParams.edgeLength : this.DEFAULT_RACE_TRACK_EDGE_LENGTH_M);
const nextArcR = this.getWaypointRadius({ ...nextWp, turnAngle: this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length) }) || 500;
const nextHoldTurnAngle = this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length);
const nextArcR = this.getHoldRadiusFromPrevSpeed(nextWp, wp, nextHoldTurnAngle) || 500;
nextLogical = this.getRaceTrackTangentEntryPoint(originalPositions[i + 1], lastPos, nextDir, nextEdge, nextArcR, holdParams && holdParams.clockwise !== false);
}
}
@ -2690,14 +2695,30 @@ export default {
},
/** 转弯半径 R = v²/(g·tanθ),v 为速度(m/s),g=9.8,θ 为转弯坡度/坡度角(弧度) */
getWaypointRadius(wp) {
const speed = wp.speed || 800;
const bankAngle = wp.turnAngle || 0;
const speed = (wp && wp.segmentMode === 'fixed_speed' && (wp.segmentTargetSpeed != null && wp.segmentTargetSpeed !== ''))
? Number(wp.segmentTargetSpeed) : (wp?.speed || 800);
const bankAngle = wp?.turnAngle || 0;
if (bankAngle <= 0) return 0;
const v_mps = speed / 3.6;
const radians = bankAngle * (Math.PI / 180);
const g = 9.8;
return (v_mps * v_mps) / (g * Math.tan(radians));
},
/** 转弯/盘旋半径使用上一航点速度计算,因飞机以进入转弯前的速度转弯 */
getHoldRadiusFromPrevSpeed(holdWp, prevWp, holdTurnAngle) {
const prevSpeed = (prevWp && prevWp.segmentMode === 'fixed_speed' && (prevWp.segmentTargetSpeed != null && prevWp.segmentTargetSpeed !== ''))
? Number(prevWp.segmentTargetSpeed) : (prevWp?.speed || 800);
const bankAngle = holdTurnAngle ?? holdWp?.turnAngle ?? 45;
if (bankAngle <= 0) return 0;
const v_mps = prevSpeed / 3.6;
const radians = bankAngle * (Math.PI / 180);
const g = 9.8;
return (v_mps * v_mps) / (g * Math.tan(radians));
},
/** 普通航点转弯半径也使用上一航点速度 */
getTurnRadiusFromPrevSpeed(prevWp, turnAngle) {
return this.getHoldRadiusFromPrevSpeed(null, prevWp, turnAngle);
},
isHoldWaypoint(wp) {
const t = (wp && wp.pointType) || (wp && wp.point_type) || 'normal';
@ -4056,7 +4077,9 @@ export default {
const arcStartIdx = path.length;
let holdPositions;
if (useCircle) {
const turnRadius = this.getWaypointRadius({ ...wp, turnAngle: this.getEffectiveTurnAngle(wp, i, waypoints.length) }) || 500;
const prevWp = waypoints[i - 1];
const holdTurnAngle = this.getEffectiveTurnAngle(wp, i, waypoints.length);
const turnRadius = this.getHoldRadiusFromPrevSpeed(wp, prevWp, holdTurnAngle) || 500;
const radius = (holdRadiusByLegIndex[legIndex] != null && Number.isFinite(holdRadiusByLegIndex[legIndex]))
? holdRadiusByLegIndex[legIndex]
: turnRadius;
@ -4076,7 +4099,9 @@ export default {
lastPos = exit;
} else {
const edgeLengthM = Math.max(1000, params && params.edgeLength != null ? params.edgeLength : this.DEFAULT_RACE_TRACK_EDGE_LENGTH_M);
const arcRadiusM = this.getWaypointRadius({ ...wp, turnAngle: this.getEffectiveTurnAngle(wp, i, waypoints.length) }) || 500;
const prevWp = waypoints[i - 1];
const holdTurnAngle = this.getEffectiveTurnAngle(wp, i, waypoints.length);
const arcRadiusM = this.getHoldRadiusFromPrevSpeed(wp, prevWp, holdTurnAngle) || 500;
const directionRad = this.getRaceTrackDirectionRad(
currPos,
lastPos,
@ -4103,7 +4128,9 @@ export default {
const nextHoldIsCircle = (nextWp.pointType || nextWp.point_type) === 'hold_circle';
if (nextHoldIsCircle) {
const holdPos = originalPositions[i + 1];
const computedNextR2 = this.getWaypointRadius({ ...nextWp, turnAngle: this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length) });
const currWp = waypoints[i];
const nextHoldTurnAngle = this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length);
const computedNextR2 = this.getHoldRadiusFromPrevSpeed(nextWp, currWp, nextHoldTurnAngle);
const r = (computedNextR2 > 0) ? computedNextR2 : 500;
const clock = holdParams && holdParams.clockwise !== false;
const center = this.getHoldCenterFromPrevNext(currPos, holdPos, r, clock);
@ -4112,12 +4139,15 @@ export default {
const nextNext = i + 2 < waypoints.length ? originalPositions[i + 2] : null;
const nextDir = this.getRaceTrackDirectionRad(originalPositions[i + 1], currPos, nextNext, holdParams && holdParams.headingDeg != null ? holdParams.headingDeg : 0);
const nextEdge = Math.max(1000, holdParams && holdParams.edgeLength != null ? holdParams.edgeLength : this.DEFAULT_RACE_TRACK_EDGE_LENGTH_M);
const nextArcR = this.getWaypointRadius({ ...nextWp, turnAngle: this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length) }) || 500;
const currWp = waypoints[i];
const nextHoldTurnAngle = this.getEffectiveTurnAngle(nextWp, i + 1, waypoints.length);
const nextArcR = this.getHoldRadiusFromPrevSpeed(nextWp, currWp, nextHoldTurnAngle) || 500;
nextLogical = this.getRaceTrackTangentEntryPoint(originalPositions[i + 1], lastPos, nextDir, nextEdge, nextArcR, holdParams && holdParams.clockwise !== false);
}
}
const effectiveAngle = this.getEffectiveTurnAngle(wp, i, waypoints.length);
const radius = this.getWaypointRadius({ ...wp, turnAngle: effectiveAngle });
const prevWpForTurn = i > 0 ? waypoints[i - 1] : null;
const radius = this.getTurnRadiusFromPrevSpeed(prevWpForTurn, effectiveAngle);
if (i < waypoints.length - 1 && nextLogical) {
if (prevWasHold) {
//
@ -4797,7 +4827,10 @@ export default {
routeId: ctx.routeId,
waypointIndex: ctx.waypointIndex,
mode: ctx.mode,
position: { lng, lat, alt }
position: { lng, lat, alt },
segmentMode: ctx.segmentMode,
segmentTargetMinutes: ctx.segmentTargetMinutes,
segmentTargetSpeed: ctx.segmentTargetSpeed
});
}
this.clearAddWaypointContext();
@ -7965,8 +7998,8 @@ export default {
this.contextMenu.visible = false;
this.$emit('toggle-waypoint-hold', payload);
},
/** 开始“在航点前/后增加航点”模式:显示预览折线,左键放置、右键取消。waypoints 为当前航线航点数组。 */
startAddWaypointAt(routeId, waypointIndex, mode, waypoints) {
/** 开始“在航点前/后增加航点”模式:显示预览折线,左键放置、右键取消。segmentMode 可选:null(默认)、fixed_time(定时点)、fixed_speed(定速点)。segmentTargetMinutes/segmentTargetSpeed 为用户填写的固定值。 */
startAddWaypointAt(routeId, waypointIndex, mode, waypoints, segmentMode, segmentTargetMinutes, segmentTargetSpeed) {
if (!waypoints || waypoints.length === 0) return;
this.clearAddWaypointContext();
const ctx = {
@ -7974,12 +8007,16 @@ export default {
waypointIndex: Math.max(0, Math.min(waypointIndex, waypoints.length - 1)),
mode,
waypoints,
segmentMode: segmentMode || null,
segmentTargetMinutes: segmentTargetMinutes != null ? segmentTargetMinutes : null,
segmentTargetSpeed: segmentTargetSpeed != null ? segmentTargetSpeed : null,
mouseCartesian: null
};
const toCartesian = (wp) => Cesium.Cartesian3.fromDegrees(parseFloat(wp.lng), parseFloat(wp.lat), Number(wp.alt) || 5000);
ctx.mouseCartesian = toCartesian(waypoints[ctx.waypointIndex]);
this.addWaypointContext = ctx;
this.$message && this.$message.info(mode === 'before' ? '点击地图在当前位置前插入新航点,右键取消' : '点击地图在当前位置后插入新航点,右键取消');
const modeLabel = segmentMode === 'fixed_time' ? '定时点' : (segmentMode === 'fixed_speed' ? '定速点' : '默认航点');
this.$message && this.$message.info(`${mode === 'before' ? '在当前位置前' : '在当前位置后'}插入${modeLabel},点击地图放置,右键取消`);
this.updateAddWaypointPreview();
},
/** 更新“增加航点”预览折线:上一/当前/下一与鼠标位置连线,含盘旋段视觉效果 */

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

@ -278,9 +278,7 @@
<el-radio label="hold_ellipse">椭圆</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="addHoldForm.holdType === 'hold_circle'" label="半径(米)">
<el-input-number v-model="addHoldForm.radius" :min="100" :max="50000" style="width:100%" />
</el-form-item>
<!-- 圆形盘旋半径由速度与转弯坡度自动计算无需填写 -->
<template v-if="addHoldForm.holdType === 'hold_ellipse'">
<el-form-item label="跑道边长(km)">
<el-input-number v-model="addHoldForm.edgeLengthKm" :min="1" :max="200" :step="1" style="width:100%" />
@ -342,6 +340,7 @@
<waypoint-edit-dialog
v-model="showWaypointDialog"
:waypoint="selectedWaypoint"
:prev-waypoint="prevWaypointForEdit"
@save="updateWaypoint"
/>
@ -760,7 +759,7 @@ export default {
deductionEarlyArrivalByRoute: {}, // routeId -> earlyArrivalLegs
showAddHoldDialog: false,
addHoldContext: null, // { routeId, routeName, legIndex, fromName, toName }
addHoldForm: { holdType: 'hold_circle', radius: 15000, edgeLengthKm: 20, clockwise: true, startTime: '', startTimeMinutes: null },
addHoldForm: { holdType: 'hold_circle', edgeLengthKm: 20, clockwise: true, startTime: '', startTimeMinutes: null },
missionDrawingActive: false,
missionDrawingPointsCount: 0,
isPlaying: false,
@ -848,6 +847,14 @@ export default {
}
},
computed: {
/** 航点编辑时:上一航点,用于定时点“设定的时间”与“到达K时”的转换 */
prevWaypointForEdit() {
const wp = this.selectedWaypoint;
const waypoints = this.selectedRouteDetails?.waypoints;
if (!wp || !waypoints || !Array.isArray(waypoints)) return null;
const idx = wp.currentIndex != null ? wp.currentIndex : waypoints.findIndex(w => w.id === wp.id);
return idx > 0 ? waypoints[idx - 1] : null;
},
currentUserId() {
const id = this.$store.getters.id;
return id != null ? Number(id) : null;
@ -1171,7 +1178,7 @@ export default {
},
/** 右键航点“向前/向后增加航点”:进入放置模式,传入 waypoints 给地图预览 */
handleAddWaypointAt({ routeId, waypointIndex, mode }) {
handleAddWaypointAt({ routeId, waypointIndex, mode, segmentMode, segmentTargetMinutes, segmentTargetSpeed }) {
if (this.isRouteLockedByOther(routeId)) {
this.$message.warning('该航线正被其他成员编辑,无法添加航点');
return;
@ -1185,37 +1192,104 @@ export default {
this.$message.warning('航线无航点数据');
return;
}
//
if (segmentMode === 'fixed_time') {
const prevIdx = mode === 'before' ? waypointIndex - 1 : waypointIndex;
const prevWp = prevIdx >= 0 ? route.waypoints[prevIdx] : null;
if (prevWp && prevWp.segmentMode === 'fixed_speed') {
this.$message.warning('定速点的下一航点不能设置为定时点,请先将上一航点改为默认或定时');
return;
}
}
if (!this.$refs.cesiumMap || typeof this.$refs.cesiumMap.startAddWaypointAt !== 'function') return;
this.$refs.cesiumMap.startAddWaypointAt(routeId, waypointIndex, mode, route.waypoints);
this.$refs.cesiumMap.startAddWaypointAt(routeId, waypointIndex, mode, route.waypoints, segmentMode, segmentTargetMinutes, segmentTargetSpeed);
},
/** 地图放置新航点后:调用 addWaypoints 插入,再按插入位置重排 seq 并重绘。向后添加时:当前第 K 个,新航点为第 K+1 个,原第 K+1 个及以后依次后移。 */
async handleAddWaypointPlaced({ routeId, waypointIndex, mode, position }) {
/** 地图放置新航点后:调用 addWaypoints 插入,再按插入位置重排 seq 并重绘。segmentMode 为 null/fixed_time/fixed_speed 时按所选方式规划该航段。segmentTargetMinutes/segmentTargetSpeed 为用户在弹窗填写的固定值(可选)。 */
async handleAddWaypointPlaced({ routeId, waypointIndex, mode, position, segmentMode, segmentTargetMinutes: userSegmentTargetMinutes, segmentTargetSpeed: userSegmentTargetSpeed }) {
const route = this.routes.find(r => r.id === routeId);
if (!route || !route.waypoints) {
this.$message.warning('航线不存在或无航点');
return;
}
const waypoints = route.waypoints;
//
if (segmentMode === 'fixed_time') {
const insertIndex = mode === 'before' ? waypointIndex : waypointIndex + 1;
const prevWp = insertIndex > 0 ? waypoints[insertIndex - 1] : null;
if (prevWp && prevWp.segmentMode === 'fixed_speed') {
this.$message.warning('定速点的下一航点不能设置为定时点,请先将上一航点改为默认或定时');
return;
}
}
const refWp = waypoints[waypointIndex];
// insertIndex = waypointIndex+1 (waypointIndex+2) waypointIndex+2 waypointIndex+3
const insertIndex = mode === 'before' ? waypointIndex : waypointIndex + 1;
const prevWp = insertIndex > 0 ? waypoints[insertIndex - 1] : null;
const startTime = prevWp && prevWp.startTime ? prevWp.startTime : 'K+00:00:00';
const nextWp = insertIndex < waypoints.length ? waypoints[insertIndex] : null;
let startTime = prevWp && prevWp.startTime ? prevWp.startTime : 'K+00:00:00';
let segmentTargetMinutes = null;
let segmentTargetSpeed = null;
let plannedPrevSpeed = null;
const count = waypoints.length + 1;
const newName = `WP${insertIndex + 1}`;
const pos = { lat: position.lat, lng: position.lng, alt: position.alt != null ? position.alt : (refWp && refWp.alt) || 5000 };
if (segmentMode === 'fixed_time' && prevWp) {
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prevWp.startTime);
const distPrevNew = this.segmentDistance({ lat: prevWp.lat, lng: prevWp.lng, alt: prevWp.alt }, pos);
if (userSegmentTargetMinutes != null && userSegmentTargetMinutes !== '' && Number(userSegmentTargetMinutes) > 0) {
// ()1010
const deltaMin = Number(userSegmentTargetMinutes);
segmentTargetMinutes = prevMinutes + deltaMin; // K = +
startTime = this.minutesToStartTimeWithSeconds(segmentTargetMinutes);
if (deltaMin > 0.001) {
plannedPrevSpeed = Math.round(((distPrevNew / 1000) / (deltaMin / 60)) * 10) / 10;
}
} else {
const nextMinutes = nextWp ? this.waypointStartTimeToMinutesDecimal(nextWp.startTime) : prevMinutes + 10;
const distPrevNext = nextWp ? this.segmentDistance({ lat: prevWp.lat, lng: prevWp.lng, alt: prevWp.alt }, { lat: nextWp.lat, lng: nextWp.lng, alt: nextWp.alt }) : distPrevNew;
const ratio = distPrevNext > 0 ? Math.min(1, distPrevNew / distPrevNext) : 0.5;
segmentTargetMinutes = prevMinutes + (nextMinutes - prevMinutes) * ratio;
const deltaMin = segmentTargetMinutes - prevMinutes;
if (deltaMin > 0.001) {
plannedPrevSpeed = Math.round(((distPrevNew / 1000) / (deltaMin / 60)) * 10) / 10;
}
}
} else if (segmentMode === 'fixed_speed' && prevWp) {
const speedKmh = (userSegmentTargetSpeed != null && userSegmentTargetSpeed !== '' && Number(userSegmentTargetSpeed) > 0)
? Number(userSegmentTargetSpeed)
: Number(refWp && refWp.speed != null ? refWp.speed : 800);
segmentTargetSpeed = speedKmh;
plannedPrevSpeed = speedKmh; // 使
const distPrevNew = this.segmentDistance({ lat: prevWp.lat, lng: prevWp.lng, alt: prevWp.alt }, pos);
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prevWp.startTime);
const newMinutesFromK = prevMinutes + (distPrevNew / 1000) / speedKmh * 60;
startTime = this.minutesToStartTimeWithSeconds(newMinutesFromK);
} else if (!segmentMode && prevWp) {
// 800K
plannedPrevSpeed = 800;
const distPrevNew = this.segmentDistance({ lat: prevWp.lat, lng: prevWp.lng, alt: prevWp.alt }, pos);
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prevWp.startTime);
const newMinutesFromK = prevMinutes + (distPrevNew / 1000) / 800 * 60;
startTime = this.minutesToStartTimeWithSeconds(newMinutesFromK);
}
try {
const addRes = await addWaypoints({
const defaultSpeed = segmentTargetSpeed != null ? segmentTargetSpeed : ((refWp && refWp.speed != null) ? refWp.speed : 800);
const defaultTurnAngle = insertIndex === 0 || insertIndex === count - 1 ? 0 : 45;
const addPayload = {
routeId,
name: newName,
seq: insertIndex + 1,
lat: position.lat,
lng: position.lng,
alt: position.alt,
speed: (refWp && refWp.speed != null) ? refWp.speed : 800,
alt: pos.alt,
speed: defaultSpeed,
startTime,
turnAngle: (refWp && refWp.turnAngle != null) ? refWp.turnAngle : (insertIndex === 0 || insertIndex === count - 1 ? 0 : 45)
});
turnAngle: defaultTurnAngle
};
if (segmentMode != null) addPayload.segmentMode = segmentMode;
if (segmentTargetMinutes != null) addPayload.segmentTargetMinutes = segmentTargetMinutes;
if (segmentTargetSpeed != null) addPayload.segmentTargetSpeed = segmentTargetSpeed;
const addRes = await addWaypoints(addPayload);
await this.getList();
let updated = this.routes.find(r => r.id === routeId);
if (!updated || !updated.waypoints || updated.waypoints.length !== count) {
@ -1247,8 +1321,11 @@ export default {
const wp = reordered[i];
const newSeq = i + 1;
const isNewlyInserted = wp.id === newWp.id;
const isPrevToNew = prevWp && wp.id === prevWp.id;
const nameToUse = isNewlyInserted ? (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`) : (wp.name || (isHold(wp) ? `HOLD${newSeq}` : `WP${newSeq}`));
await updateWaypoints({
// 450
const prevTurnAngle = (isPrevToNew && insertIndex > 1) ? 45 : wp.turnAngle;
const updatePayload = {
id: wp.id,
routeId,
name: nameToUse,
@ -1256,14 +1333,22 @@ export default {
lat: wp.lat,
lng: wp.lng,
alt: wp.alt,
speed: wp.speed,
startTime: wp.startTime,
turnAngle: wp.turnAngle,
speed: isPrevToNew && plannedPrevSpeed != null ? plannedPrevSpeed : (isNewlyInserted && segmentTargetSpeed != null ? segmentTargetSpeed : wp.speed),
startTime: isNewlyInserted && segmentMode === 'fixed_speed' ? startTime : wp.startTime,
turnAngle: isNewlyInserted ? defaultTurnAngle : prevTurnAngle,
...(wp.pointType != null && { pointType: wp.pointType }),
...(wp.holdParams != null && { holdParams: wp.holdParams }),
...(wp.labelFontSize != null && { labelFontSize: wp.labelFontSize }),
...(wp.labelColor != null && { labelColor: wp.labelColor })
});
};
if (isNewlyInserted && segmentMode != null) {
updatePayload.segmentMode = segmentMode;
updatePayload.displayStyle = { ...(wp.displayStyle || {}), segmentMode, segmentTargetMinutes, segmentTargetSpeed };
}
if (isNewlyInserted && segmentTargetMinutes != null) updatePayload.segmentTargetMinutes = segmentTargetMinutes;
if (isNewlyInserted && segmentTargetSpeed != null) updatePayload.segmentTargetSpeed = segmentTargetSpeed;
if (isNewlyInserted && segmentMode === 'fixed_speed') updatePayload.startTime = startTime;
await updateWaypoints(updatePayload);
}
await this.getList();
updated = this.routes.find(r => r.id === routeId);
@ -1293,7 +1378,8 @@ export default {
if (this.selectedRouteId === routeId) {
this.selectedRouteDetails = { ...this.selectedRouteDetails, waypoints: sortedWaypoints };
}
this.$message.success('已添加航点');
const modeLabel = segmentMode === 'fixed_time' ? '定时点' : (segmentMode === 'fixed_speed' ? '定速点' : '航点');
this.$message.success(`已添加${modeLabel},该航段已按所选方式规划`);
this.wsConnection?.sendSyncWaypoints?.(routeId);
} catch (e) {
this.$message.error(e.msg || e.message || '添加航点失败');
@ -1348,7 +1434,7 @@ export default {
turnAngle = preserveTurnAngle();
} else {
pointType = 'hold_circle';
holdParams = JSON.stringify({ radius: 15000, clockwise: true });
holdParams = JSON.stringify({ type: 'hold_circle', clockwise: true });
turnAngle = preserveTurnAngle();
}
try {
@ -1370,7 +1456,8 @@ export default {
if (wp.labelFontSize != null) payload.labelFontSize = wp.labelFontSize;
if (wp.labelColor != null) payload.labelColor = wp.labelColor;
if (turnAngle > 0 && this.$refs.cesiumMap) {
payload.turnRadius = this.$refs.cesiumMap.getWaypointRadius(payload);
const prevWp = index > 0 ? waypoints[index - 1] : null;
payload.turnRadius = this.$refs.cesiumMap.getTurnRadiusFromPrevSpeed(prevWp, turnAngle);
} else {
payload.turnRadius = 0;
}
@ -1494,11 +1581,15 @@ export default {
if (wp.color != null) payload.color = wp.color;
if (wp.pixelSize != null) payload.pixelSize = wp.pixelSize;
if (wp.outlineColor != null) payload.outlineColor = wp.outlineColor;
const idx = waypoints.findIndex(p => p.id === dbId);
if (wp.turnAngle > 0 && this.$refs.cesiumMap && idx >= 0) {
const prevWp = idx > 0 ? waypoints[idx - 1] : null;
payload.turnRadius = this.$refs.cesiumMap.getTurnRadiusFromPrevSpeed(prevWp, wp.turnAngle);
}
try {
const response = await updateWaypoints(payload);
if (response.code === 200) {
const merged = { ...wp, ...payload };
const idx = waypoints.findIndex(p => p.id === dbId);
if (idx !== -1) waypoints.splice(idx, 1, merged);
if (this.selectedRouteDetails && this.selectedRouteDetails.id === routeId) {
const i = this.selectedRouteDetails.waypoints.findIndex(p => p.id === dbId);
@ -2139,7 +2230,9 @@ export default {
};
// 线
if (updatedRoute.waypoints && updatedRoute.waypoints.length > 0) {
for (const wp of updatedRoute.waypoints) {
for (let idx = 0; idx < updatedRoute.waypoints.length; idx++) {
const wp = updatedRoute.waypoints[idx];
const prevWp = idx > 0 ? updatedRoute.waypoints[idx - 1] : null;
const payload = {
id: wp.id,
routeId: wp.routeId,
@ -2160,7 +2253,7 @@ export default {
if (wp.color != null) payload.color = wp.color;
if (wp.outlineColor != null) payload.outlineColor = wp.outlineColor;
if (payload.turnAngle > 0 && this.$refs.cesiumMap) {
payload.turnRadius = this.$refs.cesiumMap.getWaypointRadius(payload);
payload.turnRadius = this.$refs.cesiumMap.getTurnRadiusFromPrevSpeed(prevWp, wp.turnAngle);
} else {
payload.turnRadius = 0;
}
@ -2394,7 +2487,7 @@ export default {
openAddHoldDuringDrawing() {
this.addHoldContext = { mode: 'drawing' };
this.addHoldForm = { holdType: 'hold_circle', radius: 15000, edgeLengthKm: 20, clockwise: true, startTime: '', startTimeMinutes: 60 };
this.addHoldForm = { holdType: 'hold_circle', edgeLengthKm: 20, clockwise: true, startTime: '', startTimeMinutes: 60 };
this.showAddHoldDialog = true;
},
@ -2532,7 +2625,7 @@ export default {
this.selectedWaypoint = data;
this.showWaypointDialog = true;
},
/** 航点编辑保存:更新数据库并同步地图显示 */
/** 航点编辑保存:更新数据库并同步地图显示。定时/定速点变更会级联重算整条航线后续航点的 K 时。 */
async updateWaypoint(updatedWaypoint) {
if (!this.selectedRouteDetails || !this.selectedRouteDetails.waypoints) return;
const routeId = updatedWaypoint.routeId != null ? updatedWaypoint.routeId : this.selectedRouteDetails.id;
@ -2540,13 +2633,33 @@ export default {
this.$message.warning('该航线正被其他成员编辑,无法保存');
return;
}
const sd = this.selectedRouteDetails;
const waypointsBefore = JSON.parse(JSON.stringify(sd.waypoints));
try {
if (this.$refs.cesiumMap && updatedWaypoint.turnAngle > 0) {
updatedWaypoint.turnRadius = this.$refs.cesiumMap.getWaypointRadius(updatedWaypoint);
const idxForRadius = sd.waypoints.findIndex(p => p.id === updatedWaypoint.id);
if (this.$refs.cesiumMap && updatedWaypoint.turnAngle > 0 && idxForRadius >= 0) {
const prevWpForTurn = idxForRadius > 0 ? sd.waypoints[idxForRadius - 1] : null;
updatedWaypoint.turnRadius = this.$refs.cesiumMap.getTurnRadiusFromPrevSpeed(prevWpForTurn, updatedWaypoint.turnAngle);
} else {
updatedWaypoint.turnRadius = 0;
}
// startTimeK
let startTimeToUse = (updatedWaypoint.startTime != null && updatedWaypoint.startTime !== '')
? updatedWaypoint.startTime
: 'K+00:00:00';
// KK
const indexForSpeed = sd.waypoints.findIndex(p => p.id === updatedWaypoint.id);
if (updatedWaypoint.segmentMode === 'fixed_speed' && (updatedWaypoint.segmentTargetSpeed != null && updatedWaypoint.segmentTargetSpeed !== '') && indexForSpeed > 0) {
const prev = sd.waypoints[indexForSpeed - 1];
const distM = this.segmentDistance(
{ lat: prev.lat, lng: prev.lng, alt: prev.alt },
{ lat: updatedWaypoint.lat, lng: updatedWaypoint.lng, alt: updatedWaypoint.alt }
);
const prevMinutes = this.waypointStartTimeToMinutesDecimal(prev.startTime);
const speedKmh = Number(updatedWaypoint.segmentTargetSpeed) || 800;
const newMinutesFromK = prevMinutes + (distM / 1000) / speedKmh * 60;
startTimeToUse = this.minutesToStartTimeWithSeconds(newMinutesFromK);
}
const payload = {
id: updatedWaypoint.id,
routeId: updatedWaypoint.routeId,
@ -2556,9 +2669,7 @@ export default {
lng: updatedWaypoint.lng,
alt: updatedWaypoint.alt,
speed: updatedWaypoint.speed,
startTime: (updatedWaypoint.startTime != null && updatedWaypoint.startTime !== '')
? updatedWaypoint.startTime
: 'K+00:00:00',
startTime: startTimeToUse,
turnAngle: updatedWaypoint.turnAngle
};
if (updatedWaypoint.pointType != null) payload.pointType = updatedWaypoint.pointType;
@ -2574,7 +2685,6 @@ export default {
const response = await updateWaypoints(payload);
if (response.code === 200) {
const roomId = this.currentRoomId;
const sd = this.selectedRouteDetails;
const index = sd.waypoints.findIndex(p => p.id === updatedWaypoint.id);
if (index !== -1) {
// payload startTime
@ -2657,6 +2767,54 @@ export default {
}
}
}
// / K 使线
const oldMinutesAtIdx = this.waypointStartTimeToMinutesDecimal(waypointsBefore[index]?.startTime);
const newMinutesAtIdx = this.waypointStartTimeToMinutesDecimal(sd.waypoints[index]?.startTime);
const deltaMin = newMinutesAtIdx - oldMinutesAtIdx;
if (Math.abs(deltaMin) > 0.001 && index < sd.waypoints.length - 1) {
for (let i = index + 1; i < sd.waypoints.length; i++) {
const wp = sd.waypoints[i];
const oldArrival = (wp.segmentTargetMinutes != null && wp.segmentTargetMinutes !== '') ? Number(wp.segmentTargetMinutes) : this.waypointStartTimeToMinutesDecimal(wp.startTime);
const newArrival = oldArrival + deltaMin;
const newStartTime = this.minutesToStartTimeWithSeconds(newArrival);
const cascadePayload = {
id: wp.id,
routeId,
name: wp.name,
seq: wp.seq,
lat: wp.lat,
lng: wp.lng,
alt: wp.alt,
speed: wp.speed,
startTime: newStartTime,
turnAngle: wp.turnAngle
};
if (wp.pointType != null) cascadePayload.pointType = wp.pointType;
if (wp.holdParams != null) cascadePayload.holdParams = wp.holdParams;
if (wp.segmentMode != null) cascadePayload.segmentMode = wp.segmentMode;
if (wp.segmentMode === 'fixed_time') cascadePayload.segmentTargetMinutes = newArrival;
else if (wp.segmentTargetMinutes != null) cascadePayload.segmentTargetMinutes = wp.segmentTargetMinutes;
if (wp.segmentTargetSpeed != null) cascadePayload.segmentTargetSpeed = wp.segmentTargetSpeed;
if (wp.labelFontSize != null) cascadePayload.labelFontSize = wp.labelFontSize;
if (wp.labelColor != null) cascadePayload.labelColor = wp.labelColor;
if (wp.color != null) cascadePayload.color = wp.color;
if (wp.pixelSize != null) cascadePayload.pixelSize = wp.pixelSize;
if (wp.outlineColor != null) cascadePayload.outlineColor = wp.outlineColor;
try {
const rCascade = await updateWaypoints(cascadePayload);
if (rCascade.code === 200) {
Object.assign(wp, { startTime: newStartTime, ...(wp.segmentMode === 'fixed_time' && { segmentTargetMinutes: newArrival }) });
sd.waypoints.splice(i, 1, wp);
if (routeInList && routeInList.waypoints) {
const idxInList = routeInList.waypoints.findIndex(p => p.id === wp.id);
if (idxInList !== -1) routeInList.waypoints.splice(idxInList, 1, wp);
}
}
} catch (e) {
console.warn(`级联更新航点 ${i + 1} K时失败`, e);
}
}
}
if (this.$refs.cesiumMap) {
if (roomId && sd.platformId) {
try {
@ -4269,9 +4427,10 @@ export default {
const posCur = { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt };
const entryPos = toEntrySlice.length ? toEntrySlice[toEntrySlice.length - 1] : posCur;
const holdWp = waypoints[i + 1];
const prevWpForHold = waypoints[i];
const holdParams = this.parseHoldParams(holdWp);
const holdCenter = holdWp ? { lng: parseFloat(holdWp.lng), lat: parseFloat(holdWp.lat), alt: Number(holdWp.alt) || 0 } : null;
const computedR = this.$refs.cesiumMap ? this.$refs.cesiumMap.getWaypointRadius(holdWp) : null;
const computedR = this.$refs.cesiumMap?.getHoldRadiusFromPrevSpeed?.(holdWp, prevWpForHold) ?? (this.$refs.cesiumMap ? this.$refs.cesiumMap.getWaypointRadius(holdWp) : null);
const holdRadius = (computedR != null && computedR > 0) ? computedR : 500;
const holdClockwise = holdParams && holdParams.clockwise !== false;
const holdCircumference = holdRadius != null ? 2 * Math.PI * holdRadius : null;
@ -4624,7 +4783,7 @@ export default {
if (!this.addHoldContext) return;
if (this.addHoldContext.mode === 'drawing') {
const holdParams = this.addHoldForm.holdType === 'hold_circle'
? { radius: this.addHoldForm.radius, clockwise: this.addHoldForm.clockwise }
? { type: 'hold_circle', clockwise: this.addHoldForm.clockwise }
: { edgeLength: (this.addHoldForm.edgeLengthKm != null ? this.addHoldForm.edgeLengthKm : 20) * 1000, clockwise: this.addHoldForm.clockwise };
if (this.$refs.cesiumMap && this.$refs.cesiumMap.insertHoldBetweenLastTwo) {
this.$refs.cesiumMap.insertHoldBetweenLastTwo(holdParams);
@ -4646,7 +4805,7 @@ export default {
const newSeq = (prevWp.seq != null ? Number(prevWp.seq) : legIndex + 1) + 1;
const baseSeq = prevWp.seq != null ? Number(prevWp.seq) : legIndex + 1;
const holdParams = this.addHoldForm.holdType === 'hold_circle'
? { radius: this.addHoldForm.radius, clockwise: this.addHoldForm.clockwise }
? { type: 'hold_circle', clockwise: this.addHoldForm.clockwise }
: { edgeLength: (this.addHoldForm.edgeLengthKm != null ? this.addHoldForm.edgeLengthKm : 20) * 1000, clockwise: this.addHoldForm.clockwise };
const startTime = this.addHoldForm.startTimeMinutes !== '' && this.addHoldForm.startTimeMinutes != null && !Number.isNaN(Number(this.addHoldForm.startTimeMinutes))
? this.minutesToStartTime(Number(this.addHoldForm.startTimeMinutes))

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

@ -243,7 +243,8 @@
</el-table-column>
<el-table-column label="速度(km/h)" min-width="100">
<template slot-scope="scope">
<span v-if="!waypointsEditMode">{{ formatNumOneDecimal(scope.row.speed) }}</span>
<span v-if="!waypointsEditMode">{{ formatNumOneDecimal(displaySpeedForRow(scope.row)) }}</span>
<el-input-number v-else-if="scope.row.segmentMode === 'fixed_speed'" v-model.number="scope.row.segmentTargetSpeed" size="mini" :precision="1" :min="0" :controls="false" placeholder="定速" style="width: 100%" />
<el-input-number v-else v-model.number="scope.row.speed" size="mini" :precision="1" :min="0" :controls="false" placeholder="速度" style="width: 100%" />
</template>
</el-table-column>
@ -651,6 +652,13 @@ export default {
if (mode === 'fixed_time') return '定时'
return '无'
},
/** 定速点显示 segmentTargetSpeed,否则显示 speed */
displaySpeedForRow(row) {
if (row && row.segmentMode === 'fixed_speed' && (row.segmentTargetSpeed != null && row.segmentTargetSpeed !== '')) {
return row.segmentTargetSpeed
}
return row?.speed
},
/** 数值保留一位小数显示,空/非数字显示 — */
formatNumOneDecimal(val) {
if (val === undefined || val === null || val === '') return '—'
@ -705,6 +713,13 @@ export default {
this.syncWaypointsTableData(this.route.waypoints || [])
this.$message.info('已取消编辑')
},
/** 根据上一航点速度与转弯坡度计算转弯半径 R=v²/(g·tanθ),与 cesiumMap 一致 */
calcTurnRadiusFromPrevSpeed(prevSpeedKmh, turnAngleDeg) {
if (!turnAngleDeg || turnAngleDeg <= 0) return 0
const v = (prevSpeedKmh || 800) / 3.6
const rad = (turnAngleDeg || 45) * (Math.PI / 180)
return (v * v) / (9.8 * Math.tan(rad))
},
getWaypointsPayloadForSave() {
const routeId = this.form.id
const rows = this.waypointsTableData
@ -723,7 +738,21 @@ export default {
}
}
}
return rows.map((row, idx) => ({
return rows.map((row, idx) => {
const speedVal = prevSpeedByIndex[idx] != null ? prevSpeedByIndex[idx]
: (row.segmentMode === 'fixed_speed' && (row.segmentTargetSpeed != null && row.segmentTargetSpeed !== ''))
? Math.round(Number(row.segmentTargetSpeed) * 10) / 10
: (row.speed != null ? Math.round(Number(row.speed) * 10) / 10 : null)
// 使
let turnRadius = 0
if (idx > 0 && (row.turnAngle != null ? row.turnAngle : 0) > 0) {
const prev = rows[idx - 1]
const prevSpeedKmh = prev.segmentMode === 'fixed_speed' && (prev.segmentTargetSpeed != null && prev.segmentTargetSpeed !== '')
? Number(prev.segmentTargetSpeed)
: (prevSpeedByIndex[idx - 1] != null ? prevSpeedByIndex[idx - 1] : (prev.speed != null ? Number(prev.speed) : 800))
turnRadius = this.calcTurnRadiusFromPrevSpeed(prevSpeedKmh, row.turnAngle != null ? row.turnAngle : 45)
}
return {
id: row.id,
routeId,
seq: row.seq,
@ -731,9 +760,10 @@ export default {
lng: row.lng,
lat: row.lat,
alt: row.alt,
speed: prevSpeedByIndex[idx] != null ? prevSpeedByIndex[idx] : (row.speed != null ? Math.round(Number(row.speed) * 10) / 10 : null),
speed: speedVal,
startTime: this.minutesToStartTime(row.minutesFromK),
turnAngle: row.turnAngle != null ? row.turnAngle : 0,
turnRadius,
pointType: row.pointType || null,
holdParams: row.holdParams || null,
segmentMode: row.segmentMode || null,
@ -744,9 +774,18 @@ export default {
color: row.color != null && row.color !== '' ? row.color : '#f1f5f9',
pixelSize: row.pixelSize != null ? row.pixelSize : 10,
outlineColor: row.outlineColor != null && row.outlineColor !== '' ? row.outlineColor : '#64748b'
}))
}
})
},
handleSave() {
//
const rows = this.waypointsTableData || []
for (let i = 1; i < rows.length; i++) {
if (rows[i].segmentMode === 'fixed_time' && rows[i - 1].segmentMode === 'fixed_speed') {
this.$message.warning(`航点「${rows[i].name || 'WP' + (i + 1)}」不能设为定时点:其上一航点「${rows[i - 1].name || 'WP' + i}」为定速点,定速点的下一航点不能设置为定时点`)
return
}
}
const payload = {
...this.form,
attributes: this.getAttributesForSave(),

47
ruoyi-ui/src/views/dialogs/WaypointEditDialog.vue

@ -85,17 +85,17 @@
</el-radio-group>
<div class="form-tip form-hold-tip">定速移动下一航点会按距离÷速度重算下一航点的相对K时定时移动本航点会按距离÷时间重算上一航点的速度</div>
<template v-if="formData.segmentMode === 'fixed_time'">
<el-form-item label="定时到达(分)" class="segment-target-item">
<el-form-item label="设定时间(分)" class="segment-target-item">
<el-input-number
v-model="formData.segmentTargetMinutes"
:min="-9999"
:min="0.1"
:max="9999"
:precision="1"
controls-position="right"
placeholder="到达本航点的相对K时,可与下方相对K时分离以支持“定时到达+盘旋至相对K时出发”"
placeholder="从上一航点飞至此点的飞行时间"
class="full-width-input"
/>
<div class="form-tip form-hold-tip">定时到达时间可与相对K时不同如盘旋点到达K+5盘旋至K+10再前往下一航点则此处填5相对K时填10</div>
<div class="form-tip form-hold-tip">从上一航点到本航点的飞行时间非盘旋点此时间即到达时刻与上一航点的差值盘旋点到达时刻=上一航点+此时间相对K时=到达+盘旋时长</div>
</el-form-item>
</template>
<template v-else-if="formData.segmentMode === 'fixed_speed'">
@ -188,6 +188,10 @@ export default {
waypoint: {
type: Object,
default: () => ({})
},
prevWaypoint: {
type: Object,
default: null
}
},
data() {
@ -284,13 +288,20 @@ export default {
const outlineColor = this.waypoint.outlineColor != null ? this.waypoint.outlineColor : '#64748b';
const segmentMode = this.waypoint.segmentMode || (this.waypoint.displayStyle && this.waypoint.displayStyle.segmentMode) || null;
const disp = this.waypoint.displayStyle;
const storedArrival = (disp && disp.segmentTargetMinutes != null) ? Number(disp.segmentTargetMinutes) : (this.waypoint.segmentTargetMinutes != null ? Number(this.waypoint.segmentTargetMinutes) : null);
// K=
let segmentTargetMinutesDisplay = storedArrival;
if (segmentMode === 'fixed_time' && storedArrival != null && this.prevWaypoint) {
const prevMin = this.startTimeToMinutes(this.prevWaypoint.startTime);
segmentTargetMinutesDisplay = Math.max(0.1, storedArrival - prevMin);
}
this.formData = {
name: this.waypoint.name || '',
alt: this.waypoint.alt !== undefined && this.waypoint.alt !== null ? Number(this.waypoint.alt) : 0,
speed: this.waypoint.speed !== undefined && this.waypoint.speed !== null ? Number(this.waypoint.speed) : 0,
minutesFromK: this.startTimeToMinutes(this.waypoint.startTime),
segmentMode: segmentMode === 'fixed_speed' || segmentMode === 'fixed_time' ? segmentMode : null,
segmentTargetMinutes: (disp && disp.segmentTargetMinutes != null) ? Number(disp.segmentTargetMinutes) : (this.waypoint.segmentTargetMinutes != null ? Number(this.waypoint.segmentTargetMinutes) : null),
segmentTargetMinutes: segmentTargetMinutesDisplay,
segmentTargetSpeed: (disp && disp.segmentTargetSpeed != null) ? Number(disp.segmentTargetSpeed) : (this.waypoint.segmentTargetSpeed != null ? Number(this.waypoint.segmentTargetSpeed) : (this.waypoint.speed != null ? Number(this.waypoint.speed) : null)),
currentIndex: index,
totalPoints: total,
@ -319,19 +330,32 @@ export default {
this.$refs.formRef.validate((valid) => {
if (!valid) return;
const { minutesFromK, ...rest } = this.formData;
//
if (this.formData.segmentMode === 'fixed_time' && this.prevWaypoint && this.prevWaypoint.segmentMode === 'fixed_speed') {
this.$message.warning('定速点的下一航点不能设置为定时点,请先将上一航点改为默认或定时');
return;
}
if (this.isHoldWaypoint && this.formData.segmentMode === 'fixed_time') {
const target = this.formData.segmentTargetMinutes;
if (target == null || target === '') {
this.$message.warning('盘旋点需设置定时到达时间,以便计算盘旋半径/长短轴');
const segmentDuration = this.formData.segmentTargetMinutes;
if (segmentDuration == null || segmentDuration === '') {
this.$message.warning('盘旋点需设置设定时间(上一航点到本点的飞行时间),以便计算盘旋半径/长短轴');
return;
}
if (Number(target) >= Number(minutesFromK)) {
this.$message.warning('盘旋点要求定时 < 相对K时(到达时间须早于切出时间),请调整定时或相对K时');
const prevMin = this.prevWaypoint ? this.startTimeToMinutes(this.prevWaypoint.startTime) : 0;
const arrivalMin = prevMin + Number(segmentDuration);
if (arrivalMin >= Number(minutesFromK)) {
this.$message.warning('盘旋点要求到达时间 < 相对K时(到达须早于切出时间才有盘旋时长),请调整设定时间或相对K时');
return;
}
}
const startTimeStr = this.minutesToStartTime(minutesFromK);
const segmentTargetMinutes = this.formData.segmentMode === 'fixed_time' && this.formData.segmentTargetMinutes != null ? this.formData.segmentTargetMinutes : null;
// K = K +
let segmentTargetMinutes = null;
if (this.formData.segmentMode === 'fixed_time' && this.formData.segmentTargetMinutes != null && this.formData.segmentTargetMinutes !== '') {
const segmentDuration = Number(this.formData.segmentTargetMinutes);
const prevMin = this.prevWaypoint ? this.startTimeToMinutes(this.prevWaypoint.startTime) : 0;
segmentTargetMinutes = prevMin + segmentDuration;
}
const segmentTargetSpeed = this.formData.segmentMode === 'fixed_speed' && this.formData.segmentTargetSpeed != null ? this.formData.segmentTargetSpeed : null;
const payload = {
...this.waypoint,
@ -340,6 +364,7 @@ export default {
segmentMode: this.formData.segmentMode || null,
segmentTargetMinutes,
segmentTargetSpeed,
...(segmentTargetSpeed != null && { speed: segmentTargetSpeed }),
displayStyle: { ...(this.waypoint.displayStyle || {}), segmentMode: this.formData.segmentMode, segmentTargetMinutes, segmentTargetSpeed },
labelFontSize: this.formData.labelFontSize,
labelColor: this.formData.labelColor,

Loading…
Cancel
Save