Browse Source

右侧树形菜单栏

master
ctw 2 months ago
parent
commit
1a4434d9f6
  1. 365
      ruoyi-ui/src/views/childRoom/RightPanel.vue
  2. 156
      ruoyi-ui/src/views/childRoom/index.vue

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

@ -26,98 +26,79 @@
新建方案
</el-button>
</div>
<div class="route-list">
<div class="tree-list">
<!-- 方案列表 -->
<div
v-for="plan in plans"
:key="plan.id"
class="route-item"
class="tree-item plan-item"
:class="{ selected: selectedPlanId === plan.id }"
@click="handleSelectPlan(plan)"
>
<i class="el-icon-folder-opened"></i>
<div class="route-info">
<div class="route-name">{{ plan.name }}</div>
<div class="route-meta">{{ plan.routes.length }}个航线</div>
</div>
<div class="route-actions">
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenPlanDialog(plan)"></i>
</div>
</div>
</div>
</div>
<div v-if="selectedPlanDetails" class="section">
<div class="section-header">
<div class="section-title">航线列表</div>
<el-button
type="primary"
size="mini"
@click="handleCreateRoute"
class="create-route-btn-new"
>
新建航线
</el-button>
</div>
<div class="route-list">
<div
v-for="route in routes"
:key="route.id"
class="route-item"
:class="{ 'active': activeRouteIds.includes(route.id) }"
@click="$emit('select-route', route)"
>
<i class="el-icon-map-location"></i>
<div class="route-info">
<div class="route-name">{{ route.name }}</div>
<div class="route-meta">{{ route.points }}个航点</div>
</div>
<el-tag
v-if="route.conflict"
size="mini"
type="danger"
class="conflict-tag"
>
冲突
</el-tag>
<div class="route-actions">
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenRouteDialog(route)"></i>
</div>
</div>
</div>
</div>
<div v-if="selectedRouteDetails" class="section">
<div class="section-title">航点列表</div>
<div class="waypoint-list">
<div
v-for="point in selectedRouteDetails.waypoints"
:key="point.name"
class="waypoint-item"
>
<i class="el-icon-location"></i>
<div class="waypoint-info">
<div class="waypoint-name">{{ point.name }}</div>
<div class="waypoint-meta">高度: {{ point.altitude }}m | 速度: {{ point.speed }}</div>
<div class="tree-item-header" @click="togglePlan(plan.id)">
<i :class="expandedPlans.includes(plan.id) ? 'el-icon-folder-opened' : 'el-icon-folder'" class="tree-icon"></i>
<div class="tree-item-info">
<div class="tree-item-name">{{ plan.name }}</div>
<div class="tree-item-meta">{{ routes.filter(r => r.scenarioId === plan.id).length }}个航线</div>
</div>
<div class="tree-item-actions">
<i class="el-icon-plus" title="新建航线" @click.stop="handleCreateRouteForPlan(plan)"></i>
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenPlanDialog(plan)"></i>
<i class="el-icon-delete" title="删除" @click.stop="handleDeletePlan(plan)"></i>
</div>
</div>
<div class="waypoint-actions">
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenWaypointDialog(point)"></i>
<i class="el-icon-delete" title="删除"></i>
<!-- 航线列表 -->
<div v-if="expandedPlans.includes(plan.id)" class="tree-children route-children">
<div
v-for="route in routes.filter(r => r.scenarioId === plan.id)"
:key="route.id"
class="tree-item route-item"
:class="getRouteClasses(route.id)"
>
<div class="tree-item-header" @click="toggleRoute(route.id)">
<i :class="expandedRoutes.includes(route.id) ? 'el-icon-map-location' : 'el-icon-map-location'" class="tree-icon"></i>
<div class="tree-item-info">
<div class="tree-item-name">{{ route.name }}</div>
<div class="tree-item-meta">{{ route.points }}个航点</div>
</div>
<el-tag
v-if="route.conflict"
size="mini"
type="danger"
class="conflict-tag"
>
冲突
</el-tag>
<div class="tree-item-actions">
<i class="el-icon-view" title="显示/隐藏" @click.stop="handleToggleRouteVisibility(route)"></i>
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenRouteDialog(route)"></i>
<i class="el-icon-delete" title="删除" @click.stop="handleDeleteRoute(route)"></i>
</div>
</div>
<!-- 航点列表 -->
<div v-if="expandedRoutes.includes(route.id)" class="tree-children waypoint-children">
<div
v-for="point in (expandedRoutes.includes(route.id) && route.waypoints ? route.waypoints : [])"
:key="point.name"
class="tree-item waypoint-item"
>
<div class="tree-item-header">
<i class="el-icon-location tree-icon"></i>
<div class="tree-item-info">
<div class="tree-item-name">{{ point.name }}</div>
<div class="tree-item-meta">高度: {{ point.altitude }}m | 速度: {{ point.speed }}</div>
</div>
<div class="tree-item-actions">
<i class="el-icon-edit" title="编辑" @click.stop="handleOpenWaypointDialog(point)"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="action-buttons">
<el-button type="primary" size="mini" icon="el-icon-circle-plus" class="blue-btn" @click="handleAddWaypoint">
添加航点
</el-button>
<el-button size="mini" class="blue-btn" @click="handleCancelRoute">
取消
</el-button>
</div>
</div>
<div v-if="activeTab === 'conflict'" class="tab-content conflict-content">
<div v-if="conflicts.length > 0" class="conflict-list">
<div
@ -162,7 +143,6 @@
</el-button>
</div>
</div>
<div v-if="activeTab === 'platform'" class="tab-content platform-content">
<div class="platform-categories">
<el-tabs v-model="activePlatformTab" type="card" size="mini" class="blue-tabs">
@ -187,7 +167,6 @@
</div>
</div>
</el-tab-pane>
<el-tab-pane label="海上" name="sea">
<div class="platform-list">
<div
@ -209,7 +188,6 @@
</div>
</div>
</el-tab-pane>
<el-tab-pane label="地面" name="ground">
<div class="platform-list">
<div
@ -301,10 +279,82 @@ export default {
},
data() {
return {
activePlatformTab: 'air'
activePlatformTab: 'air',
expandedPlans: [], //
expandedRoutes: [] // 线
}
},
watch: {
activeRouteIds: {
handler(newVal) {
console.log('activeRouteIds updated:', newVal);
},
deep: true
}
},
methods: {
// /
togglePlan(planId) {
const index = this.expandedPlans.indexOf(planId)
if (index > -1) {
// 线
this.expandedPlans.splice(index, 1)
const planRoutes = this.routes.filter(r => r.scenarioId === planId)
planRoutes.forEach(route => {
if (this.activeRouteIds.includes(route.id)) {
// 线
this.handleToggleRouteVisibility(route)
}
})
//
this.$emit('select-plan', { id: null })
} else {
//
this.expandedPlans.push(planId)
const plan = this.plans.find(p => p.id === planId)
if (plan) {
this.$emit('select-plan', plan)
}
// 线
const planRoutes = this.routes.filter(r => r.scenarioId === planId)
planRoutes.forEach(route => {
if (!this.activeRouteIds.includes(route.id)) {
this.handleSelectRoute(route)
}
})
}
},
// 线/
toggleRoute(routeId) {
const route = this.routes.find(r => r.id === routeId)
if (!route) return
const isRouteSelected = this.activeRouteIds.includes(routeId)
const isRouteExpanded = this.expandedRoutes.includes(routeId)
if (isRouteSelected) {
// 线
if (isRouteExpanded) {
// 线
const index = this.expandedRoutes.indexOf(routeId)
this.expandedRoutes.splice(index, 1)
} else {
// 线
this.expandedRoutes.push(routeId)
}
} else {
// 线线
this.handleSelectRoute(route)
//
this.$nextTick(() => {
if (!this.expandedRoutes.includes(routeId)) {
this.expandedRoutes.push(routeId)
}
})
}
},
handleHide() {
this.$emit('hide')
},
@ -314,6 +364,7 @@ export default {
},
handleSelectRoute(route) {
// 线
this.$emit('select-route', route)
},
@ -325,6 +376,14 @@ export default {
this.$emit('create-route')
},
handleCreateRouteForPlan(plan) {
this.$emit('create-route', plan)
},
handleDeletePlan(plan) {
//
},
handleOpenPlanDialog(plan) {
this.$emit('open-plan-dialog', plan)
},
@ -333,16 +392,27 @@ export default {
this.$emit('open-route-dialog', route)
},
handleOpenWaypointDialog(point) {
this.$emit('open-waypoint-dialog', point)
handleToggleRouteVisibility(route) {
this.$emit('toggle-route-visibility', route)
// 线
const routeIndex = this.expandedRoutes.indexOf(route.id)
if (routeIndex > -1) {
this.expandedRoutes.splice(routeIndex, 1)
}
},
handleAddWaypoint() {
this.$emit('add-waypoint')
getRouteClasses(routeId) {
return {
active: this.activeRouteIds.includes(routeId)
}
},
handleCancelRoute() {
this.$emit('cancel-route')
handleDeleteRoute(route) {
//
},
handleOpenWaypointDialog(point) {
this.$emit('open-waypoint-dialog', point)
},
handleViewConflict(conflict) {
@ -466,115 +536,90 @@ export default {
opacity: 0.9;
}
.route-list {
.tree-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.route-item {
.tree-item {
border-radius: 6px;
transition: all 0.3s;
border: 1px solid rgba(0, 138, 255, 0.1);
}
.tree-item-header {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: rgba(255, 255, 255, 0.8);
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
border: 1px solid rgba(0, 138, 255, 0.1);
position: relative;
border-radius: 6px;
}
.route-item:hover {
.tree-item-header:hover {
background: rgba(0, 138, 255, 0.1);
transform: translateX(-2px);
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15);
}
.route-item.active {
background: rgba(0, 138, 255, 0.15);
border-color: rgba(0, 138, 255, 0.3);
box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25);
.tree-item.plan-item .tree-item-header {
background: rgba(255, 255, 255, 0.9) !important;
}
.route-info {
flex: 1;
.tree-item.route-item .tree-item-header {
background: rgba(255, 255, 255, 0.8) !important;
}
.route-name {
font-size: 14px;
font-weight: 500;
color: #333;
.tree-item.route-item:not(.active) .tree-item-header {
background: rgba(255, 255, 255, 0.8) !important;
}
.route-meta {
font-size: 12px;
color: #999;
}
.route-actions {
display: flex;
gap: 8px;
}
.route-actions i {
cursor: pointer;
color: #008aff;
font-size: 14px;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
.tree-item.waypoint-item .tree-item-header {
background: rgba(224, 238, 255, 0.8);
}
.route-actions i:hover {
background: rgba(0, 138, 255, 0.1);
transform: scale(1.2);
}
.waypoint-list {
display: flex;
flex-direction: column;
gap: 8px;
.tree-item.active .tree-item-header {
background: rgba(0, 138, 255, 0.15) !important;
border-color: rgba(0, 138, 255, 0.3);
box-shadow: 0 2px 10px rgba(0, 138, 255, 0.25);
}
.waypoint-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px;
background: rgba(255, 255, 255, 0.8);
border-radius: 6px;
transition: all 0.3s;
border: 1px solid rgba(0, 138, 255, 0.1);
.tree-item.selected .tree-item-header {
background: rgba(0, 138, 255, 0.1) !important;
border-color: rgba(0, 138, 255, 0.2);
}
.waypoint-item:hover {
background: rgba(0, 138, 255, 0.1);
transform: translateX(-2px);
box-shadow: 0 2px 8px rgba(0, 138, 255, 0.15);
.tree-icon {
font-size: 16px;
color: #008aff;
flex-shrink: 0;
}
.waypoint-info {
.tree-item-info {
flex: 1;
}
.waypoint-name {
.tree-item-name {
font-size: 14px;
font-weight: 500;
color: #333;
}
.waypoint-meta {
.tree-item-meta {
font-size: 12px;
color: #999;
}
.waypoint-actions {
.tree-item-actions {
display: flex;
gap: 8px;
flex-shrink: 0;
}
.waypoint-actions i {
.tree-item-actions i {
cursor: pointer;
color: #008aff;
font-size: 14px;
@ -583,11 +628,27 @@ export default {
transition: all 0.2s;
}
.waypoint-actions i:hover {
.tree-item-actions i:hover {
background: rgba(0, 138, 255, 0.1);
transform: scale(1.2);
}
.tree-children {
margin-left: 20px;
margin-top: 4px;
display: flex;
flex-direction: column;
gap: 4px;
}
.route-children {
margin-left: 25px;
}
.waypoint-children {
margin-left: 50px;
}
.action-buttons {
display: flex;
gap: 10px;

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

@ -106,6 +106,7 @@
<!-- 右侧实体列表浮动- 蓝色主题 -->
<right-panel
ref="rightPanel"
:is-hidden="isRightPanelHidden"
:active-tab="activeRightTab"
:plans="plans"
@ -113,7 +114,7 @@
:selected-plan-details="selectedPlanDetails"
:selected-route-id="selectedRouteId"
:routes="routes"
:active-route-ids="activeRouteIds"
:activeRouteIds="activeRouteIds"
:selected-route-details="selectedRouteDetails"
:conflicts="conflicts"
:conflict-count="conflictCount"
@ -130,6 +131,7 @@
@open-waypoint-dialog="openWaypointDialog"
@add-waypoint="addWaypoint"
@cancel-route="cancelRoute"
@toggle-route-visibility="toggleRouteVisibility"
@view-conflict="viewConflict"
@resolve-conflict="resolveConflict"
@run-conflict-check="runConflictCheck"
@ -467,8 +469,6 @@ export default {
this.isMenuHidden = true;
//
this.isRightPanelHidden = true;
//
this.selectPlan(this.plans[0]);
//
this.updateTime();
@ -538,7 +538,13 @@ export default {
}
},
// 线
createRoute() {
createRoute(plan) {
// 线
if (plan) {
this.selectedPlanId = plan.id;
this.selectedPlanDetails = plan;
}
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.startMissionRouteDrawing();
this.$message.success('进入航线规划模式');
@ -547,7 +553,7 @@ export default {
},
/** 从数据库拉取最新的航线列表数据 */
async getList() {
const query = { scenarioId: 1 };
const query = {}; // scenarioId线
try {
const response = await listRoutes(query);
if (response.code === 200) {
@ -555,7 +561,9 @@ export default {
id: item.id,
name: item.callSign,
points: item.waypoints ? item.waypoints.length : 0,
conflict: false
waypoints: item.waypoints || [],
conflict: false,
scenarioId: item.scenarioId
}));
}
} catch (error) {
@ -581,7 +589,7 @@ export default {
// Routes
const routeData = {
callSign: this.newRouteName,
scenarioId: 1, // ID
scenarioId: this.selectedPlanId || 1, // 使ID1
platformId: 1,
attributes: "{}",
waypoints: this.tempMapPoints.map((p, index) => ({
@ -1139,56 +1147,92 @@ export default {
const minutes = (val % 4) * 15;
return `K+${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`;
},
createPlan() {
this.$message.success('创建方案');
},
openPlanDialog(plan) {
this.$message.success('打开方案编辑对话框');
},
selectPlan(plan) {
this.selectedPlanId = plan.id;
this.selectedPlanDetails = plan;
if (plan && plan.id) {
this.selectedPlanId = plan.id;
this.selectedPlanDetails = plan;
} else {
this.selectedPlanId = null;
this.selectedPlanDetails = null;
}
this.selectedRouteId = null;
this.selectedRouteDetails = null;
},
/** 切换航线:实现多选/开关逻辑 */
/** 切换航线:实现复杂的交互逻辑 */
async selectRoute(route) {
const index = this.activeRouteIds.indexOf(route.id);
const isRouteExpanded = this.$refs.rightPanel ? this.$refs.rightPanel.expandedRoutes.includes(route.id) : false;
// 线
if (index > -1) {
this.activeRouteIds.splice(index, 1);
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.removeRouteById(route.id);
}
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
if (this.activeRouteIds.length > 0) {
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1];
try {
const res = await getRoutes(lastId);
if (res.code === 200 && res.data) {
this.selectedRouteDetails = {
id: res.data.id,
name: res.data.callSign,
waypoints: res.data.waypoints || []
};
if (isRouteExpanded) {
// 线
// RightPanel toggleRoute
return;
} else {
// 线
this.activeRouteIds.splice(index, 1);
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.removeRouteById(route.id);
}
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
if (this.activeRouteIds.length > 0) {
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1];
try {
const res = await getRoutes(lastId);
if (res.code === 200 && res.data) {
this.selectedRouteId = res.data.id;
this.selectedRouteDetails = {
id: res.data.id,
name: res.data.callSign,
waypoints: res.data.waypoints || []
};
}
} catch (e) {
console.error("回显剩余航线失败", e);
}
} catch (e) {
console.error("回显剩余航线失败", e);
} else {
this.selectedRouteId = null;
this.selectedRouteDetails = null;
}
} else {
this.selectedRouteDetails = null;
}
this.$message.info(`已取消航线: ${route.name}`);
return;
}
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
};
this.selectedRouteId = fullRouteData.id;
this.selectedRouteDetails = {
id: fullRouteData.id,
name: fullRouteData.callSign,
waypoints: waypoints
};
// routes 线 waypoints
const routeIndex = this.routes.findIndex(r => r.id === route.id);
if (routeIndex > -1) {
this.$set(this.routes, routeIndex, {
...this.routes[routeIndex],
waypoints: waypoints
});
}
if (waypoints.length > 0) {
//
if (this.$refs.cesiumMap) {
@ -1229,6 +1273,44 @@ export default {
this.$message.info('已清空所有选中航线');
},
// 线/
toggleRouteVisibility(route) {
const index = this.activeRouteIds.indexOf(route.id);
if (index > -1) {
// 线
// 使 Vue
this.activeRouteIds = this.activeRouteIds.filter(id => id !== route.id);
if (this.$refs.cesiumMap) {
this.$refs.cesiumMap.removeRouteById(route.id);
}
if (this.selectedRouteDetails && this.selectedRouteDetails.id === route.id) {
if (this.activeRouteIds.length > 0) {
const lastId = this.activeRouteIds[this.activeRouteIds.length - 1];
getRoutes(lastId).then(res => {
if (res.code === 200 && res.data) {
this.selectedRouteId = res.data.id;
this.selectedRouteDetails = {
id: res.data.id,
name: res.data.callSign,
waypoints: res.data.waypoints || []
};
}
}).catch(e => {
console.error("获取航线详情失败", e);
});
} else {
this.selectedRouteId = null;
this.selectedRouteDetails = null;
}
}
this.$message.info(`已隐藏航线: ${route.name}`);
} else {
// 线
this.selectRoute(route);
}
},
//
runConflictCheck() {
this.conflictCount = 2;

Loading…
Cancel
Save