Initial commit

This commit is contained in:
libertyspy
2026-02-08 16:44:50 +08:00
parent c9d5c38b52
commit a246c88341
6 changed files with 59 additions and 216 deletions

View File

@@ -8,23 +8,24 @@
*/
import { HttpRequestClient } from '@/utils/request';
import type { NodeTemplateQuery, NodeTemplatesResponse, TreeModelDetailsResponse, TreeModelGraph, TreeModelsResponse } from './types';
import type { ApiPaginationQuery, BasicResponse } from '@/types';
import type { BehaviorTree, BehaviorTreeDetailsResponse, BehaviorTreePageResponse, NodeTemplatesResponse, TreeModelDetailsResponse, TreeModelGraph } from './types';
import type { BasicResponse } from '@/types';
const req = HttpRequestClient.create<BasicResponse>({
baseURL: '/bhtree/api',
baseURL: '/api',
});
export const findTemplatesByQuery = (query: Partial<NodeTemplateQuery>): Promise<NodeTemplatesResponse> => {
return req.postJson('/node-templates', query);
export const findNodeTemplates = (): Promise<NodeTemplatesResponse> => {
return req.get('/system/nodetemplate/listAll');
};
export const findTreesByQuery = (query: Partial<ApiPaginationQuery>): Promise<TreeModelsResponse> => {
return req.postJson<TreeModelsResponse>('/behavior-trees', query);
export const findTreesByQuery = (query: Partial<BehaviorTree> = {}): Promise<BehaviorTreePageResponse> => {
return req.get<BehaviorTreePageResponse>('/system/behaviortree/list', query);
};
export const findOneTreeById = (id: number): Promise<TreeModelDetailsResponse> => {
return req.get(`/behavior-trees/${id}`);
export const findOneTreeById = (id: number): Promise<BehaviorTreeDetailsResponse> => {
return req.get(`/system/behaviortree/${id}`);
};
export const updateTree = (rt: Partial<TreeModelGraph>): Promise<BasicResponse> => {

View File

@@ -17,7 +17,7 @@
<div class="ks-model-builder-content">
<div class="ks-model-builder-actions">
<a-space>
<a-tooltip v-if="graph && currentTreeModelGraph" placement="top">
<a-tooltip v-if="graph && currentNodeGraph" placement="top">
<template #title>
保存
</template>
@@ -68,7 +68,7 @@ import Header from './header.vue';
import Properties from './properties.vue';
import { useGraphCanvas } from './builder/hooks';
import { registerNodeElement } from './builder/register';
import type { NodeGraph, NodeTemplate, SettingTaskNodeElement, TreeModel, TreeModelGraph } from './types';
import type { BehaviorTree, NodeGraph, NodeTemplate, SettingTaskNodeElement, TreeModel, TreeModelGraph } from './types';
import { createTree, findOneTreeById, updateTree } from './api';
import { createTaskNodeElement, createTaskNodeElementFromTemplate, hasElements, hasRootElementNode, resolveNodeGraph } from './utils/node';
import TressCard from './trees-card.vue';
@@ -97,7 +97,7 @@ export default defineComponent({
const currentZoom = ref<number>(1);
const draggedNodeData = ref<NodeTemplate | null>(null);
const isDraggingOver = ref(false);
const currentTreeModelGraph = ref<TreeModelGraph | null>(null);
const currentNodeGraph = ref<NodeGraph | null>(null);
const selectedModelNode = ref<Node<NodeProperties> | null>(null);
const selectedNodeTaskElement = ref<SettingTaskNodeElement | null>(null);
const changed = ref<boolean>(false)
@@ -151,7 +151,7 @@ export default defineComponent({
safeStopPropagation(e);
isDraggingOver.value = false;
if (!currentTreeModelGraph.value) {
if (!currentNodeGraph.value) {
message.error('请先选择或者创建行为树.');
return;
}
@@ -201,19 +201,19 @@ export default defineComponent({
};
const createElements = () => {
if(graph.value && currentTreeModelGraph.value){
if(graph.value && currentNodeGraph.value){
graph.value.clearCells();
setTimeout(()=> {
nextTick(()=> {
try{
if (currentTreeModelGraph.value && !currentTreeModelGraph.value?.graph){
currentTreeModelGraph.value.graph = {
if (currentNodeGraph.value && !currentNodeGraph.value?.graph){
currentNodeGraph.value.graph = {
nodes: [],
edges: [],
}
}
const nodes = currentTreeModelGraph.value?.graph?.nodes ?? [];
const edges = currentTreeModelGraph.value?.graph?.edges ?? [];
const nodes = currentNodeGraph.value?.graph?.nodes ?? [];
const edges = currentNodeGraph.value?.graph?.edges ?? [];
nodes.forEach(n=> {
const node = createTaskNodeElement(n);
@@ -297,7 +297,7 @@ export default defineComponent({
console.error('handleSelectTree', treeModel);
findOneTreeById(treeModel.id).then(r => {
if (r.data) {
currentTreeModelGraph.value = r.data;
currentNodeGraph.value = r.data;
createElements();
} else {
message.error(r.message ?? '行为树不存在.');
@@ -308,16 +308,16 @@ export default defineComponent({
const handleSave = () => {
const graphData: NodeGraph = resolveNodeGraph(graph.value as Graph);
console.info('handleSave', graphData);
if (!currentTreeModelGraph.value) {
if (!currentNodeGraph.value) {
message.error('当前决策树不存在');
return;
}
const newModel: TreeModelGraph = {
...currentTreeModelGraph.value,
...currentNodeGraph.value,
graph: graphData,
};
let res = null;
if (currentTreeModelGraph.value.id > 0) {
if (currentNodeGraph.value.id > 0) {
res = createTree(newModel);
} else {
res = updateTree(newModel);
@@ -351,7 +351,7 @@ export default defineComponent({
});
return {
currentTreeModelGraph,
currentNodeGraph,
selectedNodeTaskElement,
selectedModelNode,
graph,

View File

@@ -66,60 +66,13 @@
</a-collapse-panel>
</a-collapse>
<!-- <a-card class="ks-model-builder-card">-->
<!-- <template #title>-->
<!-- <span class="ks-model-builder-title-icon icon-model"></span>控制节点-->
<!-- </template>-->
<!-- <div-->
<!-- v-for="nm in controlTemplates"-->
<!-- :key="nm.id"-->
<!-- :data-type="nm.type"-->
<!-- class="ks-model-drag-item"-->
<!-- >-->
<!-- <img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''"/>-->
<!-- <span class="desc">{{ nm.name }}</span>-->
<!-- </div>-->
<!-- </a-card>-->
<!-- <a-card class="ks-model-builder-card">-->
<!-- <template #title>-->
<!-- <span class="ks-model-builder-title-icon icon-model"></span>条件节点-->
<!-- </template>-->
<!-- <div-->
<!-- v-for="nm in conditionTemplates"-->
<!-- :key="nm.id"-->
<!-- :data-type="nm.type"-->
<!-- class="ks-model-drag-item"-->
<!-- >-->
<!-- <img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''"/>-->
<!-- <span class="desc">{{ nm.name }}</span>-->
<!-- </div>-->
<!-- </a-card>-->
<!-- <a-card class="ks-model-builder-card">-->
<!-- <template #title>-->
<!-- <span class="ks-model-builder-title-icon icon-model"></span>行为节点-->
<!-- </template>-->
<!-- <div-->
<!-- v-for="nm in actionsTemplates"-->
<!-- :key="nm.id"-->
<!-- :data-type="nm.type"-->
<!-- class="ks-model-drag-item"-->
<!-- >-->
<!-- <img class="icon" src="@/assets/icons/model-4.svg" :alt="nm.name ?? ''"/>-->
<!-- <span class="desc">{{ nm.name }}</span>-->
<!-- </div>-->
<!-- </a-card>-->
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { defaultNodeTemplateData, defaultNodeTemplateQuery } from './constants';
import type { NodeTemplate, NodeTemplateData, NodeTemplateQuery } from './types';
import { findTemplatesByQuery } from './api';
import type { NodeTemplate } from './types';
import { findNodeTemplates } from './api';
import { safePreventDefault, safeStopPropagation } from '@/utils/event';
export default defineComponent({
@@ -127,8 +80,7 @@ export default defineComponent({
setup(_props, { emit }) {
const activeKey = ref<number>(1);
const templateData = ref<NodeTemplateData>({ ...defaultNodeTemplateData });
const templateQuery = ref<NodeTemplateQuery>({ ...defaultNodeTemplateQuery });
const templateData = ref<NodeTemplate[]>([]);
const isDraggingOver = ref(false);
const draggedNodeData = ref<NodeTemplate | null>(null);
@@ -144,14 +96,12 @@ export default defineComponent({
conditionTemplates.value = [];
actionsTemplates.value = [];
findTemplatesByQuery(templateQuery.value).then(r => {
findNodeTemplates().then(r => {
templateData.value = r.data;
if (r.data.templates) {
r.data.templates.forEach(tpl => {
if (r.data) {
r.data.forEach(tpl => {
if (tpl.type === 'action') {
if(tpl.parameter_defs && tpl.parameter_defs.length>0){
actionsTemplates.value.push(tpl);
}
actionsTemplates.value.push(tpl);
} else if (tpl.type === 'parallel' || tpl.type === 'sequence' || tpl.type === 'precondition') {
conditionTemplates.value.push(tpl);
} else {

View File

@@ -1,11 +1,10 @@
<template>
<a-collapse v-model:activeKey="activeKey" :accordion="false">
<a-collapse-panel key="1">
<template #header>
<span class="ks-model-builder-title-icon icon-model"></span>我的行为树
</template>
<a-list size="small" :data-source="treeModelsData.trees || []" style="min-height: 25vh">
<a-list size="small" :data-source="behaviorTrees || []" style="min-height: 25vh">
<template #renderItem="{ item }">
<a-tooltip placement="right">
<template #title>
@@ -16,60 +15,15 @@
</a-list-item>
</a-tooltip>
</template>
<!-- <template #footer>-->
<!-- <div>Footer</div>-->
<!-- </template>-->
</a-list>
</a-collapse-panel>
</a-collapse>
<!-- <a-card class="ks-model-builder-card tress-list-card">-->
<!-- <template #title>-->
<!-- <span class="ks-model-builder-title-icon icon-model"></span>我的行为树-->
<!-- </template>-->
<!--&lt;!&ndash; <template #extra>&ndash;&gt;-->
<!--&lt;!&ndash; <a-tooltip placement="right">&ndash;&gt;-->
<!--&lt;!&ndash; <template #title>&ndash;&gt;-->
<!--&lt;!&ndash; 创建行为树&ndash;&gt;-->
<!--&lt;!&ndash; </template>&ndash;&gt;-->
<!--&lt;!&ndash; <PlusOutlined class="create-tree-icon"></PlusOutlined>&ndash;&gt;-->
<!--&lt;!&ndash; </a-tooltip>&ndash;&gt;-->
<!--&lt;!&ndash; </template>&ndash;&gt;-->
<!-- <a-list size="small" :data-source="treeModelsData.trees || []">-->
<!-- <template #renderItem="{ item }">-->
<!-- <a-tooltip placement="right">-->
<!-- <template #title>-->
<!-- {{ item.description }}-->
<!-- </template>-->
<!-- <a-list-item @click="()=> handleSelect(item)">-->
<!-- {{ item.name }}-->
<!-- </a-list-item>-->
<!-- </a-tooltip>-->
<!-- </template>-->
<!--&lt;!&ndash; <template #footer>&ndash;&gt;-->
<!--&lt;!&ndash; <div>Footer</div>&ndash;&gt;-->
<!--&lt;!&ndash; </template>&ndash;&gt;-->
<!-- </a-list>-->
<!-- <a-table-->
<!-- size="small"-->
<!-- :data-source="treeModelsData.trees || []"-->
<!-- :columns="columns"-->
<!-- :customRow="customRow"-->
<!-- :row-key="(record: any) => record.id">-->
<!-- <template #bodyCell="{ text }">-->
<!-- {{ text }}-->
<!-- </template>-->
<!-- </a-table>-->
<!-- </a-card>-->
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { defaultPaginationRequest, defaultTreeModelsData } from './constants';
import { CheckOutlined, PlusOutlined } from '@ant-design/icons-vue';
import type { ApiPaginationQuery } from '@/types';
import type { TreeModel, TreeModelsData } from './types';
import { PlusOutlined } from '@ant-design/icons-vue';
import type { BehaviorTree } from './types';
import { findTreesByQuery } from './api';
export default defineComponent({
@@ -78,12 +32,12 @@ export default defineComponent({
PlusOutlined
},
setup(_props, { emit }) {
const treeModelsData = ref<TreeModelsData>({ ...defaultTreeModelsData });
const treeModelsQuery = ref<ApiPaginationQuery>({ ...defaultPaginationRequest });
const behaviorTrees = ref<BehaviorTree[]>([]);
const behaviorTreeQuery = ref<Partial<BehaviorTree>>({});
const activeKey = ref<number>(1)
const loadTress = () => {
findTreesByQuery(treeModelsQuery.value).then(r => {
treeModelsData.value = r.data;
findTreesByQuery(behaviorTreeQuery.value).then(r => {
behaviorTrees.value = r.rows;
});
};
@@ -94,11 +48,11 @@ export default defineComponent({
},
];
const handleSelect = (record: TreeModel) => {
const handleSelect = (record: BehaviorTree) => {
emit('select-tree', record);
}
const customRow = (record: TreeModel) => {
const customRow = (record: BehaviorTree) => {
return {
onClick: (event: any) => {
emit('select-tree', record, event);
@@ -112,8 +66,8 @@ export default defineComponent({
return {
activeKey,
treeModelsData,
treeModelsQuery,
behaviorTrees,
behaviorTreeQuery,
loadTress,
columns,
customRow,

View File

@@ -7,16 +7,16 @@
* that was distributed with this source code.
*/
import type { ApiResponse, NullableString ,ApiPaginationQuery} from '@/types';
import type { NodeSetting } from './parameter';
import type { ApiDataResponse, ApiPaginationQuery, NullableString } from '@/types';
export interface NodeTemplate {
id: number;
name: NullableString;
type: NullableString;
english_name: NullableString;
logicHandler: NullableString;
description: NullableString;
parameter_defs: NodeSetting[];
templeteType: NullableString;
englishName: NullableString;
}
export interface NodeTemplateData {
@@ -29,7 +29,7 @@ export interface NodeTemplateQuery extends ApiPaginationQuery {
type: NullableString;
}
export interface NodeTemplatesResponse extends ApiResponse<NodeTemplateData> {
export interface NodeTemplatesResponse extends ApiDataResponse<NodeTemplate[]> {
}

View File

@@ -7,86 +7,24 @@
* that was distributed with this source code.
*/
import type { ApiErrors, ApiPagination, ApiResponse, NullableString } from '@/types';
import type { NodeGraph } from './node';
import type { NullableString, PageableResponse, ApiDataResponse } from '@/types';
export type TreeModelStatus = string | 'active'
export interface BehaviorTree {
export type TreeNodeType = 'selector' | string;
// 获取树列表
export interface TreeModel {
id: number;
name: NullableString;
english_name: NullableString;
description: NullableString;
created_at: NullableString;
updated_at: NullableString;
node_count: number;
status: TreeModelStatus;
id: number,
name: NullableString,
description: NullableString,
createdAt: NullableString,
updatedAt: NullableString,
englishName: NullableString,
xmlContent: NullableString,
}
export interface TreeModelGraph extends TreeModel {
graph: NodeGraph;
}
// 所有行为树列表
export interface TreeModelsData {
trees: TreeModel[];
pagination: ApiPagination;
}
export interface TreeModelsResponse extends ApiResponse<TreeModelsData> {
export interface BehaviorTreeDetailsResponse extends ApiDataResponse<BehaviorTree> {
}
export interface TreeModelDetailsResponse extends ApiResponse<TreeModelGraph> {
export interface BehaviorTreePageResponse extends PageableResponse<BehaviorTree> {
}
// 创建行为树
export interface RootTreeNodeConfig {
type: TreeNodeType;
instance_name: NullableString;
}
export interface RootTreeNode {
id: number;
name: NullableString;
english_name: NullableString;
created_at: NullableString;
root_node_id: number;
}
export interface CreateTreeModel {
name: NullableString;
english_name: NullableString;
description: NullableString;
template_id: number;
root_node_config: RootTreeNodeConfig;
}
export type CreateTreeModelData = RootTreeNode | ApiErrors
export interface CreateTreeModelResponse extends ApiResponse<CreateTreeModelData> {
}
// 行为树详细信息
export interface TreeNodeDetailRequest {
tree_id: number;
include_structure: boolean;
include_parameters: boolean;
}
export interface TreeNodeStructure {
id: number;
template_name: NullableString;
instance_name: NullableString;
children: TreeNodeStructure[];
}
export interface TreeNodeDetail {
tree: TreeModel;
}
}