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

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

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

Loading…
Cancel
Save