|
|
@ -331,6 +331,7 @@ import { listRoutes, getRoutes, addRoutes, updateRoutes, delRoutes } from "@/api |
|
|
import { updateWaypoints } from "@/api/system/waypoints"; |
|
|
import { updateWaypoints } from "@/api/system/waypoints"; |
|
|
import { listLib,addLib,delLib} from "@/api/system/lib"; |
|
|
import { listLib,addLib,delLib} from "@/api/system/lib"; |
|
|
import { getRooms, updateRooms } from "@/api/system/rooms"; |
|
|
import { getRooms, updateRooms } from "@/api/system/rooms"; |
|
|
|
|
|
import { getMenuConfig, saveMenuConfig } from "@/api/system/userMenuConfig"; |
|
|
import PlatformImportDialog from "@/views/dialogs/PlatformImportDialog.vue"; |
|
|
import PlatformImportDialog from "@/views/dialogs/PlatformImportDialog.vue"; |
|
|
export default { |
|
|
export default { |
|
|
name: 'MissionPlanningView', |
|
|
name: 'MissionPlanningView', |
|
|
@ -544,8 +545,9 @@ export default { |
|
|
this.isMenuHidden = true; |
|
|
this.isMenuHidden = true; |
|
|
// 初始化时右侧面板隐藏 |
|
|
// 初始化时右侧面板隐藏 |
|
|
this.isRightPanelHidden = true; |
|
|
this.isRightPanelHidden = true; |
|
|
// 初始化菜单项为默认配置 |
|
|
// 初始化菜单项为默认配置,再尝试加载当前用户已保存的配置 |
|
|
this.menuItems = [...this.defaultMenuItems]; |
|
|
this.menuItems = [...this.defaultMenuItems]; |
|
|
|
|
|
this.loadUserMenuConfig(); |
|
|
|
|
|
|
|
|
// 更新时间 |
|
|
// 更新时间 |
|
|
this.updateTime(); |
|
|
this.updateTime(); |
|
|
@ -1111,9 +1113,7 @@ export default { |
|
|
}, |
|
|
}, |
|
|
openKTimeSetDialog() { |
|
|
openKTimeSetDialog() { |
|
|
console.log("当前登录 ID (myId):", this.$store.getters.id); |
|
|
console.log("当前登录 ID (myId):", this.$store.getters.id); |
|
|
|
|
|
|
|
|
console.log("当前房间 ownerId:", this.roomDetail ? this.roomDetail.ownerId : '无房间信息'); |
|
|
console.log("当前房间 ownerId:", this.roomDetail ? this.roomDetail.ownerId : '无房间信息'); |
|
|
|
|
|
|
|
|
console.log("当前角色 roles:", this.$store.getters.roles); |
|
|
console.log("当前角色 roles:", this.$store.getters.roles); |
|
|
if (!this.canSetKTime) { |
|
|
if (!this.canSetKTime) { |
|
|
this.$message.info('仅房主或管理员可设定或修改 K 时'); |
|
|
this.$message.info('仅房主或管理员可设定或修改 K 时'); |
|
|
@ -1229,8 +1229,14 @@ export default { |
|
|
this.isIconEditMode = false |
|
|
this.isIconEditMode = false |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
handleResetMenuItems() { |
|
|
async handleResetMenuItems() { |
|
|
this.menuItems = [...this.defaultMenuItems] |
|
|
this.menuItems = [...this.defaultMenuItems] |
|
|
|
|
|
try { |
|
|
|
|
|
await saveMenuConfig({ |
|
|
|
|
|
menuItems: JSON.stringify(this.menuItems), |
|
|
|
|
|
position: this.menuPosition || 'left' |
|
|
|
|
|
}) |
|
|
|
|
|
} catch (e) { /* 未登录时仅本地恢复默认 */ } |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
updateMenuItems(newItems) { |
|
|
updateMenuItems(newItems) { |
|
|
@ -1308,8 +1314,43 @@ export default { |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
handleSaveMenuItems(savedItems) { |
|
|
async handleSaveMenuItems(savedItems) { |
|
|
this.menuItems = [...savedItems] |
|
|
this.menuItems = [...savedItems] |
|
|
|
|
|
// 持久化到当前账号 |
|
|
|
|
|
try { |
|
|
|
|
|
await saveMenuConfig({ |
|
|
|
|
|
menuItems: JSON.stringify(this.menuItems), |
|
|
|
|
|
position: this.menuPosition || 'left' |
|
|
|
|
|
}) |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// 未登录或接口失败时仅本地生效,仍提示保存成功(LeftMenu 已提示) |
|
|
|
|
|
if (e && e.response && e.response.status === 401) { |
|
|
|
|
|
this.$message.info('当前未登录,配置仅在本页有效;登录后保存可同步到账号') |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 加载当前用户的左侧菜单配置(登录且有过保存时生效) */ |
|
|
|
|
|
async loadUserMenuConfig() { |
|
|
|
|
|
try { |
|
|
|
|
|
const res = await getMenuConfig() |
|
|
|
|
|
const data = res && res.data |
|
|
|
|
|
if (!data) return |
|
|
|
|
|
if (data.menuItems) { |
|
|
|
|
|
let arr = [] |
|
|
|
|
|
try { |
|
|
|
|
|
arr = typeof data.menuItems === 'string' ? JSON.parse(data.menuItems) : data.menuItems |
|
|
|
|
|
} catch (e) { /* 解析失败保留默认 */ } |
|
|
|
|
|
if (Array.isArray(arr) && arr.length > 0) { |
|
|
|
|
|
this.menuItems = arr |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
if (data.position && ['left', 'top', 'bottom'].includes(data.position)) { |
|
|
|
|
|
this.menuPosition = data.position |
|
|
|
|
|
} |
|
|
|
|
|
} catch (e) { |
|
|
|
|
|
// 未登录或接口失败则使用默认菜单,不提示 |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
attributeEdit() { |
|
|
attributeEdit() { |
|
|
@ -1418,9 +1459,15 @@ export default { |
|
|
this.$message.success('外部参数保存成功'); |
|
|
this.$message.success('外部参数保存成功'); |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
savePageLayout(position) { |
|
|
async savePageLayout(position) { |
|
|
this.menuPosition = position; |
|
|
this.menuPosition = position; |
|
|
this.$message.success(`菜单位置已设置为:${this.getPositionLabel(position)}`); |
|
|
this.$message.success(`菜单位置已设置为:${this.getPositionLabel(position)}`); |
|
|
|
|
|
try { |
|
|
|
|
|
await saveMenuConfig({ |
|
|
|
|
|
menuItems: JSON.stringify(this.menuItems), |
|
|
|
|
|
position: this.menuPosition || 'left' |
|
|
|
|
|
}) |
|
|
|
|
|
} catch (e) { /* 未登录时仅本地生效 */ } |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
getPositionLabel(position) { |
|
|
getPositionLabel(position) { |
|
|
@ -1721,25 +1768,70 @@ export default { |
|
|
effectiveTime[i + 1] = Math.max(actualArrival, scheduled); |
|
|
effectiveTime[i + 1] = Math.max(actualArrival, scheduled); |
|
|
const posCur = { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt }; |
|
|
const posCur = { lng: points[i].lng, lat: points[i].lat, alt: points[i].alt }; |
|
|
const posNext = { lng: points[i + 1].lng, lat: points[i + 1].lat, alt: points[i + 1].alt }; |
|
|
const posNext = { lng: points[i + 1].lng, lat: points[i + 1].lat, alt: points[i + 1].alt }; |
|
|
segments.push({ startTime: effectiveTime[i], endTime: actualArrival, startPos: posCur, endPos: posNext, type: 'fly' }); |
|
|
segments.push({ startTime: effectiveTime[i], endTime: actualArrival, startPos: posCur, endPos: posNext, type: 'fly', legIndex: i }); |
|
|
if (actualArrival < effectiveTime[i + 1]) { |
|
|
if (actualArrival < effectiveTime[i + 1]) { |
|
|
segments.push({ startTime: actualArrival, endTime: effectiveTime[i + 1], startPos: posNext, endPos: posNext, type: 'wait' }); |
|
|
segments.push({ startTime: actualArrival, endTime: effectiveTime[i + 1], startPos: posNext, endPos: posNext, type: 'wait', legIndex: i }); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
return { segments, warnings }; |
|
|
return { segments, warnings }; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
/** 从时间轴中取当前推演时间对应的位置 */ |
|
|
/** 从路径片段中按比例 t(0~1)取点:按弧长比例插值 */ |
|
|
getPositionFromTimeline(segments, minutesFromK) { |
|
|
getPositionAlongPathSlice(pathSlice, t) { |
|
|
|
|
|
if (!pathSlice || pathSlice.length === 0) return null; |
|
|
|
|
|
if (pathSlice.length === 1 || t <= 0) return pathSlice[0]; |
|
|
|
|
|
if (t >= 1) return pathSlice[pathSlice.length - 1]; |
|
|
|
|
|
let totalLen = 0; |
|
|
|
|
|
const lengths = [0]; |
|
|
|
|
|
for (let i = 1; i < pathSlice.length; i++) { |
|
|
|
|
|
totalLen += this.segmentDistance(pathSlice[i - 1], pathSlice[i]); |
|
|
|
|
|
lengths.push(totalLen); |
|
|
|
|
|
} |
|
|
|
|
|
const targetDist = t * totalLen; |
|
|
|
|
|
let idx = 0; |
|
|
|
|
|
while (idx < lengths.length - 1 && lengths[idx + 1] < targetDist) idx++; |
|
|
|
|
|
const a = pathSlice[idx]; |
|
|
|
|
|
const b = pathSlice[idx + 1]; |
|
|
|
|
|
const segLen = lengths[idx + 1] - lengths[idx]; |
|
|
|
|
|
const segT = segLen > 0 ? (targetDist - lengths[idx]) / segLen : 0; |
|
|
|
|
|
return { |
|
|
|
|
|
lng: a.lng + (b.lng - a.lng) * segT, |
|
|
|
|
|
lat: a.lat + (b.lat - a.lat) * segT, |
|
|
|
|
|
alt: a.alt + (b.alt - a.alt) * segT |
|
|
|
|
|
}; |
|
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
/** 从时间轴中取当前推演时间对应的位置;若有 path/segmentEndIndices 则沿带转弯弧的路径插值 */ |
|
|
|
|
|
getPositionFromTimeline(segments, minutesFromK, path, segmentEndIndices) { |
|
|
if (!segments || segments.length === 0) return null; |
|
|
if (!segments || segments.length === 0) return null; |
|
|
if (minutesFromK <= segments[0].startTime) return segments[0].startPos; |
|
|
if (minutesFromK <= segments[0].startTime) return segments[0].startPos; |
|
|
const last = segments[segments.length - 1]; |
|
|
const last = segments[segments.length - 1]; |
|
|
if (minutesFromK >= last.endTime) return last.endPos; |
|
|
if (minutesFromK >= last.endTime) { |
|
|
|
|
|
// 若最后一段是等待且有弧线路径,终点取弧线终点 |
|
|
|
|
|
if (last.type === 'wait' && path && segmentEndIndices && last.legIndex != null && last.legIndex < segmentEndIndices.length && path[segmentEndIndices[last.legIndex]]) { |
|
|
|
|
|
return path[segmentEndIndices[last.legIndex]]; |
|
|
|
|
|
} |
|
|
|
|
|
return last.endPos; |
|
|
|
|
|
} |
|
|
for (let i = 0; i < segments.length; i++) { |
|
|
for (let i = 0; i < segments.length; i++) { |
|
|
const s = segments[i]; |
|
|
const s = segments[i]; |
|
|
if (minutesFromK < s.endTime) { |
|
|
if (minutesFromK < s.endTime) { |
|
|
const t = (minutesFromK - s.startTime) / (s.endTime - s.startTime); |
|
|
const t = (minutesFromK - s.startTime) / (s.endTime - s.startTime); |
|
|
if (s.type === 'wait') return s.startPos; |
|
|
if (s.type === 'wait') { |
|
|
|
|
|
// 有弧线路径时在弧线终点等待,不跳到航点 |
|
|
|
|
|
if (path && segmentEndIndices && s.legIndex != null && s.legIndex < segmentEndIndices.length) { |
|
|
|
|
|
const endIdx = segmentEndIndices[s.legIndex]; |
|
|
|
|
|
if (path[endIdx]) return path[endIdx]; |
|
|
|
|
|
} |
|
|
|
|
|
return s.startPos; |
|
|
|
|
|
} |
|
|
|
|
|
// 有带弧路径且当前为飞行段且存在对应航段索引时,沿路径弧线插值(含上一段终点,保证从弧线终点匀速运动到本段终点、不闪现) |
|
|
|
|
|
if (path && segmentEndIndices && s.legIndex != null && s.legIndex < segmentEndIndices.length) { |
|
|
|
|
|
const startIdx = s.legIndex === 0 ? 0 : segmentEndIndices[s.legIndex - 1]; |
|
|
|
|
|
const endIdx = segmentEndIndices[s.legIndex]; |
|
|
|
|
|
const pathSlice = path.slice(startIdx, endIdx + 1); |
|
|
|
|
|
if (pathSlice.length > 0) return this.getPositionAlongPathSlice(pathSlice, t); |
|
|
|
|
|
} |
|
|
return { |
|
|
return { |
|
|
lng: s.startPos.lng + (s.endPos.lng - s.startPos.lng) * t, |
|
|
lng: s.startPos.lng + (s.endPos.lng - s.startPos.lng) * t, |
|
|
lat: s.startPos.lat + (s.endPos.lat - s.startPos.lat) * t, |
|
|
lat: s.startPos.lat + (s.endPos.lat - s.startPos.lat) * t, |
|
|
@ -1750,12 +1842,24 @@ export default { |
|
|
return last.endPos; |
|
|
return last.endPos; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;返回 { position, warnings } */ |
|
|
/** 根据当前推演时间(相对 K 的分钟)得到平台位置,使用速度与等待逻辑;若有地图 ref 则沿转弯弧路径运动;返回 { position, nextPosition, previousPosition, warnings },用于计算机头朝向 */ |
|
|
getPositionAtMinutesFromK(waypoints, minutesFromK, globalMin, globalMax) { |
|
|
getPositionAtMinutesFromK(waypoints, minutesFromK, globalMin, globalMax) { |
|
|
if (!waypoints || waypoints.length === 0) return { position: null, warnings: [] }; |
|
|
if (!waypoints || waypoints.length === 0) return { position: null, nextPosition: null, previousPosition: null, warnings: [] }; |
|
|
const { segments, warnings } = this.buildRouteTimeline(waypoints, globalMin, globalMax); |
|
|
const { segments, warnings } = this.buildRouteTimeline(waypoints, globalMin, globalMax); |
|
|
const position = this.getPositionFromTimeline(segments, minutesFromK); |
|
|
let path = null; |
|
|
return { position, warnings }; |
|
|
let segmentEndIndices = null; |
|
|
|
|
|
if (this.$refs.cesiumMap && this.$refs.cesiumMap.getRoutePathWithSegmentIndices) { |
|
|
|
|
|
const ret = this.$refs.cesiumMap.getRoutePathWithSegmentIndices(waypoints); |
|
|
|
|
|
if (ret.path && ret.path.length > 0 && ret.segmentEndIndices && ret.segmentEndIndices.length > 0) { |
|
|
|
|
|
path = ret.path; |
|
|
|
|
|
segmentEndIndices = ret.segmentEndIndices; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
const position = this.getPositionFromTimeline(segments, minutesFromK, path, segmentEndIndices); |
|
|
|
|
|
const stepMin = 1 / 60; |
|
|
|
|
|
const nextPosition = this.getPositionFromTimeline(segments, minutesFromK + stepMin, path, segmentEndIndices); |
|
|
|
|
|
const previousPosition = this.getPositionFromTimeline(segments, minutesFromK - stepMin, path, segmentEndIndices); |
|
|
|
|
|
return { position, nextPosition, previousPosition, warnings }; |
|
|
}, |
|
|
}, |
|
|
|
|
|
|
|
|
/** 仅根据当前展示的航线(activeRouteIds)更新平台图标位置,并汇总航段提示 */ |
|
|
/** 仅根据当前展示的航线(activeRouteIds)更新平台图标位置,并汇总航段提示 */ |
|
|
@ -1767,9 +1871,9 @@ export default { |
|
|
this.activeRouteIds.forEach(routeId => { |
|
|
this.activeRouteIds.forEach(routeId => { |
|
|
const route = this.routes.find(r => r.id === routeId); |
|
|
const route = this.routes.find(r => r.id === routeId); |
|
|
if (!route || !route.waypoints || route.waypoints.length === 0) return; |
|
|
if (!route || !route.waypoints || route.waypoints.length === 0) return; |
|
|
const { position, warnings } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes); |
|
|
const { position, nextPosition, previousPosition, warnings } = this.getPositionAtMinutesFromK(route.waypoints, minutesFromK, minMinutes, maxMinutes); |
|
|
if (warnings && warnings.length) allWarnings.push(...warnings); |
|
|
if (warnings && warnings.length) allWarnings.push(...warnings); |
|
|
if (position) this.$refs.cesiumMap.updatePlatformPosition(routeId, position); |
|
|
if (position) this.$refs.cesiumMap.updatePlatformPosition(routeId, position, nextPosition || previousPosition); |
|
|
}); |
|
|
}); |
|
|
this.deductionWarnings = [...new Set(allWarnings)]; |
|
|
this.deductionWarnings = [...new Set(allWarnings)]; |
|
|
}, |
|
|
}, |
|
|
|