Browse Source

移花接木

lbj
sd 3 months ago
parent
commit
ffbf92930f
  1. 1
      ruoyi-ui/package.json
  2. 128
      ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue
  3. 104
      ruoyi-ui/src/views/cesiumMap/MeasurementPanel.vue
  4. 863
      ruoyi-ui/src/views/cesiumMap/index.vue
  5. 391
      ruoyi-ui/src/views/childRoom/BottomLeftPanel.vue
  6. 167
      ruoyi-ui/src/views/childRoom/index.vue

1
ruoyi-ui/package.json

@ -52,6 +52,7 @@
"devDependencies": {
"@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6",
"@babel/plugin-transform-optional-chaining": "^7.28.6",
"@open-wc/webpack-import-meta-loader": "^0.4.7",
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-service": "4.4.6",
"babel-plugin-dynamic-import-node": "2.3.3",

128
ruoyi-ui/src/views/cesiumMap/DrawingToolbar.vue

@ -0,0 +1,128 @@
<template>
<div class="drawing-toolbar" v-if="drawDomClick">
<div class="toolbar-icons">
<div
v-for="item in toolbarItems"
:key="item.id"
class="toolbar-item"
:class="{ active: drawingMode === item.id }"
@click="handleItemClick(item)"
:title="item.name"
>
<i :class="item.icon"></i>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DrawingToolbar',
props: {
drawDomClick: {
type: Boolean,
default: false
},
drawingMode: {
type: String,
default: null
},
hasEntities: {
type: Boolean,
default: false
}
},
data() {
return {
toolbarItems: [
{ id: 'point', name: '点', icon: 'el-icon-location' },
{ id: 'line', name: '线', icon: 'el-icon-edit-outline' },
{ id: 'polygon', name: '面', icon: 'el-icon-s-grid' },
{ id: 'rectangle', name: '矩形', icon: 'el-icon-s-data' },
{ id: 'circle', name: '圆形', icon: 'el-icon-circle-plus-outline' },
{ id: 'locate', name: '定位', icon: 'el-icon-aim' },
{ id: 'clear', name: '清除', icon: 'el-icon-delete' },
{ id: 'import', name: '导入', icon: 'el-icon-upload' },
{ id: 'export', name: '导出', icon: 'el-icon-download' }
]
}
},
methods: {
handleItemClick(item) {
if (item.id === 'clear') {
this.$emit('clear-all')
} else if (item.id === 'export') {
this.$emit('export-data')
} else if (item.id === 'import') {
this.$emit('import-data')
} else if (item.id === 'locate') {
this.$emit('locate')
} else {
this.$emit('toggle-drawing', item.id)
}
}
}
}
</script>
<style scoped>
.drawing-toolbar {
position: absolute;
top: 70px;
right: 20px;
width: 40px;
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(0, 138, 255, 0.1);
border-radius: 8px;
z-index: 90;
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.2);
padding: 15px 5px;
transition: all 0.3s ease;
overflow: hidden;
opacity: 1;
transform: translateX(0);
}
.toolbar-icons {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 30px;
}
.toolbar-item {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
cursor: pointer;
color: #555;
font-size: 20px;
position: relative;
transition: all 0.3s;
border-radius: 4px;
padding: 0 5px;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(5px);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.toolbar-item:hover {
background: rgba(0, 138, 255, 0.1);
color: #008aff;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(0, 138, 255, 0.2);
}
.toolbar-item.active {
background: rgba(0, 138, 255, 0.15);
color: #008aff;
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.3);
}
.toolbar-item:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

104
ruoyi-ui/src/views/cesiumMap/MeasurementPanel.vue

@ -0,0 +1,104 @@
<template>
<div class="measurement-panel">
<div class="measurement-content">
<h5>测量结果</h5>
<div class="measurement-item" v-if="result.distance">
<span>长度</span>
<strong>{{ result.distance.toFixed(2) }} </strong>
</div>
<div class="measurement-item" v-if="result.area">
<span>面积</span>
<strong>{{ result.area.toFixed(2) }} 平方米</strong>
</div>
<div class="measurement-item" v-if="result.radius">
<span>半径</span>
<strong>{{ result.radius.toFixed(2) }} </strong>
</div>
<button @click="handleClose" class="close-btn">关闭</button>
</div>
</div>
</template>
<script>
export default {
name: 'MeasurementPanel',
props: {
result: {
type: Object,
default: null
}
},
methods: {
handleClose() {
this.$emit('close')
}
}
}
</script>
<style scoped>
.measurement-panel {
position: absolute;
bottom: 20px;
right: 80px;
z-index: 85;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
padding: 20px;
min-width: 250px;
}
.measurement-content h5 {
margin: 0 0 15px 0;
font-size: 16px;
font-weight: 600;
color: #333;
border-bottom: 2px solid #409EFF;
padding-bottom: 8px;
}
.measurement-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
font-size: 14px;
color: #666;
}
.measurement-item strong {
color: #409EFF;
font-weight: 600;
}
.close-btn {
width: 100%;
padding: 10px;
background: #409EFF;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.3s;
margin-top: 15px;
}
.close-btn:hover {
background: #66b1ff;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
}
@media (max-width: 768px) {
.measurement-panel {
bottom: 10px;
right: 10px;
left: 10px;
min-width: auto;
}
}
</style>

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

File diff suppressed because it is too large

391
ruoyi-ui/src/views/childRoom/BottomLeftPanel.vue

@ -0,0 +1,391 @@
<template>
<div class="bottom-left-panel">
<div class="panel-toggle" @click="togglePanel" :title="isExpanded ? '收起' : '展开'">
<i :class="isExpanded ? 'el-icon-s-fold' : 'el-icon-s-unfold'"></i>
<span v-if="!isExpanded" class="toggle-text">工具</span>
</div>
<div class="panel-content" :class="{ expanded: isExpanded }">
<div class="panel-item" @click="showTimeline">
<i class="el-icon-time"></i>
<span>时间线</span>
</div>
<div class="panel-item" @click="showProgress">
<i class="el-icon-s-data"></i>
<span>进度检查</span>
</div>
<div class="panel-item" @click="showSixSteps">
<i class="el-icon-s-operation"></i>
<span>六步法</span>
</div>
</div>
<el-dialog
:visible.sync="dialogVisible"
:title="dialogTitle"
width="350px"
:modal="false"
custom-class="panel-dialog"
>
<div class="dialog-content">
<div v-if="activeTab === 'timeline'" class="timeline-content">
<h3>时间线</h3>
<div class="timeline-list">
<div v-for="(item, index) in timelineData" :key="index" class="timeline-item">
<div class="timeline-dot"></div>
<div class="timeline-info">
<div class="timeline-time">{{ item.time }}</div>
<div class="timeline-event">{{ item.event }}</div>
</div>
</div>
</div>
</div>
<div v-if="activeTab === 'progress'" class="progress-content">
<h3>进度检查</h3>
<div class="progress-list">
<div v-for="(item, index) in progressData" :key="index" class="progress-item">
<div class="progress-label">{{ item.label }}</div>
<el-progress :percentage="item.percentage" :status="item.status"></el-progress>
</div>
</div>
</div>
<div v-if="activeTab === 'sixsteps'" class="sixsteps-content">
<h3>六步法</h3>
<div class="steps-container">
<div v-for="(step, index) in sixStepsData" :key="index" class="step-item" :class="{ active: step.active, completed: step.completed }">
<div class="step-number">{{ index + 1 }}</div>
<div class="step-content">
<div class="step-title">{{ step.title }}</div>
<div class="step-desc">{{ step.desc }}</div>
</div>
</div>
</div>
</div>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
name: 'BottomLeftPanel',
data() {
return {
isExpanded: false,
dialogVisible: false,
dialogTitle: '',
activeTab: '',
timelineData: [
{ time: 'K-02:00', event: '任务准备阶段' },
{ time: 'K-01:00', event: '资源调配' },
{ time: 'K时', event: '任务执行' },
{ time: 'K+01:00', event: '任务监控' },
{ time: 'K+02:00', event: '任务完成' }
],
progressData: [
{ label: '任务准备', percentage: 100, status: 'success' },
{ label: '资源调配', percentage: 80, status: '' },
{ label: '任务执行', percentage: 45, status: '' },
{ label: '任务监控', percentage: 20, status: 'exception' },
{ label: '任务完成', percentage: 0, status: '' }
],
sixStepsData: [
{ title: '理解', desc: '明确任务目标和要求', active: true, completed: true },
{ title: '判断', desc: '评估可用资源和能力', active: false, completed: true },
{ title: '规划', desc: '制定详细执行方案', active: false, completed: false },
{ title: '准备', desc: '识别和评估潜在风险', active: false, completed: false },
{ title: '执行', desc: '实时监控执行过程', active: false, completed: false },
{ title: '评估', desc: '评估任务完成效果', active: false, completed: false }
]
}
},
methods: {
togglePanel() {
this.isExpanded = !this.isExpanded
},
showTimeline() {
this.activeTab = 'timeline'
this.dialogTitle = '时间线'
this.dialogVisible = true
},
showProgress() {
this.activeTab = 'progress'
this.dialogTitle = '进度检查'
this.dialogVisible = true
},
showSixSteps() {
this.activeTab = 'sixsteps'
this.dialogTitle = '六步法'
this.dialogVisible = true
}
}
}
</script>
<style scoped>
.bottom-left-panel {
position: absolute;
bottom: 20px;
left: 20px;
z-index: 100;
}
.panel-toggle {
width: 50px;
height: 50px;
background: rgba(0, 138, 255, 0.9);
border-radius: 50%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
color: white;
font-size: 20px;
box-shadow: 0 4px 12px rgba(0, 138, 255, 0.4);
transition: all 0.3s;
}
.panel-toggle:hover {
transform: scale(1.1);
box-shadow: 0 6px 16px rgba(0, 138, 255, 0.6);
}
.toggle-text {
font-size: 10px;
margin-top: 2px;
}
.panel-content {
position: absolute;
bottom: 60px;
left: 0;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
padding: 10px;
min-width: 150px;
opacity: 0;
transform: translateY(20px);
pointer-events: none;
transition: all 0.3s;
}
.panel-content.expanded {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.panel-item {
display: flex;
align-items: center;
padding: 12px 15px;
cursor: pointer;
border-radius: 8px;
transition: all 0.3s;
color: #333;
}
.panel-item:hover {
background: rgba(0, 138, 255, 0.1);
color: #008aff;
transform: translateX(5px);
}
.panel-item i {
font-size: 18px;
margin-right: 10px;
color: #008aff;
}
.panel-item span {
font-size: 14px;
font-weight: 500;
}
.dialog-content {
padding: 0;
}
.timeline-content h3,
.progress-content h3,
.sixsteps-content h3 {
margin: 0 0 15px 0;
font-size: 14px;
color: #333;
border-bottom: 2px solid #409EFF;
padding-bottom: 8px;
}
.timeline-list {
max-height: 280px;
overflow-y: auto;
}
.timeline-item {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
position: relative;
}
.timeline-item::before {
content: '';
position: absolute;
left: 5px;
top: 15px;
bottom: -12px;
width: 2px;
background: #e0e0e0;
}
.timeline-item:last-child::before {
display: none;
}
.timeline-dot {
width: 12px;
height: 12px;
background: #409EFF;
border-radius: 50%;
margin-right: 10px;
flex-shrink: 0;
box-shadow: 0 0 0 3px rgba(64, 158, 255, 0.2);
}
.timeline-info {
flex: 1;
}
.timeline-time {
font-size: 12px;
color: #409EFF;
font-weight: 600;
margin-bottom: 3px;
}
.timeline-event {
font-size: 12px;
color: #666;
}
.progress-list {
max-height: 280px;
overflow-y: auto;
}
.progress-item {
margin-bottom: 15px;
}
.progress-label {
font-size: 12px;
color: #333;
margin-bottom: 5px;
font-weight: 500;
}
.steps-container {
display: flex;
flex-direction: column;
gap: 8px;
}
.step-item {
display: flex;
align-items: flex-start;
padding: 10px;
background: #f5f7fa;
border-radius: 6px;
transition: all 0.3s;
border-left: 3px solid #dcdfe6;
}
.step-item.active {
background: rgba(64, 158, 255, 0.1);
border-left-color: #409EFF;
}
.step-item.completed {
background: rgba(103, 194, 58, 0.1);
border-left-color: #67c23a;
}
.step-number {
width: 24px;
height: 24px;
background: #dcdfe6;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 12px;
font-weight: 600;
color: #909399;
margin-right: 10px;
flex-shrink: 0;
}
.step-item.active .step-number {
background: #409EFF;
color: white;
}
.step-item.completed .step-number {
background: #67c23a;
color: white;
}
.step-content {
flex: 1;
}
.step-title {
font-size: 13px;
font-weight: 600;
color: #333;
margin-bottom: 3px;
}
.step-desc {
font-size: 11px;
color: #666;
}
</style>
<style>
.panel-dialog {
position: absolute !important;
left: 170px !important;
bottom: 20px !important;
top: auto !important;
margin: 0 !important;
max-height: 400px;
}
.panel-dialog .el-dialog__header {
padding: 12px 15px;
background: #409EFF;
color: white;
}
.panel-dialog .el-dialog__title {
color: white;
font-size: 14px;
font-weight: 600;
}
.panel-dialog .el-dialog__headerbtn .el-dialog__close {
color: white;
}
.panel-dialog .el-dialog__body {
padding: 15px;
max-height: 350px;
overflow-y: auto;
}
</style>

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

@ -280,6 +280,9 @@
@open-platform-dialog="openPlatformDialog"
/>
<!-- 左下角工具面板 -->
<bottom-left-panel />
<!-- 底部时间轴最初版本的样式- 蓝色主题 -->
<div
class="floating-timeline blue-theme"
@ -305,12 +308,34 @@
/>
</div>
<div class="timeline-marks">
<span class="mark">K-02:00</span>
<span class="mark">K-01:00</span>
<span class="mark current blue-mark">K时</span>
<span class="mark">K+01:00</span>
<span class="mark">K+02:00</span>
<div class="playback-controls">
<button
class="control-btn blue-control-btn"
@click="togglePlay"
:title="isPlaying ? '暂停' : '播放'"
>
<i :class="isPlaying ? 'el-icon-video-pause' : 'el-icon-video-play'"></i>
</button>
<div class="speed-control">
<button
class="control-btn blue-control-btn"
@click="decreaseSpeed"
:disabled="playbackSpeed <= 1"
title="减速"
>
<i class="el-icon-arrow-down"></i>
</button>
<span class="speed-text">{{ playbackSpeed }}x</span>
<button
class="control-btn blue-control-btn"
@click="increaseSpeed"
:disabled="playbackSpeed >= 25"
title="加速"
>
<i class="el-icon-arrow-up"></i>
</button>
</div>
</div>
</div>
@ -353,6 +378,7 @@ import RouteEditDialog from '@/views/dialogs/RouteEditDialog'
import WaypointEditDialog from '@/views/dialogs/WaypointEditDialog'
import LeftMenu from './LeftMenu'
import RightPanel from './RightPanel'
import BottomLeftPanel from './BottomLeftPanel'
export default {
name: 'MissionPlanningView',
components: {
@ -362,7 +388,8 @@ export default {
RouteEditDialog,
WaypointEditDialog,
LeftMenu,
RightPanel
RightPanel,
BottomLeftPanel
},
data() {
return {
@ -473,6 +500,9 @@ export default {
//
timeProgress: 45,
currentTime: 'K+01:15:30',
isPlaying: false,
playbackSpeed: 1,
playbackInterval: null,
//
userAvatar: 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
@ -490,6 +520,13 @@ export default {
//
setInterval(this.updateCombatTime, 1000);
},
beforeDestroy() {
//
if (this.playbackInterval) {
clearInterval(this.playbackInterval);
this.playbackInterval = null;
}
},
methods: {
// 线
showOnlineMembersDialog() {
@ -813,6 +850,68 @@ export default {
this.$message.info('隐藏推演时钟控制');
},
//
togglePlay() {
this.isPlaying = !this.isPlaying;
if (this.isPlaying) {
this.startPlayback();
this.$message.success('开始播放');
} else {
this.stopPlayback();
this.$message.info('暂停播放');
}
},
startPlayback() {
if (this.playbackInterval) {
clearInterval(this.playbackInterval);
}
this.playbackInterval = setInterval(() => {
this.timeProgress += this.playbackSpeed * 0.1;
if (this.timeProgress >= 100) {
this.timeProgress = 0;
}
this.updateTimeFromProgress();
}, 100);
},
stopPlayback() {
if (this.playbackInterval) {
clearInterval(this.playbackInterval);
this.playbackInterval = null;
}
},
increaseSpeed() {
if (this.playbackSpeed < 25) {
this.playbackSpeed++;
if (this.isPlaying) {
this.startPlayback();
}
}
},
decreaseSpeed() {
if (this.playbackSpeed > 1) {
this.playbackSpeed--;
if (this.isPlaying) {
this.startPlayback();
}
}
},
updateTimeFromProgress() {
const totalSeconds = Math.floor(this.timeProgress * 72);
const hours = Math.floor(totalSeconds / 3600) - 2;
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const sign = hours >= 0 ? '+' : '-';
const absHours = Math.abs(hours);
this.currentTime = `K${sign}${String(absHours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
},
//
play() {
this.$message.success('推演开始');
@ -1495,16 +1594,56 @@ background: url('~@/assets/map-background.png');
background-color: rgba(0, 138, 255, 0.8);
}
.timeline-marks {
.playback-controls {
display: flex;
justify-content: space-between;
font-size: 11px;
color: #666;
margin-top: 5px;
align-items: center;
gap: 10px;
}
.mark.current {
font-weight: bold;
.control-btn {
width: 26px;
height: 26px;
border: 1px solid rgba(0, 138, 255, 0.3);
background: rgba(255, 255, 255, 0.9);
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
color: #008aff;
}
.control-btn:hover:not(:disabled) {
background: rgba(0, 138, 255, 0.1);
border-color: rgba(0, 138, 255, 0.5);
}
.control-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.control-btn i {
font-size: 14px;
}
.speed-control {
display: flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid rgba(0, 138, 255, 0.3);
border-radius: 4px;
}
.speed-text {
font-size: 11px;
font-weight: 600;
color: #008aff;
min-width: 24px;
text-align: center;
}
.system-status {

Loading…
Cancel
Save