支持在通信节点右键菜单中挂载行为树
This commit is contained in:
@@ -11,6 +11,7 @@ import { HttpRequestClient } from '@/utils/request';
|
||||
import type { Scenario, ScenarioDetailsResponse, ScenarioPageableResponse, ScenarioRequest } from './types';
|
||||
import type { PlatformWithComponentsResponse } from '../types';
|
||||
import type { BasicResponse } from '@/types';
|
||||
import type { BehaviorTree } from '../designer/tree';
|
||||
|
||||
const req = HttpRequestClient.create<BasicResponse>({
|
||||
baseURL: '/api',
|
||||
@@ -34,4 +35,14 @@ export const findPlatformWithComponents = (id: number): Promise<PlatformWithComp
|
||||
|
||||
export const saveScenario = (scenario: Scenario): Promise<BasicResponse> => {
|
||||
return req.postJson<BasicResponse>(`/system/scene/saveSceneConfig`,scenario);
|
||||
};
|
||||
|
||||
// 获取场景下的所有行为树列表
|
||||
export const getAllBehaviorTreesBySceneId = (sceneId: number): Promise<{ code: number; msg: string; data: BehaviorTree[] }> => {
|
||||
return req.get<{ code: number; msg: string; data: BehaviorTree[] }>(`/system/scene/getAllTree/${sceneId}`);
|
||||
};
|
||||
|
||||
// 更新行为树(挂载到平台)
|
||||
export const updateBehaviorTree = (behaviorTree: BehaviorTree): Promise<BasicResponse> => {
|
||||
return req.putJson<BasicResponse>(`/system/behaviortree`, behaviorTree);
|
||||
};
|
||||
@@ -64,7 +64,7 @@ import { createGraphScenarioElement, createGraphTaskElementFromScenario } from '
|
||||
|
||||
import PlatformCard from './platform-card.vue';
|
||||
import NodesCard from './nodes-card.vue';
|
||||
import { findOneScenarioById, saveScenario } from './api';
|
||||
import { findOneScenarioById, saveScenario, getAllBehaviorTreesBySceneId } from './api';
|
||||
import { resolveConnectionRelation } from './relation';
|
||||
|
||||
const TeleportContainer = defineComponent(getTeleport());
|
||||
@@ -211,7 +211,7 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelect = (scenario: Scenario) => {
|
||||
const handleSelect = async (scenario: Scenario) => {
|
||||
let nodeGraph: GraphContainer | null = null;
|
||||
try {
|
||||
nodeGraph = JSON.parse(scenario.communicationGraph as unknown as string) as unknown as GraphContainer;
|
||||
@@ -230,6 +230,27 @@ export default defineComponent({
|
||||
relations: []
|
||||
};
|
||||
currentScenarioEditing.value = true;
|
||||
|
||||
// 将场景ID存储到graph对象中,供子组件访问
|
||||
if (graph.value) {
|
||||
(graph.value as any).currentScenario = currentScenario.value;
|
||||
|
||||
// 加载该场景下的行为树列表
|
||||
try {
|
||||
const response = await getAllBehaviorTreesBySceneId(scenario.id);
|
||||
if (response.code === 200 && response.data) {
|
||||
(graph.value as any).behaviorTrees = response.data;
|
||||
console.log(`加载场景${scenario.id}的行为树列表:`, response.data.length, '个');
|
||||
} else {
|
||||
(graph.value as any).behaviorTrees = [];
|
||||
console.warn('获取行为树列表失败:', response.msg);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取行为树列表失败:', error);
|
||||
(graph.value as any).behaviorTrees = [];
|
||||
}
|
||||
}
|
||||
|
||||
createElements();
|
||||
};
|
||||
|
||||
@@ -277,6 +298,12 @@ export default defineComponent({
|
||||
nodes: [],
|
||||
},
|
||||
};
|
||||
|
||||
// 清空graph中的场景信息
|
||||
if (graph.value) {
|
||||
(graph.value as any).currentScenario = null;
|
||||
}
|
||||
|
||||
currentGraph.value = {
|
||||
edges: [],
|
||||
nodes: [],
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<a-dropdown :trigger="['contextmenu']" @openChange="handleVisibleChange">
|
||||
<a-dropdown
|
||||
:trigger="['contextmenu']"
|
||||
:getPopupContainer="getPopupContainer"
|
||||
@openChange="handleVisibleChange"
|
||||
>
|
||||
<a-card
|
||||
:class="[
|
||||
'ks-scenario-node',
|
||||
@@ -55,6 +59,27 @@
|
||||
|
||||
<template #overlay>
|
||||
<a-menu @click="handleMenuClick">
|
||||
<a-sub-menu key="mount">
|
||||
<template #icon>
|
||||
<LinkOutlined />
|
||||
</template>
|
||||
<template #title>挂载</template>
|
||||
<a-menu-item
|
||||
v-for="tree in availableTrees"
|
||||
:key="`tree-${tree.id}`"
|
||||
:disabled="isTreeMounted(tree.id)"
|
||||
@click="() => handleMountTree(tree)"
|
||||
>
|
||||
<template #icon>
|
||||
<CheckOutlined v-if="isTreeMounted(tree.id)" />
|
||||
</template>
|
||||
{{ tree.name }}
|
||||
</a-menu-item>
|
||||
<a-menu-item v-if="availableTrees.length === 0" disabled>
|
||||
暂无可用行为树
|
||||
</a-menu-item>
|
||||
</a-sub-menu>
|
||||
<a-menu-divider />
|
||||
<a-menu-item key="delete">
|
||||
<template #icon>
|
||||
<DeleteOutlined />
|
||||
@@ -70,15 +95,20 @@
|
||||
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { elementProps, type ModelElement } from '../graph';
|
||||
|
||||
import { DeleteOutlined, SettingOutlined } from '@ant-design/icons-vue';
|
||||
import { DeleteOutlined, LinkOutlined, CheckOutlined, SettingOutlined } from '@ant-design/icons-vue';
|
||||
import type { Graph } from '@antv/x6';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { substring } from '@/utils/strings';
|
||||
import { getAllBehaviorTreesBySceneId, updateBehaviorTree } from './api';
|
||||
import type { BehaviorTree } from '../designer/tree';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ModelElement',
|
||||
components: {
|
||||
SettingOutlined,
|
||||
DeleteOutlined,
|
||||
LinkOutlined,
|
||||
CheckOutlined,
|
||||
},
|
||||
props: elementProps,
|
||||
setup(_props) {
|
||||
@@ -87,6 +117,17 @@ export default defineComponent({
|
||||
);
|
||||
const updateKey = ref(0);
|
||||
const isMenuVisible = ref(false);
|
||||
|
||||
// 挂载行为树相关状态
|
||||
const availableTrees = ref<BehaviorTree[]>([]);
|
||||
|
||||
// 获取 popup 容器
|
||||
const getPopupContainer = () => {
|
||||
if (typeof document !== 'undefined') {
|
||||
return document.body;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// 获取画布实例
|
||||
const getGraph = (): Graph | null => {
|
||||
@@ -103,8 +144,35 @@ export default defineComponent({
|
||||
updateKey.value++;
|
||||
};
|
||||
|
||||
// 获取行为树名称
|
||||
const getBehaviorTreeName = (treeId: number | undefined | null): string => {
|
||||
if (!treeId) return '';
|
||||
const tree = availableTrees.value.find(t => t.id === treeId);
|
||||
return tree?.name || `行为树${treeId}`;
|
||||
};
|
||||
|
||||
// 判断行为树是否已挂载到当前节点
|
||||
const isTreeMounted = (treeId: number): boolean => {
|
||||
if (!element.value) return false;
|
||||
const currentTreeId = (element.value as any).behaviorTreeId as number | undefined;
|
||||
return currentTreeId === treeId;
|
||||
};
|
||||
|
||||
// 处理挂载行为树 - 当右键菜单打开时从graph中读取已缓存的行为树列表
|
||||
const handleVisibleChange = (visible: boolean) => {
|
||||
isMenuVisible.value = visible;
|
||||
|
||||
if (!visible || !element.value) return;
|
||||
|
||||
// 从graph对象中获取已缓存的行为树列表
|
||||
const graph = _props.graph as any;
|
||||
if (graph?.behaviorTrees) {
|
||||
availableTrees.value = graph.behaviorTrees;
|
||||
console.log('从缓存中读取行为树列表:', availableTrees.value.length, '个');
|
||||
} else {
|
||||
availableTrees.value = [];
|
||||
console.warn('未找到缓存的行为树列表');
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuClick = ({ key }: { key: string }) => {
|
||||
@@ -113,6 +181,37 @@ export default defineComponent({
|
||||
}
|
||||
};
|
||||
|
||||
// 处理挂载具体的行为树
|
||||
const handleMountTree = async (tree: BehaviorTree) => {
|
||||
if (!element.value) return;
|
||||
|
||||
try {
|
||||
// 更新节点的behaviorTreeId属性
|
||||
const updatedElement = { ...(element.value as any), behaviorTreeId: tree.id };
|
||||
|
||||
// 调用后端API更新行为树(将platformId关联到该平台)
|
||||
const platformIdValue = (element.value as any).platformId as number | undefined;
|
||||
const treeToUpdate = {
|
||||
...tree,
|
||||
platformId: platformIdValue ?? null
|
||||
};
|
||||
|
||||
const updateResponse = await updateBehaviorTree(treeToUpdate);
|
||||
if (updateResponse.code === 200) {
|
||||
// 更新本地节点数据
|
||||
if (_props.node) {
|
||||
_props.node.setData(updatedElement);
|
||||
}
|
||||
message.success(`已成功挂载行为树: ${tree.name}`);
|
||||
} else {
|
||||
message.error(updateResponse.msg || '挂载失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('挂载行为树失败:', error);
|
||||
message.error('挂载行为树失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
if (!_props.node) return;
|
||||
|
||||
@@ -134,10 +233,37 @@ export default defineComponent({
|
||||
|
||||
onMounted(() => {
|
||||
_props.node?.on('change:data', handleDataChange);
|
||||
|
||||
// 监听画布各种事件,操作时立即关闭菜单
|
||||
const graph = getGraph();
|
||||
if (graph) {
|
||||
const closeMenuHandler = () => {
|
||||
if (isMenuVisible.value) {
|
||||
isMenuVisible.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 监听多种可能导致菜单位置变化的事件
|
||||
graph.on('pan', closeMenuHandler);
|
||||
graph.on('translate', closeMenuHandler);
|
||||
graph.on('scale', closeMenuHandler);
|
||||
graph.on('zoom', closeMenuHandler);
|
||||
graph.on('resize', closeMenuHandler);
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
_props.node?.off('change:data', handleDataChange);
|
||||
|
||||
// 清理事件监听
|
||||
const graph = getGraph();
|
||||
if (graph) {
|
||||
graph.off('pan');
|
||||
graph.off('translate');
|
||||
graph.off('scale');
|
||||
graph.off('zoom');
|
||||
graph.off('resize');
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -145,6 +271,11 @@ export default defineComponent({
|
||||
substring,
|
||||
handleMenuClick,
|
||||
handleVisibleChange,
|
||||
availableTrees,
|
||||
getBehaviorTreeName,
|
||||
isTreeMounted,
|
||||
handleMountTree,
|
||||
getPopupContainer,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user